316 lines
11 KiB
Python
316 lines
11 KiB
Python
import asyncio
|
|
import aiofiles
|
|
import json
|
|
import logging
|
|
import os.path
|
|
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.media_player import (
|
|
MediaPlayerEntity, PLATFORM_SCHEMA)
|
|
from homeassistant.components.media_player.const import (
|
|
MediaPlayerEntityFeature, MediaType)
|
|
from homeassistant.const import (
|
|
CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN)
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.helpers.restore_state import RestoreEntity
|
|
from . import COMPONENT_ABS_DIR, Helper
|
|
from .controller import get_controller
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
DEFAULT_NAME = "Infrarize Media Player"
|
|
DEFAULT_DEVICE_CLASS = "tv"
|
|
DEFAULT_DELAY = 0.5
|
|
|
|
CONF_UNIQUE_ID = 'unique_id'
|
|
CONF_DEVICE_CODE = 'device_code'
|
|
CONF_CONTROLLER_DATA = "controller_data"
|
|
CONF_DELAY = "delay"
|
|
CONF_POWER_SENSOR = 'power_sensor'
|
|
CONF_SOURCE_NAMES = 'source_names'
|
|
CONF_DEVICE_CLASS = 'device_class'
|
|
|
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
vol.Required(CONF_DEVICE_CODE): cv.positive_int,
|
|
vol.Required(CONF_CONTROLLER_DATA): cv.string,
|
|
vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.string,
|
|
vol.Optional(CONF_POWER_SENSOR): cv.entity_id,
|
|
vol.Optional(CONF_SOURCE_NAMES): dict,
|
|
vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): cv.string
|
|
})
|
|
|
|
|
|
async def async_setup_entry(hass, entry, async_add_entities):
|
|
"""Set up Infrarize Media Player from a config entry."""
|
|
config = {**entry.data, **entry.options}
|
|
config.pop('platform', None)
|
|
config.setdefault('unique_id', entry.unique_id)
|
|
await async_setup_platform(hass, config, async_add_entities)
|
|
|
|
|
|
async def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
|
|
"""Set up the IR Media Player platform."""
|
|
device_code = config.get(CONF_DEVICE_CODE)
|
|
device_files_subdir = os.path.join('codes', 'media_player')
|
|
device_files_absdir = os.path.join(COMPONENT_ABS_DIR, device_files_subdir)
|
|
|
|
if not os.path.isdir(device_files_absdir):
|
|
os.makedirs(device_files_absdir)
|
|
|
|
device_json_filename = str(device_code) + '.json'
|
|
device_json_path = os.path.join(device_files_absdir, device_json_filename)
|
|
|
|
if not os.path.exists(device_json_path):
|
|
_LOGGER.warning("Couldn't find the device Json file. The component will " \
|
|
"try to download it from the GitHub repo.")
|
|
|
|
try:
|
|
codes_source = ("https://raw.githubusercontent.com/"
|
|
"kobimx/Infrarize/master/"
|
|
"codes/media_player/{}.json")
|
|
|
|
await Helper.downloader(codes_source.format(device_code), device_json_path)
|
|
except Exception:
|
|
_LOGGER.error("There was an error while downloading the device Json file. " \
|
|
"Please check your internet connection or if the device code " \
|
|
"exists on GitHub. If the problem still exists please " \
|
|
"place the file manually in the proper directory.")
|
|
return
|
|
|
|
try:
|
|
async with aiofiles.open(device_json_path, mode='r') as j:
|
|
_LOGGER.debug(f"loading json file {device_json_path}")
|
|
content = await j.read()
|
|
device_data = json.loads(content)
|
|
_LOGGER.debug(f"{device_json_path} file loaded")
|
|
except Exception:
|
|
_LOGGER.error("The device JSON file is invalid")
|
|
return
|
|
|
|
async_add_entities([InfrarizeMediaPlayer(
|
|
hass, config, device_data
|
|
)])
|
|
|
|
class InfrarizeMediaPlayer(MediaPlayerEntity, RestoreEntity):
|
|
def __init__(self, hass, config, device_data):
|
|
self.hass = hass
|
|
self._unique_id = config.get(CONF_UNIQUE_ID)
|
|
self._name = config.get(CONF_NAME)
|
|
self._device_code = config.get(CONF_DEVICE_CODE)
|
|
self._controller_data = config.get(CONF_CONTROLLER_DATA)
|
|
self._delay = config.get(CONF_DELAY)
|
|
self._power_sensor = config.get(CONF_POWER_SENSOR)
|
|
|
|
self._manufacturer = device_data['manufacturer']
|
|
self._supported_models = device_data['supportedModels']
|
|
self._supported_controller = device_data['supportedController']
|
|
self._commands_encoding = device_data['commandsEncoding']
|
|
self._commands = device_data['commands']
|
|
|
|
self._state = STATE_OFF
|
|
self._sources_list = []
|
|
self._source = None
|
|
self._support_flags = 0
|
|
|
|
self._device_class = config.get(CONF_DEVICE_CLASS)
|
|
|
|
#Supported features
|
|
if 'off' in self._commands and self._commands['off'] is not None:
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.TURN_OFF
|
|
|
|
if 'on' in self._commands and self._commands['on'] is not None:
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.TURN_ON
|
|
|
|
if 'previousChannel' in self._commands and self._commands['previousChannel'] is not None:
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.PREVIOUS_TRACK
|
|
|
|
if 'nextChannel' in self._commands and self._commands['nextChannel'] is not None:
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.NEXT_TRACK
|
|
|
|
if ('volumeDown' in self._commands and self._commands['volumeDown'] is not None) \
|
|
or ('volumeUp' in self._commands and self._commands['volumeUp'] is not None):
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.VOLUME_STEP
|
|
|
|
if 'mute' in self._commands and self._commands['mute'] is not None:
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.VOLUME_MUTE
|
|
|
|
if 'sources' in self._commands and self._commands['sources'] is not None:
|
|
self._support_flags = self._support_flags | MediaPlayerEntityFeature.SELECT_SOURCE | MediaPlayerEntityFeature.PLAY_MEDIA
|
|
|
|
for source, new_name in config.get(CONF_SOURCE_NAMES, {}).items():
|
|
if source in self._commands['sources']:
|
|
if new_name is not None:
|
|
self._commands['sources'][new_name] = self._commands['sources'][source]
|
|
|
|
del self._commands['sources'][source]
|
|
|
|
#Sources list
|
|
for key in self._commands['sources']:
|
|
self._sources_list.append(key)
|
|
|
|
self._temp_lock = asyncio.Lock()
|
|
|
|
#Init the IR/RF controller
|
|
self._controller = get_controller(
|
|
self.hass,
|
|
self._supported_controller,
|
|
self._commands_encoding,
|
|
self._controller_data,
|
|
self._delay)
|
|
|
|
async def async_added_to_hass(self):
|
|
"""Run when entity about to be added."""
|
|
await super().async_added_to_hass()
|
|
|
|
last_state = await self.async_get_last_state()
|
|
|
|
if last_state is not None:
|
|
self._state = last_state.state
|
|
|
|
@property
|
|
def should_poll(self):
|
|
"""Push an update after each command."""
|
|
return True
|
|
|
|
@property
|
|
def unique_id(self):
|
|
"""Return a unique ID."""
|
|
return self._unique_id
|
|
|
|
@property
|
|
def name(self):
|
|
"""Return the name of the media player."""
|
|
return self._name
|
|
|
|
@property
|
|
def device_class(self):
|
|
"""Return the device_class of the media player."""
|
|
return self._device_class
|
|
|
|
@property
|
|
def state(self):
|
|
"""Return the state of the player."""
|
|
return self._state
|
|
|
|
@property
|
|
def media_title(self):
|
|
"""Return the title of current playing media."""
|
|
return None
|
|
|
|
@property
|
|
def media_content_type(self):
|
|
"""Content type of current playing media."""
|
|
return MediaType.CHANNEL
|
|
|
|
@property
|
|
def source_list(self):
|
|
return self._sources_list
|
|
|
|
@property
|
|
def source(self):
|
|
return self._source
|
|
|
|
@property
|
|
def supported_features(self):
|
|
"""Flag media player features that are supported."""
|
|
return self._support_flags
|
|
|
|
@property
|
|
def extra_state_attributes(self):
|
|
"""Platform specific attributes."""
|
|
return {
|
|
'device_code': self._device_code,
|
|
'manufacturer': self._manufacturer,
|
|
'supported_models': self._supported_models,
|
|
'supported_controller': self._supported_controller,
|
|
'commands_encoding': self._commands_encoding,
|
|
}
|
|
|
|
async def async_turn_off(self):
|
|
"""Turn the media player off."""
|
|
await self.send_command(self._commands['off'])
|
|
|
|
if self._power_sensor is None:
|
|
self._state = STATE_OFF
|
|
self._source = None
|
|
self.async_write_ha_state()
|
|
|
|
async def async_turn_on(self):
|
|
"""Turn the media player off."""
|
|
await self.send_command(self._commands['on'])
|
|
|
|
if self._power_sensor is None:
|
|
self._state = STATE_ON
|
|
self.async_write_ha_state()
|
|
|
|
async def async_media_previous_track(self):
|
|
"""Send previous track command."""
|
|
await self.send_command(self._commands['previousChannel'])
|
|
self.async_write_ha_state()
|
|
|
|
async def async_media_next_track(self):
|
|
"""Send next track command."""
|
|
await self.send_command(self._commands['nextChannel'])
|
|
self.async_write_ha_state()
|
|
|
|
async def async_volume_down(self):
|
|
"""Turn volume down for media player."""
|
|
await self.send_command(self._commands['volumeDown'])
|
|
self.async_write_ha_state()
|
|
|
|
async def async_volume_up(self):
|
|
"""Turn volume up for media player."""
|
|
await self.send_command(self._commands['volumeUp'])
|
|
self.async_write_ha_state()
|
|
|
|
async def async_mute_volume(self, mute):
|
|
"""Mute the volume."""
|
|
await self.send_command(self._commands['mute'])
|
|
self.async_write_ha_state()
|
|
|
|
async def async_select_source(self, source):
|
|
"""Select channel from source."""
|
|
self._source = source
|
|
await self.send_command(self._commands['sources'][source])
|
|
self.async_write_ha_state()
|
|
|
|
async def async_play_media(self, media_type, media_id, **kwargs):
|
|
"""Support channel change through play_media service."""
|
|
if self._state == STATE_OFF:
|
|
await self.async_turn_on()
|
|
|
|
if media_type != MediaType.CHANNEL:
|
|
_LOGGER.error("invalid media type")
|
|
return
|
|
if not media_id.isdigit():
|
|
_LOGGER.error("media_id must be a channel number")
|
|
return
|
|
|
|
self._source = "Channel {}".format(media_id)
|
|
for digit in media_id:
|
|
await self.send_command(self._commands['sources']["Channel {}".format(digit)])
|
|
self.async_write_ha_state()
|
|
|
|
async def send_command(self, command):
|
|
async with self._temp_lock:
|
|
try:
|
|
await self._controller.send(command)
|
|
except Exception as e:
|
|
_LOGGER.exception(e)
|
|
|
|
async def async_update(self):
|
|
if self._power_sensor is None:
|
|
return
|
|
|
|
power_state = self.hass.states.get(self._power_sensor)
|
|
|
|
if power_state:
|
|
if power_state.state == STATE_OFF:
|
|
self._state = STATE_OFF
|
|
self._source = None
|
|
elif power_state.state == STATE_ON:
|
|
self._state = STATE_ON
|