Skip to content

Commit

Permalink
feat: add logic to catch login errors
Browse files Browse the repository at this point in the history
  • Loading branch information
alandtse committed Sep 29, 2019
1 parent 0b9bc3c commit 6633598
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 17 deletions.
16 changes: 12 additions & 4 deletions custom_components/alexa_media/__init__.py
Expand Up @@ -12,7 +12,8 @@
from typing import Optional, Text

import voluptuous as vol
from alexapy import WebsocketEchoClient, hide_email, hide_serial
from alexapy import (AlexapyLoginError, WebsocketEchoClient, hide_email,
hide_serial)
from homeassistant import util
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (CONF_EMAIL, CONF_NAME, CONF_PASSWORD,
Expand Down Expand Up @@ -134,6 +135,12 @@ async def close_alexa_media(event=None) -> None:
login = AlexaLogin(url, email, password, hass.config.path,
account.get(CONF_DEBUG))
(hass.data[DATA_ALEXAMEDIA]['accounts'][email]['login_obj']) = login
(hass.data[DATA_ALEXAMEDIA]['accounts'][email]
['config_entry']) = config_entry
(hass.data[DATA_ALEXAMEDIA]['accounts'][email]
['setup_platform_callback']) = setup_platform_callback
(hass.data[DATA_ALEXAMEDIA]['accounts'][email]
['test_login_status']) = test_login_status
await login.login_with_cookie()
await test_login_status(hass, config_entry, login,
setup_platform_callback)
Expand All @@ -146,6 +153,7 @@ async def setup_platform_callback(hass, config_entry, login, callback_data):
Args:
callback_data (json): Returned data from configurator passed through
request_configuration and configuration_callback
"""
_LOGGER.debug(("Configurator closed for Status: %s\n"
" got captcha: %s securitycode: %s"
Expand Down Expand Up @@ -358,11 +366,11 @@ async def update_devices(login_obj):
if ((devices is None or bluetooth is None)
and not (hass.data[DATA_ALEXAMEDIA]
['accounts'][email]['configurator'])):
raise RuntimeError()
except RuntimeError:
raise AlexapyLoginError()
except (AlexapyLoginError, RuntimeError):
_LOGGER.debug("%s: Alexa API disconnected; attempting to relogin",
hide_email(email))
await login_obj.login()
await login_obj.login_with_cookie()
await test_login_status(hass,
config_entry, login_obj,
setup_platform_callback)
Expand Down
6 changes: 5 additions & 1 deletion custom_components/alexa_media/alarm_control_panel.py
Expand Up @@ -19,7 +19,7 @@
DATA_ALEXAMEDIA)
from . import DOMAIN as ALEXA_DOMAIN
from . import MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS, hide_email
from .helpers import add_devices, retry_async
from .helpers import _catch_login_errors, add_devices, retry_async

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -165,6 +165,7 @@ def _handle_event(self, event):
self.async_update(no_throttle=True)))

@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
@_catch_login_errors
async def async_update(self):
"""Update Guard state."""
try:
Expand Down Expand Up @@ -205,6 +206,7 @@ async def async_update(self):
_LOGGER.debug("%s: Alarm State: %s", self.account, self.state)
self.async_schedule_update_ha_state()

@_catch_login_errors
async def async_alarm_disarm(self, code=None) -> None:
# pylint: disable=unexpected-keyword-arg
"""Send disarm command.
Expand All @@ -218,6 +220,7 @@ async def async_alarm_disarm(self, code=None) -> None:
pass
await self.async_alarm_arm_home()

@_catch_login_errors
async def async_alarm_arm_home(self, code=None) -> None:
"""Send arm home command."""
try:
Expand All @@ -231,6 +234,7 @@ async def async_alarm_arm_home(self, code=None) -> None:
await self.async_update(no_throttle=True)
self.async_schedule_update_ha_state()

@_catch_login_errors
async def async_alarm_arm_away(self, code=None) -> None:
"""Send arm away command."""
# pylint: disable=unexpected-keyword-arg
Expand Down
73 changes: 63 additions & 10 deletions custom_components/alexa_media/helpers.py
Expand Up @@ -11,9 +11,12 @@
import logging
from typing import Any, Callable, List, Text

from alexapy import AlexapyLoginError, hide_email
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_component import EntityComponent

from . import DATA_ALEXAMEDIA

_LOGGER = logging.getLogger(__name__)


Expand Down Expand Up @@ -114,18 +117,68 @@ async def wrapper(*args, **kwargs) -> Any:
message = template.format(type(ex).__name__, ex.args)
_LOGGER.debug(
"%s.%s: failure caught due to exception: %s",
func.__module__[func.__module__.find('.')+1:],
func.__name__,
message)
func.__module__[func.__module__.find('.')+1:],
func.__name__,
message)
_LOGGER.debug(
"%s.%s: Try: %s/%s after waiting %s seconds result: %s",
func.__module__[func.__module__.find('.')+1:],
func.__name__,
retries,
limit,
next_try,
result
)
func.__module__[func.__module__.find('.')+1:],
func.__name__,
retries,
limit,
next_try,
result
)
return result
return wrapper
return wrap


def _catch_login_errors(func) -> Callable:
"""Detect AlexapyLoginError and attempt relogin."""
import functools
@functools.wraps(func)
async def wrapper(*args, **kwargs) -> Any:
try:
result = await func(*args, **kwargs)
except AlexapyLoginError as ex: # pylint: disable=broad-except
template = ("An exception of type {0} occurred."
" Arguments:\n{1!r}")
message = template.format(type(ex).__name__, ex.args)
_LOGGER.debug("%s.%s: detected bad login: %s",
func.__module__[func.__module__.find('.')+1:],
func.__name__,
message)
instance = args[0]
if hasattr(instance, '_login'):
login = instance._login
email = login.email
hass = instance.hass if instance.hass else None
if (hass and not
(hass.data[DATA_ALEXAMEDIA]['accounts'][email]
['configurator'])):
config_entry = (
hass.data[DATA_ALEXAMEDIA]
['accounts']
[email]
['config_entry'])
callback = (
hass.data[DATA_ALEXAMEDIA]
['accounts']
[email]
['setup_platform_callback'])
test_login_status = (
hass.data[DATA_ALEXAMEDIA]
['accounts']
[email]
['test_login_status'])
_LOGGER.debug(
"%s: Alexa API disconnected; attempting to relogin",
hide_email(email))
await login.login_with_cookie()
await test_login_status(hass,
config_entry, login,
callback)
return None
return result
return wrapper
17 changes: 16 additions & 1 deletion custom_components/alexa_media/media_player.py
Expand Up @@ -27,7 +27,7 @@
from . import (MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS,
hide_email, hide_serial)
from .const import PLAY_SCAN_INTERVAL
from .helpers import add_devices, retry_async
from .helpers import _catch_login_errors, add_devices, retry_async

SUPPORT_ALEXA = (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK |
SUPPORT_NEXT_TRACK | SUPPORT_STOP |
Expand Down Expand Up @@ -278,6 +278,7 @@ async def _set_authentication_details(self, auth):
self._customer_name = auth['customerName']

@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
@_catch_login_errors
async def refresh(self, device=None):
"""Refresh device data.
Expand Down Expand Up @@ -392,6 +393,7 @@ def source_list(self):
"""List of available input sources."""
return self._source_list

@_catch_login_errors
async def async_select_source(self, source):
"""Select input source."""
if source == 'Local Speaker':
Expand Down Expand Up @@ -606,6 +608,7 @@ def dnd_state(self, state):
"""Set the Do Not Disturb state."""
self._dnd = state

@_catch_login_errors
async def async_set_shuffle(self, shuffle):
"""Enable/disable shuffle mode."""
await self.alexa_api.shuffle(shuffle)
Expand Down Expand Up @@ -636,6 +639,7 @@ def supported_features(self):
"""Flag media player features that are supported."""
return SUPPORT_ALEXA

@_catch_login_errors
async def async_set_volume_level(self, volume):
"""Set volume level, range 0..1."""
if not self.available:
Expand All @@ -658,6 +662,7 @@ def is_volume_muted(self):
return True
return False

@_catch_login_errors
async def async_mute_volume(self, mute):
"""Mute the volume.
Expand All @@ -681,6 +686,7 @@ async def async_mute_volume(self, mute):
['accounts'][self._login.email]['websocket']):
await self.async_update()

@_catch_login_errors
async def async_media_play(self):
"""Send play command."""
if not (self.state in [STATE_PLAYING, STATE_PAUSED]
Expand All @@ -691,6 +697,7 @@ async def async_media_play(self):
['accounts'][self._login.email]['websocket']):
await self.async_update()

@_catch_login_errors
async def async_media_pause(self):
"""Send pause command."""
if not (self.state in [STATE_PLAYING, STATE_PAUSED]
Expand All @@ -701,6 +708,7 @@ async def async_media_pause(self):
['accounts'][self._login.email]['websocket']):
await self.async_update()

@_catch_login_errors
async def async_turn_off(self):
"""Turn the client off.
Expand All @@ -711,6 +719,7 @@ async def async_turn_off(self):
await self.async_media_pause()
await self._clear_media_details()

@_catch_login_errors
async def async_turn_on(self):
"""Turn the client on.
Expand All @@ -720,6 +729,7 @@ async def async_turn_on(self):
self._should_poll = True
await self.async_media_pause()

@_catch_login_errors
async def async_media_next_track(self):
"""Send next track command."""
if not (self.state in [STATE_PLAYING, STATE_PAUSED]
Expand All @@ -730,6 +740,7 @@ async def async_media_next_track(self):
['accounts'][self._login.email]['websocket']):
await self.async_update()

@_catch_login_errors
async def async_media_previous_track(self):
"""Send previous track command."""
if not (self.state in [STATE_PLAYING, STATE_PAUSED]
Expand All @@ -740,25 +751,29 @@ async def async_media_previous_track(self):
['accounts'][self._login.email]['websocket']):
await self.async_update()

@_catch_login_errors
async def async_send_tts(self, message):
"""Send TTS to Device.
NOTE: Does not work on WHA Groups.
"""
await self.alexa_api.send_tts(message, customer_id=self._customer_id)

@_catch_login_errors
async def async_send_announcement(self, message, **kwargs):
"""Send announcement to the media player."""
await self.alexa_api.send_announcement(message,
customer_id=self._customer_id,
**kwargs)

@_catch_login_errors
async def async_send_mobilepush(self, message, **kwargs):
"""Send push to the media player's associated mobile devices."""
await self.alexa_api.send_mobilepush(message,
customer_id=self._customer_id,
**kwargs)

@_catch_login_errors
async def async_play_media(self,
media_type, media_id, enqueue=None, **kwargs):
"""Send the play_media command to the media player."""
Expand Down
4 changes: 3 additions & 1 deletion custom_components/alexa_media/switch.py
Expand Up @@ -17,7 +17,7 @@
DATA_ALEXAMEDIA)
from . import DOMAIN as ALEXA_DOMAIN
from . import (hide_email, hide_serial)
from .helpers import add_devices, retry_async
from .helpers import _catch_login_errors, add_devices, retry_async

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -124,6 +124,7 @@ def __init__(self,
"""Initialize the Alexa Switch device."""
# Class info
self._client = client
self._login = client._login
self._account = account
self._name = name
self._switch_property = switch_property
Expand Down Expand Up @@ -165,6 +166,7 @@ def _handle_event(self, event):
self._state = getattr(self._client, self._switch_property)
self.async_schedule_update_ha_state()

@_catch_login_errors
async def _set_switch(self, state, **kwargs):
try:
if not self.enabled:
Expand Down

0 comments on commit 6633598

Please sign in to comment.