New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A new platform for controlling Monoprice 6-Zone amplifier #9662
Changes from 6 commits
e094856
73a180d
71e3947
f75471c
3edad05
0b628ee
eafade6
091bd8d
d4530a7
3a10e74
a460ccc
b5442db
91e92d0
a550396
e72e144
707af2d
5d1246d
0e5aa95
5f6f1ae
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
""" | ||
Support for interfacing with Monoprice 6 zone home audio controller | ||
via serial interface. | ||
|
||
https://www.monoprice.com/product?p_id=10761 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please point to the documentation at https://home-assistant.io/components/media_player.monoprice/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
""" | ||
import logging | ||
|
||
import voluptuous as vol | ||
from homeassistant.components.media_player import ( | ||
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, | ||
SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, | ||
MediaPlayerDevice, PLATFORM_SCHEMA) | ||
from homeassistant.const import (CONF_PORT, STATE_OFF, STATE_ON, CONF_NAME) | ||
import homeassistant.helpers.config_validation as cv | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please keep it ordered -> https://home-assistant.io/developers/development_guidelines/ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
|
||
REQUIREMENTS = ['pymonoprice==0.2'] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
SUPPORT_MONOPRICE = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \ | ||
SUPPORT_VOLUME_STEP | SUPPORT_TURN_ON | \ | ||
SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE | ||
|
||
ZONE_SCHEMA = vol.Schema({ | ||
vol.Required(CONF_NAME): cv.string, | ||
}) | ||
|
||
SOURCE_SCHEMA = vol.Schema({ | ||
vol.Required(CONF_NAME): cv.string, | ||
}) | ||
|
||
CONF_ZONES = 'zones' | ||
CONF_SOURCES = 'sources' | ||
|
||
# Valid zone ids: 11-16 or 21-26 or 31-36 | ||
ZONE_IDS = vol.All(vol.Coerce(int), vol.Any(vol.Range(min=11, max=16), | ||
vol.Range(min=21, max=26), | ||
vol.Range(min=31, max=36))) | ||
|
||
# Valid source ids: 1-6 | ||
SOURCE_IDS = vol.All(vol.Coerce(int), vol.Range(min=1, max=6)) | ||
|
||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ | ||
vol.Required(CONF_PORT): cv.string, | ||
vol.Required(CONF_ZONES): vol.Schema({ZONE_IDS: ZONE_SCHEMA}), | ||
vol.Required(CONF_SOURCES): vol.Schema({SOURCE_IDS: SOURCE_SCHEMA}), | ||
}) | ||
|
||
|
||
# pylint: disable=unused-argument | ||
def setup_platform(hass, config, add_devices, discovery_info=None): | ||
""" Sets up the Monoprice 6-zone amplifier platform. """ | ||
|
||
port = config.get(CONF_PORT) | ||
|
||
if port is None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
_LOGGER.error("Invalid config. Expected %s", CONF_PORT) | ||
return False | ||
|
||
from pymonoprice import Monoprice | ||
monoprice = Monoprice(port) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if the device is not available? I guess that there will be an exception. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fabaff Yes, a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You need to catch the exception. We don't want to fill the log with tracebacks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
sources = {source_id: extra[CONF_NAME] for source_id, extra | ||
in config[CONF_SOURCES].items()} | ||
|
||
for zone_id, extra in config[CONF_ZONES].items(): | ||
_LOGGER.info("Adding zone {} - {}".format(zone_id, extra[CONF_NAME])) | ||
add_devices([MonopriceZone(monoprice, sources, | ||
zone_id, extra[CONF_NAME])], True) | ||
return True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
|
||
class MonopriceZone(MediaPlayerDevice): | ||
""" Represents an Monoprice amplifier zone. """ | ||
|
||
# pylint: disable=too-many-public-methods | ||
|
||
def __init__(self, monoprice, sources, zone_id, zone_name): | ||
self._monoprice = monoprice | ||
# dict source_id -> source name | ||
self._source_id_name = sources | ||
# dict source name -> source_id | ||
self._source_name_id = {v: k for k, v in sources.items()} | ||
# ordered list of all source names | ||
self._source_names = sorted(self._source_name_id.keys(), | ||
key=lambda v: self._source_name_id[v]) | ||
self._zone_id = zone_id | ||
self._name = zone_name | ||
|
||
self._state = None | ||
self._volume = None | ||
self._source = None | ||
self._mute = None | ||
|
||
def update(self): | ||
"""Retrieve latest state.""" | ||
state = self._monoprice.zone_status(self._zone_id) | ||
if not state: | ||
return False | ||
self._state = STATE_ON if state.power else STATE_OFF | ||
self._volume = state.volume | ||
self._mute = state.mute | ||
idx = state.source | ||
if idx in self._source_id_name: | ||
self._source = self._source_id_name[idx] | ||
else: | ||
self._source = None | ||
return True | ||
|
||
@property | ||
def name(self): | ||
""" Returns the name of the zone. """ | ||
return self._name | ||
|
||
@property | ||
def state(self): | ||
""" Returns the state of the zone. """ | ||
return self._state | ||
|
||
@property | ||
def volume_level(self): | ||
""" Volume level of the media player (0..1). """ | ||
if self._volume is None: | ||
return None | ||
return self._volume / 38.0 | ||
|
||
@property | ||
def is_volume_muted(self): | ||
""" Boolean if volume is currently muted. """ | ||
return self._mute | ||
|
||
@property | ||
def supported_features(self): | ||
""" Flags of media commands that are supported. """ | ||
return SUPPORT_MONOPRICE | ||
|
||
@property | ||
def source(self): | ||
""""Return the current input source of the device.""" | ||
return self._source | ||
|
||
@property | ||
def source_list(self): | ||
"""List of available input sources.""" | ||
return self._source_names | ||
|
||
def select_source(self, source): | ||
""" Set input source. """ | ||
if source not in self._source_name_id: | ||
return | ||
idx = self._source_name_id[source] | ||
self._monoprice.set_source(self._zone_id, idx) | ||
|
||
def turn_on(self): | ||
""" turn the media player on. """ | ||
self._monoprice.set_power(self._zone_id, True) | ||
|
||
def turn_off(self): | ||
""" turn_off media player. """ | ||
self._monoprice.set_power(self._zone_id, False) | ||
|
||
def mute_volume(self, mute): | ||
""" Mute (true) or unmute (false) media player. """ | ||
self._monoprice.set_mute(self._zone_id, mute) | ||
|
||
def set_volume_level(self, volume): | ||
""" Set volume level, range 0..1. """ | ||
self._monoprice.set_volume(self._zone_id, int(volume * 38)) | ||
|
||
def volume_up(self): | ||
"""Volume up the media player.""" | ||
if self._volume is None: | ||
return | ||
self._monoprice.set_volume(self._zone_id, | ||
min(self._volume + 1, 38)) | ||
|
||
def volume_down(self): | ||
"""Volume down media player.""" | ||
if self._volume is None: | ||
return | ||
self._monoprice.set_volume(self._zone_id, | ||
max(self._volume - 1, 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed if there are tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done