diff --git a/.coveragerc b/.coveragerc index 8f67671a5a2..a33538b7e63 100644 --- a/.coveragerc +++ b/.coveragerc @@ -201,6 +201,7 @@ omit = homeassistant/components/light/yeelight.py homeassistant/components/light/zengge.py homeassistant/components/lirc.py + homeassistant/components/media_player/anthemav.py homeassistant/components/media_player/aquostv.py homeassistant/components/media_player/braviatv.py homeassistant/components/media_player/cast.py diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py new file mode 100644 index 00000000000..2707a62f7bf --- /dev/null +++ b/homeassistant/components/media_player/anthemav.py @@ -0,0 +1,175 @@ +""" +Support for Anthem Network Receivers and Processors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/media_player.anthemav/ +""" +import logging +import asyncio + +import voluptuous as vol + +from homeassistant.components.media_player import ( + PLATFORM_SCHEMA, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_SELECT_SOURCE, + SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, MediaPlayerDevice) +from homeassistant.const import ( + CONF_NAME, CONF_HOST, CONF_PORT, STATE_OFF, STATE_ON, STATE_UNKNOWN, + EVENT_HOMEASSISTANT_STOP) +import homeassistant.helpers.config_validation as cv + +REQUIREMENTS = ['anthemav==1.1.7'] + +_LOGGER = logging.getLogger(__name__) + +DOMAIN = 'anthemav' + +DEFAULT_PORT = 14999 + +SUPPORT_ANTHEMAV = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ + SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + }) + + +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_devices, discovery_info=None): + """Set up our socket to the AVR.""" + import anthemav + + host = config.get(CONF_HOST) + port = config.get(CONF_PORT) + name = config.get(CONF_NAME) + device = None + + _LOGGER.info('Provisioning Anthem AVR device at %s:%d', host, port) + + def async_anthemav_update_callback(message): + """Receive notification from transport that new data exists.""" + _LOGGER.info('Received update calback from AVR: %s', message) + hass.async_add_job(device.async_update_ha_state()) + + avr = yield from anthemav.Connection.create( + host=host, port=port, loop=hass.loop, + update_callback=async_anthemav_update_callback) + + device = AnthemAVR(avr, name) + + _LOGGER.debug('dump_devicedata: '+device.dump_avrdata) + _LOGGER.debug('dump_conndata: '+avr.dump_conndata) + _LOGGER.debug('dump_rawdata: '+avr.protocol.dump_rawdata) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, device.avr.close) + yield from async_add_devices([device]) + + +class AnthemAVR(MediaPlayerDevice): + """Entity reading values from Anthem AVR protocol.""" + + def __init__(self, avr, name): + """"Initialize entity with transport.""" + super().__init__() + self.avr = avr + self._name = name + + def _lookup(self, propname, dval=None): + return getattr(self.avr.protocol, propname, dval) + + @property + def supported_media_commands(self): + """Return flag of media commands that are supported.""" + return SUPPORT_ANTHEMAV + + @property + def should_poll(self): + """No polling needed.""" + return False + + @property + def name(self): + """Return name of device.""" + return self._name or self._lookup('model') + + @property + def state(self): + """Return state of power on/off.""" + pwrstate = self._lookup('power') + + if pwrstate is True: + return STATE_ON + elif pwrstate is False: + return STATE_OFF + else: + return STATE_UNKNOWN + + @property + def is_volume_muted(self): + """Return boolean reflecting mute state on device.""" + return self._lookup('mute', False) + + @property + def volume_level(self): + """Return volume level from 0 to 1.""" + return self._lookup('volume_as_percentage', 0.0) + + @property + def media_title(self): + """Return current input name (closest we have to media title).""" + return self._lookup('input_name', 'No Source') + + @property + def app_name(self): + """Return details about current video and audio stream.""" + return self._lookup('video_input_resolution_text', '') + ' ' \ + + self._lookup('audio_input_name', '') + + @property + def source(self): + """Return currently selected input.""" + return self._lookup('input_name', "Unknown") + + @property + def source_list(self): + """Return all active, configured inputs.""" + return self._lookup('input_list', ["Unknown"]) + + @asyncio.coroutine + def async_select_source(self, source): + """Change AVR to the designated source (by name).""" + self._update_avr('input_name', source) + + @asyncio.coroutine + def async_turn_off(self): + """Turn AVR power off.""" + self._update_avr('power', False) + + @asyncio.coroutine + def async_turn_on(self): + """Turn AVR power on.""" + self._update_avr('power', True) + + @asyncio.coroutine + def async_set_volume_level(self, volume): + """Set AVR volume (0 to 1).""" + self._update_avr('volume_as_percentage', volume) + + @asyncio.coroutine + def async_mute_volume(self, mute): + """Engage AVR mute.""" + self._update_avr('mute', mute) + + def _update_avr(self, propname, value): + """Update a property in the AVR.""" + _LOGGER.info('Sending command to AVR: set '+propname+' to '+str(value)) + setattr(self.avr.protocol, propname, value) + + @property + def dump_avrdata(self): + """Return state of avr object for debugging forensics.""" + attrs = vars(self) + return( + 'dump_avrdata: ' + + ', '.join('%s: %s' % item for item in attrs.items())) diff --git a/requirements_all.txt b/requirements_all.txt index b8fe4898d70..58339edbbaa 100755 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -40,6 +40,9 @@ aiohttp_cors==0.5.0 # homeassistant.components.sensor.amcrest amcrest==1.1.0 +# homeassistant.components.media_player.anthemav +anthemav==1.1.7 + # homeassistant.components.apcupsd apcaccess==0.0.4