2019-04-03 15:40:03 +00:00
|
|
|
"""Support for functionality to interact with Android TV / Fire TV devices."""
|
2018-11-19 06:05:58 +00:00
|
|
|
import functools
|
2015-10-10 20:45:13 +00:00
|
|
|
import logging
|
2016-09-05 15:40:57 +00:00
|
|
|
import voluptuous as vol
|
2015-10-09 01:07:36 +00:00
|
|
|
|
|
|
|
from homeassistant.components.media_player import (
|
2019-02-08 22:18:18 +00:00
|
|
|
MediaPlayerDevice, PLATFORM_SCHEMA)
|
|
|
|
from homeassistant.components.media_player.const import (
|
2019-02-28 11:29:56 +00:00
|
|
|
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PREVIOUS_TRACK,
|
2019-03-13 10:18:59 +00:00
|
|
|
SUPPORT_SELECT_SOURCE, SUPPORT_STOP, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
|
|
|
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP)
|
2016-02-19 05:27:50 +00:00
|
|
|
from homeassistant.const import (
|
2019-03-13 10:18:59 +00:00
|
|
|
ATTR_COMMAND, ATTR_ENTITY_ID, CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME,
|
|
|
|
CONF_PORT, STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
|
|
|
STATE_STANDBY)
|
2019-03-29 15:08:36 +00:00
|
|
|
from homeassistant.exceptions import PlatformNotReady
|
2016-09-05 15:40:57 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
ANDROIDTV_DOMAIN = 'androidtv'
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2016-09-05 15:40:57 +00:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
SUPPORT_ANDROIDTV = SUPPORT_PAUSE | SUPPORT_PLAY | \
|
|
|
|
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
|
|
|
|
SUPPORT_NEXT_TRACK | SUPPORT_STOP | SUPPORT_VOLUME_MUTE | \
|
|
|
|
SUPPORT_VOLUME_STEP
|
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
SUPPORT_FIRETV = SUPPORT_PAUSE | SUPPORT_PLAY | \
|
2015-10-09 01:07:36 +00:00
|
|
|
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
|
2019-02-24 23:16:49 +00:00
|
|
|
SUPPORT_NEXT_TRACK | SUPPORT_SELECT_SOURCE | SUPPORT_STOP
|
2018-11-19 06:05:58 +00:00
|
|
|
|
|
|
|
CONF_ADBKEY = 'adbkey'
|
2019-02-24 23:16:49 +00:00
|
|
|
CONF_ADB_SERVER_IP = 'adb_server_ip'
|
|
|
|
CONF_ADB_SERVER_PORT = 'adb_server_port'
|
2019-03-03 20:39:39 +00:00
|
|
|
CONF_APPS = 'apps'
|
2018-11-19 06:05:58 +00:00
|
|
|
CONF_GET_SOURCES = 'get_sources'
|
2019-03-25 12:37:10 +00:00
|
|
|
CONF_TURN_ON_COMMAND = 'turn_on_command'
|
|
|
|
CONF_TURN_OFF_COMMAND = 'turn_off_command'
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
DEFAULT_NAME = 'Android TV'
|
2018-11-19 06:05:58 +00:00
|
|
|
DEFAULT_PORT = 5555
|
2019-02-24 23:16:49 +00:00
|
|
|
DEFAULT_ADB_SERVER_PORT = 5037
|
2018-11-19 06:05:58 +00:00
|
|
|
DEFAULT_GET_SOURCES = True
|
2019-03-13 10:18:59 +00:00
|
|
|
DEFAULT_DEVICE_CLASS = 'auto'
|
|
|
|
|
|
|
|
DEVICE_ANDROIDTV = 'androidtv'
|
|
|
|
DEVICE_FIRETV = 'firetv'
|
|
|
|
DEVICE_CLASSES = [DEFAULT_DEVICE_CLASS, DEVICE_ANDROIDTV, DEVICE_FIRETV]
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-02-28 11:29:56 +00:00
|
|
|
SERVICE_ADB_COMMAND = 'adb_command'
|
|
|
|
|
|
|
|
SERVICE_ADB_COMMAND_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
|
|
|
|
vol.Required(ATTR_COMMAND): cv.string,
|
|
|
|
})
|
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2016-09-05 15:40:57 +00:00
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
2018-11-19 06:05:58 +00:00
|
|
|
vol.Required(CONF_HOST): cv.string,
|
2019-03-13 10:18:59 +00:00
|
|
|
vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS):
|
|
|
|
vol.In(DEVICE_CLASSES),
|
2016-09-05 15:40:57 +00:00
|
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
|
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
2019-03-28 01:19:24 +00:00
|
|
|
vol.Optional(CONF_ADBKEY): cv.isfile,
|
2019-02-24 23:16:49 +00:00
|
|
|
vol.Optional(CONF_ADB_SERVER_IP): cv.string,
|
2019-03-13 10:18:59 +00:00
|
|
|
vol.Optional(CONF_ADB_SERVER_PORT, default=DEFAULT_ADB_SERVER_PORT):
|
|
|
|
cv.port,
|
2019-03-03 20:39:39 +00:00
|
|
|
vol.Optional(CONF_GET_SOURCES, default=DEFAULT_GET_SOURCES): cv.boolean,
|
2019-03-13 10:18:59 +00:00
|
|
|
vol.Optional(CONF_APPS, default=dict()):
|
2019-03-25 12:37:10 +00:00
|
|
|
vol.Schema({cv.string: cv.string}),
|
|
|
|
vol.Optional(CONF_TURN_ON_COMMAND): cv.string,
|
|
|
|
vol.Optional(CONF_TURN_OFF_COMMAND): cv.string
|
2016-09-05 15:40:57 +00:00
|
|
|
})
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
# Translate from `AndroidTV` / `FireTV` reported state to HA state.
|
|
|
|
ANDROIDTV_STATES = {'off': STATE_OFF,
|
|
|
|
'idle': STATE_IDLE,
|
|
|
|
'standby': STATE_STANDBY,
|
|
|
|
'playing': STATE_PLAYING,
|
|
|
|
'paused': STATE_PAUSED}
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-08-24 14:37:30 +00:00
|
|
|
def setup_platform(hass, config, add_entities, discovery_info=None):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Set up the Android TV / Fire TV platform."""
|
|
|
|
from androidtv import setup
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
hass.data.setdefault(ANDROIDTV_DOMAIN, {})
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
host = '{0}:{1}'.format(config[CONF_HOST], config[CONF_PORT])
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
if CONF_ADB_SERVER_IP not in config:
|
|
|
|
# Use "python-adb" (Python ADB implementation)
|
2019-05-23 20:57:00 +00:00
|
|
|
adb_log = "using Python ADB implementation "
|
2019-02-24 23:16:49 +00:00
|
|
|
if CONF_ADBKEY in config:
|
2019-03-13 10:18:59 +00:00
|
|
|
aftv = setup(host, config[CONF_ADBKEY],
|
|
|
|
device_class=config[CONF_DEVICE_CLASS])
|
2019-05-23 20:57:00 +00:00
|
|
|
adb_log += "with adbkey='{0}'".format(config[CONF_ADBKEY])
|
2019-03-13 10:18:59 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
else:
|
2019-03-13 10:18:59 +00:00
|
|
|
aftv = setup(host, device_class=config[CONF_DEVICE_CLASS])
|
2019-05-23 20:57:00 +00:00
|
|
|
adb_log += "without adbkey authentication"
|
2018-11-19 06:05:58 +00:00
|
|
|
else:
|
2019-02-24 23:16:49 +00:00
|
|
|
# Use "pure-python-adb" (communicate with ADB server)
|
2019-03-13 10:18:59 +00:00
|
|
|
aftv = setup(host, adb_server_ip=config[CONF_ADB_SERVER_IP],
|
|
|
|
adb_server_port=config[CONF_ADB_SERVER_PORT],
|
|
|
|
device_class=config[CONF_DEVICE_CLASS])
|
2019-05-23 20:57:00 +00:00
|
|
|
adb_log = "using ADB server at {0}:{1}".format(
|
2019-02-24 23:16:49 +00:00
|
|
|
config[CONF_ADB_SERVER_IP], config[CONF_ADB_SERVER_PORT])
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
if not aftv.available:
|
|
|
|
# Determine the name that will be used for the device in the log
|
|
|
|
if CONF_NAME in config:
|
|
|
|
device_name = config[CONF_NAME]
|
|
|
|
elif config[CONF_DEVICE_CLASS] == DEVICE_ANDROIDTV:
|
|
|
|
device_name = 'Android TV device'
|
|
|
|
elif config[CONF_DEVICE_CLASS] == DEVICE_FIRETV:
|
|
|
|
device_name = 'Fire TV device'
|
|
|
|
else:
|
|
|
|
device_name = 'Android TV / Fire TV device'
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-05-23 20:57:00 +00:00
|
|
|
_LOGGER.warning("Could not connect to %s at %s %s",
|
2019-03-13 10:18:59 +00:00
|
|
|
device_name, host, adb_log)
|
2019-03-29 15:08:36 +00:00
|
|
|
raise PlatformNotReady
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
if host in hass.data[ANDROIDTV_DOMAIN]:
|
2019-02-28 11:29:56 +00:00
|
|
|
_LOGGER.warning("Platform already setup on %s, skipping", host)
|
|
|
|
else:
|
2019-03-13 10:18:59 +00:00
|
|
|
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
|
|
|
|
device = AndroidTVDevice(aftv, config[CONF_NAME],
|
2019-03-25 12:37:10 +00:00
|
|
|
config[CONF_APPS],
|
|
|
|
config.get(CONF_TURN_ON_COMMAND),
|
|
|
|
config.get(CONF_TURN_OFF_COMMAND))
|
2019-03-13 10:18:59 +00:00
|
|
|
device_name = config[CONF_NAME] if CONF_NAME in config \
|
|
|
|
else 'Android TV'
|
|
|
|
else:
|
|
|
|
device = FireTVDevice(aftv, config[CONF_NAME], config[CONF_APPS],
|
2019-03-25 12:37:10 +00:00
|
|
|
config[CONF_GET_SOURCES],
|
|
|
|
config.get(CONF_TURN_ON_COMMAND),
|
|
|
|
config.get(CONF_TURN_OFF_COMMAND))
|
2019-03-13 10:18:59 +00:00
|
|
|
device_name = config[CONF_NAME] if CONF_NAME in config \
|
|
|
|
else 'Fire TV'
|
|
|
|
|
2019-02-28 11:29:56 +00:00
|
|
|
add_entities([device])
|
2019-03-13 10:18:59 +00:00
|
|
|
_LOGGER.debug("Setup %s at %s%s", device_name, host, adb_log)
|
|
|
|
hass.data[ANDROIDTV_DOMAIN][host] = device
|
2019-02-28 11:29:56 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND):
|
2019-02-28 11:29:56 +00:00
|
|
|
return
|
|
|
|
|
|
|
|
def service_adb_command(service):
|
|
|
|
"""Dispatch service calls to target entities."""
|
|
|
|
cmd = service.data.get(ATTR_COMMAND)
|
|
|
|
entity_id = service.data.get(ATTR_ENTITY_ID)
|
2019-03-13 10:18:59 +00:00
|
|
|
target_devices = [dev for dev in hass.data[ANDROIDTV_DOMAIN].values()
|
2019-02-28 11:29:56 +00:00
|
|
|
if dev.entity_id in entity_id]
|
|
|
|
|
|
|
|
for target_device in target_devices:
|
|
|
|
output = target_device.adb_command(cmd)
|
|
|
|
|
|
|
|
# log the output if there is any
|
2019-03-22 12:47:06 +00:00
|
|
|
if output and (not isinstance(output, str) or output.strip()):
|
2019-02-28 11:29:56 +00:00
|
|
|
_LOGGER.info("Output of command '%s' from '%s': %s",
|
|
|
|
cmd, target_device.entity_id, repr(output))
|
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
hass.services.register(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND,
|
2019-02-28 11:29:56 +00:00
|
|
|
service_adb_command,
|
|
|
|
schema=SERVICE_ADB_COMMAND_SCHEMA)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
def adb_decorator(override_available=False):
|
2019-02-24 23:16:49 +00:00
|
|
|
"""Send an ADB command if the device is available and catch exceptions."""
|
|
|
|
def _adb_decorator(func):
|
2018-11-19 06:05:58 +00:00
|
|
|
"""Wait if previous ADB commands haven't finished."""
|
|
|
|
@functools.wraps(func)
|
2019-02-24 23:16:49 +00:00
|
|
|
def _adb_exception_catcher(self, *args, **kwargs):
|
2018-11-19 06:05:58 +00:00
|
|
|
# If the device is unavailable, don't do anything
|
|
|
|
if not self.available and not override_available:
|
|
|
|
return None
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
try:
|
2019-02-24 23:16:49 +00:00
|
|
|
return func(self, *args, **kwargs)
|
2019-01-26 22:11:16 +00:00
|
|
|
except self.exceptions as err:
|
|
|
|
_LOGGER.error(
|
|
|
|
"Failed to execute an ADB command. ADB connection re-"
|
|
|
|
"establishing attempt in the next update. Error: %s", err)
|
2018-11-19 06:05:58 +00:00
|
|
|
self._available = False # pylint: disable=protected-access
|
2019-02-24 23:16:49 +00:00
|
|
|
return None
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
return _adb_exception_catcher
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-02-24 23:16:49 +00:00
|
|
|
return _adb_decorator
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
class ADBDevice(MediaPlayerDevice):
|
|
|
|
"""Representation of an Android TV or Fire TV device."""
|
2019-03-03 20:39:39 +00:00
|
|
|
|
2019-03-25 12:37:10 +00:00
|
|
|
def __init__(self, aftv, name, apps, turn_on_command,
|
|
|
|
turn_off_command):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Initialize the Android TV / Fire TV device."""
|
|
|
|
from androidtv.constants import APPS, KEYS
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
self.aftv = aftv
|
2015-10-09 01:07:36 +00:00
|
|
|
self._name = name
|
2019-03-13 10:18:59 +00:00
|
|
|
self._apps = APPS
|
|
|
|
self._apps.update(apps)
|
|
|
|
self._keys = KEYS
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-25 12:37:10 +00:00
|
|
|
self.turn_on_command = turn_on_command
|
|
|
|
self.turn_off_command = turn_off_command
|
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
# ADB exceptions to catch
|
2019-03-13 10:18:59 +00:00
|
|
|
if not self.aftv.adb_server_ip:
|
2019-02-24 23:16:49 +00:00
|
|
|
# Using "python-adb" (Python ADB implementation)
|
|
|
|
from adb.adb_protocol import (InvalidChecksumError,
|
|
|
|
InvalidCommandError,
|
|
|
|
InvalidResponseError)
|
|
|
|
from adb.usb_exceptions import TcpTimeoutException
|
|
|
|
|
|
|
|
self.exceptions = (AttributeError, BrokenPipeError, TypeError,
|
|
|
|
ValueError, InvalidChecksumError,
|
|
|
|
InvalidCommandError, InvalidResponseError,
|
|
|
|
TcpTimeoutException)
|
|
|
|
else:
|
|
|
|
# Using "pure-python-adb" (communicate with ADB server)
|
2019-03-22 12:47:06 +00:00
|
|
|
self.exceptions = (ConnectionResetError, RuntimeError)
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
# Property attributes
|
|
|
|
self._available = self.aftv.available
|
2018-06-18 16:13:50 +00:00
|
|
|
self._current_app = None
|
2019-03-13 10:18:59 +00:00
|
|
|
self._state = None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def app_id(self):
|
|
|
|
"""Return the current app."""
|
|
|
|
return self._current_app
|
|
|
|
|
|
|
|
@property
|
|
|
|
def app_name(self):
|
|
|
|
"""Return the friendly name of the current app."""
|
|
|
|
return self._apps.get(self._current_app, self._current_app)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def available(self):
|
|
|
|
"""Return whether or not the ADB connection is valid."""
|
|
|
|
return self._available
|
2015-10-09 01:07:36 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Return the device name."""
|
2015-10-09 01:07:36 +00:00
|
|
|
return self._name
|
|
|
|
|
|
|
|
@property
|
|
|
|
def should_poll(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Device should be polled."""
|
2015-10-09 01:07:36 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
@property
|
|
|
|
def state(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Return the state of the player."""
|
2015-10-10 20:45:13 +00:00
|
|
|
return self._state
|
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
|
|
|
def media_play(self):
|
|
|
|
"""Send play command."""
|
|
|
|
self.aftv.media_play()
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
|
|
|
def media_pause(self):
|
|
|
|
"""Send pause command."""
|
|
|
|
self.aftv.media_pause()
|
2019-02-03 21:57:17 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
|
|
|
def media_play_pause(self):
|
|
|
|
"""Send play/pause command."""
|
|
|
|
self.aftv.media_play_pause()
|
2019-03-03 20:39:39 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
|
|
|
def turn_on(self):
|
|
|
|
"""Turn on the device."""
|
2019-03-25 12:37:10 +00:00
|
|
|
if self.turn_on_command:
|
|
|
|
self.aftv.adb_shell(self.turn_on_command)
|
|
|
|
else:
|
|
|
|
self.aftv.turn_on()
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@adb_decorator()
|
|
|
|
def turn_off(self):
|
|
|
|
"""Turn off the device."""
|
2019-03-25 12:37:10 +00:00
|
|
|
if self.turn_off_command:
|
|
|
|
self.aftv.adb_shell(self.turn_off_command)
|
|
|
|
else:
|
|
|
|
self.aftv.turn_off()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
|
|
|
def media_previous_track(self):
|
|
|
|
"""Send previous track command (results in rewind)."""
|
2019-04-06 15:18:50 +00:00
|
|
|
self.aftv.media_previous_track()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
|
|
|
def media_next_track(self):
|
|
|
|
"""Send next track command (results in fast-forward)."""
|
2019-04-06 15:18:50 +00:00
|
|
|
self.aftv.media_next_track()
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
|
|
|
def adb_command(self, cmd):
|
|
|
|
"""Send an ADB command to an Android TV / Fire TV device."""
|
|
|
|
key = self._keys.get(cmd)
|
|
|
|
if key:
|
|
|
|
return self.aftv.adb_shell('input keyevent {}'.format(key))
|
|
|
|
|
|
|
|
if cmd == 'GET_PROPERTIES':
|
|
|
|
return self.aftv.get_properties_dict()
|
|
|
|
|
|
|
|
return self.aftv.adb_shell(cmd)
|
|
|
|
|
|
|
|
|
|
|
|
class AndroidTVDevice(ADBDevice):
|
|
|
|
"""Representation of an Android TV device."""
|
|
|
|
|
2019-03-25 12:37:10 +00:00
|
|
|
def __init__(self, aftv, name, apps, turn_on_command,
|
|
|
|
turn_off_command):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Initialize the Android TV device."""
|
2019-03-25 12:37:10 +00:00
|
|
|
super().__init__(aftv, name, apps, turn_on_command,
|
|
|
|
turn_off_command)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
self._device = None
|
|
|
|
self._device_properties = self.aftv.device_properties
|
2019-04-06 15:18:50 +00:00
|
|
|
self._is_volume_muted = None
|
2019-04-15 17:28:58 +00:00
|
|
|
self._unique_id = self._device_properties.get('serialno')
|
2019-04-06 15:18:50 +00:00
|
|
|
self._volume_level = None
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator(override_available=True)
|
2015-10-10 20:45:13 +00:00
|
|
|
def update(self):
|
2019-02-24 23:16:49 +00:00
|
|
|
"""Update the device state and, if necessary, re-connect."""
|
2018-11-19 06:05:58 +00:00
|
|
|
# Check if device is disconnected.
|
|
|
|
if not self._available:
|
|
|
|
# Try to connect
|
2019-03-13 10:18:59 +00:00
|
|
|
self._available = self.aftv.connect(always_log_errors=False)
|
2019-02-24 23:16:49 +00:00
|
|
|
|
|
|
|
# To be safe, wait until the next update to run ADB commands.
|
|
|
|
return
|
2018-11-19 06:05:58 +00:00
|
|
|
|
|
|
|
# If the ADB connection is not intact, don't update.
|
|
|
|
if not self._available:
|
|
|
|
return
|
|
|
|
|
2019-04-06 15:18:50 +00:00
|
|
|
# Get the updated state and attributes.
|
|
|
|
state, self._current_app, self._device, self._is_volume_muted, \
|
|
|
|
self._volume_level = self.aftv.update()
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
self._state = ANDROIDTV_STATES[state]
|
2018-11-19 06:05:58 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def is_volume_muted(self):
|
|
|
|
"""Boolean if volume is currently muted."""
|
2019-04-06 15:18:50 +00:00
|
|
|
return self._is_volume_muted
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def source(self):
|
|
|
|
"""Return the current playback device."""
|
|
|
|
return self._device
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag media player features that are supported."""
|
|
|
|
return SUPPORT_ANDROIDTV
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def unique_id(self):
|
|
|
|
"""Return the device unique id."""
|
|
|
|
return self._unique_id
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2019-03-13 10:18:59 +00:00
|
|
|
@property
|
|
|
|
def volume_level(self):
|
|
|
|
"""Return the volume level."""
|
2019-04-06 15:18:50 +00:00
|
|
|
return self._volume_level
|
2018-11-19 06:05:58 +00:00
|
|
|
|
|
|
|
@adb_decorator()
|
|
|
|
def media_stop(self):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Send stop command."""
|
|
|
|
self.aftv.media_stop()
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2019-03-13 10:18:59 +00:00
|
|
|
def mute_volume(self, mute):
|
|
|
|
"""Mute the volume."""
|
|
|
|
self.aftv.mute_volume()
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2015-10-09 01:07:36 +00:00
|
|
|
def volume_down(self):
|
2016-03-08 09:34:33 +00:00
|
|
|
"""Send volume down command."""
|
2019-04-06 15:18:50 +00:00
|
|
|
self._volume_level = self.aftv.volume_down(self._volume_level)
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2019-03-13 10:18:59 +00:00
|
|
|
def volume_up(self):
|
|
|
|
"""Send volume up command."""
|
2019-04-06 15:18:50 +00:00
|
|
|
self._volume_level = self.aftv.volume_up(self._volume_level)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
|
|
|
|
class FireTVDevice(ADBDevice):
|
|
|
|
"""Representation of a Fire TV device."""
|
|
|
|
|
2019-03-25 12:37:10 +00:00
|
|
|
def __init__(self, aftv, name, apps, get_sources,
|
|
|
|
turn_on_command, turn_off_command):
|
2019-03-13 10:18:59 +00:00
|
|
|
"""Initialize the Fire TV device."""
|
2019-03-25 12:37:10 +00:00
|
|
|
super().__init__(aftv, name, apps, turn_on_command,
|
|
|
|
turn_off_command)
|
2019-03-13 10:18:59 +00:00
|
|
|
|
|
|
|
self._get_sources = get_sources
|
|
|
|
self._running_apps = None
|
|
|
|
|
|
|
|
@adb_decorator(override_available=True)
|
|
|
|
def update(self):
|
|
|
|
"""Update the device state and, if necessary, re-connect."""
|
|
|
|
# Check if device is disconnected.
|
|
|
|
if not self._available:
|
|
|
|
# Try to connect
|
|
|
|
self._available = self.aftv.connect(always_log_errors=False)
|
|
|
|
|
|
|
|
# To be safe, wait until the next update to run ADB commands.
|
|
|
|
return
|
|
|
|
|
|
|
|
# If the ADB connection is not intact, don't update.
|
|
|
|
if not self._available:
|
|
|
|
return
|
|
|
|
|
|
|
|
# Get the `state`, `current_app`, and `running_apps`.
|
|
|
|
state, self._current_app, self._running_apps = \
|
|
|
|
self.aftv.update(self._get_sources)
|
|
|
|
|
|
|
|
self._state = ANDROIDTV_STATES[state]
|
|
|
|
|
|
|
|
@property
|
|
|
|
def source(self):
|
|
|
|
"""Return the current app."""
|
|
|
|
return self._current_app
|
|
|
|
|
|
|
|
@property
|
|
|
|
def source_list(self):
|
|
|
|
"""Return a list of running apps."""
|
|
|
|
return self._running_apps
|
|
|
|
|
|
|
|
@property
|
|
|
|
def supported_features(self):
|
|
|
|
"""Flag media player features that are supported."""
|
|
|
|
return SUPPORT_FIRETV
|
2015-10-09 01:07:36 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2019-03-13 10:18:59 +00:00
|
|
|
def media_stop(self):
|
|
|
|
"""Send stop (back) command."""
|
|
|
|
self.aftv.back()
|
2018-06-18 16:13:50 +00:00
|
|
|
|
2018-11-19 06:05:58 +00:00
|
|
|
@adb_decorator()
|
2018-06-18 16:13:50 +00:00
|
|
|
def select_source(self, source):
|
2018-11-19 06:05:58 +00:00
|
|
|
"""Select input source.
|
|
|
|
|
|
|
|
If the source starts with a '!', then it will close the app instead of
|
|
|
|
opening it.
|
|
|
|
"""
|
|
|
|
if isinstance(source, str):
|
|
|
|
if not source.startswith('!'):
|
2019-03-13 10:18:59 +00:00
|
|
|
self.aftv.launch_app(source)
|
2018-11-19 06:05:58 +00:00
|
|
|
else:
|
2019-03-13 10:18:59 +00:00
|
|
|
self.aftv.stop_app(source[1:].lstrip())
|