diff --git a/custom_components/alexa_media/__init__.py b/custom_components/alexa_media/__init__.py
index 8eb8b5b2..7ef77d00 100644
--- a/custom_components/alexa_media/__init__.py
+++ b/custom_components/alexa_media/__init__.py
@@ -7,65 +7,90 @@
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 logging
from datetime import timedelta
+import logging
from typing import List, Optional, Text
-import voluptuous as vol
-from alexapy import (AlexapyLoginError, 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,
- 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.discovery import async_load_platform
from homeassistant.helpers.event import async_call_later
+import voluptuous as vol
from .config_flow import configured_instances
-from .const import (ALEXA_COMPONENTS, ATTR_EMAIL, ATTR_NUM_ENTRIES,
- 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_CLEAR_HISTORY,
- SERVICE_UPDATE_LAST_CALLED, STARTUP)
+from .const import (
+ ALEXA_COMPONENTS,
+ ATTR_EMAIL,
+ ATTR_NUM_ENTRIES,
+ 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_CLEAR_HISTORY,
+ SERVICE_UPDATE_LAST_CALLED,
+ STARTUP,
+)
from .helpers import retry_async
_LOGGER = logging.getLogger(__name__)
-ACCOUNT_CONFIG_SCHEMA = vol.Schema({
- vol.Required(CONF_EMAIL): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
- vol.Required(CONF_URL): cv.string,
- vol.Optional(CONF_DEBUG, default=False): cv.boolean,
- vol.Optional(CONF_INCLUDE_DEVICES, default=[]):
- vol.All(cv.ensure_list, [cv.string]),
- vol.Optional(CONF_EXCLUDE_DEVICES, default=[]):
- vol.All(cv.ensure_list, [cv.string]),
- vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
- cv.time_period,
-})
-
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Optional(CONF_ACCOUNTS):
- vol.All(cv.ensure_list, [ACCOUNT_CONFIG_SCHEMA]),
- }),
-}, extra=vol.ALLOW_EXTRA)
-
-CLEAR_HISTORY_SCHEMA = vol.Schema({
- vol.Optional(ATTR_EMAIL, default=[]):
- vol.All(cv.ensure_list, [cv.string]),
- vol.Optional(ATTR_NUM_ENTRIES, default=50):
- vol.All(int, vol.Range(min=1, max=50))
-})
-
-LAST_CALL_UPDATE_SCHEMA = vol.Schema({
- vol.Optional(ATTR_EMAIL, default=[]):
- vol.All(cv.ensure_list, [cv.string]),
-})
+ACCOUNT_CONFIG_SCHEMA = vol.Schema(
+ {
+ vol.Required(CONF_EMAIL): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Required(CONF_URL): cv.string,
+ vol.Optional(CONF_DEBUG, default=False): cv.boolean,
+ vol.Optional(CONF_INCLUDE_DEVICES, default=[]): vol.All(
+ cv.ensure_list, [cv.string]
+ ),
+ vol.Optional(CONF_EXCLUDE_DEVICES, default=[]): vol.All(
+ cv.ensure_list, [cv.string]
+ ),
+ vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period,
+ }
+)
+
+CONFIG_SCHEMA = vol.Schema(
+ {
+ DOMAIN: vol.Schema(
+ {
+ vol.Optional(CONF_ACCOUNTS): vol.All(
+ cv.ensure_list, [ACCOUNT_CONFIG_SCHEMA]
+ ),
+ }
+ ),
+ },
+ extra=vol.ALLOW_EXTRA,
+)
+
+CLEAR_HISTORY_SCHEMA = vol.Schema(
+ {
+ vol.Optional(ATTR_EMAIL, default=[]): vol.All(cv.ensure_list, [cv.string]),
+ vol.Optional(ATTR_NUM_ENTRIES, default=50): vol.All(
+ int, vol.Range(min=1, max=50)
+ ),
+ }
+)
+
+LAST_CALL_UPDATE_SCHEMA = vol.Schema(
+ {vol.Optional(ATTR_EMAIL, default=[]): vol.All(cv.ensure_list, [cv.string])}
+)
async def async_setup(hass, config, discovery_info=None):
@@ -86,13 +111,12 @@ async def async_setup(hass, config, discovery_info=None):
CONF_PASSWORD: account[CONF_PASSWORD],
CONF_URL: account[CONF_URL],
CONF_DEBUG: account[CONF_DEBUG],
- CONF_INCLUDE_DEVICES:
- account[CONF_INCLUDE_DEVICES],
- CONF_EXCLUDE_DEVICES:
- account[CONF_EXCLUDE_DEVICES],
- CONF_SCAN_INTERVAL:
- account[CONF_SCAN_INTERVAL].total_seconds(),
- }
+ CONF_INCLUDE_DEVICES: account[CONF_INCLUDE_DEVICES],
+ CONF_EXCLUDE_DEVICES: account[CONF_EXCLUDE_DEVICES],
+ CONF_SCAN_INTERVAL: account[
+ CONF_SCAN_INTERVAL
+ ].total_seconds(),
+ },
)
break
else:
@@ -107,8 +131,7 @@ async def async_setup(hass, config, discovery_info=None):
CONF_DEBUG: account[CONF_DEBUG],
CONF_INCLUDE_DEVICES: account[CONF_INCLUDE_DEVICES],
CONF_EXCLUDE_DEVICES: account[CONF_EXCLUDE_DEVICES],
- CONF_SCAN_INTERVAL:
- account[CONF_SCAN_INTERVAL].total_seconds(),
+ CONF_SCAN_INTERVAL: account[CONF_SCAN_INTERVAL].total_seconds(),
},
)
)
@@ -118,16 +141,18 @@ async def async_setup(hass, config, discovery_info=None):
@retry_async(limit=5, delay=5, catch_exceptions=True)
async def async_setup_entry(hass, config_entry):
"""Set up Alexa Media Player as config entry."""
+
async def close_alexa_media(event=None) -> None:
"""Clean up Alexa connections."""
_LOGGER.debug("Received shutdown request: %s", event)
- for email, _ in (hass.data
- [DATA_ALEXAMEDIA]['accounts'].items()):
+ for email, _ in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
await close_connections(hass, email)
+
if DATA_ALEXAMEDIA not in hass.data:
hass.data[DATA_ALEXAMEDIA] = {}
- hass.data[DATA_ALEXAMEDIA]['accounts'] = {}
+ hass.data[DATA_ALEXAMEDIA]["accounts"] = {}
from alexapy import AlexaLogin, __version__ as alexapy_version
+
_LOGGER.info(STARTUP)
_LOGGER.info("Loaded alexapy==%s", alexapy_version)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, close_alexa_media)
@@ -135,23 +160,24 @@ async def close_alexa_media(event=None) -> None:
email = account.get(CONF_EMAIL)
password = account.get(CONF_PASSWORD)
url = account.get(CONF_URL)
- if email not in hass.data[DATA_ALEXAMEDIA]['accounts']:
- hass.data[DATA_ALEXAMEDIA]['accounts'][email] = {}
- if 'login_obj' in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- login = hass.data[DATA_ALEXAMEDIA]['accounts'][email]['login_obj']
+ if email not in hass.data[DATA_ALEXAMEDIA]["accounts"]:
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email] = {}
+ if "login_obj" in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ login = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["login_obj"]
else:
- 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
+ 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)
+ await test_login_status(hass, config_entry, login, setup_platform_callback)
return True
@@ -163,189 +189,210 @@ async def setup_platform_callback(hass, config_entry, login, callback_data):
request_configuration and configuration_callback
"""
- _LOGGER.debug(("Configurator closed for Status: %s\n"
- " got captcha: %s securitycode: %s"
- " Claimsoption: %s AuthSelectOption: %s "
- " VerificationCode: %s"),
- login.status,
- callback_data.get('captcha'),
- callback_data.get('securitycode'),
- callback_data.get('claimsoption'),
- callback_data.get('authselectoption'),
- callback_data.get('verificationcode'))
+ _LOGGER.debug(
+ (
+ "Configurator closed for Status: %s\n"
+ " got captcha: %s securitycode: %s"
+ " Claimsoption: %s AuthSelectOption: %s "
+ " VerificationCode: %s"
+ ),
+ login.status,
+ callback_data.get("captcha"),
+ callback_data.get("securitycode"),
+ callback_data.get("claimsoption"),
+ callback_data.get("authselectoption"),
+ callback_data.get("verificationcode"),
+ )
await login.login(data=callback_data)
- await test_login_status(hass, config_entry, login,
- setup_platform_callback)
+ await test_login_status(hass, config_entry, login, setup_platform_callback)
-async def request_configuration(hass, config_entry, login,
- setup_platform_callback):
+async def request_configuration(hass, config_entry, login, setup_platform_callback):
"""Request configuration steps from the user using the configurator."""
+
async def configuration_callback(callback_data):
"""Handle the submitted configuration."""
- await hass.async_add_job(setup_platform_callback, hass, config_entry,
- login, callback_data)
+ await hass.async_add_job(
+ setup_platform_callback, hass, config_entry, login, callback_data
+ )
configurator = hass.components.configurator
status = login.status
email = login.email
# links = ""
footer = ""
- if 'error_message' in status and status['error_message']:
- footer = ('\nNOTE: Actual Amazon error message in red below. '
- 'Remember password will be provided automatically'
- ' and Amazon error message normally appears first!')
+ if "error_message" in status and status["error_message"]:
+ footer = (
+ "\nNOTE: Actual Amazon error message in red below. "
+ "Remember password will be provided automatically"
+ " and Amazon error message normally appears first!"
+ )
# if login.links:
# links = '\n\nGo to link with link# (e.g. link0)\n' + login.links
# Get Captcha
- if (status and 'captcha_image_url' in status and
- status['captcha_image_url'] is not None):
+ if (
+ status
+ and "captcha_image_url" in status
+ and status["captcha_image_url"] is not None
+ ):
config_id = configurator.async_request_config(
"Alexa Media Player - Captcha - {}".format(email),
configuration_callback,
- description=('Please enter the text for the captcha.'
- ' Please hit confirm to reload image.'
- # + links
- + footer
- ),
- description_image=status['captcha_image_url'],
+ description=(
+ "Please enter the text for the captcha."
+ " Please hit confirm to reload image."
+ # + links
+ + footer
+ ),
+ description_image=status["captcha_image_url"],
submit_caption="Confirm",
- fields=[{'id': 'captcha', 'name': 'Captcha'}]
+ fields=[{"id": "captcha", "name": "Captcha"}],
)
- elif (status and 'securitycode_required' in status and
- status['securitycode_required']): # Get 2FA code
+ elif (
+ status and "securitycode_required" in status and status["securitycode_required"]
+ ): # Get 2FA code
config_id = configurator.async_request_config(
"Alexa Media Player - 2FA - {}".format(email),
configuration_callback,
- description=('Please enter your Two-Factor Security code.'
- # + links
- + footer),
+ description=(
+ "Please enter your Two-Factor Security code."
+ # + links
+ + footer
+ ),
submit_caption="Confirm",
- fields=[{'id': 'securitycode', 'name': 'Security Code'}]
+ fields=[{"id": "securitycode", "name": "Security Code"}],
)
- elif (status and 'claimspicker_required' in status and
- status['claimspicker_required']): # Get picker method
- options = status['claimspicker_message']
+ elif (
+ status and "claimspicker_required" in status and status["claimspicker_required"]
+ ): # Get picker method
+ options = status["claimspicker_message"]
if options:
config_id = configurator.async_request_config(
"Alexa Media Player - Verification Method - {}".format(email),
configuration_callback,
- description=('Please select the verification method. '
- '(e.g., `sms` or `email`).\n{}'.format(options)
- # + links
- + footer),
+ description=(
+ "Please select the verification method. "
+ "(e.g., `sms` or `email`).\n{}".format(options)
+ # + links
+ + footer
+ ),
submit_caption="Confirm",
- fields=[{'id': 'claimsoption', 'name': 'Option'}]
+ fields=[{"id": "claimsoption", "name": "Option"}],
)
else:
await configuration_callback({})
- elif (status and 'authselect_required' in status and
- status['authselect_required']): # Get picker method
- options = status['authselect_message']
+ elif (
+ status and "authselect_required" in status and status["authselect_required"]
+ ): # Get picker method
+ options = status["authselect_message"]
if options:
config_id = configurator.async_request_config(
"Alexa Media Player - OTP Method - {}".format(email),
configuration_callback,
- description=('Please select the OTP method. '
- '(e.g., `0`, `1`).
{}'.format(options)
- # + links
- + footer),
+ description=(
+ "Please select the OTP method. "
+ "(e.g., `0`, `1`).
{}".format(options)
+ # + links
+ + footer
+ ),
submit_caption="Confirm",
- fields=[{'id': 'authselectoption', 'name': 'Option'}]
+ fields=[{"id": "authselectoption", "name": "Option"}],
)
else:
await configuration_callback({})
- elif (status and 'verificationcode_required' in status and
- status['verificationcode_required']): # Get picker method
+ elif (
+ status
+ and "verificationcode_required" in status
+ and status["verificationcode_required"]
+ ): # Get picker method
config_id = configurator.async_request_config(
"Alexa Media Player - Verification Code - {}".format(email),
configuration_callback,
- description=('Please enter received verification code.'
- # + links
- + footer),
+ description=(
+ "Please enter received verification code."
+ # + links
+ + footer
+ ),
submit_caption="Confirm",
- fields=[{'id': 'verificationcode', 'name': 'Verification Code'}]
+ fields=[{"id": "verificationcode", "name": "Verification Code"}],
)
else: # Check login
config_id = configurator.async_request_config(
"Alexa Media Player - Begin - {}".format(email),
configuration_callback,
- description=('Please hit confirm to begin login attempt.'),
+ description=("Please hit confirm to begin login attempt."),
submit_caption="Confirm",
- fields=[]
+ fields=[],
)
- if 'configurator' not in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- hass.data[DATA_ALEXAMEDIA]['accounts'][email] = {"configurator": []}
- hass.data[DATA_ALEXAMEDIA]['accounts'][email]['configurator'].append(
- config_id)
- if 'error_message' in status and status['error_message']:
- configurator.async_notify_errors(
- config_id,
- status['error_message'])
- if len(hass.data[DATA_ALEXAMEDIA]['accounts'][email]['configurator']) > 1:
+ if "configurator" not in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email] = {"configurator": []}
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["configurator"].append(config_id)
+ if "error_message" in status and status["error_message"]:
+ configurator.async_notify_errors(config_id, status["error_message"])
+ if len(hass.data[DATA_ALEXAMEDIA]["accounts"][email]["configurator"]) > 1:
configurator.async_request_done(
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- ['configurator']).pop(0))
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["configurator"]).pop(0)
+ )
-async def test_login_status(hass, config_entry, login,
- setup_platform_callback) -> None:
+async def test_login_status(hass, config_entry, login, setup_platform_callback) -> None:
"""Test the login status and spawn requests for info."""
_LOGGER.debug("Testing login status: %s", login.status)
- if 'login_successful' in login.status and login.status['login_successful']:
- _LOGGER.debug("Setting up Alexa devices for %s",
- hide_email(login.email))
- await hass.async_add_job(setup_alexa, hass, config_entry,
- login)
+ if "login_successful" in login.status and login.status["login_successful"]:
+ _LOGGER.debug("Setting up Alexa devices for %s", hide_email(login.email))
+ await hass.async_add_job(setup_alexa, hass, config_entry, login)
return
- if ('captcha_required' in login.status and
- login.status['captcha_required']):
+ if "captcha_required" in login.status and login.status["captcha_required"]:
_LOGGER.debug("Creating configurator to request captcha")
- elif ('securitycode_required' in login.status and
- login.status['securitycode_required']):
+ elif (
+ "securitycode_required" in login.status
+ and login.status["securitycode_required"]
+ ):
_LOGGER.debug("Creating configurator to request 2FA")
- elif ('claimspicker_required' in login.status and
- login.status['claimspicker_required']):
+ elif (
+ "claimspicker_required" in login.status
+ and login.status["claimspicker_required"]
+ ):
_LOGGER.debug("Creating configurator to select verification option")
- elif ('authselect_required' in login.status and
- login.status['authselect_required']):
+ elif "authselect_required" in login.status and login.status["authselect_required"]:
_LOGGER.debug("Creating configurator to select OTA option")
- elif ('verificationcode_required' in login.status and
- login.status['verificationcode_required']):
+ elif (
+ "verificationcode_required" in login.status
+ and login.status["verificationcode_required"]
+ ):
_LOGGER.debug("Creating configurator to enter verification code")
- elif ('login_failed' in login.status and
- login.status['login_failed']):
+ elif "login_failed" in login.status and login.status["login_failed"]:
_LOGGER.debug("Creating configurator to start new login attempt")
- await hass.async_add_job(request_configuration, hass, config_entry, login,
- setup_platform_callback)
+ await hass.async_add_job(
+ request_configuration, hass, config_entry, login, setup_platform_callback
+ )
async def setup_alexa(hass, config_entry, login_obj):
"""Set up a alexa api based on host parameter."""
+
def _existing_serials() -> List:
email: Text = login_obj.email
- existing_serials = (list(hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['entities']
- ['media_player'].keys()) if 'entities' in (
- hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email])
- else [])
+ existing_serials = (
+ list(
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][
+ "media_player"
+ ].keys()
+ )
+ if "entities" in (hass.data[DATA_ALEXAMEDIA]["accounts"][email])
+ else []
+ )
for serial in existing_serials:
- device = (
- hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['devices']
- ['media_player']
- [serial])
- if 'appDeviceList' in device and device['appDeviceList']:
- apps = list(map(
- lambda x: x['serialNumber'] if 'serialNumber' in x
- else None,
- device['appDeviceList']))
+ device = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
+ "media_player"
+ ][serial]
+ if "appDeviceList" in device and device["appDeviceList"]:
+ apps = list(
+ map(
+ lambda x: x["serialNumber"] if "serialNumber" in x else None,
+ device["appDeviceList"],
+ )
+ )
# _LOGGER.debug("Combining %s with %s",
# existing_serials, apps)
existing_serials = existing_serials + apps
@@ -366,21 +413,21 @@ async def update_devices(login_obj):
Each AlexaAPI call generally results in two webpage requests.
"""
from alexapy import AlexaAPI
+
email: Text = login_obj.email
- if email not in hass.data[DATA_ALEXAMEDIA]['accounts']:
+ if email not in hass.data[DATA_ALEXAMEDIA]["accounts"]:
return
existing_serials = _existing_serials()
- existing_entities = (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['entities']
- ['media_player'].values())
- if ('websocket' in hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- and hass.data[DATA_ALEXAMEDIA]['accounts'][email]['websocket']
- and not (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['new_devices'])):
+ existing_entities = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"][
+ "media_player"
+ ].values()
+ if (
+ "websocket" in hass.data[DATA_ALEXAMEDIA]["accounts"][email]
+ and hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
+ and not (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"])
+ ):
return
- hass.data[DATA_ALEXAMEDIA]['accounts'][email]['new_devices'] = False
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False
try:
auth_info = await AlexaAPI.get_authentication(login_obj)
devices = await AlexaAPI.get_devices(login_obj)
@@ -388,21 +435,24 @@ async def update_devices(login_obj):
preferences = await AlexaAPI.get_device_preferences(login_obj)
dnd = await AlexaAPI.get_dnd_state(login_obj)
raw_notifications = await AlexaAPI.get_notifications(login_obj)
- _LOGGER.debug("%s: Found %s devices, %s bluetooth",
- hide_email(email),
- len(devices) if devices is not None else '',
- len(bluetooth) if bluetooth is not None else '')
- if ((devices is None or bluetooth is None)
- and not (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['configurator'])):
+ _LOGGER.debug(
+ "%s: Found %s devices, %s bluetooth",
+ hide_email(email),
+ len(devices) if devices is not None else "",
+ len(bluetooth) if bluetooth is not None else "",
+ )
+ if (devices is None or bluetooth is None) and not (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["configurator"]
+ ):
raise AlexapyLoginError()
except (AlexapyLoginError, RuntimeError):
- _LOGGER.debug("%s: Alexa API disconnected; attempting to relogin",
- hide_email(email))
+ _LOGGER.debug(
+ "%s: Alexa API disconnected; attempting to relogin", hide_email(email)
+ )
await login_obj.login_with_cookie()
- await test_login_status(hass,
- config_entry, login_obj,
- setup_platform_callback)
+ await test_login_status(
+ hass, config_entry, login_obj, setup_platform_callback
+ )
return
new_alexa_clients = [] # list of newly discovered device names
@@ -410,77 +460,82 @@ async def update_devices(login_obj):
include_filter = []
for device in devices:
- if include and device['accountName'] not in include:
- include_filter.append(device['accountName'])
- if 'appDeviceList' in device:
- for app in device['appDeviceList']:
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['excluded']
- [app['serialNumber']]) = device
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['excluded']
- [device['serialNumber']]) = device
+ if include and device["accountName"] not in include:
+ include_filter.append(device["accountName"])
+ if "appDeviceList" in device:
+ for app in device["appDeviceList"]:
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
+ app["serialNumber"]
+ ]
+ ) = device
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
+ device["serialNumber"]
+ ]
+ ) = device
continue
- elif exclude and device['accountName'] in exclude:
- exclude_filter.append(device['accountName'])
- if 'appDeviceList' in device:
- for app in device['appDeviceList']:
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['excluded']
- [app['serialNumber']]) = device
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['excluded']
- [device['serialNumber']]) = device
+ elif exclude and device["accountName"] in exclude:
+ exclude_filter.append(device["accountName"])
+ if "appDeviceList" in device:
+ for app in device["appDeviceList"]:
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
+ app["serialNumber"]
+ ]
+ ) = device
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
+ device["serialNumber"]
+ ]
+ ) = device
continue
- if 'bluetoothStates' in bluetooth:
- for b_state in bluetooth['bluetoothStates']:
- if device['serialNumber'] == b_state['deviceSerialNumber']:
- device['bluetooth_state'] = b_state
-
- if 'devicePreferences' in preferences:
- for dev in preferences['devicePreferences']:
- if dev['deviceSerialNumber'] == device['serialNumber']:
- device['locale'] = dev['locale']
- device['timeZoneId'] = dev['timeZoneId']
- _LOGGER.debug("Locale %s timezone %s found for %s",
- device['locale'],
- device['timeZoneId'],
- hide_serial(device['serialNumber']))
-
- if 'doNotDisturbDeviceStatusList' in dnd:
- for dev in dnd['doNotDisturbDeviceStatusList']:
- if dev['deviceSerialNumber'] == device['serialNumber']:
- device['dnd'] = dev['enabled']
- _LOGGER.debug("DND %s found for %s",
- device['dnd'],
- hide_serial(device['serialNumber']))
- device['auth_info'] = auth_info
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['devices']
- ['media_player']
- [device['serialNumber']]) = device
-
- if device['serialNumber'] not in existing_serials:
- new_alexa_clients.append(device['accountName'])
- _LOGGER.debug("%s: Existing: %s New: %s;"
- " Filtered out by not being in include: %s "
- "or in exclude: %s",
- hide_email(email),
- list(existing_entities),
- new_alexa_clients,
- include_filter,
- exclude_filter)
+ if "bluetoothStates" in bluetooth:
+ for b_state in bluetooth["bluetoothStates"]:
+ if device["serialNumber"] == b_state["deviceSerialNumber"]:
+ device["bluetooth_state"] = b_state
+
+ if "devicePreferences" in preferences:
+ for dev in preferences["devicePreferences"]:
+ if dev["deviceSerialNumber"] == device["serialNumber"]:
+ device["locale"] = dev["locale"]
+ device["timeZoneId"] = dev["timeZoneId"]
+ _LOGGER.debug(
+ "Locale %s timezone %s found for %s",
+ device["locale"],
+ device["timeZoneId"],
+ hide_serial(device["serialNumber"]),
+ )
+
+ if "doNotDisturbDeviceStatusList" in dnd:
+ for dev in dnd["doNotDisturbDeviceStatusList"]:
+ if dev["deviceSerialNumber"] == device["serialNumber"]:
+ device["dnd"] = dev["enabled"]
+ _LOGGER.debug(
+ "DND %s found for %s",
+ device["dnd"],
+ hide_serial(device["serialNumber"]),
+ )
+ device["auth_info"] = auth_info
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
+ "media_player"
+ ][device["serialNumber"]]
+ ) = device
+
+ if device["serialNumber"] not in existing_serials:
+ new_alexa_clients.append(device["accountName"])
+ _LOGGER.debug(
+ "%s: Existing: %s New: %s;"
+ " Filtered out by not being in include: %s "
+ "or in exclude: %s",
+ hide_email(email),
+ list(existing_entities),
+ new_alexa_clients,
+ include_filter,
+ exclude_filter,
+ )
if new_alexa_clients:
cleaned_config = config.copy()
@@ -489,56 +544,60 @@ async def update_devices(login_obj):
for component in ALEXA_COMPONENTS:
if component == "notify":
hass.async_create_task(
- async_load_platform(hass,
- component,
- DOMAIN,
- {CONF_NAME: DOMAIN,
- "config": cleaned_config},
- config))
+ async_load_platform(
+ hass,
+ component,
+ DOMAIN,
+ {CONF_NAME: DOMAIN, "config": cleaned_config},
+ config,
+ )
+ )
else:
hass.async_add_job(
hass.config_entries.async_forward_entry_setup(
- config_entry,
- component))
+ config_entry, component
+ )
+ )
await process_notifications(login_obj, raw_notifications)
# Process last_called data to fire events
await update_last_called(login_obj)
- async_call_later(hass, scan_interval, lambda _:
- hass.async_create_task(
- update_devices(login_obj,
- no_throttle=True)))
+ async_call_later(
+ hass,
+ scan_interval,
+ lambda _: hass.async_create_task(
+ update_devices(login_obj, no_throttle=True)
+ ),
+ )
async def process_notifications(login_obj, raw_notifications=None):
"""Process raw notifications json."""
from alexapy import AlexaAPI
+
if not raw_notifications:
raw_notifications = await AlexaAPI.get_notifications(login_obj)
email: Text = login_obj.email
notifications = {}
for notification in raw_notifications:
- n_dev_id = notification['deviceSerialNumber']
- n_type = notification['type']
+ n_dev_id = notification["deviceSerialNumber"]
+ n_type = notification["type"]
if n_type == "MusicAlarm":
n_type = "Alarm"
- n_id = notification['notificationIndex']
- n_date = notification['originalDate']
- n_time = notification['originalTime']
- notification['date_time'] = f"{n_date} {n_time}"
+ n_id = notification["notificationIndex"]
+ n_date = notification["originalDate"]
+ n_time = notification["originalTime"]
+ notification["date_time"] = f"{n_date} {n_time}"
if n_dev_id not in notifications:
notifications[n_dev_id] = {}
if n_type not in notifications[n_dev_id]:
notifications[n_dev_id][n_type] = {}
notifications[n_dev_id][n_type][n_id] = notification
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['notifications']) = notifications
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["notifications"]) = notifications
_LOGGER.debug(
"%s: Updated %s notifications for %s devices",
hide_email(email),
len(raw_notifications),
- len(notifications)
+ len(notifications),
)
async def update_last_called(login_obj, last_called=None):
@@ -548,53 +607,54 @@ async def update_last_called(login_obj, last_called=None):
to notify listeners.
"""
from alexapy import AlexaAPI
+
if not last_called:
last_called = await AlexaAPI.get_last_device_serial(login_obj)
- _LOGGER.debug("%s: Updated last_called: %s",
- hide_email(email),
- hide_serial(last_called))
- stored_data = hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- if (('last_called' in stored_data and
- last_called != stored_data['last_called']) or
- ('last_called' not in stored_data and
- last_called is not None)):
- _LOGGER.debug("%s: last_called changed: %s to %s",
- hide_email(email),
- hide_serial(stored_data['last_called'] if
- 'last_called' in stored_data else None),
- hide_serial(last_called))
+ _LOGGER.debug(
+ "%s: Updated last_called: %s", hide_email(email), hide_serial(last_called)
+ )
+ stored_data = hass.data[DATA_ALEXAMEDIA]["accounts"][email]
+ if (
+ "last_called" in stored_data and last_called != stored_data["last_called"]
+ ) or ("last_called" not in stored_data and last_called is not None):
+ _LOGGER.debug(
+ "%s: last_called changed: %s to %s",
+ hide_email(email),
+ hide_serial(
+ stored_data["last_called"] if "last_called" in stored_data else None
+ ),
+ hide_serial(last_called),
+ )
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'last_called_change': last_called})
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['last_called']) = last_called
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"last_called_change": last_called},
+ )
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["last_called"]) = last_called
async def update_bluetooth_state(login_obj, device_serial):
"""Update the bluetooth state on ws bluetooth event."""
from alexapy import AlexaAPI
+
bluetooth = await AlexaAPI.get_bluetooth(login_obj)
- device = (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['devices']
- ['media_player']
- [device_serial])
-
- if 'bluetoothStates' in bluetooth:
- for b_state in bluetooth['bluetoothStates']:
- if device_serial == b_state['deviceSerialNumber']:
+ device = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
+ "media_player"
+ ][device_serial]
+
+ if "bluetoothStates" in bluetooth:
+ for b_state in bluetooth["bluetoothStates"]:
+ if device_serial == b_state["deviceSerialNumber"]:
# _LOGGER.debug("%s: setting value for: %s to %s",
# hide_email(email),
# hide_serial(device_serial),
# hide_serial(b_state))
- device['bluetooth_state'] = b_state
- return device['bluetooth_state']
- _LOGGER.debug("%s: get_bluetooth for: %s failed with %s",
- hide_email(email),
- hide_serial(device_serial),
- hide_serial(bluetooth))
+ device["bluetooth_state"] = b_state
+ return device["bluetooth_state"]
+ _LOGGER.debug(
+ "%s: get_bluetooth for: %s failed with %s",
+ hide_email(email),
+ hide_serial(device_serial),
+ hide_serial(bluetooth),
+ )
return None
async def clear_history(call):
@@ -610,17 +670,17 @@ async def clear_history(call):
"""
from alexapy import AlexaAPI
+
requested_emails = call.data.get(ATTR_EMAIL)
items: int = int(call.data.get(ATTR_NUM_ENTRIES))
- _LOGGER.debug("Service clear_history called for: %i items for %s",
- items,
- requested_emails)
- for email, account_dict in (hass.data
- [DATA_ALEXAMEDIA]['accounts'].items()):
+ _LOGGER.debug(
+ "Service clear_history called for: %i items for %s", items, requested_emails
+ )
+ for email, account_dict in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
if requested_emails and email not in requested_emails:
continue
- login_obj = account_dict['login_obj']
+ login_obj = account_dict["login_obj"]
return await AlexaAPI.clear_history(login_obj, items)
async def last_call_handler(call):
@@ -633,11 +693,10 @@ async def last_call_handler(call):
"""
requested_emails = call.data.get(ATTR_EMAIL)
_LOGGER.debug("Service update_last_called for: %s", requested_emails)
- for email, account_dict in (hass.data
- [DATA_ALEXAMEDIA]['accounts'].items()):
+ for email, account_dict in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
if requested_emails and email not in requested_emails:
continue
- login_obj = account_dict['login_obj']
+ login_obj = account_dict["login_obj"]
await update_last_called(login_obj)
async def ws_connect() -> WebsocketEchoClient:
@@ -647,18 +706,19 @@ async def ws_connect() -> WebsocketEchoClient:
"""
websocket: Optional[WebsocketEchoClient] = None
try:
- websocket = WebsocketEchoClient(login_obj,
- ws_handler,
- ws_open_handler,
- ws_close_handler,
- ws_error_handler)
- _LOGGER.debug("%s: Websocket created: %s", hide_email(email),
- websocket)
+ websocket = WebsocketEchoClient(
+ login_obj,
+ ws_handler,
+ ws_open_handler,
+ ws_close_handler,
+ ws_error_handler,
+ )
+ _LOGGER.debug("%s: Websocket created: %s", hide_email(email), websocket)
await websocket.async_run()
except BaseException as exception_:
- _LOGGER.debug("%s: Websocket creation failed: %s",
- hide_email(email),
- exception_)
+ _LOGGER.debug(
+ "%s: Websocket creation failed: %s", hide_email(email), exception_
+ )
return
return websocket
@@ -668,134 +728,168 @@ async def ws_handler(message_obj):
This allows push notifications from Alexa to update last_called
and media state.
"""
- command = (message_obj.json_payload['command']
- if isinstance(message_obj.json_payload, dict) and
- 'command' in message_obj.json_payload
- else None)
- json_payload = (message_obj.json_payload['payload']
- if isinstance(message_obj.json_payload, dict) and
- 'payload' in message_obj.json_payload
- else None)
+ command = (
+ message_obj.json_payload["command"]
+ if isinstance(message_obj.json_payload, dict)
+ and "command" in message_obj.json_payload
+ else None
+ )
+ json_payload = (
+ message_obj.json_payload["payload"]
+ if isinstance(message_obj.json_payload, dict)
+ and "payload" in message_obj.json_payload
+ else None
+ )
existing_serials = _existing_serials()
- if 'websocket_commands' not in (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]):
- (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_commands']) = {}
- seen_commands = (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_commands'])
+ if "websocket_commands" not in (hass.data[DATA_ALEXAMEDIA]["accounts"][email]):
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket_commands"]) = {}
+ seen_commands = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
+ "websocket_commands"
+ ]
if command and json_payload:
import time
- _LOGGER.debug("%s: Received websocket command: %s : %s",
- hide_email(email),
- command, hide_serial(json_payload))
+
+ _LOGGER.debug(
+ "%s: Received websocket command: %s : %s",
+ hide_email(email),
+ command,
+ hide_serial(json_payload),
+ )
serial = None
if command not in seen_commands:
seen_commands[command] = time.time()
- _LOGGER.debug(
- "Adding %s to seen_commands: %s",
- command,
- seen_commands)
- if ('dopplerId' in json_payload and
- 'deviceSerialNumber' in json_payload['dopplerId']):
- serial = (json_payload['dopplerId']['deviceSerialNumber'])
- elif ('key' in json_payload and 'entryId' in json_payload['key']
- and json_payload['key']['entryId'].find('#') != -1):
- serial = (json_payload['key']['entryId']).split('#')[2]
+ _LOGGER.debug("Adding %s to seen_commands: %s", command, seen_commands)
+ if (
+ "dopplerId" in json_payload
+ and "deviceSerialNumber" in json_payload["dopplerId"]
+ ):
+ serial = json_payload["dopplerId"]["deviceSerialNumber"]
+ elif (
+ "key" in json_payload
+ and "entryId" in json_payload["key"]
+ and json_payload["key"]["entryId"].find("#") != -1
+ ):
+ serial = (json_payload["key"]["entryId"]).split("#")[2]
else:
serial = None
- if command == 'PUSH_ACTIVITY':
+ if command == "PUSH_ACTIVITY":
# Last_Alexa Updated
last_called = {
- 'serialNumber': serial,
- 'timestamp': json_payload['timestamp']
+ "serialNumber": serial,
+ "timestamp": json_payload["timestamp"],
}
- if (serial and serial in existing_serials):
- await update_last_called(login_obj,
- last_called)
+ if serial and serial in existing_serials:
+ await update_last_called(login_obj, last_called)
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'push_activity': json_payload})
- elif command in ('PUSH_AUDIO_PLAYER_STATE', 'PUSH_MEDIA_CHANGE',
- 'PUSH_MEDIA_PROGRESS_CHANGE'):
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"push_activity": json_payload},
+ )
+ elif command in (
+ "PUSH_AUDIO_PLAYER_STATE",
+ "PUSH_MEDIA_CHANGE",
+ "PUSH_MEDIA_PROGRESS_CHANGE",
+ ):
# Player update/ Push_media from tune_in
- if (serial and serial in existing_serials):
- _LOGGER.debug("Updating media_player: %s",
- hide_serial(json_payload))
+ if serial and serial in existing_serials:
+ _LOGGER.debug(
+ "Updating media_player: %s", hide_serial(json_payload)
+ )
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'player_state': json_payload})
- elif command == 'PUSH_VOLUME_CHANGE':
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"player_state": json_payload},
+ )
+ elif command == "PUSH_VOLUME_CHANGE":
# Player volume update
- if (serial and serial in existing_serials):
- _LOGGER.debug("Updating media_player volume: %s",
- hide_serial(json_payload))
+ if serial and serial in existing_serials:
+ _LOGGER.debug(
+ "Updating media_player volume: %s", hide_serial(json_payload)
+ )
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'player_state': json_payload})
- elif command in ('PUSH_DOPPLER_CONNECTION_CHANGE',
- 'PUSH_EQUALIZER_STATE_CHANGE'):
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"player_state": json_payload},
+ )
+ elif command in (
+ "PUSH_DOPPLER_CONNECTION_CHANGE",
+ "PUSH_EQUALIZER_STATE_CHANGE",
+ ):
# Player availability update
- if (serial and serial in existing_serials):
- _LOGGER.debug("Updating media_player availability %s",
- hide_serial(json_payload))
+ if serial and serial in existing_serials:
+ _LOGGER.debug(
+ "Updating media_player availability %s",
+ hide_serial(json_payload),
+ )
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'player_state': json_payload})
- elif command == 'PUSH_BLUETOOTH_STATE_CHANGE':
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"player_state": json_payload},
+ )
+ elif command == "PUSH_BLUETOOTH_STATE_CHANGE":
# Player bluetooth update
- bt_event = json_payload['bluetoothEvent']
- bt_success = json_payload['bluetoothEventSuccess']
- if (serial and serial in existing_serials and
- bt_success and bt_event and bt_event in [
- 'DEVICE_CONNECTED',
- 'DEVICE_DISCONNECTED']):
- _LOGGER.debug("Updating media_player bluetooth %s",
- hide_serial(json_payload))
- bluetooth_state = await update_bluetooth_state(login_obj,
- serial)
+ bt_event = json_payload["bluetoothEvent"]
+ bt_success = json_payload["bluetoothEventSuccess"]
+ if (
+ serial
+ and serial in existing_serials
+ and bt_success
+ and bt_event
+ and bt_event in ["DEVICE_CONNECTED", "DEVICE_DISCONNECTED"]
+ ):
+ _LOGGER.debug(
+ "Updating media_player bluetooth %s", hide_serial(json_payload)
+ )
+ bluetooth_state = await update_bluetooth_state(login_obj, serial)
# _LOGGER.debug("bluetooth_state %s",
# hide_serial(bluetooth_state))
if bluetooth_state:
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'bluetooth_change': bluetooth_state})
- elif command == 'PUSH_MEDIA_QUEUE_CHANGE':
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"bluetooth_change": bluetooth_state},
+ )
+ elif command == "PUSH_MEDIA_QUEUE_CHANGE":
# Player availability update
- if (serial and serial in existing_serials):
- _LOGGER.debug("Updating media_player queue %s",
- hide_serial(json_payload))
+ if serial and serial in existing_serials:
+ _LOGGER.debug(
+ "Updating media_player queue %s", hide_serial(json_payload)
+ )
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'queue_state': json_payload})
- elif command == 'PUSH_NOTIFICATION_CHANGE':
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"queue_state": json_payload},
+ )
+ elif command == "PUSH_NOTIFICATION_CHANGE":
# Player update
await process_notifications(login_obj)
- if (serial and serial in existing_serials):
- _LOGGER.debug("Updating mediaplayer notifications: %s",
- hide_serial(json_payload))
+ if serial and serial in existing_serials:
+ _LOGGER.debug(
+ "Updating mediaplayer notifications: %s",
+ hide_serial(json_payload),
+ )
hass.bus.async_fire(
- f'{DOMAIN}_{hide_email(email)}'[0:32],
- {'notification_update': json_payload})
- if (serial and serial not in existing_serials
- and serial not in (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['excluded'].keys())):
+ f"{DOMAIN}_{hide_email(email)}"[0:32],
+ {"notification_update": json_payload},
+ )
+ if (
+ serial
+ and serial not in existing_serials
+ and serial
+ not in (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"].keys()
+ )
+ ):
_LOGGER.debug("Discovered new media_player %s", serial)
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['new_devices']) = True
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"]) = True
await update_devices(login_obj, no_throttle=True)
async def ws_open_handler():
"""Handle websocket open."""
import time
+
email: Text = login_obj.email
- _LOGGER.debug("%s: Websocket succesfully connected",
- hide_email(email))
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocketerror']) = 0 # set errors to 0
- (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_lastattempt']) = time.time()
+ _LOGGER.debug("%s: Websocket succesfully connected", hide_email(email))
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]
+ ) = 0 # set errors to 0
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket_lastattempt"]
+ ) = time.time()
async def ws_close_handler():
"""Handle websocket close.
@@ -804,33 +898,39 @@ async def ws_close_handler():
"""
from asyncio import sleep
import time
+
email: Text = login_obj.email
- errors: int = (hass.data
- [DATA_ALEXAMEDIA]['accounts'][email]['websocketerror'])
+ errors: int = (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"])
delay: int = 5 * 2 ** errors
- last_attempt = (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_lastattempt'])
+ last_attempt = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
+ "websocket_lastattempt"
+ ]
now = time.time()
- if ((now - last_attempt) < delay):
+ if (now - last_attempt) < delay:
return
- while (errors < 5 and not (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket'])):
- _LOGGER.debug("%s: Websocket closed; reconnect #%i in %is",
- hide_email(email),
- errors,
- delay)
+ while errors < 5 and not (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
+ ):
+ _LOGGER.debug(
+ "%s: Websocket closed; reconnect #%i in %is",
+ hide_email(email),
+ errors,
+ delay,
+ )
await sleep(delay)
- (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_lastattempt']) = time.time()
- (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket']) = await ws_connect()
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket_lastattempt"]
+ ) = time.time()
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
+ ) = await ws_connect()
errors += 1
delay = 5 * 2 ** errors
else:
- _LOGGER.debug("%s: Websocket closed; retries exceeded; polling",
- hide_email(email))
- (hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket']) = None
+ _LOGGER.debug(
+ "%s: Websocket closed; retries exceeded; polling", hide_email(email)
+ )
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]) = None
await update_devices(login_obj, no_throttle=True)
async def ws_error_handler(message):
@@ -841,15 +941,13 @@ async def ws_error_handler(message):
specification, websockets will issue a close after every error.
"""
email = login_obj.email
- errors = (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocketerror'])
- _LOGGER.debug("%s: Received websocket error #%i %s",
- hide_email(email),
- errors,
- message)
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['websocket']) = None
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocketerror']) = errors + 1
+ errors = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]
+ _LOGGER.debug(
+ "%s: Received websocket error #%i %s", hide_email(email), errors, message
+ )
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]) = None
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]) = errors + 1
+
config = config_entry.data
email = config.get(CONF_EMAIL)
include = config.get(CONF_INCLUDE_DEVICES)
@@ -859,32 +957,36 @@ async def ws_error_handler(message):
if isinstance(config.get(CONF_SCAN_INTERVAL), timedelta)
else config.get(CONF_SCAN_INTERVAL)
)
- if 'login_obj' not in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- ['login_obj']) = login_obj
- if 'devices' not in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- ['devices']) = {'media_player': {}}
- if 'excluded' not in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- ['excluded']) = {}
- if 'entities' not in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- ['entities']) = {'media_player': {}}
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['new_devices']) = True # force initial update
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocket_lastattempt']) = 0
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocketerror']) = 0 # set errors to 0
- (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['websocket']) = \
- await ws_connect()
+ if "login_obj" not in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["login_obj"]) = login_obj
+ if "devices" not in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"]) = {
+ "media_player": {}
+ }
+ if "excluded" not in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"]) = {}
+ if "entities" not in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"]) = {
+ "media_player": {}
+ }
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"]
+ ) = True # force initial update
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket_lastattempt"]) = 0
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocketerror"]
+ ) = 0 # set errors to 0
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]) = await ws_connect()
await update_devices(login_obj, no_throttle=True)
- hass.services.async_register(DOMAIN, SERVICE_UPDATE_LAST_CALLED,
- last_call_handler,
- schema=LAST_CALL_UPDATE_SCHEMA)
- hass.services.async_register(DOMAIN, SERVICE_CLEAR_HISTORY, clear_history,
- schema=CLEAR_HISTORY_SCHEMA)
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_UPDATE_LAST_CALLED,
+ last_call_handler,
+ schema=LAST_CALL_UPDATE_SCHEMA,
+ )
+ hass.services.async_register(
+ DOMAIN, SERVICE_CLEAR_HISTORY, clear_history, schema=CLEAR_HISTORY_SCHEMA
+ )
# Clear configurator. We delay till here to avoid leaving a modal orphan
await clear_configurator(hass, email)
return True
@@ -898,36 +1000,38 @@ async def async_unload_entry(hass, entry) -> bool:
await hass.config_entries.async_forward_entry_unload(entry, component)
# notify has to be handled manually as the forward does not work yet
from .notify import async_unload_entry
+
await async_unload_entry(hass, entry)
- email = entry.data['email']
+ email = entry.data["email"]
await close_connections(hass, email)
await clear_configurator(hass, email)
- hass.data[DATA_ALEXAMEDIA]['accounts'].pop(email)
+ hass.data[DATA_ALEXAMEDIA]["accounts"].pop(email)
_LOGGER.debug("Unloaded entry for %s", hide_email(email))
return True
async def clear_configurator(hass, email: Text) -> None:
"""Clear open configurators for email."""
- if email not in hass.data[DATA_ALEXAMEDIA]['accounts']:
+ if email not in hass.data[DATA_ALEXAMEDIA]["accounts"]:
return
- if 'configurator' in hass.data[DATA_ALEXAMEDIA]['accounts'][email]:
- for config_id in (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- ['configurator']):
+ if "configurator" in hass.data[DATA_ALEXAMEDIA]["accounts"][email]:
+ for config_id in hass.data[DATA_ALEXAMEDIA]["accounts"][email]["configurator"]:
configurator = hass.components.configurator
configurator.async_request_done(config_id)
- hass.data[DATA_ALEXAMEDIA]['accounts'][email]['configurator'] = []
+ hass.data[DATA_ALEXAMEDIA]["accounts"][email]["configurator"] = []
async def close_connections(hass, email: Text) -> None:
"""Clear open aiohttp connections for email."""
- if (email not in hass.data[DATA_ALEXAMEDIA]['accounts'] or
- 'login_obj' not in hass.data[DATA_ALEXAMEDIA]['accounts'][email]):
+ if (
+ email not in hass.data[DATA_ALEXAMEDIA]["accounts"]
+ or "login_obj" not in hass.data[DATA_ALEXAMEDIA]["accounts"][email]
+ ):
return
- account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][email]
- login_obj = account_dict['login_obj']
+ account_dict = hass.data[DATA_ALEXAMEDIA]["accounts"][email]
+ login_obj = account_dict["login_obj"]
await login_obj.close()
- _LOGGER.debug("%s: Connection closed: %s",
- hide_email(email),
- login_obj._session.closed)
+ _LOGGER.debug(
+ "%s: Connection closed: %s", hide_email(email), login_obj._session.closed
+ )
await clear_configurator(hass, email)
diff --git a/custom_components/alexa_media/alarm_control_panel.py b/custom_components/alexa_media/alarm_control_panel.py
index 568d7cd2..76fefa20 100644
--- a/custom_components/alexa_media/alarm_control_panel.py
+++ b/custom_components/alexa_media/alarm_control_panel.py
@@ -15,9 +15,16 @@
from homeassistant.const import STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED
from homeassistant.helpers.event import async_call_later
-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
+from . import (
+ CONF_EMAIL,
+ CONF_EXCLUDE_DEVICES,
+ CONF_INCLUDE_DEVICES,
+ DATA_ALEXAMEDIA,
+ DOMAIN as ALEXA_DOMAIN,
+ MIN_TIME_BETWEEN_FORCED_SCANS,
+ MIN_TIME_BETWEEN_SCANS,
+ hide_email,
+)
from .helpers import _catch_login_errors, add_devices, retry_async
_LOGGER = logging.getLogger(__name__)
diff --git a/custom_components/alexa_media/config_flow.py b/custom_components/alexa_media/config_flow.py
index 9f7b4e78..ab85b8b1 100644
--- a/custom_components/alexa_media/config_flow.py
+++ b/custom_components/alexa_media/config_flow.py
@@ -7,21 +7,31 @@
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 logging
from collections import OrderedDict
+import logging
from typing import Text
-import voluptuous as vol
from alexapy import AlexapyConnectionError
from homeassistant import config_entries
-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.core import callback
from homeassistant.helpers import config_validation as cv
+import voluptuous as vol
-from .const import (CONF_DEBUG, CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES,
- DATA_ALEXAMEDIA, DOMAIN)
+from .const import (
+ CONF_DEBUG,
+ CONF_EXCLUDE_DEVICES,
+ CONF_INCLUDE_DEVICES,
+ DATA_ALEXAMEDIA,
+ DOMAIN,
+)
_LOGGER = logging.getLogger(__name__)
@@ -164,8 +174,8 @@ async def async_step_user(self, user_input=None):
if isinstance(user_input[CONF_INCLUDE_DEVICES], str):
self.config[CONF_INCLUDE_DEVICES] = (
user_input[CONF_INCLUDE_DEVICES].split(",")
- if CONF_INCLUDE_DEVICES in user_input and
- user_input[CONF_INCLUDE_DEVICES] != ""
+ if CONF_INCLUDE_DEVICES in user_input
+ and user_input[CONF_INCLUDE_DEVICES] != ""
else []
)
else:
@@ -173,8 +183,8 @@ async def async_step_user(self, user_input=None):
if isinstance(user_input[CONF_EXCLUDE_DEVICES], str):
self.config[CONF_EXCLUDE_DEVICES] = (
user_input[CONF_EXCLUDE_DEVICES].split(",")
- if CONF_EXCLUDE_DEVICES in user_input and
- user_input[CONF_EXCLUDE_DEVICES] != ""
+ if CONF_EXCLUDE_DEVICES in user_input
+ and user_input[CONF_EXCLUDE_DEVICES] != ""
else []
)
else:
@@ -230,8 +240,7 @@ async def async_step_process(self, user_input=None):
try:
await self.login.login(data=user_input)
except AlexapyConnectionError:
- return await self._show_form(
- errors={"base": "connection_error"})
+ return await self._show_form(errors={"base": "connection_error"})
except BaseException as ex:
_LOGGER.warning("Unknown error: %s", ex)
return await self._show_form(errors={"base": "unknown_error"})
@@ -276,8 +285,7 @@ async def _test_login(self):
):
_LOGGER.debug("Creating config_flow to request 2FA")
message = "> {0}".format(
- login.status["error_message"]
- if "error_message" in login.status else ""
+ login.status["error_message"] if "error_message" in login.status else ""
)
return await self._show_form(
"twofactor",
@@ -294,8 +302,7 @@ async def _test_login(self):
and login.status["claimspicker_required"]
):
error_message = "> {0}".format(
- login.status["error_message"]
- if "error_message" in login.status else ""
+ login.status["error_message"] if "error_message" in login.status else ""
)
_LOGGER.debug("Creating config_flow to select verification method")
claimspicker_message = login.status["claimspicker_message"]
@@ -306,8 +313,9 @@ async def _test_login(self):
placeholders={
"email": login.email,
"url": login.url,
- "message": "> {0}\n> {1}".format(claimspicker_message,
- error_message),
+ "message": "> {0}\n> {1}".format(
+ claimspicker_message, error_message
+ ),
},
)
elif (
@@ -316,8 +324,7 @@ async def _test_login(self):
):
_LOGGER.debug("Creating config_flow to select OTA method")
error_message = (
- login.status["error_message"]
- if "error_message" in login.status else ""
+ login.status["error_message"] if "error_message" in login.status else ""
)
authselect_message = login.status["authselect_message"]
return await self._show_form(
@@ -326,8 +333,7 @@ async def _test_login(self):
placeholders={
"email": login.email,
"url": login.url,
- "message": "> {0}\n> {1}".format(authselect_message,
- error_message),
+ "message": "> {0}\n> {1}".format(authselect_message, error_message),
},
)
elif (
diff --git a/custom_components/alexa_media/const.py b/custom_components/alexa_media/const.py
index 5f363d28..8bb0b183 100644
--- a/custom_components/alexa_media/const.py
+++ b/custom_components/alexa_media/const.py
@@ -13,32 +13,26 @@
PROJECT_URL = "https://github.com/custom-components/alexa_media_player/"
ISSUE_URL = "{}issues".format(PROJECT_URL)
-DOMAIN = 'alexa_media'
-DATA_ALEXAMEDIA = 'alexa_media'
+DOMAIN = "alexa_media"
+DATA_ALEXAMEDIA = "alexa_media"
PLAY_SCAN_INTERVAL = 20
SCAN_INTERVAL = timedelta(seconds=60)
MIN_TIME_BETWEEN_SCANS = SCAN_INTERVAL
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
-ALEXA_COMPONENTS = [
- 'media_player',
- 'notify',
- 'alarm_control_panel',
- 'switch',
- 'sensor'
-]
+ALEXA_COMPONENTS = ["media_player", "notify", "alarm_control_panel", "switch", "sensor"]
-CONF_ACCOUNTS = 'accounts'
-CONF_DEBUG = 'debug'
-CONF_INCLUDE_DEVICES = 'include_devices'
-CONF_EXCLUDE_DEVICES = 'exclude_devices'
+CONF_ACCOUNTS = "accounts"
+CONF_DEBUG = "debug"
+CONF_INCLUDE_DEVICES = "include_devices"
+CONF_EXCLUDE_DEVICES = "exclude_devices"
-SERVICE_CLEAR_HISTORY = 'clear_history'
-SERVICE_UPDATE_LAST_CALLED = 'update_last_called'
-ATTR_MESSAGE = 'message'
-ATTR_EMAIL = 'email'
-ATTR_NUM_ENTRIES = 'entries'
+SERVICE_CLEAR_HISTORY = "clear_history"
+SERVICE_UPDATE_LAST_CALLED = "update_last_called"
+ATTR_MESSAGE = "message"
+ATTR_EMAIL = "email"
+ATTR_NUM_ENTRIES = "entries"
STARTUP = """
-------------------------------------------------------------------
{}
@@ -47,4 +41,6 @@
If you have any issues with this you need to open an issue here:
{}
-------------------------------------------------------------------
-""".format(DOMAIN, __version__, ISSUE_URL)
+""".format(
+ DOMAIN, __version__, ISSUE_URL
+)
diff --git a/custom_components/alexa_media/helpers.py b/custom_components/alexa_media/helpers.py
index 5ca9466b..dd016131 100644
--- a/custom_components/alexa_media/helpers.py
+++ b/custom_components/alexa_media/helpers.py
@@ -20,19 +20,23 @@
_LOGGER = logging.getLogger(__name__)
-async def add_devices(account: Text,
- devices: List[EntityComponent],
- add_devices_callback: Callable,
- include_filter: List[Text] = [],
- exclude_filter: List[Text] = []) -> bool:
+async def add_devices(
+ account: Text,
+ devices: List[EntityComponent],
+ add_devices_callback: Callable,
+ include_filter: List[Text] = [],
+ exclude_filter: List[Text] = [],
+) -> bool:
"""Add devices using add_devices_callback."""
new_devices = []
for device in devices:
- if (include_filter and device.name not in include_filter
- or exclude_filter and device.name in exclude_filter):
- _LOGGER.debug("%s: Excluding device: %s",
- account,
- device)
+ if (
+ include_filter
+ and device.name not in include_filter
+ or exclude_filter
+ and device.name in exclude_filter
+ ):
+ _LOGGER.debug("%s: Excluding device: %s", account, device)
continue
new_devices.append(device)
devices = new_devices
@@ -44,30 +48,23 @@ async def add_devices(account: Text,
except HomeAssistantError as exception_:
message = exception_.message # type: str
if message.startswith("Entity id already exists"):
- _LOGGER.debug("%s: Device already added: %s",
- account,
- message)
+ _LOGGER.debug("%s: Device already added: %s", account, message)
else:
- _LOGGER.debug("%s: Unable to add devices: %s : %s",
- account,
- devices,
- message)
+ _LOGGER.debug(
+ "%s: Unable to add devices: %s : %s", account, devices, message
+ )
except BaseException as ex:
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
+ template = "An exception of type {0} occurred." " Arguments:\n{1!r}"
message = template.format(type(ex).__name__, ex.args)
- _LOGGER.debug("%s: Unable to add devices: %s",
- account,
- message)
+ _LOGGER.debug("%s: Unable to add devices: %s", account, message)
else:
return True
return False
-def retry_async(limit: int = 5,
- delay: float = 1,
- catch_exceptions: bool = True
- ) -> Callable:
+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
@@ -87,22 +84,25 @@ def retry_async(limit: int = 5,
Wrapped function.
"""
+
def wrap(func) -> Callable:
import functools
import asyncio
+
@functools.wraps(func)
async def wrapper(*args, **kwargs) -> Any:
_LOGGER.debug(
"%s.%s: Trying with limit %s delay %s catch_exceptions %s",
- func.__module__[func.__module__.find('.')+1:],
+ func.__module__[func.__module__.find(".") + 1 :],
func.__name__,
limit,
delay,
- catch_exceptions)
+ catch_exceptions,
+ )
retries: int = 0
result: bool = False
next_try: int = 0
- while (not result and retries < limit):
+ while not result and retries < limit:
if retries != 0:
next_try = delay * 2 ** retries
await asyncio.sleep(next_try)
@@ -112,75 +112,75 @@ async def wrapper(*args, **kwargs) -> Any:
except Exception as ex: # pylint: disable=broad-except
if not catch_exceptions:
raise
- template = ("An exception of type {0} occurred."
- " Arguments:\n{1!r}")
+ template = "An exception of type {0} occurred." " Arguments:\n{1!r}"
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.__module__[func.__module__.find(".") + 1 :],
func.__name__,
- message)
+ message,
+ )
_LOGGER.debug(
"%s.%s: Try: %s/%s after waiting %s seconds result: %s",
- func.__module__[func.__module__.find('.')+1:],
+ func.__module__[func.__module__.find(".") + 1 :],
func.__name__,
retries,
limit,
next_try,
- result
- )
+ 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}")
+ 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)
+ _LOGGER.debug(
+ "%s.%s: detected bad login: %s",
+ func.__module__[func.__module__.find(".") + 1 :],
+ func.__name__,
+ message,
+ )
instance = args[0]
- if hasattr(instance, '_login'):
+ if hasattr(instance, "_login"):
login = instance._login
email = login.email
hass = instance.hass if instance.hass else None
- if (hass and (
- 'configurator' not in (hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email])
- or 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'])
+ if hass and (
+ "configurator"
+ not in (hass.data[DATA_ALEXAMEDIA]["accounts"][email])
+ or 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))
+ hide_email(email),
+ )
await login.login_with_cookie()
- await test_login_status(hass,
- config_entry, login,
- callback)
+ await test_login_status(hass, config_entry, login, callback)
return None
return result
+
return wrapper
diff --git a/custom_components/alexa_media/media_player.py b/custom_components/alexa_media/media_player.py
index 11a04ffd..e419b590 100644
--- a/custom_components/alexa_media/media_player.py
+++ b/custom_components/alexa_media/media_player.py
@@ -14,76 +14,93 @@
from homeassistant import util
from homeassistant.components.media_player import MediaPlayerDevice
from homeassistant.components.media_player.const import (
- 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)
+ 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.event import async_call_later
-from . import CONF_EMAIL, DATA_ALEXAMEDIA
-from . import DOMAIN as ALEXA_DOMAIN
-from . import (MIN_TIME_BETWEEN_FORCED_SCANS, MIN_TIME_BETWEEN_SCANS,
- hide_email, hide_serial)
+from . import (
+ CONF_EMAIL,
+ DATA_ALEXAMEDIA,
+ DOMAIN as ALEXA_DOMAIN,
+ MIN_TIME_BETWEEN_FORCED_SCANS,
+ MIN_TIME_BETWEEN_SCANS,
+ hide_email,
+ hide_serial,
+)
from .const import PLAY_SCAN_INTERVAL
from .helpers import _catch_login_errors, add_devices, retry_async
-SUPPORT_ALEXA = (SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK |
- SUPPORT_NEXT_TRACK | SUPPORT_STOP |
- SUPPORT_VOLUME_SET | SUPPORT_PLAY |
- SUPPORT_PLAY_MEDIA | SUPPORT_TURN_OFF | SUPPORT_TURN_ON |
- SUPPORT_VOLUME_MUTE | SUPPORT_PAUSE |
- SUPPORT_SELECT_SOURCE | SUPPORT_SHUFFLE_SET)
+SUPPORT_ALEXA = (
+ SUPPORT_PAUSE
+ | SUPPORT_PREVIOUS_TRACK
+ | SUPPORT_NEXT_TRACK
+ | SUPPORT_STOP
+ | SUPPORT_VOLUME_SET
+ | SUPPORT_PLAY
+ | SUPPORT_PLAY_MEDIA
+ | SUPPORT_TURN_OFF
+ | SUPPORT_TURN_ON
+ | SUPPORT_VOLUME_MUTE
+ | SUPPORT_PAUSE
+ | SUPPORT_SELECT_SOURCE
+ | SUPPORT_SHUFFLE_SET
+)
_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,
- discovery_info=None):
+async def async_setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up the Alexa media player platform."""
devices = [] # type: List[AlexaClient]
account = config[CONF_EMAIL]
- account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][account]
- for key, device in account_dict['devices']['media_player'].items():
- if key not in account_dict['entities']['media_player']:
- alexa_client = AlexaClient(device,
- account_dict['login_obj']
- )
+ account_dict = hass.data[DATA_ALEXAMEDIA]["accounts"][account]
+ for key, device in account_dict["devices"]["media_player"].items():
+ if key not in account_dict["entities"]["media_player"]:
+ alexa_client = AlexaClient(device, account_dict["login_obj"])
await alexa_client.init(device)
devices.append(alexa_client)
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [account]
- ['entities']
- ['media_player'][key]) = alexa_client
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][account]["entities"][
+ "media_player"
+ ][key]
+ ) = alexa_client
else:
- _LOGGER.debug("%s: Skipping already added device: %s:%s",
- hide_email(account),
- hide_serial(key),
- alexa_client
- )
- return await add_devices(hide_email(account),
- devices,
- add_devices_callback)
+ _LOGGER.debug(
+ "%s: Skipping already added device: %s:%s",
+ hide_email(account),
+ hide_serial(key),
+ alexa_client,
+ )
+ return await add_devices(hide_email(account), devices, add_devices_callback)
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the Alexa media player platform by config_entry."""
return await async_setup_platform(
- hass,
- config_entry.data,
- async_add_devices,
- discovery_info=None)
+ hass, config_entry.data, async_add_devices, discovery_info=None
+ )
async def async_unload_entry(hass, entry) -> bool:
"""Unload a config entry."""
account = entry.data[CONF_EMAIL]
- account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][account]
- for device in account_dict['entities']['media_player'].values():
+ account_dict = hass.data[DATA_ALEXAMEDIA]["accounts"][account]
+ for device in account_dict["entities"]["media_player"].values():
await device.async_remove()
return True
@@ -154,8 +171,8 @@ async def async_added_to_hass(self):
"""Perform tasks after loading."""
# Register event handler on bus
self._listener = self.hass.bus.async_listen(
- f'{ALEXA_DOMAIN}_{hide_email(self._login.email)}'[0:32],
- self._handle_event)
+ f"{ALEXA_DOMAIN}_{hide_email(self._login.email)}"[0:32], self._handle_event
+ )
async def async_will_remove_from_hass(self):
"""Prepare to remove entity."""
@@ -178,23 +195,33 @@ async def _handle_event(self, event):
is self.update will pull data from Amazon, while schedule_update
assumes the MediaClient state is already updated.
"""
+
async def _refresh_if_no_audiopush(already_refreshed=False):
email = self._login.email
- seen_commands = ((self.hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_commands'].keys()
- if 'websocket_commands' in (
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]) else None))
- if (not already_refreshed and seen_commands and
- ('PUSH_AUDIO_PLAYER_STATE' not in seen_commands
- and 'PUSH_MEDIA_CHANGE' not in seen_commands)):
+ seen_commands = (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][email][
+ "websocket_commands"
+ ].keys()
+ if "websocket_commands"
+ in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email])
+ else None
+ )
+ if (
+ not already_refreshed
+ and seen_commands
+ and (
+ "PUSH_AUDIO_PLAYER_STATE" not in seen_commands
+ and "PUSH_MEDIA_CHANGE" not in seen_commands
+ )
+ ):
# force refresh if player_state update not found, see #397
_LOGGER.debug(
"%s: No PUSH_AUDIO_PLAYER_STATE in %s; forcing refresh",
hide_email(email),
- seen_commands)
+ seen_commands,
+ )
await self.async_update()
+
try:
if not self.enabled:
return
@@ -202,103 +229,126 @@ async def _refresh_if_no_audiopush(already_refreshed=False):
pass
already_refreshed = False
event_serial = None
- if 'last_called_change' in event.data:
- event_serial = (event.data['last_called_change']['serialNumber']
- if event.data['last_called_change'] else None)
- elif 'bluetooth_change' in event.data:
+ if "last_called_change" in event.data:
+ event_serial = (
+ event.data["last_called_change"]["serialNumber"]
+ if event.data["last_called_change"]
+ else None
+ )
+ elif "bluetooth_change" in event.data:
event_serial = (
- event.data['bluetooth_change']['deviceSerialNumber']
- if event.data['bluetooth_change'] else None)
- elif 'player_state' in event.data:
- event_serial = (event.data['player_state']['dopplerId']
- ['deviceSerialNumber']
- if event.data['player_state'] else None)
- elif 'queue_state' in event.data:
- event_serial = (event.data['queue_state']['dopplerId']
- ['deviceSerialNumber']
- if event.data['queue_state'] else None)
+ event.data["bluetooth_change"]["deviceSerialNumber"]
+ if event.data["bluetooth_change"]
+ else None
+ )
+ elif "player_state" in event.data:
+ event_serial = (
+ event.data["player_state"]["dopplerId"]["deviceSerialNumber"]
+ if event.data["player_state"]
+ else None
+ )
+ elif "queue_state" in event.data:
+ event_serial = (
+ event.data["queue_state"]["dopplerId"]["deviceSerialNumber"]
+ if event.data["queue_state"]
+ else None
+ )
if not event_serial:
return
self._available = True
self.async_schedule_update_ha_state()
- if 'last_called_change' in event.data:
- if (event_serial == self.device_serial_number or
- any(item['serialNumber'] ==
- event_serial for item in self._app_device_list)):
- _LOGGER.debug("%s is last_called: %s", self.name,
- hide_serial(self.device_serial_number))
+ if "last_called_change" in event.data:
+ if event_serial == self.device_serial_number or any(
+ item["serialNumber"] == event_serial for item in self._app_device_list
+ ):
+ _LOGGER.debug(
+ "%s is last_called: %s",
+ self.name,
+ hide_serial(self.device_serial_number),
+ )
self._last_called = True
else:
self._last_called = False
- if (self.hass and self.async_schedule_update_ha_state):
+ if self.hass and self.async_schedule_update_ha_state:
email = self._login.email
- force_refresh = not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocket'])
- self.async_schedule_update_ha_state(
- force_refresh=force_refresh)
- elif 'bluetooth_change' in event.data:
+ force_refresh = not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
+ )
+ self.async_schedule_update_ha_state(force_refresh=force_refresh)
+ elif "bluetooth_change" in event.data:
if event_serial == self.device_serial_number:
- _LOGGER.debug("%s bluetooth_state update: %s",
- self.name,
- hide_serial(event.data['bluetooth_change']))
- self._bluetooth_state = event.data['bluetooth_change']
+ _LOGGER.debug(
+ "%s bluetooth_state update: %s",
+ self.name,
+ hide_serial(event.data["bluetooth_change"]),
+ )
+ self._bluetooth_state = event.data["bluetooth_change"]
# the setting of bluetooth_state is not consistent as this
# takes from the event instead of the hass storage. We're
# setting the value twice. Architectually we should have a
# single authorative source of truth.
self._source = await self._get_source()
self._source_list = await self._get_source_list()
- if (self.hass and self.async_schedule_update_ha_state):
+ if self.hass and self.async_schedule_update_ha_state:
self.async_schedule_update_ha_state()
- elif 'player_state' in event.data:
- player_state = event.data['player_state']
+ elif "player_state" in event.data:
+ player_state = event.data["player_state"]
if event_serial == self.device_serial_number:
- if 'audioPlayerState' in player_state:
- _LOGGER.debug("%s state update: %s",
- self.name,
- player_state['audioPlayerState'])
+ if "audioPlayerState" in player_state:
+ _LOGGER.debug(
+ "%s state update: %s",
+ self.name,
+ player_state["audioPlayerState"],
+ )
await self.async_update()
already_refreshed = True
# refresh is necessary to pull all data
- elif 'mediaReferenceId' in player_state:
- _LOGGER.debug("%s media update: %s",
- self.name,
- player_state['mediaReferenceId'])
+ elif "mediaReferenceId" in player_state:
+ _LOGGER.debug(
+ "%s media update: %s",
+ self.name,
+ player_state["mediaReferenceId"],
+ )
await self.async_update()
already_refreshed = True
# refresh is necessary to pull all data
- elif 'volumeSetting' in player_state:
- _LOGGER.debug("%s volume updated: %s",
- self.name,
- player_state['volumeSetting'])
- self._media_vol_level = player_state['volumeSetting']/100
- if (self.hass and self.async_schedule_update_ha_state):
+ elif "volumeSetting" in player_state:
+ _LOGGER.debug(
+ "%s volume updated: %s",
+ self.name,
+ player_state["volumeSetting"],
+ )
+ self._media_vol_level = player_state["volumeSetting"] / 100
+ if self.hass and self.async_schedule_update_ha_state:
self.async_schedule_update_ha_state()
- elif 'dopplerConnectionState' in player_state:
- self._available = (player_state['dopplerConnectionState']
- == "ONLINE")
- if (self.hass and self.async_schedule_update_ha_state):
+ elif "dopplerConnectionState" in player_state:
+ self._available = player_state["dopplerConnectionState"] == "ONLINE"
+ if self.hass and self.async_schedule_update_ha_state:
self.async_schedule_update_ha_state()
await _refresh_if_no_audiopush(already_refreshed)
- if 'queue_state' in event.data:
- queue_state = event.data['queue_state']
+ if "queue_state" in event.data:
+ queue_state = event.data["queue_state"]
if event_serial == self.device_serial_number:
- if ('trackOrderChanged' in queue_state and
- not queue_state['trackOrderChanged'] and
- 'loopMode' in queue_state):
- self._repeat = (queue_state['loopMode']
- == 'LOOP_QUEUE')
- _LOGGER.debug("%s repeat updated to: %s %s",
- self.name,
- self._repeat,
- queue_state['loopMode'])
- elif 'playBackOrder' in queue_state:
- self._shuffle = (queue_state['playBackOrder']
- == 'SHUFFLE_ALL')
- _LOGGER.debug("%s shuffle updated to: %s %s",
- self.name,
- self._shuffle,
- queue_state['playBackOrder'])
+ if (
+ "trackOrderChanged" in queue_state
+ and not queue_state["trackOrderChanged"]
+ and "loopMode" in queue_state
+ ):
+ self._repeat = queue_state["loopMode"] == "LOOP_QUEUE"
+ _LOGGER.debug(
+ "%s repeat updated to: %s %s",
+ self.name,
+ self._repeat,
+ queue_state["loopMode"],
+ )
+ elif "playBackOrder" in queue_state:
+ self._shuffle = queue_state["playBackOrder"] == "SHUFFLE_ALL"
+ _LOGGER.debug(
+ "%s shuffle updated to: %s %s",
+ self.name,
+ self._shuffle,
+ queue_state["playBackOrder"],
+ )
await _refresh_if_no_audiopush(already_refreshed)
async def _clear_media_details(self):
@@ -317,11 +367,11 @@ async def _clear_media_details(self):
async def _set_authentication_details(self, auth):
"""Set Authentication based off auth."""
- self._authenticated = auth['authenticated']
- self._can_access_prime_music = auth['canAccessPrimeMusicContent']
- self._customer_email = auth['customerEmail']
- self._customer_id = auth['customerId']
- self._customer_name = auth['customerName']
+ self._authenticated = auth["authenticated"]
+ self._can_access_prime_music = auth["canAccessPrimeMusicContent"]
+ self._customer_email = auth["customerEmail"]
+ self._customer_id = auth["customerId"]
+ self._customer_name = auth["customerName"]
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
@_catch_login_errors
@@ -340,35 +390,41 @@ async def refresh(self, device=None):
"""
if device is not None:
self._device = device
- self._device_name = device['accountName']
- self._device_family = device['deviceFamily']
- self._device_type = device['deviceType']
- self._device_serial_number = device['serialNumber']
- self._app_device_list = device['appDeviceList']
- self._device_owner_customer_id = device['deviceOwnerCustomerId']
- self._software_version = device['softwareVersion']
- self._available = device['online']
- self._capabilities = device['capabilities']
- self._cluster_members = device['clusterMembers']
- self._parent_clusters = device['parentClusters']
- self._bluetooth_state = device['bluetooth_state']
- self._locale = device['locale'] if 'locale' in device else 'en-US'
- self._timezone = (device['timeZoneId']
- if 'timeZoneId' in device else 'UTC')
- self._dnd = device['dnd'] if 'dnd' in device else None
- await self._set_authentication_details(device['auth_info'])
+ self._device_name = device["accountName"]
+ self._device_family = device["deviceFamily"]
+ self._device_type = device["deviceType"]
+ self._device_serial_number = device["serialNumber"]
+ self._app_device_list = device["appDeviceList"]
+ self._device_owner_customer_id = device["deviceOwnerCustomerId"]
+ self._software_version = device["softwareVersion"]
+ self._available = device["online"]
+ self._capabilities = device["capabilities"]
+ self._cluster_members = device["clusterMembers"]
+ self._parent_clusters = device["parentClusters"]
+ self._bluetooth_state = device["bluetooth_state"]
+ self._locale = device["locale"] if "locale" in device else "en-US"
+ self._timezone = device["timeZoneId"] if "timeZoneId" in device else "UTC"
+ self._dnd = device["dnd"] if "dnd" in device else None
+ await self._set_authentication_details(device["auth_info"])
session = None
if self._available:
_LOGGER.debug("%s: Refreshing %s", self.account, self.name)
if self._parent_clusters and self.hass:
- playing_parents = list(filter(
- lambda x: (
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [self._login.email]
- ['entities']
- ['media_player'][x].state == STATE_PLAYING),
- self._parent_clusters))
+ playing_parents = list(
+ filter(
+ lambda x: (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][
+ self._login.email
+ ]["entities"]["media_player"].get(x)
+ and
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][
+ self._login.email
+ ]["entities"]["media_player"][x].state
+ == STATE_PLAYING
+ ),
+ self._parent_clusters,
+ )
+ )
else:
playing_parents = []
if "PAIR_BT_SOURCE" in self._capabilities:
@@ -380,15 +436,11 @@ async def refresh(self, device=None):
if playing_parents:
if len(playing_parents) > 1:
_LOGGER.warning(
- "Found multiple playing parents "
- "please file an issue")
- parent = (
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [self._login.email]
- ['entities']
- ['media_player'][playing_parents[0]]
- )
+ "Found multiple playing parents " "please file an issue"
+ )
+ parent = self.hass.data[DATA_ALEXAMEDIA]["accounts"][
+ self._login.email
+ ]["entities"]["media_player"][playing_parents[0]]
self._playing_parent = parent
parent_session = parent.session
if parent_session:
@@ -396,11 +448,13 @@ async def refresh(self, device=None):
session["isPlayingInLemur"] = False
session["lemurVolume"] = None
session["volume"] = (
- parent_session["lemurVolume"]["memberVolume"]
- [self.device_serial_number] if
- parent_session["lemurVolume"]
+ parent_session["lemurVolume"]["memberVolume"][
+ self.device_serial_number
+ ]
+ if parent_session["lemurVolume"]
and "memberVolume" in parent_session["lemurVolume"]
- else session["volume"])
+ else session["volume"]
+ )
session = {"playerInfo": session}
else:
self._playing_parent = None
@@ -409,122 +463,143 @@ async def refresh(self, device=None):
# update the session if it exists; not doing relogin here
if session:
self._session = session
- if self._session and 'playerInfo' in self._session:
- self._session = self._session['playerInfo']
- if self._session['transport'] is not None:
- self._shuffle = (self._session['transport']
- ['shuffle'] == "SELECTED"
- if ('shuffle' in self._session['transport']
- and self._session['transport']['shuffle']
- != 'DISABLED')
- else None)
- self._repeat = (self._session['transport']
- ['repeat'] == "SELECTED"
- if ('repeat' in self._session['transport']
- and self._session['transport']['repeat']
- != 'DISABLED')
- else None)
- if self._session['state'] is not None:
- self._media_player_state = self._session['state']
- self._media_pos = (self._session['progress']['mediaProgress']
- if (self._session['progress'] is not None
- and 'mediaProgress' in
- self._session['progress'])
- else None)
- self._media_title = (self._session['infoText']['title']
- if (self._session['infoText'] is not None
- and 'title' in
- self._session['infoText'])
- else None)
- self._media_artist = (self._session['infoText']['subText1']
- if (self._session['infoText'] is not None
- and 'subText1' in
- self._session['infoText'])
- else None)
- self._media_album_name = (self._session['infoText']['subText2']
- if (self._session['infoText'] is not
- None and 'subText2' in
- self._session['infoText'])
- else None)
- self._media_image_url = (self._session['mainArt']['url']
- if (self._session['mainArt'] is not
- None and 'url' in
- self._session['mainArt'])
- else None)
- self._media_duration = (self._session['progress']
- ['mediaLength']
- if (self._session['progress'] is not
- None and 'mediaLength' in
- self._session['progress'])
- else None)
+ if self._session and "playerInfo" in self._session:
+ self._session = self._session["playerInfo"]
+ if self._session["transport"] is not None:
+ self._shuffle = (
+ self._session["transport"]["shuffle"] == "SELECTED"
+ if (
+ "shuffle" in self._session["transport"]
+ and self._session["transport"]["shuffle"] != "DISABLED"
+ )
+ else None
+ )
+ self._repeat = (
+ self._session["transport"]["repeat"] == "SELECTED"
+ if (
+ "repeat" in self._session["transport"]
+ and self._session["transport"]["repeat"] != "DISABLED"
+ )
+ else None
+ )
+ if self._session["state"] is not None:
+ self._media_player_state = self._session["state"]
+ self._media_pos = (
+ self._session["progress"]["mediaProgress"]
+ if (
+ self._session["progress"] is not None
+ and "mediaProgress" in self._session["progress"]
+ )
+ else None
+ )
+ self._media_title = (
+ self._session["infoText"]["title"]
+ if (
+ self._session["infoText"] is not None
+ and "title" in self._session["infoText"]
+ )
+ else None
+ )
+ self._media_artist = (
+ self._session["infoText"]["subText1"]
+ if (
+ self._session["infoText"] is not None
+ and "subText1" in self._session["infoText"]
+ )
+ else None
+ )
+ self._media_album_name = (
+ self._session["infoText"]["subText2"]
+ if (
+ self._session["infoText"] is not None
+ and "subText2" in self._session["infoText"]
+ )
+ else None
+ )
+ self._media_image_url = (
+ self._session["mainArt"]["url"]
+ if (
+ self._session["mainArt"] is not None
+ and "url" in self._session["mainArt"]
+ )
+ else None
+ )
+ self._media_duration = (
+ self._session["progress"]["mediaLength"]
+ if (
+ self._session["progress"] is not None
+ and "mediaLength" in self._session["progress"]
+ )
+ else None
+ )
if not self._session["lemurVolume"]:
self._media_is_muted = (
- self._session['volume']['muted']
- if (self._session['volume'] is not None
- and 'muted' in
- self._session['volume'])
- else None)
+ self._session["volume"]["muted"]
+ if (
+ self._session["volume"] is not None
+ and "muted" in self._session["volume"]
+ )
+ else None
+ )
self._media_vol_level = (
- self._session['volume']
- ['volume'] / 100
+ self._session["volume"]["volume"] / 100
if (
- self._session['volume'] is not None
- and 'volume' in
- self._session['volume'])
- else self._media_vol_level)
+ self._session["volume"] is not None
+ and "volume" in self._session["volume"]
+ )
+ else self._media_vol_level
+ )
else:
self._media_is_muted = (
- self._session['lemurVolume']['compositeVolume']['muted']
- if (self._session['lemurVolume'] and
- 'compositeVolume' in self._session['lemurVolume']
- and
- self._session['lemurVolume']['compositeVolume']
- and 'muted' in
- self._session['lemurVolume']['compositeVolume'])
- else None)
+ self._session["lemurVolume"]["compositeVolume"]["muted"]
+ if (
+ self._session["lemurVolume"]
+ and "compositeVolume" in self._session["lemurVolume"]
+ and self._session["lemurVolume"]["compositeVolume"]
+ and "muted"
+ in self._session["lemurVolume"]["compositeVolume"]
+ )
+ else None
+ )
self._media_vol_level = (
- self._session['lemurVolume']['compositeVolume']
- ['volume'] / 100
+ self._session["lemurVolume"]["compositeVolume"]["volume"] / 100
if (
- self._session['lemurVolume'] and
- 'compositeVolume' in self._session['lemurVolume']
- and
- 'volume' in
- self._session['lemurVolume']['compositeVolume']
- and
- (self._session['lemurVolume']['compositeVolume']
- ['volume']
- )
+ self._session["lemurVolume"]
+ and "compositeVolume" in self._session["lemurVolume"]
+ and "volume"
+ in self._session["lemurVolume"]["compositeVolume"]
+ and (
+ self._session["lemurVolume"]["compositeVolume"][
+ "volume"
+ ]
+ )
)
- else self._media_vol_level)
+ else self._media_vol_level
+ )
if not self.hass:
return
asyncio.gather(
*map(
lambda x: (
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [self._login.email]
- ['entities']
- ['media_player'][x].async_update()),
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][
+ self._login.email
+ ]["entities"]["media_player"][x].async_update()
+ ),
filter(
lambda x: (
- x in (
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [self._login.email]
- ['entities']
- ['media_player'])
- and
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [self._login.email]
- ['entities']
- ['media_player'][x].available
- ),
- self._cluster_members
- )
- )
+ x
+ in (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][
+ self._login.email
+ ]["entities"]["media_player"]
+ )
+ and self.hass.data[DATA_ALEXAMEDIA]["accounts"][
+ self._login.email
+ ]["entities"]["media_player"][x].available
+ ),
+ self._cluster_members,
+ ),
+ )
)
@property
@@ -540,54 +615,64 @@ def source_list(self):
@_catch_login_errors
async def async_select_source(self, source):
"""Select input source."""
- if source == 'Local Speaker':
+ if source == "Local Speaker":
await self.alexa_api.disconnect_bluetooth()
- self._source = 'Local Speaker'
- elif self._bluetooth_state['pairedDeviceList'] is not None:
- for devices in self._bluetooth_state['pairedDeviceList']:
- if devices['friendlyName'] == source:
- await self.alexa_api.set_bluetooth(devices['address'])
+ self._source = "Local Speaker"
+ elif self._bluetooth_state["pairedDeviceList"] is not None:
+ for devices in self._bluetooth_state["pairedDeviceList"]:
+ if devices["friendlyName"] == source:
+ await self.alexa_api.set_bluetooth(devices["address"])
self._source = source
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email]["websocket"]
+ ):
await self.async_update()
async def _get_source(self):
- source = 'Local Speaker'
- if self._bluetooth_state['pairedDeviceList'] is not None:
- for device in self._bluetooth_state['pairedDeviceList']:
- if (device['connected'] is True and
- device['friendlyName'] in self.source_list):
- return device['friendlyName']
+ source = "Local Speaker"
+ if self._bluetooth_state["pairedDeviceList"] is not None:
+ for device in self._bluetooth_state["pairedDeviceList"]:
+ if (
+ device["connected"] is True
+ and device["friendlyName"] in self.source_list
+ ):
+ return device["friendlyName"]
return source
async def _get_source_list(self):
sources = []
- if self._bluetooth_state['pairedDeviceList'] is not None:
- for devices in self._bluetooth_state['pairedDeviceList']:
- if (devices['profiles'] and
- 'A2DP-SOURCE' in devices['profiles']):
- sources.append(devices['friendlyName'])
- return ['Local Speaker'] + sources
+ if self._bluetooth_state["pairedDeviceList"] is not None:
+ for devices in self._bluetooth_state["pairedDeviceList"]:
+ if devices["profiles"] and "A2DP-SOURCE" in devices["profiles"]:
+ sources.append(devices["friendlyName"])
+ return ["Local Speaker"] + sources
async def _get_last_called(self):
try:
- last_called_serial = (None if self.hass is None else
- (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [self._login.email]
- ['last_called']
- ['serialNumber']))
+ last_called_serial = (
+ None
+ if self.hass is None
+ else (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email][
+ "last_called"
+ ]["serialNumber"]
+ )
+ )
except (TypeError, KeyError):
last_called_serial = None
- _LOGGER.debug("%s: Last_called check: self: %s reported: %s",
- self._device_name,
- hide_serial(self._device_serial_number),
- hide_serial(last_called_serial))
- return (last_called_serial is not None and
- (self._device_serial_number == last_called_serial or
- any(item['serialNumber'] ==
- last_called_serial for item in self._app_device_list)))
+ _LOGGER.debug(
+ "%s: Last_called check: self: %s reported: %s",
+ self._device_name,
+ hide_serial(self._device_serial_number),
+ hide_serial(last_called_serial),
+ )
+ return last_called_serial is not None and (
+ self._device_serial_number == last_called_serial
+ or any(
+ item["serialNumber"] == last_called_serial
+ for item in self._app_device_list
+ )
+ )
@property
def available(self):
@@ -622,11 +707,11 @@ def session(self):
@property
def state(self):
"""Return the state of the device."""
- if self._media_player_state == 'PLAYING':
+ if self._media_player_state == "PLAYING":
return STATE_PLAYING
- if self._media_player_state == 'PAUSED':
+ if self._media_player_state == "PAUSED":
return STATE_PAUSED
- if self._media_player_state == 'IDLE':
+ if self._media_player_state == "IDLE":
return STATE_IDLE
return STATE_STANDBY
@@ -645,58 +730,70 @@ async def async_update(self):
return
except AttributeError:
pass
- if (self._device is None or self.entity_id is None):
+ if self._device is None or self.entity_id is None:
# Device has not initialized yet
return
email = self._login.email
- if email not in self.hass.data[DATA_ALEXAMEDIA]['accounts']:
+ if email not in self.hass.data[DATA_ALEXAMEDIA]["accounts"]:
return
- device = (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]
- ['devices']
- ['media_player']
- [self.unique_id])
- seen_commands = ((self.hass.data[DATA_ALEXAMEDIA]['accounts']
- [email]['websocket_commands'].keys()
- if 'websocket_commands' in (
- self.hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [email]) else None))
- await self.refresh(device, # pylint: disable=unexpected-keyword-arg
- no_throttle=True)
- if (self.state in [STATE_PLAYING] and
- # only enable polling if websocket not connected
- (not self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocket'] or
- # or if no PUSH_AUDIO_PLAYER_STATE
- not seen_commands or
- 'PUSH_AUDIO_PLAYER_STATE' not in seen_commands)):
+ device = self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
+ "media_player"
+ ][self.unique_id]
+ seen_commands = (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][email][
+ "websocket_commands"
+ ].keys()
+ if "websocket_commands"
+ in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email])
+ else None
+ )
+ await self.refresh(
+ device, no_throttle=True # pylint: disable=unexpected-keyword-arg
+ )
+ if (
+ self.state in [STATE_PLAYING]
+ and
+ # only enable polling if websocket not connected
+ (
+ not self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]
+ or
+ # or if no PUSH_AUDIO_PLAYER_STATE
+ not seen_commands
+ or "PUSH_AUDIO_PLAYER_STATE" not in seen_commands
+ )
+ ):
self._should_poll = False # disable polling since manual update
- if(self._last_update == 0 or util.dt.as_timestamp(util.utcnow()) -
- util.dt.as_timestamp(self._last_update)
- > PLAY_SCAN_INTERVAL):
- _LOGGER.debug("%s playing; scheduling update in %s seconds",
- self.name, PLAY_SCAN_INTERVAL)
- async_call_later(self.hass, PLAY_SCAN_INTERVAL, lambda _:
- self.async_schedule_update_ha_state(
- force_refresh=True))
+ if (
+ self._last_update == 0
+ or util.dt.as_timestamp(util.utcnow())
+ - util.dt.as_timestamp(self._last_update)
+ > PLAY_SCAN_INTERVAL
+ ):
+ _LOGGER.debug(
+ "%s playing; scheduling update in %s seconds",
+ self.name,
+ PLAY_SCAN_INTERVAL,
+ )
+ async_call_later(
+ self.hass,
+ PLAY_SCAN_INTERVAL,
+ lambda _: self.async_schedule_update_ha_state(force_refresh=True),
+ )
elif self._should_poll: # Not playing, one last poll
self._should_poll = False
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][email]['websocket']):
- _LOGGER.debug("Disabling polling and scheduling last update in"
- " 300 seconds for %s",
- self.name)
+ if not (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["websocket"]):
+ _LOGGER.debug(
+ "Disabling polling and scheduling last update in"
+ " 300 seconds for %s",
+ self.name,
+ )
async_call_later(
self.hass,
300,
- lambda _:
- self.async_schedule_update_ha_state(
- force_refresh=True))
+ lambda _: self.async_schedule_update_ha_state(force_refresh=True),
+ )
else:
- _LOGGER.debug("Disabling polling for %s",
- self.name)
+ _LOGGER.debug("Disabling polling for %s", self.name)
self._last_update = util.utcnow()
self.async_schedule_update_ha_state()
@@ -800,8 +897,9 @@ async def async_set_volume_level(self, volume):
return
await self.alexa_api.set_volume(volume)
self._media_vol_level = volume
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email]["websocket"]
+ ):
await self.async_update()
@property
@@ -836,36 +934,37 @@ async def async_mute_volume(self, mute):
await self.alexa_api.set_volume(self._previous_volume)
else:
await self.alexa_api.set_volume(50)
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["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]
- and self.available):
+ if not (self.state in [STATE_PLAYING, STATE_PAUSED] and self.available):
return
if self._playing_parent:
await self._playing_parent.async_media_play()
else:
await self.alexa_api.play()
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["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]
- and self.available):
+ if not (self.state in [STATE_PLAYING, STATE_PAUSED] and self.available):
return
if self._playing_parent:
await self._playing_parent.async_media_pause()
else:
await self.alexa_api.pause()
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email]["websocket"]
+ ):
await self.async_update()
@_catch_login_errors
@@ -892,29 +991,29 @@ async def async_turn_on(self):
@_catch_login_errors
async def async_media_next_track(self):
"""Send next track command."""
- if not (self.state in [STATE_PLAYING, STATE_PAUSED]
- and self.available):
+ if not (self.state in [STATE_PLAYING, STATE_PAUSED] and self.available):
return
if self._playing_parent:
await self._playing_parent.async_media_next_track()
else:
await self.alexa_api.next()
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["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]
- and self.available):
+ if not (self.state in [STATE_PLAYING, STATE_PAUSED] and self.available):
return
if self._playing_parent:
await self._playing_parent.async_media_previous_track()
else:
await self.alexa_api.previous()
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email]["websocket"]
+ ):
await self.async_update()
@_catch_login_errors
@@ -928,51 +1027,49 @@ async def async_send_tts(self, message):
@_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)
+ 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)
+ 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):
+ async def async_play_media(self, media_type, media_id, enqueue=None, **kwargs):
"""Send the play_media command to the media player."""
if media_type == "music":
await self.async_send_tts(
"Sorry, text to speech can only be called"
" with the notify.alexa_media service."
- " Please see the alexa_media wiki for details.")
+ " Please see the alexa_media wiki for details."
+ )
elif media_type == "sequence":
- await self.alexa_api.send_sequence(media_id,
- customer_id=self._customer_id,
- **kwargs)
+ await self.alexa_api.send_sequence(
+ media_id, customer_id=self._customer_id, **kwargs
+ )
elif media_type == "routine":
await self.alexa_api.run_routine(media_id)
elif media_type == "sound":
await self.alexa_api.play_sound(
- media_id,
- customer_id=self._customer_id, **kwargs)
+ media_id, customer_id=self._customer_id, **kwargs
+ )
else:
await self.alexa_api.play_music(
- media_type, media_id,
- customer_id=self._customer_id, **kwargs)
- if not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._login.email]['websocket']):
+ media_type, media_id, customer_id=self._customer_id, **kwargs
+ )
+ if not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._login.email]["websocket"]
+ ):
await self.async_update()
@property
def device_state_attributes(self):
"""Return the scene state attributes."""
- attr = {
- 'available': self._available,
- 'last_called': self._last_called
- }
+ attr = {"available": self._available, "last_called": self._last_called}
return attr
@property
@@ -984,11 +1081,9 @@ def should_poll(self):
def device_info(self):
"""Return the device_info of the device."""
return {
- 'identifiers': {
- (ALEXA_DOMAIN, self.unique_id)
- },
- 'name': self.name,
- 'manufacturer': "Amazon",
- 'model': f"{self._device_family} {self._device_type}",
- 'sw_version': self._software_version,
+ "identifiers": {(ALEXA_DOMAIN, self.unique_id)},
+ "name": self.name,
+ "manufacturer": "Amazon",
+ "model": f"{self._device_family} {self._device_type}",
+ "sw_version": self._software_version,
}
diff --git a/custom_components/alexa_media/notify.py b/custom_components/alexa_media/notify.py
index f04e10e4..d802f9e6 100644
--- a/custom_components/alexa_media/notify.py
+++ b/custom_components/alexa_media/notify.py
@@ -9,10 +9,14 @@
"""
import logging
-from homeassistant.components.notify import (ATTR_DATA, ATTR_TARGET,
- ATTR_TITLE, ATTR_TITLE_DEFAULT,
- SERVICE_NOTIFY,
- BaseNotificationService)
+from homeassistant.components.notify import (
+ ATTR_DATA,
+ ATTR_TARGET,
+ ATTR_TITLE,
+ ATTR_TITLE_DEFAULT,
+ SERVICE_NOTIFY,
+ BaseNotificationService,
+)
from . import CONF_EMAIL, DATA_ALEXAMEDIA, DOMAIN, hide_email, hide_serial
from .helpers import retry_async
@@ -24,14 +28,14 @@
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, _ in account_dict['devices']['media_player'].items():
- if key not in account_dict['entities']['media_player']:
+ for account, account_dict in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+ for key, _ 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))
+ hide_serial(key),
+ )
return False
return AlexaNotificationService(hass)
@@ -40,17 +44,13 @@ async def async_unload_entry(hass, entry) -> bool:
"""Unload a config entry."""
target_account = entry.data[CONF_EMAIL]
other_accounts = False
- for account, account_dict in (hass.data[DATA_ALEXAMEDIA]
- ['accounts'].items()):
+ for account, account_dict in hass.data[DATA_ALEXAMEDIA]["accounts"].items():
if account == target_account:
- if 'entities' not in account_dict:
+ if "entities" not in account_dict:
continue
- for device in (account_dict['entities']
- ['media_player'].values()):
- entity_id = device.entity_id.split('.')
- hass.services.async_remove(
- SERVICE_NOTIFY,
- f"{DOMAIN}_{entity_id[1]}")
+ for device in account_dict["entities"]["media_player"].values():
+ entity_id = device.entity_id.split(".")
+ hass.services.async_remove(SERVICE_NOTIFY, f"{DOMAIN}_{entity_id[1]}")
else:
other_accounts = True
if not other_accounts:
@@ -92,14 +92,15 @@ async def convert(self, names, type_="entities", filter_matches=False):
for item in names:
matched = False
for alexa in self.devices:
- _LOGGER.debug("Testing item: %s against (%s, %s, %s, %s)",
- item,
- alexa,
- alexa.name,
- hide_serial(alexa.unique_id),
- alexa.entity_id)
- if item in (alexa, alexa.name, alexa.unique_id,
- alexa.entity_id):
+ _LOGGER.debug(
+ "Testing item: %s against (%s, %s, %s, %s)",
+ item,
+ alexa,
+ alexa.name,
+ hide_serial(alexa.unique_id),
+ alexa.entity_id,
+ )
+ if item in (alexa, alexa.name, alexa.unique_id, alexa.entity_id):
if type_ == "entities":
converted = alexa
elif type_ == "serialnumbers":
@@ -110,10 +111,7 @@ async def convert(self, names, type_="entities", filter_matches=False):
converted = alexa.entity_id
devices.append(converted)
matched = True
- _LOGGER.debug("Converting: %s to (%s): %s",
- item,
- type_,
- converted)
+ _LOGGER.debug("Converting: %s to (%s): %s", item, type_, converted)
if not filter_matches and not matched:
devices.append(item)
return devices
@@ -122,81 +120,80 @@ async def convert(self, names, type_="entities", filter_matches=False):
def targets(self):
"""Return a dictionary of Alexa devices."""
devices = {}
- for _, account_dict in (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'].items()):
- if ('devices' not in account_dict):
+ for _, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+ if "devices" not in account_dict:
return devices
- for serial, alexa in (account_dict
- ['devices']['media_player'].items()):
- devices[alexa['accountName']] = serial
+ for serial, alexa in account_dict["devices"]["media_player"].items():
+ devices[alexa["accountName"]] = serial
return devices
@property
def devices(self):
"""Return a list of Alexa devices."""
devices = []
- if ('accounts' not in self.hass.data[DATA_ALEXAMEDIA] and
- not self.hass.data[DATA_ALEXAMEDIA]['accounts'].items()):
+ if (
+ "accounts" not in self.hass.data[DATA_ALEXAMEDIA]
+ and not self.hass.data[DATA_ALEXAMEDIA]["accounts"].items()
+ ):
return devices
- for _, account_dict in (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'].items()):
- devices = devices + list(account_dict
- ['entities']['media_player'].values())
+ for _, account_dict in self.hass.data[DATA_ALEXAMEDIA]["accounts"].items():
+ devices = devices + list(account_dict["entities"]["media_player"].values())
return devices
async def async_send_message(self, message="", **kwargs):
"""Send a message to a Alexa device."""
- _LOGGER.debug("Message: %s, kwargs: %s",
- message,
- kwargs)
- kwargs['message'] = message
+ _LOGGER.debug("Message: %s, kwargs: %s", message, kwargs)
+ kwargs["message"] = message
targets = kwargs.get(ATTR_TARGET)
- title = (kwargs.get(ATTR_TITLE) if ATTR_TITLE in kwargs
- else ATTR_TITLE_DEFAULT)
+ title = kwargs.get(ATTR_TITLE) if ATTR_TITLE in kwargs else ATTR_TITLE_DEFAULT
data = kwargs.get(ATTR_DATA)
if isinstance(targets, str):
targets = [targets]
entities = await self.convert(targets, type_="entities")
try:
- entities.extend(self.hass.components.group.expand_entity_ids(
- entities))
+ entities.extend(self.hass.components.group.expand_entity_ids(entities))
except ValueError:
_LOGGER.debug("Invalid Home Assistant entity in %s", entities)
- if data['type'] == "tts":
- targets = await self.convert(entities, type_="entities",
- filter_matches=True)
+ if data["type"] == "tts":
+ targets = await self.convert(
+ entities, type_="entities", filter_matches=True
+ )
_LOGGER.debug("TTS entities: %s", targets)
for alexa in targets:
_LOGGER.debug("TTS by %s : %s", alexa, message)
await alexa.async_send_tts(message)
- elif data['type'] == "announce":
- targets = await self.convert(entities, type_="serialnumbers",
- filter_matches=True)
- _LOGGER.debug("Announce targets: %s entities: %s",
- list(map(hide_serial, targets)),
- entities)
- for account, account_dict in (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'].items()):
- for alexa in (account_dict['entities']
- ['media_player'].values()):
+ elif data["type"] == "announce":
+ targets = await self.convert(
+ entities, type_="serialnumbers", filter_matches=True
+ )
+ _LOGGER.debug(
+ "Announce targets: %s entities: %s",
+ list(map(hide_serial, targets)),
+ entities,
+ )
+ for account, account_dict in self.hass.data[DATA_ALEXAMEDIA][
+ "accounts"
+ ].items():
+ for alexa in account_dict["entities"]["media_player"].values():
if alexa.unique_id in targets and alexa.available:
- _LOGGER.debug(("%s: Announce by %s to "
- "targets: %s: %s"),
- hide_email(account),
- alexa,
- list(map(hide_serial, targets)),
- message)
+ _LOGGER.debug(
+ ("%s: Announce by %s to " "targets: %s: %s"),
+ hide_email(account),
+ alexa,
+ list(map(hide_serial, targets)),
+ message,
+ )
await alexa.async_send_announcement(
message,
targets=targets,
title=title,
- method=(data['method'] if
- 'method' in data
- else 'all'))
+ method=(data["method"] if "method" in data else "all"),
+ )
break
- elif data['type'] == "push":
- targets = await self.convert(entities, type_="entities",
- filter_matches=True)
+ elif data["type"] == "push":
+ targets = await self.convert(
+ entities, type_="entities", filter_matches=True
+ )
for alexa in targets:
_LOGGER.debug("Push by %s : %s %s", alexa, title, message)
await alexa.async_send_mobilepush(message, title=title)
diff --git a/custom_components/alexa_media/sensor.py b/custom_components/alexa_media/sensor.py
index 88e8844d..d84dd56a 100644
--- a/custom_components/alexa_media/sensor.py
+++ b/custom_components/alexa_media/sensor.py
@@ -16,16 +16,20 @@
from homeassistant.helpers.entity import Entity
from homeassistant.util import dt
-from . import (CONF_EMAIL, CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES,
- DATA_ALEXAMEDIA)
-from . import DOMAIN as ALEXA_DOMAIN
-from . import (hide_email, hide_serial)
+from . import (
+ CONF_EMAIL,
+ CONF_EXCLUDE_DEVICES,
+ CONF_INCLUDE_DEVICES,
+ DATA_ALEXAMEDIA,
+ DOMAIN as ALEXA_DOMAIN,
+ hide_email,
+ hide_serial,
+)
from .helpers import add_devices, retry_async
_LOGGER = logging.getLogger(__name__)
-LOCAL_TIMEZONE = datetime.datetime.now(
- datetime.timezone.utc).astimezone().tzinfo
+LOCAL_TIMEZONE = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
RECURRING_PATTERN = {
None: "Never Repeat",
@@ -38,101 +42,98 @@
"XXXX-WXX-4": "Every Thursday",
"XXXX-WXX-5": "Every Friday",
"XXXX-WXX-6": "Every Saturday",
- "XXXX-WXX-7": "Every Sunday"
+ "XXXX-WXX-7": "Every Sunday",
}
@retry_async(limit=5, delay=5, catch_exceptions=False)
-async def async_setup_platform(hass, config, add_devices_callback,
- discovery_info=None):
+async def async_setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up the Alexa sensor platform."""
devices: List[AlexaMediaSensor] = []
SENSOR_TYPES = {
- 'Alarm': AlarmSensor,
- 'Timer': TimerSensor,
- 'Reminder': ReminderSensor
+ "Alarm": AlarmSensor,
+ "Timer": TimerSensor,
+ "Reminder": ReminderSensor,
}
account = config[CONF_EMAIL]
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 sensors",
- hide_email(account))
- if 'sensor' not in account_dict['entities']:
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [account]
- ['entities']
- ['sensor']) = {}
- 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))
+ account_dict = hass.data[DATA_ALEXAMEDIA]["accounts"][account]
+ _LOGGER.debug("%s: Loading sensors", hide_email(account))
+ if "sensor" not in account_dict["entities"]:
+ (hass.data[DATA_ALEXAMEDIA]["accounts"][account]["entities"]["sensor"]) = {}
+ 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
- if key not in (account_dict
- ['entities']
- ['sensor']):
- (account_dict
- ['entities']
- ['sensor'][key]) = {}
+ if key not in (account_dict["entities"]["sensor"]):
+ (account_dict["entities"]["sensor"][key]) = {}
for (n_type, class_) in SENSOR_TYPES.items():
n_type_dict = (
- account_dict['notifications'][key][n_type]
- if key in account_dict['notifications'] and
- n_type in account_dict['notifications'][key] else {})
- if (n_type in ('Alarm, Timer') and
- 'TIMERS_AND_ALARMS' in device['capabilities']):
+ account_dict["notifications"][key][n_type]
+ if key in account_dict["notifications"]
+ and n_type in account_dict["notifications"][key]
+ else {}
+ )
+ if (
+ n_type in ("Alarm, Timer")
+ and "TIMERS_AND_ALARMS" in device["capabilities"]
+ ):
alexa_client = class_(
- account_dict['entities']['media_player'][key],
+ account_dict["entities"]["media_player"][key],
n_type_dict,
- account)
- elif (n_type in ('Reminder') and
- 'REMINDERS' in device['capabilities']):
+ account,
+ )
+ elif n_type in ("Reminder") and "REMINDERS" in device["capabilities"]:
alexa_client = class_(
- account_dict['entities']['media_player'][key],
+ account_dict["entities"]["media_player"][key],
n_type_dict,
- account)
+ account,
+ )
else:
continue
- _LOGGER.debug("%s: Found %s %s sensor (%s) with next: %s",
- hide_email(account),
- hide_serial(key),
- n_type,
- len(n_type_dict.keys()),
- alexa_client.state)
+ _LOGGER.debug(
+ "%s: Found %s %s sensor (%s) with next: %s",
+ hide_email(account),
+ hide_serial(key),
+ n_type,
+ len(n_type_dict.keys()),
+ alexa_client.state,
+ )
devices.append(alexa_client)
- (account_dict
- ['entities']
- ['sensor']
- [key]
- [n_type]) = alexa_client
+ (account_dict["entities"]["sensor"][key][n_type]) = alexa_client
else:
- for alexa_client in (account_dict['entities']
- ['sensor']
- [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)
+ for alexa_client in account_dict["entities"]["sensor"][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,
+ )
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(
- hass,
- config_entry.data,
- async_add_devices,
- discovery_info=None)
+ hass, config_entry.data, async_add_devices, discovery_info=None
+ )
async def async_unload_entry(hass, entry) -> bool:
"""Unload a config entry."""
account = entry.data[CONF_EMAIL]
- account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][account]
- for key, sensors in (account_dict['entities']['sensor'].items()):
+ account_dict = hass.data[DATA_ALEXAMEDIA]["accounts"][account]
+ for key, sensors in account_dict["entities"]["sensor"].items():
for device in sensors[key].values():
await device.async_remove()
return True
@@ -141,13 +142,15 @@ async def async_unload_entry(hass, entry) -> bool:
class AlexaMediaSensor(Entity):
"""Representation of Alexa Media sensors."""
- def __init__(self,
- client,
- n_dict,
- sensor_property: Text,
- account,
- name="Next Notification",
- icon=None):
+ def __init__(
+ self,
+ client,
+ n_dict,
+ sensor_property: Text,
+ account,
+ name="Next Notification",
+ icon=None,
+ ):
"""Initialize the Alexa sensor device."""
# Class info
self._client = client
@@ -159,16 +162,22 @@ def __init__(self,
self._unit = None
self._device_class = DEVICE_CLASS_TIMESTAMP
self._icon = icon
- self._all = (sorted(self._n_dict.items(),
- key=lambda x: x[1][self._sensor_property])
- if self._n_dict else [])
+ self._all = (
+ sorted(self._n_dict.items(), key=lambda x: x[1][self._sensor_property])
+ if self._n_dict
+ else []
+ )
self._all = list(map(self._fix_alarm_date_time, self._all))
- self._sorted = list(filter(lambda x: x[1]['status'] == 'ON',
- self._all)) if self._all else []
+ self._sorted = (
+ list(filter(lambda x: x[1]["status"] == "ON", self._all))
+ if self._all
+ else []
+ )
self._next = self._sorted[0][1] if self._sorted else None
def _fix_alarm_date_time(self, value):
import pytz
+
if self._sensor_property != "date_time" or not value:
return value
naive_time = dt.parse_datetime(value[1][self._sensor_property])
@@ -180,7 +189,8 @@ def _fix_alarm_date_time(self, value):
"%s does not have a timezone set. "
"Returned times may be wrong. "
"Please set the timezone in the Alexa app.",
- self._client.name)
+ self._client.name,
+ )
return value
async def async_added_to_hass(self):
@@ -192,8 +202,8 @@ async def async_added_to_hass(self):
pass
# Register event handler on bus
self._listener = self.hass.bus.async_listen(
- f'{ALEXA_DOMAIN}_{hide_email(self._account)}'[0:32],
- self._handle_event)
+ f"{ALEXA_DOMAIN}_{hide_email(self._account)}"[0:32], self._handle_event
+ )
async def async_will_remove_from_hass(self):
"""Prepare to remove entity."""
@@ -211,9 +221,11 @@ def _handle_event(self, event):
return
except AttributeError:
pass
- if 'notification_update' in event.data:
- if (event.data['notification_update']['dopplerId']
- ['deviceSerialNumber'] == self._client.unique_id):
+ if "notification_update" in event.data:
+ if (
+ event.data["notification_update"]["dopplerId"]["deviceSerialNumber"]
+ == self._client.unique_id
+ ):
_LOGGER.debug("Updating sensor %s", self.name)
self.async_schedule_update_ha_state(True)
@@ -235,14 +247,18 @@ def name(self):
@property
def should_poll(self):
"""Return the polling state."""
- return not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._account]['websocket'])
+ return not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._account]["websocket"]
+ )
@property
def state(self):
"""Return the state of the sensor."""
- return self._next[self._sensor_property].replace(
- tzinfo=LOCAL_TIMEZONE).isoformat() if self._next else STATE_UNAVAILABLE
+ return (
+ self._next[self._sensor_property].replace(tzinfo=LOCAL_TIMEZONE).isoformat()
+ if self._next
+ else STATE_UNAVAILABLE
+ )
@property
def unit_of_measurement(self):
@@ -261,19 +277,22 @@ async def async_update(self):
return
except AttributeError:
pass
- account_dict = (self.hass.data[DATA_ALEXAMEDIA]['accounts']
- [self._account])
+ account_dict = self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._account]
try:
- self._n_dict = (account_dict['notifications'][self._dev_id]
- [self._type])
+ self._n_dict = account_dict["notifications"][self._dev_id][self._type]
except KeyError:
self._n_dict = None
- self._all = (sorted(self._n_dict.items(),
- key=lambda x: x[1][self._sensor_property])
- if self._n_dict else [])
+ self._all = (
+ sorted(self._n_dict.items(), key=lambda x: x[1][self._sensor_property])
+ if self._n_dict
+ else []
+ )
self._all = list(map(self._fix_alarm_date_time, self._all))
- self._sorted = list(filter(lambda x: x[1]['status'] == 'ON',
- self._all)) if self._all else []
+ self._sorted = (
+ list(filter(lambda x: x[1]["status"] == "ON", self._all))
+ if self._all
+ else []
+ )
self._next = self._sorted[0][1] if self._sorted else None
try:
self.async_schedule_update_ha_state()
@@ -284,10 +303,8 @@ async def async_update(self):
def device_info(self):
"""Return the device_info of the device."""
return {
- 'identifiers': {
- (ALEXA_DOMAIN, self._dev_id)
- },
- 'via_device': (ALEXA_DOMAIN, self._dev_id),
+ "identifiers": {(ALEXA_DOMAIN, self._dev_id)},
+ "via_device": (ALEXA_DOMAIN, self._dev_id),
}
@property
@@ -298,19 +315,19 @@ def icon(self):
@property
def recurrence(self):
"""Return the icon of the sensor."""
- return (RECURRING_PATTERN[self._next['recurringPattern']]
- if self._next else None)
+ return RECURRING_PATTERN[self._next["recurringPattern"]] if self._next else None
@property
def device_state_attributes(self):
"""Return additional attributes."""
import json
+
attr = {
- 'recurrence': self.recurrence,
- 'total_active': len(self._sorted),
- 'total_all': len(self._all),
- 'sorted_active': json.dumps(self._sorted, default=str),
- 'sorted_all': json.dumps(self._all, default=str),
+ "recurrence": self.recurrence,
+ "total_active": len(self._sorted),
+ "total_all": len(self._all),
+ "sorted_active": json.dumps(self._sorted, default=str),
+ "sorted_all": json.dumps(self._all, default=str),
}
return attr
@@ -321,14 +338,10 @@ class AlarmSensor(AlexaMediaSensor):
def __init__(self, client, n_json, account):
"""Initialize the Alexa sensor."""
# Class info
- self._type = 'Alarm'
+ self._type = "Alarm"
super().__init__(
- client,
- n_json,
- 'date_time',
- account,
- f"next {self._type}",
- 'mdi:alarm')
+ client, n_json, "date_time", account, f"next {self._type}", "mdi:alarm"
+ )
class TimerSensor(AlexaMediaSensor):
@@ -337,28 +350,28 @@ class TimerSensor(AlexaMediaSensor):
def __init__(self, client, n_json, account):
"""Initialize the Alexa sensor."""
# Class info
- self._type = 'Timer'
+ self._type = "Timer"
super().__init__(
- client,
- n_json,
- 'remainingTime',
- account,
- f"next {self._type}",
- "mdi:timer")
+ client, n_json, "remainingTime", account, f"next {self._type}", "mdi:timer"
+ )
@property
def state(self) -> datetime.datetime:
"""Return the state of the sensor."""
return (
- dt.as_local(dt.utc_from_timestamp(
- dt.utcnow().timestamp() +
- self._next[self._sensor_property]/1000)).isoformat()
- if self._next else STATE_UNAVAILABLE)
+ dt.as_local(
+ dt.utc_from_timestamp(
+ dt.utcnow().timestamp() + self._next[self._sensor_property] / 1000
+ )
+ ).isoformat()
+ if self._next
+ else STATE_UNAVAILABLE
+ )
@property
def paused(self) -> bool:
"""Return the paused state of the sensor."""
- return self._next['status'] == 'PAUSED' if self._next else None
+ return self._next["status"] == "PAUSED" if self._next else None
@property
def icon(self):
@@ -372,34 +385,32 @@ class ReminderSensor(AlexaMediaSensor):
def __init__(self, client, n_json, account):
"""Initialize the Alexa sensor."""
# Class info
- self._type = 'Reminder'
+ self._type = "Reminder"
super().__init__(
- client,
- n_json,
- 'alarmTime',
- account,
- f"next {self._type}",
- 'mdi:reminder')
+ client, n_json, "alarmTime", account, f"next {self._type}", "mdi:reminder"
+ )
@property
def state(self):
"""Return the state of the sensor."""
- return dt.as_local(datetime.datetime.fromtimestamp(
- self._next[self._sensor_property]/1000,
- tz=LOCAL_TIMEZONE)).isoformat() if self._next else STATE_UNAVAILABLE
+ return (
+ dt.as_local(
+ datetime.datetime.fromtimestamp(
+ self._next[self._sensor_property] / 1000, tz=LOCAL_TIMEZONE
+ )
+ ).isoformat()
+ if self._next
+ else STATE_UNAVAILABLE
+ )
@property
def reminder(self):
"""Return the reminder of the sensor."""
- return self._next['reminderLabel'] if self._next else None
+ return self._next["reminderLabel"] if self._next else None
@property
def device_state_attributes(self):
"""Return the scene state attributes."""
attr = super().device_state_attributes
- attr.update(
- {
- 'reminder': self.reminder
- }
- )
+ attr.update({"reminder": self.reminder})
return attr
diff --git a/custom_components/alexa_media/switch.py b/custom_components/alexa_media/switch.py
index f0e75590..106b960a 100644
--- a/custom_components/alexa_media/switch.py
+++ b/custom_components/alexa_media/switch.py
@@ -13,100 +13,99 @@
from homeassistant.components.switch import SwitchDevice
from homeassistant.exceptions import NoEntitySpecifiedError
-from . import (CONF_EMAIL, CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES,
- DATA_ALEXAMEDIA)
-from . import DOMAIN as ALEXA_DOMAIN
-from . import (hide_email, hide_serial)
+from . import (
+ CONF_EMAIL,
+ CONF_EXCLUDE_DEVICES,
+ CONF_INCLUDE_DEVICES,
+ DATA_ALEXAMEDIA,
+ DOMAIN as ALEXA_DOMAIN,
+ hide_email,
+ hide_serial,
+)
from .helpers import _catch_login_errors, add_devices, retry_async
_LOGGER = logging.getLogger(__name__)
@retry_async(limit=5, delay=5, catch_exceptions=True)
-async def async_setup_platform(hass, config, add_devices_callback,
- discovery_info=None):
+async def async_setup_platform(hass, config, add_devices_callback, discovery_info=None):
"""Set up the Alexa switch platform."""
devices = [] # type: List[DNDSwitch]
SWITCH_TYPES = [
- ('dnd', DNDSwitch),
- ('shuffle', ShuffleSwitch),
- ('repeat', RepeatSwitch)
+ ("dnd", DNDSwitch),
+ ("shuffle", ShuffleSwitch),
+ ("repeat", RepeatSwitch),
]
account = config[CONF_EMAIL]
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']
- [account]
- ['entities']
- ['switch']) = {}
- for key, _ 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))
+ 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"][account]["entities"]["switch"]) = {}
+ for key, _ 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
- if key not in (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [account]
- ['entities']
- ['switch']):
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [account]
- ['entities']
- ['switch'][key]) = {}
+ if key not in (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][account]["entities"]["switch"]
+ ):
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][account]["entities"]["switch"][
+ key
+ ]
+ ) = {}
for (switch_key, class_) in SWITCH_TYPES:
- alexa_client = class_(account_dict['entities']
- ['media_player']
- [key],
- account) # type: AlexaMediaSwitch
- _LOGGER.debug("%s: Found %s %s switch with status: %s",
- hide_email(account),
- hide_serial(key),
- switch_key,
- alexa_client.is_on)
+ alexa_client = class_(
+ account_dict["entities"]["media_player"][key], account
+ ) # type: AlexaMediaSwitch
+ _LOGGER.debug(
+ "%s: Found %s %s switch with status: %s",
+ hide_email(account),
+ hide_serial(key),
+ switch_key,
+ alexa_client.is_on,
+ )
devices.append(alexa_client)
- (hass.data[DATA_ALEXAMEDIA]
- ['accounts']
- [account]
- ['entities']
- ['switch']
- [key]
- [switch_key]) = alexa_client
+ (
+ hass.data[DATA_ALEXAMEDIA]["accounts"][account]["entities"][
+ "switch"
+ ][key][switch_key]
+ ) = alexa_client
else:
- 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)
+ 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,
+ )
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up the Alexa switch platform by config_entry."""
return await async_setup_platform(
- hass,
- config_entry.data,
- async_add_devices,
- discovery_info=None)
+ hass, config_entry.data, async_add_devices, discovery_info=None
+ )
async def async_unload_entry(hass, entry) -> bool:
"""Unload a config entry."""
account = entry.data[CONF_EMAIL]
- account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][account]
- for key, switches in (account_dict['entities']['switch'].items()):
+ account_dict = hass.data[DATA_ALEXAMEDIA]["accounts"][account]
+ for key, switches in account_dict["entities"]["switch"].items():
for device in switches[key].values():
await device.async_remove()
return True
@@ -115,12 +114,7 @@ async def async_unload_entry(hass, entry) -> bool:
class AlexaMediaSwitch(SwitchDevice):
"""Representation of a Alexa Media switch."""
- def __init__(self,
- client,
- switch_property,
- switch_function,
- account,
- name="Alexa"):
+ def __init__(self, client, switch_property, switch_function, account, name="Alexa"):
"""Initialize the Alexa Switch device."""
# Class info
self._client = client
@@ -140,8 +134,8 @@ async def async_added_to_hass(self):
pass
# Register event handler on bus
self._listener = self.hass.bus.async_listen(
- f'{ALEXA_DOMAIN}_{hide_email(self._login.email)}'[0:32],
- self._handle_event)
+ f"{ALEXA_DOMAIN}_{hide_email(self._login.email)}"[0:32], self._handle_event
+ )
async def async_will_remove_from_hass(self):
"""Prepare to remove entity."""
@@ -159,10 +153,9 @@ def _handle_event(self, event):
return
except AttributeError:
pass
- if 'queue_state' in event.data:
- queue_state = event.data['queue_state']
- if (queue_state['dopplerId']
- ['deviceSerialNumber'] == self._client.unique_id):
+ if "queue_state" in event.data:
+ queue_state = event.data["queue_state"]
+ if queue_state["dopplerId"]["deviceSerialNumber"] == self._client.unique_id:
self._state = getattr(self._client, self._switch_property)
self.async_schedule_update_ha_state()
@@ -177,17 +170,20 @@ async def _set_switch(self, state, **kwargs):
# if function returns success, make immediate state change
if success:
setattr(self._client, self._switch_property, state)
- _LOGGER.debug("Switch set to %s based on %s",
- getattr(self._client,
- self._switch_property),
- state)
+ _LOGGER.debug(
+ "Switch set to %s based on %s",
+ getattr(self._client, self._switch_property),
+ state,
+ )
self.async_schedule_update_ha_state()
elif self.should_poll:
# if we need to poll, refresh media_client
- _LOGGER.debug("Requesting update of %s due to %s switch to %s",
- self._client,
- self._name,
- state)
+ _LOGGER.debug(
+ "Requesting update of %s due to %s switch to %s",
+ self._client,
+ self._name,
+ state,
+ )
await self._client.async_update()
@property
@@ -211,7 +207,7 @@ def available(self):
@property
def unique_id(self):
"""Return the unique ID."""
- return self._client.unique_id + '_' + self._name
+ return self._client.unique_id + "_" + self._name
@property
def name(self):
@@ -221,8 +217,9 @@ def name(self):
@property
def should_poll(self):
"""Return the polling state."""
- return not (self.hass.data[DATA_ALEXAMEDIA]
- ['accounts'][self._account]['websocket'])
+ return not (
+ self.hass.data[DATA_ALEXAMEDIA]["accounts"][self._account]["websocket"]
+ )
@_catch_login_errors
async def async_update(self):
@@ -241,10 +238,8 @@ async def async_update(self):
def device_info(self):
"""Return device_info for device registry."""
return {
- 'identifiers': {
- (ALEXA_DOMAIN, self._client.unique_id)
- },
- 'via_device': (ALEXA_DOMAIN, self._client.unique_id),
+ "identifiers": {(ALEXA_DOMAIN, self._client.unique_id)},
+ "via_device": (ALEXA_DOMAIN, self._client.unique_id),
}
@property
@@ -264,10 +259,11 @@ def __init__(self, client, account):
# Class info
super().__init__(
client,
- 'dnd_state',
+ "dnd_state",
client.alexa_api.set_dnd_state,
account,
- "do not disturb")
+ "do not disturb",
+ )
@property
def icon(self):
@@ -282,11 +278,8 @@ def __init__(self, client, account):
"""Initialize the Alexa Switch."""
# Class info
super().__init__(
- client,
- 'shuffle',
- client.alexa_api.shuffle,
- account,
- "shuffle")
+ client, "shuffle", client.alexa_api.shuffle, account, "shuffle"
+ )
@property
def icon(self):
@@ -301,11 +294,8 @@ def __init__(self, client, account):
"""Initialize the Alexa Switch."""
# Class info
super().__init__(
- client,
- 'repeat_state',
- client.alexa_api.repeat,
- account,
- "repeat")
+ client, "repeat_state", client.alexa_api.repeat, account, "repeat"
+ )
@property
def icon(self):
diff --git a/pylintrc b/pylintrc
new file mode 100644
index 00000000..186d6654
--- /dev/null
+++ b/pylintrc
@@ -0,0 +1,17 @@
+[MASTER]
+
+[MESSAGES CONTROL]
+# Reasons disabled:
+# format - handled by black
+# unnecessary-pass - This can hurt readability
+# too-many-instance-attributes - This should be refactored later
+# duplicate-code - This should be refactored later as architecture has redundant Home-assistant devices.
+# too-many-arguments
+disable=
+ format,unnecessary-pass,too-many-instance-attributes,duplicate-code,too-many-arguments,too-many-boolean-expressions
+
+[REPORTS]
+reports=no
+
+[FORMAT]
+expected-line-ending-format=LF
diff --git a/setup.cfg b/setup.cfg
index 94fdf0d8..5feccdc1 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,55 @@
[semantic_release]
version_variable=custom_components/alexa_media/const.py:__version__
upload_to_pypi=false
+
+[pydocstyle]
+ignore = D202, D212, D416, D213, D203, D407
+
+[flake8]
+exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build
+# To work with Black
+max-line-length = 88
+# E501: line too long
+# H301: on mport per line
+# W503: Line break occurred before a binary operator
+# E203: Whitespace before ':'
+# D202 No blank lines allowed after function docstring
+# W504 line break after binary operator
+# H102 missing apache
+ignore =
+ E501,
+ H301,
+ W503,
+ E203,
+ D202,
+ W504,
+ H102
+
+[isort]
+# https://github.com/timothycrosley/isort
+# https://github.com/timothycrosley/isort/wiki/isort-Settings
+# splits long import on multiple lines indented by 4 spaces
+multi_line_output = 3
+include_trailing_comma=True
+force_grid_wrap=0
+use_parentheses=True
+line_length=88
+indent = " "
+# by default isort don't check module indexes
+not_skip = __init__.py
+# will group `import x` and `from x import` of the same module.
+force_sort_within_sections = true
+sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
+default_section = THIRDPARTY
+known_first_party = alexa_media,tests
+forced_separate = tests
+combine_as_imports = true
+
+[mypy]
+python_version = 3.7
+ignore_errors = true
+follow_imports = silent
+ignore_missing_imports = true
+warn_incomplete_stub = true
+warn_redundant_casts = true
+warn_unused_configs = true