Skip to content
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

Merged
merged 19 commits into from Oct 18, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .coveragerc
Expand Up @@ -372,6 +372,7 @@ omit =
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/monoprice.py
Copy link
Member

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
Expand Down
184 changes: 184 additions & 0 deletions homeassistant/components/media_player/monoprice.py
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed. port can't be None as it's a requirement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fabaff Yes, a serial.SerialException will be thrown

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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))