diff --git a/CODEOWNERS b/CODEOWNERS
index d78081352f21303d5262c75c4220908d5ca5e6b9..0560f5d53109c1c29e4a7eab5a66dde174dc5f2c 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -49,6 +49,7 @@ homeassistant/components/history_graph.py @andrey-git
 homeassistant/components/light/tplink.py @rytilahti
 homeassistant/components/light/yeelight.py @rytilahti
 homeassistant/components/media_player/kodi.py @armills
+homeassistant/components/media_player/monoprice.py @etsinko
 homeassistant/components/sensor/airvisual.py @bachya
 homeassistant/components/sensor/miflora.py @danielhiversen
 homeassistant/components/sensor/tibber.py @danielhiversen
diff --git a/homeassistant/components/media_player/monoprice.py b/homeassistant/components/media_player/monoprice.py
new file mode 100644
index 0000000000000000000000000000000000000000..b9a253676608298d8098e92792aec5514baff84f
--- /dev/null
+++ b/homeassistant/components/media_player/monoprice.py
@@ -0,0 +1,185 @@
+"""
+Support for interfacing with Monoprice 6 zone home audio controller.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/media_player.monoprice/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.const import (CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON)
+import homeassistant.helpers.config_validation as cv
+from homeassistant.components.media_player import (
+    MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_VOLUME_MUTE,
+    SUPPORT_SELECT_SOURCE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
+    SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP)
+
+
+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):
+    """Set up the Monoprice 6-zone amplifier platform."""
+    port = config.get(CONF_PORT)
+
+    from serial import SerialException
+    from pymonoprice import Monoprice
+    try:
+        monoprice = Monoprice(port)
+    except SerialException:
+        _LOGGER.error('Error connecting to Monoprice controller.')
+        return
+
+    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 %d - %s", zone_id, extra[CONF_NAME])
+        add_devices([MonopriceZone(monoprice, sources,
+                                   zone_id, extra[CONF_NAME])], True)
+
+
+class MonopriceZone(MediaPlayerDevice):
+    """Representation of a a Monoprice amplifier zone."""
+
+    # pylint: disable=too-many-public-methods
+
+    def __init__(self, monoprice, sources, zone_id, zone_name):
+        """Initialize new zone."""
+        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):
+        """Return the name of the zone."""
+        return self._name
+
+    @property
+    def state(self):
+        """Return 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):
+        """Return flag 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 the media player off."""
+        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))
diff --git a/requirements_all.txt b/requirements_all.txt
index 483f842379bd7b84fcad304437757422d6bc33ad..61d711d5fe2320bc8fd86352e7839dddf69199e0 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -680,6 +680,9 @@ pymochad==0.1.1
 # homeassistant.components.modbus
 pymodbus==1.3.1
 
+# homeassistant.components.media_player.monoprice
+pymonoprice==0.2
+
 # homeassistant.components.media_player.yamaha_musiccast
 pymusiccast==0.1.2
 
diff --git a/tests/components/media_player/test_monoprice.py b/tests/components/media_player/test_monoprice.py
new file mode 100644
index 0000000000000000000000000000000000000000..451b6b51febdade003932c3a38392f3ad67c0a19
--- /dev/null
+++ b/tests/components/media_player/test_monoprice.py
@@ -0,0 +1,323 @@
+"""The tests for Monoprice Media player platform."""
+import unittest
+import voluptuous as vol
+
+from collections import defaultdict
+
+from homeassistant.components.media_player import (
+    SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE,
+    SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE)
+from homeassistant.const import STATE_ON, STATE_OFF
+
+from components.media_player.monoprice import MonopriceZone, PLATFORM_SCHEMA
+
+
+class MockState(object):
+    """Mock for zone state object."""
+
+    def __init__(self):
+        """Init zone state."""
+        self.power = True
+        self.volume = 0
+        self.mute = True
+        self.source = 1
+
+
+class MockMonoprice(object):
+    """Mock for pymonoprice object."""
+
+    def __init__(self):
+        """Init mock object."""
+        self.zones = defaultdict(lambda *a: MockState())
+
+    def zone_status(self, zone_id):
+        """Get zone status."""
+        return self.zones[zone_id]
+
+    def set_source(self, zone_id, source_idx):
+        """Set source for zone."""
+        self.zones[zone_id].source = source_idx
+
+    def set_power(self, zone_id, power):
+        """Turn zone on/off."""
+        self.zones[zone_id].power = power
+
+    def set_mute(self, zone_id, mute):
+        """Mute/unmute zone."""
+        self.zones[zone_id].mute = mute
+
+    def set_volume(self, zone_id, volume):
+        """Set volume for zone."""
+        self.zones[zone_id].volume = volume
+
+
+class TestMonopriceSchema(unittest.TestCase):
+    """Test Monoprice schema."""
+
+    def test_valid_schema(self):
+        """Test valid schema."""
+        valid_schema = {
+            'platform': 'monoprice',
+            'port': '/dev/ttyUSB0',
+            'zones': {11: {'name': 'a'},
+                      12: {'name': 'a'},
+                      13: {'name': 'a'},
+                      14: {'name': 'a'},
+                      15: {'name': 'a'},
+                      16: {'name': 'a'},
+                      21: {'name': 'a'},
+                      22: {'name': 'a'},
+                      23: {'name': 'a'},
+                      24: {'name': 'a'},
+                      25: {'name': 'a'},
+                      26: {'name': 'a'},
+                      31: {'name': 'a'},
+                      32: {'name': 'a'},
+                      33: {'name': 'a'},
+                      34: {'name': 'a'},
+                      35: {'name': 'a'},
+                      36: {'name': 'a'},
+                      },
+            'sources': {
+                1: {'name': 'a'},
+                2: {'name': 'a'},
+                3: {'name': 'a'},
+                4: {'name': 'a'},
+                5: {'name': 'a'},
+                6: {'name': 'a'}
+            }
+        }
+        PLATFORM_SCHEMA(valid_schema)
+
+    def test_invalid_schemas(self):
+        """Test invalid schemas."""
+        schemas = (
+            {},  # Empty
+            None,  # None
+            # Missing port
+            {
+                'platform': 'monoprice',
+                'name': 'Name',
+                'zones': {11: {'name': 'a'}},
+                'sources': {1: {'name': 'b'}},
+            },
+            # Invalid zone number
+            {
+                'platform': 'monoprice',
+                'port': 'aaa',
+                'name': 'Name',
+                'zones': {10: {'name': 'a'}},
+                'sources': {1: {'name': 'b'}},
+            },
+            # Invalid source number
+            {
+                'platform': 'monoprice',
+                'port': 'aaa',
+                'name': 'Name',
+                'zones': {11: {'name': 'a'}},
+                'sources': {0: {'name': 'b'}},
+            },
+            # Zone missing name
+            {
+                'platform': 'monoprice',
+                'port': 'aaa',
+                'name': 'Name',
+                'zones': {11: {}},
+                'sources': {1: {'name': 'b'}},
+            },
+            # Source missing name
+            {
+                'platform': 'monoprice',
+                'port': 'aaa',
+                'name': 'Name',
+                'zones': {11: {'name': 'a'}},
+                'sources': {1: {}},
+            },
+
+        )
+        for value in schemas:
+            with self.assertRaises(vol.MultipleInvalid):
+                PLATFORM_SCHEMA(value)
+
+
+class TestMonopriceMediaPlayer(unittest.TestCase):
+    """Test the media_player module."""
+
+    def setUp(self):
+        """Set up the test case."""
+        self.monoprice = MockMonoprice()
+        # Note, source dictionary is unsorted!
+        self.media_player = MonopriceZone(self.monoprice, {1: 'one',
+                                                           3: 'three',
+                                                           2: 'two'},
+                                          12, 'Zone name')
+
+    def test_update(self):
+        """Test updating values from monoprice."""
+        self.assertIsNone(self.media_player.state)
+        self.assertIsNone(self.media_player.volume_level)
+        self.assertIsNone(self.media_player.is_volume_muted)
+        self.assertIsNone(self.media_player.source)
+
+        self.media_player.update()
+
+        self.assertEqual(STATE_ON, self.media_player.state)
+        self.assertEqual(0.0, self.media_player.volume_level, 0.0001)
+        self.assertTrue(self.media_player.is_volume_muted)
+        self.assertEqual('one', self.media_player.source)
+
+    def test_name(self):
+        """Test name property."""
+        self.assertEqual('Zone name', self.media_player.name)
+
+    def test_state(self):
+        """Test state property."""
+        self.assertIsNone(self.media_player.state)
+
+        self.media_player.update()
+        self.assertEqual(STATE_ON, self.media_player.state)
+
+        self.monoprice.zones[12].power = False
+        self.media_player.update()
+        self.assertEqual(STATE_OFF, self.media_player.state)
+
+    def test_volume_level(self):
+        """Test volume level property."""
+        self.assertIsNone(self.media_player.volume_level)
+        self.media_player.update()
+        self.assertEqual(0.0, self.media_player.volume_level, 0.0001)
+
+        self.monoprice.zones[12].volume = 38
+        self.media_player.update()
+        self.assertEqual(1.0, self.media_player.volume_level, 0.0001)
+
+        self.monoprice.zones[12].volume = 19
+        self.media_player.update()
+        self.assertEqual(.5, self.media_player.volume_level, 0.0001)
+
+    def test_is_volume_muted(self):
+        """Test volume muted property."""
+        self.assertIsNone(self.media_player.is_volume_muted)
+
+        self.media_player.update()
+        self.assertTrue(self.media_player.is_volume_muted)
+
+        self.monoprice.zones[12].mute = False
+        self.media_player.update()
+        self.assertFalse(self.media_player.is_volume_muted)
+
+    def test_supported_features(self):
+        """Test supported features property."""
+        self.assertEqual(SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET |
+                         SUPPORT_VOLUME_STEP | SUPPORT_TURN_ON |
+                         SUPPORT_TURN_OFF | SUPPORT_SELECT_SOURCE,
+                         self.media_player.supported_features)
+
+    def test_source(self):
+        """Test source property."""
+        self.assertIsNone(self.media_player.source)
+        self.media_player.update()
+        self.assertEqual('one', self.media_player.source)
+
+    def test_source_list(self):
+        """Test source list property."""
+        # Note, the list is sorted!
+        self.assertEqual(['one', 'two', 'three'],
+                         self.media_player.source_list)
+
+    def test_select_source(self):
+        """Test source selection methods."""
+        self.media_player.update()
+
+        self.assertEqual('one', self.media_player.source)
+
+        self.media_player.select_source('two')
+        self.assertEqual(2, self.monoprice.zones[12].source)
+        self.media_player.update()
+        self.assertEqual('two', self.media_player.source)
+
+        # Trying to set unknown source
+        self.media_player.select_source('no name')
+        self.assertEqual(2, self.monoprice.zones[12].source)
+        self.media_player.update()
+        self.assertEqual('two', self.media_player.source)
+
+    def test_turn_on(self):
+        """Test turning on the zone."""
+        self.monoprice.zones[12].power = False
+        self.media_player.update()
+        self.assertEqual(STATE_OFF, self.media_player.state)
+
+        self.media_player.turn_on()
+        self.assertTrue(self.monoprice.zones[12].power)
+        self.media_player.update()
+        self.assertEqual(STATE_ON, self.media_player.state)
+
+    def test_turn_off(self):
+        """Test turning off the zone."""
+        self.monoprice.zones[12].power = True
+        self.media_player.update()
+        self.assertEqual(STATE_ON, self.media_player.state)
+
+        self.media_player.turn_off()
+        self.assertFalse(self.monoprice.zones[12].power)
+        self.media_player.update()
+        self.assertEqual(STATE_OFF, self.media_player.state)
+
+    def test_mute_volume(self):
+        """Test mute functionality."""
+        self.monoprice.zones[12].mute = True
+        self.media_player.update()
+        self.assertTrue(self.media_player.is_volume_muted)
+
+        self.media_player.mute_volume(False)
+        self.assertFalse(self.monoprice.zones[12].mute)
+        self.media_player.update()
+        self.assertFalse(self.media_player.is_volume_muted)
+
+        self.media_player.mute_volume(True)
+        self.assertTrue(self.monoprice.zones[12].mute)
+        self.media_player.update()
+        self.assertTrue(self.media_player.is_volume_muted)
+
+    def test_set_volume_level(self):
+        """Test set volume level."""
+        self.media_player.set_volume_level(1.0)
+        self.assertEqual(38, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))
+
+        self.media_player.set_volume_level(0.0)
+        self.assertEqual(0, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))
+
+        self.media_player.set_volume_level(0.5)
+        self.assertEqual(19, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))
+
+    def test_volume_up(self):
+        """Test increasing volume by one."""
+        self.monoprice.zones[12].volume = 37
+        self.media_player.update()
+        self.media_player.volume_up()
+        self.assertEqual(38, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))
+
+        # Try to raise value beyond max
+        self.media_player.update()
+        self.media_player.volume_up()
+        self.assertEqual(38, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))
+
+    def test_volume_down(self):
+        """Test decreasing volume by one."""
+        self.monoprice.zones[12].volume = 1
+        self.media_player.update()
+        self.media_player.volume_down()
+        self.assertEqual(0, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))
+
+        # Try to lower value beyond minimum
+        self.media_player.update()
+        self.media_player.volume_down()
+        self.assertEqual(0, self.monoprice.zones[12].volume)
+        self.assertTrue(isinstance(self.monoprice.zones[12].volume, int))