Skip to content

Commit

Permalink
Merge pull request #348 from custom-components/dev
Browse files Browse the repository at this point in the history
v2.1.1
  • Loading branch information
alandtse authored Sep 8, 2019
2 parents ad9b95b + f834d18 commit 9ed3e7b
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 85 deletions.
32 changes: 19 additions & 13 deletions custom_components/alexa_media/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,24 @@
https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639
"""
import logging
from typing import Optional, Text

import voluptuous as vol

from typing import Optional, Text
from alexapy import WebsocketEchoClient
from homeassistant import util
from homeassistant.const import (
CONF_EMAIL, CONF_NAME, CONF_PASSWORD, CONF_SCAN_INTERVAL, CONF_URL,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.const import (CONF_EMAIL, CONF_NAME, CONF_PASSWORD,
CONF_SCAN_INTERVAL, CONF_URL,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.discovery import async_load_platform
from alexapy import WebsocketEchoClient
from homeassistant.helpers.event import async_call_later

from .const import (
ALEXA_COMPONENTS, CONF_DEBUG, CONF_ACCOUNTS, CONF_INCLUDE_DEVICES,
CONF_EXCLUDE_DEVICES, DATA_ALEXAMEDIA, DOMAIN, MIN_TIME_BETWEEN_SCANS,
MIN_TIME_BETWEEN_FORCED_SCANS, SCAN_INTERVAL, SERVICE_UPDATE_LAST_CALLED,
ATTR_EMAIL, STARTUP, __version__
)
from .const import (ALEXA_COMPONENTS, ATTR_EMAIL, CONF_ACCOUNTS, CONF_DEBUG,
CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES,
DATA_ALEXAMEDIA, DOMAIN, MIN_TIME_BETWEEN_FORCED_SCANS,
MIN_TIME_BETWEEN_SCANS, SCAN_INTERVAL,
SERVICE_UPDATE_LAST_CALLED, STARTUP, __version__)

# from .config_flow import configured_instances

Expand Down Expand Up @@ -426,12 +425,19 @@ async def update_devices(login_obj):
exclude_filter)

if new_alexa_clients:
cleaned_config = config.copy()
cleaned_config.pop(CONF_SCAN_INTERVAL, None)
# CONF_SCAN_INTERVAL causes a json error in the recorder because it
# is a timedelta object.
cleaned_config.pop(CONF_PASSWORD, None)
# CONF_PASSWORD contains sensitive info which is no longer needed
for component in ALEXA_COMPONENTS:
hass.async_create_task(
async_load_platform(hass,
component,
DOMAIN,
{CONF_NAME: DOMAIN, "config": config},
{CONF_NAME: DOMAIN,
"config": cleaned_config},
config))

# Process last_called data to fire events
Expand Down
17 changes: 6 additions & 11 deletions custom_components/alexa_media/alarm_control_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,20 @@

from homeassistant import util
from homeassistant.components.alarm_control_panel import AlarmControlPanel
from homeassistant.const import (STATE_ALARM_ARMED_AWAY,
STATE_ALARM_DISARMED)
from homeassistant.const import STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED
from homeassistant.helpers.event import async_call_later

from . import DATA_ALEXAMEDIA
from . import (CONF_EMAIL, CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES,
DATA_ALEXAMEDIA)
from . import DOMAIN as ALEXA_DOMAIN
from . import (
CONF_EMAIL,
MIN_TIME_BETWEEN_FORCED_SCANS,
MIN_TIME_BETWEEN_SCANS, hide_email,
CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES
)
from .helpers import add_devices
from . import MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS, hide_email
from .helpers import add_devices, retry_async

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = [ALEXA_DOMAIN]


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_setup_platform(hass,
config,
add_devices_callback,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/alexa_media/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"""
from datetime import timedelta

__version__ = '2.1.0'
__version__ = '2.1.1'
PROJECT_URL = "https://github.com/custom-components/alexa_media_player/"
ISSUE_URL = "{}issues".format(PROJECT_URL)

Expand Down
67 changes: 65 additions & 2 deletions custom_components/alexa_media/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
"""

import logging
from typing import List, Text
from typing import Callable, List, Text

from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_component import EntityComponent

_LOGGER = logging.getLogger(__name__)


async def add_devices(account: Text, devices: List[EntityComponent],
async def add_devices(account: Text,
devices: List[EntityComponent],
add_devices_callback: callable,
include_filter: List[Text] = [],
exclude_filter: List[Text] = []) -> bool:
Expand Down Expand Up @@ -56,3 +58,64 @@ async def add_devices(account: Text, devices: List[EntityComponent],
message)

return False


def retry_async(limit: int = 5,
delay: float = 1,
catch_exceptions: bool = True
) -> Callable:
"""Wrap function with retry logic.
The function will retry until true or the limit is reached. It will delay
for the period of time specified exponentialy increasing the delay.
Parameters
----------
limit : int
The max number of retries.
delay : float
The delay in seconds between retries.
catch_exceptions : bool
Whether exceptions should be caught and treated as failures or thrown.
Returns
-------
def
Wrapped function.
"""
def wrap(func) -> Callable:
import functools
import asyncio
@functools.wraps(func)
async def wrapper(*args, **kwargs) -> Callable:
_LOGGER.debug(
"%s: Trying with limit %s delay %s catch_exceptions %s",
func.__name__,
limit,
delay,
catch_exceptions)
retries: int = 0
result: bool = False
while (not result and retries < limit):
if retries != 0:
await asyncio.sleep(delay * 2 ** retries)
retries += 1
try:
result = await func(*args, **kwargs)
except Exception as ex: # pylint: disable=broad-except
if not catch_exceptions:
raise
template = ("An exception of type {0} occurred."
" Arguments:\n{1!r}")
message = template.format(type(ex).__name__, ex.args)
_LOGGER.debug("%s: failure caught due to exception: %s",
func.__name__,
message)
_LOGGER.debug("%s: Try: %s/%s result: %s",
func.__name__,
retries,
limit,
result)
return result
return wrapper
return wrap
2 changes: 1 addition & 1 deletion custom_components/alexa_media/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"documentation": "https://github.com/custom-components/alexa_media_player/wiki",
"dependencies": [],
"codeowners": ["@keatontaylor", "@alandtse"],
"requirements": ["alexapy==1.0.2"],
"requirements": ["alexapy==1.1.1"],
"homeassistant": "0.96.0"
}
40 changes: 15 additions & 25 deletions custom_components/alexa_media/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,31 @@
https://community.home-assistant.io/t/echo-devices-alexa-as-media-player-testers-needed/58639
"""
import logging

from typing import List # noqa pylint: disable=unused-import

import voluptuous as vol

from homeassistant import util
from homeassistant.components.media_player import (MediaPlayerDevice)
from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import (
DOMAIN,
MEDIA_TYPE_MUSIC,
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_SELECT_SOURCE,
SUPPORT_SHUFFLE_SET,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE,
SUPPORT_VOLUME_SET)
DOMAIN, MEDIA_TYPE_MUSIC, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_SELECT_SOURCE,
SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
from homeassistant.const import (STATE_IDLE, STATE_PAUSED, STATE_PLAYING,
STATE_STANDBY)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.service import extract_entity_ids
from homeassistant.helpers.discovery import async_load_platform

from . import CONF_EMAIL, CONF_NAME, DATA_ALEXAMEDIA
from . import DOMAIN as ALEXA_DOMAIN
from . import (MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS,
hide_email, hide_serial)
from .const import ATTR_MESSAGE, PLAY_SCAN_INTERVAL
from .helpers import add_devices

from . import (
DOMAIN as ALEXA_DOMAIN,
CONF_NAME, CONF_EMAIL,
DATA_ALEXAMEDIA,
MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS,
hide_email, hide_serial)
from .helpers import add_devices, retry_async

SUPPORT_ALEXA = (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK |
SUPPORT_NEXT_TRACK | SUPPORT_STOP |
SUPPORT_VOLUME_SET | SUPPORT_PLAY |
Expand All @@ -55,6 +44,7 @@
DEPENDENCIES = [ALEXA_DOMAIN]


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_setup_platform(hass, config, add_devices_callback,
discovery_info=None):
"""Set up the Alexa media player platform."""
Expand Down
29 changes: 17 additions & 12 deletions custom_components/alexa_media/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,31 @@
"""
import logging

from homeassistant.components.notify import (
ATTR_DATA, ATTR_TARGET, ATTR_TITLE, ATTR_TITLE_DEFAULT,
BaseNotificationService
)
from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET,
ATTR_TITLE, ATTR_TITLE_DEFAULT,
BaseNotificationService)

from . import (
DOMAIN as ALEXA_DOMAIN,
DATA_ALEXAMEDIA,
hide_email, hide_serial)
from . import DATA_ALEXAMEDIA
from . import DOMAIN as ALEXA_DOMAIN
from . import hide_email, hide_serial
from .helpers import retry_async

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = [ALEXA_DOMAIN]

EVENT_NOTIFY = "notify"


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_get_service(hass, config, discovery_info=None):
# pylint: disable=unused-argument
"""Get the demo notification service."""
for account, account_dict in (
hass.data[DATA_ALEXAMEDIA]['accounts'].items()):
for key, device in account_dict['devices']['media_player'].items():
if key not in account_dict['entities']['media_player']:
_LOGGER.debug(
"%s: Media player %s not loaded yet; delaying load",
hide_email(account),
hide_serial(key))
return False
return AlexaNotificationService(hass)


Expand Down
42 changes: 22 additions & 20 deletions custom_components/alexa_media/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,20 @@
from homeassistant.exceptions import NoEntitySpecifiedError
from homeassistant.helpers.event import async_call_later

from . import DATA_ALEXAMEDIA
from . import (CONF_EMAIL, CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES,
DATA_ALEXAMEDIA)
from . import DOMAIN as ALEXA_DOMAIN
from . import (
MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS,
hide_email, hide_serial, CONF_EMAIL,
CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES
)
from .helpers import add_devices
from . import (MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS,
hide_email, hide_serial)
from .helpers import add_devices, retry_async

_LOGGER = logging.getLogger(__name__)


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_setup_platform(hass, config, add_devices_callback,
discovery_info=None):
"""Set up the Alexa switch platform."""
_LOGGER.debug("Loading switches")
devices = [] # type: List[DNDSwitch]
SWITCH_TYPES = [
('dnd', DNDSwitch),
Expand All @@ -42,6 +40,8 @@ async def async_setup_platform(hass, config, add_devices_callback,
include_filter = config.get(CONF_INCLUDE_DEVICES, [])
exclude_filter = config.get(CONF_EXCLUDE_DEVICES, [])
account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][account]
_LOGGER.debug("%s: Loading switches",
hide_email(account))
if 'switch' not in account_dict['entities']:
(hass.data[DATA_ALEXAMEDIA]
['accounts']
Expand All @@ -50,14 +50,10 @@ async def async_setup_platform(hass, config, add_devices_callback,
['switch']) = {}
for key, device in account_dict['devices']['media_player'].items():
if key not in account_dict['entities']['media_player']:
_LOGGER.debug("Media Players not loaded yet; delaying load")
async_call_later(hass, 5, lambda _:
hass.async_create_task(
async_setup_platform(hass,
config,
add_devices_callback,
discovery_info)))
return True
_LOGGER.debug("%s: Media player %s not loaded yet; delaying load",
hide_email(account),
hide_serial(key))
return False
if key not in (hass.data[DATA_ALEXAMEDIA]
['accounts']
[account]
Expand Down Expand Up @@ -87,14 +83,20 @@ async def async_setup_platform(hass, config, add_devices_callback,
[key]
[switch_key]) = alexa_client
else:
_LOGGER.debug("%s: Skipping already added device: %s:%s",
hide_email(account),
key,
alexa_client)
for alexa_client in (hass.data[DATA_ALEXAMEDIA]
['accounts']
[account]
['entities']
['switch']
[key].values()):
_LOGGER.debug("%s: Skipping already added device: %s",
hide_email(account),
alexa_client)
return await add_devices(hide_email(account),
devices, add_devices_callback,
include_filter, exclude_filter)


class AlexaMediaSwitch(SwitchDevice):
"""Representation of a Alexa Media switch."""

Expand Down

0 comments on commit 9ed3e7b

Please sign in to comment.