Created
June 10, 2019 12:40
-
-
Save gerard33/4d1b62a626daddd298bd2ed53be8dc36 to your computer and use it in GitHub Desktop.
Sony Bravia PSK for Homekit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Support for interface with a Sony Bravia TV. | |
For more details about this platform, please refer to the documentation at | |
https://github.com/custom-components/media_player.braviatv_psk | |
""" | |
import logging | |
import voluptuous as vol | |
from homeassistant.components.media_player import ( | |
MediaPlayerDevice, PLATFORM_SCHEMA) | |
try: | |
from homeassistant.components.media_player.const import ( | |
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON, | |
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY, | |
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, | |
MEDIA_TYPE_TVSHOW, SUPPORT_STOP) | |
except ImportError: | |
from homeassistant.components.media_player import ( | |
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_ON, | |
SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_PLAY, | |
SUPPORT_PLAY_MEDIA, SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, | |
MEDIA_TYPE_TVSHOW, SUPPORT_STOP) | |
from homeassistant.const import ( | |
CONF_HOST, CONF_NAME, CONF_MAC, STATE_OFF, STATE_ON) | |
import homeassistant.helpers.config_validation as cv | |
__version__ = '0.2.9' | |
REQUIREMENTS = ['pySonyBraviaPSK==0.1.7'] | |
_LOGGER = logging.getLogger(__name__) | |
SUPPORT_BRAVIA = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \ | |
SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ | |
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \ | |
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \ | |
SUPPORT_SELECT_SOURCE | SUPPORT_PLAY | SUPPORT_STOP | |
DEFAULT_NAME = 'Sony Bravia TV' | |
DEVICE_CLASS_TV = 'tv' | |
# Config file | |
CONF_PSK = 'psk' | |
CONF_AMP = 'amp' | |
CONF_ANDROID = 'android' | |
CONF_SOURCE_FILTER = 'sourcefilter' | |
# Some additional info to show specific for Sony Bravia TV | |
TV_WAIT = 'TV started, waiting for program info' | |
TV_APP_OPENED = 'App opened' | |
TV_NO_INFO = 'No info (resumed after pause or app opened)' | |
PLAY_MEDIA_OPTIONS = [ | |
'Num1', 'Num2', 'Num3', 'Num4', 'Num5', 'Num6', 'Num7', 'Num8', 'Num9', | |
'Num0', 'Num11', 'Num12', 'Netflix', 'Red', 'Green', 'Yellow', 'Blue', | |
'ChannelUp', 'ChannelDown', 'Up', 'Down', 'Left', 'Right', 'Display', 'Tv', | |
'Confirm', 'Home', 'EPG', 'Return', 'Options', 'Exit', 'Teletext', 'Input', | |
'TvPause', 'Play', 'Pause', 'Stop', 'HDMI 1', 'HDMI 2', 'HDMI 3', 'HDMI 4', | |
'SleepTimer', 'GooglePlay' | |
] | |
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | |
vol.Required(CONF_HOST): cv.string, | |
vol.Required(CONF_PSK): cv.string, | |
vol.Optional(CONF_MAC): cv.string, | |
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | |
vol.Optional(CONF_AMP, default=False): cv.boolean, | |
vol.Optional(CONF_ANDROID, default=False): cv.boolean, | |
vol.Optional(CONF_SOURCE_FILTER, default=[]): vol.All( | |
cv.ensure_list, [cv.string])}) | |
# pylint: disable=unused-argument | |
def setup_platform(hass, config, add_devices, discovery_info=None): | |
"""Set up the Sony Bravia TV platform.""" | |
host = config.get(CONF_HOST) | |
psk = config.get(CONF_PSK) | |
mac = config.get(CONF_MAC) | |
name = config.get(CONF_NAME) | |
amp = config.get(CONF_AMP) | |
android = config.get(CONF_ANDROID) | |
source_filter = config.get(CONF_SOURCE_FILTER) | |
if host is None or psk is None: | |
_LOGGER.error( | |
"No TV IP address or Pre-Shared Key found in configuration file") | |
return | |
add_devices( | |
[BraviaTVDevice(host, psk, mac, name, amp, android, source_filter)]) | |
class BraviaTVDevice(MediaPlayerDevice): | |
"""Representation of a Sony Bravia TV.""" | |
def __init__(self, host, psk, mac, name, amp, android, source_filter): | |
"""Initialize the Sony Bravia device.""" | |
_LOGGER.info("Setting up Sony Bravia TV") | |
from braviapsk import sony_bravia_psk | |
self._braviarc = sony_bravia_psk.BraviaRC(host, psk, mac) | |
self._name = name | |
self._amp = amp | |
self._android = android | |
self._source_filter = source_filter | |
self._state = STATE_OFF | |
self._muted = False | |
self._program_name = None | |
self._channel_name = None | |
self._channel_number = None | |
self._source = None | |
self._source_list = [] | |
self._content_mapping = {} | |
self._duration = None | |
self._content_uri = None | |
self._playing = False | |
self._start_date_time = None | |
self._program_media_type = None | |
self._min_volume = None | |
self._max_volume = None | |
self._volume = None | |
self._start_time = None | |
self._end_time = None | |
self._device_class = DEVICE_CLASS_TV | |
if mac: | |
self._unique_id = '{}-{}'.format(mac, name) | |
else: | |
self._unique_id = '{}-{}'.format(host, name) | |
_LOGGER.debug( | |
"Set up Sony Bravia TV with IP: %s, PSK: %s, MAC: %s", host, psk, | |
mac) | |
self.update() | |
def update(self): | |
"""Update TV info.""" | |
try: | |
power_status = self._braviarc.get_power_status() | |
if power_status == 'active': | |
self._state = STATE_ON | |
self._refresh_volume() | |
self._refresh_channels() | |
playing_info = self._braviarc.get_playing_info() | |
self._reset_playing_info() | |
if playing_info is None or not playing_info: | |
self._program_name = TV_NO_INFO | |
else: | |
self._program_name = playing_info.get('programTitle') | |
self._channel_name = playing_info.get('title') | |
self._program_media_type = playing_info.get( | |
'programMediaType') | |
self._channel_number = playing_info.get('dispNum') | |
self._source = playing_info.get('title') | |
self._content_uri = playing_info.get('uri') | |
self._duration = playing_info.get('durationSec') | |
self._start_date_time = playing_info.get('startDateTime') | |
# Get time info from TV program | |
if self._start_date_time and self._duration: | |
time_info = self._braviarc.playing_time( | |
self._start_date_time, self._duration) | |
self._start_time = time_info.get('start_time') | |
self._end_time = time_info.get('end_time') | |
else: | |
if self._program_name == TV_WAIT: | |
# TV is starting up, takes some time before it responds | |
_LOGGER.info("TV is starting, no info available yet") | |
else: | |
self._state = STATE_OFF | |
except Exception as exception_instance: # pylint: disable=broad-except | |
_LOGGER.error("No data received from TV. Error message: %s", | |
exception_instance) | |
self._state = STATE_OFF | |
def _reset_playing_info(self): | |
self._program_name = None | |
self._channel_name = None | |
self._program_media_type = None | |
self._channel_number = None | |
self._source = None | |
self._content_uri = None | |
self._duration = None | |
self._start_date_time = None | |
self._start_time = None | |
self._end_time = None | |
def _refresh_volume(self): | |
"""Refresh volume information.""" | |
volume_info = self._braviarc.get_volume_info() | |
if volume_info is not None: | |
self._volume = volume_info.get('volume') | |
self._min_volume = volume_info.get('minVolume') | |
self._max_volume = volume_info.get('maxVolume') | |
self._muted = volume_info.get('mute') | |
def _refresh_channels(self): | |
if not self._source_list: | |
self._content_mapping = self._braviarc.load_source_list() | |
self._source_list = [] | |
if not self._source_filter: # list is empty | |
for key in self._content_mapping: | |
self._source_list.append(key) | |
else: | |
filtered_dict = {title: uri for (title, uri) in | |
self._content_mapping.items() | |
if any(filter_title in title for filter_title | |
in self._source_filter)} | |
for key in filtered_dict: | |
self._source_list.append(key) | |
@property | |
def name(self): | |
"""Return the name of the device.""" | |
return self._name | |
@property | |
def unique_id(self): | |
"""Return the unique ID of the device.""" | |
return self._unique_id | |
@property | |
def state(self): | |
"""Return the state of the device.""" | |
return self._state | |
@property | |
def source(self): | |
"""Return the current input source.""" | |
return self._source | |
@property | |
def source_list(self): | |
"""List of available input sources.""" | |
return self._source_list | |
@property | |
def volume_level(self): | |
"""Volume level of the media player (0..1).""" | |
if self._volume is not None: | |
return self._volume / 100 | |
return None | |
@property | |
def is_volume_muted(self): | |
"""Boolean if volume is currently muted.""" | |
return self._muted | |
@property | |
def supported_features(self): | |
"""Flag media player features that are supported.""" | |
supported = SUPPORT_BRAVIA | |
# Remove volume slider if amplifier is attached to TV | |
if self._amp: | |
supported = supported ^ SUPPORT_VOLUME_SET | |
return supported | |
@property | |
def media_content_type(self): | |
"""Content type of current playing media. | |
Used for program information below the channel in the state card. | |
""" | |
return MEDIA_TYPE_TVSHOW | |
@property | |
def media_title(self): | |
"""Title of current playing media. | |
Used to show TV channel info. | |
""" | |
return_value = None | |
if self._channel_name is not None: | |
if self._channel_number is not None: | |
return_value = '{0!s}: {1}'.format( | |
self._channel_number.lstrip('0'), self._channel_name) | |
else: | |
return_value = self._channel_name | |
return return_value | |
@property | |
def media_series_title(self): | |
"""Title of series of current playing media, TV show only. | |
Used to show TV program info. | |
""" | |
return_value = None | |
if self._program_name is not None: | |
if self._start_time is not None and self._end_time is not None: | |
return_value = '{0} [{1} - {2}]'.format( | |
self._program_name, self._start_time, self._end_time) | |
else: | |
return_value = self._program_name | |
else: | |
if not self._channel_name: # This is empty when app is opened | |
return_value = TV_APP_OPENED | |
return return_value | |
@property | |
def media_content_id(self): | |
"""Content ID of current playing media.""" | |
return self._channel_name | |
@property | |
def device_class(self): | |
"""Return the device class of the media player.""" | |
return self._device_class | |
def set_volume_level(self, volume): | |
"""Set volume level, range 0..1.""" | |
self._braviarc.set_volume_level(volume) | |
def turn_on(self): | |
"""Turn the media player on. | |
Use a different command for Android as WOL is not working. | |
""" | |
if self._android: | |
self._braviarc.turn_on_command() | |
else: | |
self._braviarc.turn_on() | |
# Show that TV is starting while it takes time | |
# before program info is available | |
self._reset_playing_info() | |
self._state = STATE_ON | |
self._program_name = TV_WAIT | |
def turn_off(self): | |
"""Turn off media player.""" | |
self._braviarc.turn_off() | |
self._state = STATE_OFF | |
def volume_up(self): | |
"""Volume up the media player.""" | |
self._braviarc.volume_up() | |
def volume_down(self): | |
"""Volume down media player.""" | |
self._braviarc.volume_down() | |
def mute_volume(self, mute): | |
"""Send mute command.""" | |
self._braviarc.mute_volume() | |
def select_source(self, source): | |
"""Set the input source.""" | |
if source in self._content_mapping: | |
uri = self._content_mapping[source] | |
self._braviarc.play_content(uri) | |
def media_play_pause(self): | |
"""Simulate play pause media player.""" | |
if self._playing: | |
self.media_pause() | |
else: | |
self.media_play() | |
def media_play(self): | |
"""Send play command.""" | |
self._playing = True | |
self._braviarc.media_play() | |
def media_pause(self): | |
"""Send media pause command to media player. | |
Will pause TV when TV tuner is on. | |
""" | |
self._playing = False | |
if self._program_media_type == 'tv' or self._program_name is not None: | |
self._braviarc.media_tvpause() | |
else: | |
self._braviarc.media_pause() | |
def media_next_track(self): | |
"""Send next track command. | |
Will switch to next channel when TV tuner is on. | |
""" | |
if self._program_media_type == 'tv' or self._program_name is not None: | |
self._braviarc.send_command('ChannelUp') | |
else: | |
self._braviarc.media_next_track() | |
def media_previous_track(self): | |
"""Send the previous track command. | |
Will switch to previous channel when TV tuner is on. | |
""" | |
if self._program_media_type == 'tv' or self._program_name is not None: | |
self._braviarc.send_command('ChannelDown') | |
else: | |
self._braviarc.media_previous_track() | |
def play_media(self, media_type, media_id, **kwargs): | |
"""Play media.""" | |
_LOGGER.debug("Play media: %s (%s)", media_id, media_type) | |
if media_id in PLAY_MEDIA_OPTIONS: | |
self._braviarc.send_command(media_id) | |
else: | |
_LOGGER.warning("Unsupported media_id: %s", media_id) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment