Merge commit 'klette/master'
This commit is contained in:
commit
fd43e17d66
@ -6,3 +6,4 @@ Contributors to Mopidy in the order of appearance:
|
||||
* Stein Magnus Jodal <stein.magnus@jodal.no>
|
||||
* Johannes Knutsen <johannes@knutseninfo.no>
|
||||
* Thomas Adamcik <adamcik@samfundet.no>
|
||||
* Kristian Klette <klette@klette.us>
|
||||
|
||||
60
mopidy/mixers/denon.py
Normal file
60
mopidy/mixers/denon.py
Normal file
@ -0,0 +1,60 @@
|
||||
import logging
|
||||
from threading import Lock
|
||||
|
||||
from serial import Serial
|
||||
|
||||
from mopidy.mixers import BaseMixer
|
||||
from mopidy.settings import MIXER_PORT
|
||||
|
||||
logger = logging.getLogger(u'mopidy.mixers.denon')
|
||||
|
||||
"""
|
||||
Mixer for controlling Denon recivers and amplifiers using the RS-232 protocol.
|
||||
|
||||
Connects using the serial specifications from
|
||||
Denon's RS-232 Protocol specification.
|
||||
|
||||
Communication speed : 9600bps
|
||||
Character length : 8 bits
|
||||
Parity control : None
|
||||
Start bit : 1 bit
|
||||
Stop bit : 1 bit
|
||||
Communication procedure : Non procedural
|
||||
Communication data length : 135 bytes (maximum)
|
||||
|
||||
The external mixer is the authoritative source for the current volume.
|
||||
This allows the user to use his remote control the volume without
|
||||
mopidy cancelling the volume setting.
|
||||
"""
|
||||
|
||||
class DenonMixer(BaseMixer):
|
||||
def __init__(self):
|
||||
self._device = Serial(port=MIXER_PORT, timeout=0.2)
|
||||
self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)]
|
||||
self._volume = 0
|
||||
self._lock = Lock()
|
||||
|
||||
def _get_volume(self):
|
||||
self._lock.acquire();
|
||||
self.ensure_open_device()
|
||||
self._device.write('MV?\r')
|
||||
vol = str(self._device.readline()[2:4])
|
||||
self._lock.release()
|
||||
logger.debug(u'_get_volume() = %s' % vol)
|
||||
return self._levels.index(vol)
|
||||
|
||||
def _set_volume(self, volume):
|
||||
# Clamp according to Denon-spec
|
||||
if volume > 99:
|
||||
volume = 99
|
||||
self._lock.acquire()
|
||||
self.ensure_open_device()
|
||||
self._device.write('MV%s\r'% self._levels[volume])
|
||||
vol = self._device.readline()[2:4]
|
||||
self._lock.release()
|
||||
self._volume = self._levels.index(vol)
|
||||
|
||||
def ensure_open_device(self):
|
||||
if not self._device.isOpen():
|
||||
logger.debug(u'(re)connecting to Denon device')
|
||||
self._device.open()
|
||||
@ -38,12 +38,26 @@ CONSOLE_LOG_FORMAT = u'%(levelname)-8s %(asctime)s [%(threadName)s] %(name)s\n
|
||||
#: Default on other operating systems::
|
||||
#:
|
||||
#: MIXER = u'mopidy.mixers.dummy.DummyMixer'
|
||||
#:
|
||||
#: **Available external mixers**
|
||||
#:
|
||||
#: .. note::
|
||||
#: Using external mixers depends on the pyserial library.
|
||||
#:
|
||||
#: Denon AVR/AVC via RS-232::
|
||||
#:
|
||||
#: MIXER = u'mopidy.mixers.denon.DenonMixer'
|
||||
#:
|
||||
MIXER = u'mopidy.mixers.dummy.DummyMixer'
|
||||
if sys.platform == 'linux2':
|
||||
MIXER = u'mopidy.mixers.alsa.AlsaMixer'
|
||||
elif sys.platform == 'darwin':
|
||||
MIXER = u'mopidy.mixers.osa.OsaMixer'
|
||||
|
||||
#: Which port the mixer is connected to if using an external mixer.
|
||||
#: This must point to the device port like ``/dev/ttyUSB0`` or similar.
|
||||
MIXER_PORT = None
|
||||
|
||||
#: Which address Mopidy should bind to. Examples:
|
||||
#:
|
||||
#: ``localhost``
|
||||
|
||||
@ -10,6 +10,7 @@ def main():
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
|
||||
r = CoverageTestRunner()
|
||||
r.add_pair('mopidy/mixers/dummy.py', 'tests/mixers/dummytest.py')
|
||||
r.add_pair('mopidy/mixers/denon.py', 'tests/mixers/denontest.py')
|
||||
r.add_pair('mopidy/models.py', 'tests/modelstest.py')
|
||||
r.add_pair('mopidy/mpd/handler.py', 'tests/mpd/handlertest.py')
|
||||
r.run()
|
||||
|
||||
45
tests/mixers/denontest.py
Normal file
45
tests/mixers/denontest.py
Normal file
@ -0,0 +1,45 @@
|
||||
import unittest
|
||||
import os
|
||||
|
||||
from mopidy.mixers.denon import DenonMixer
|
||||
|
||||
class DenonMixerDeviceMock(object):
|
||||
def __init__(self):
|
||||
self._open = True
|
||||
self.ret_val = bytes('MV00\r')
|
||||
|
||||
def write(self, x):
|
||||
if x[2] != '?':
|
||||
self.ret_val = bytes(x)
|
||||
def read(self, x):
|
||||
return self.ret_val
|
||||
def isOpen(self):
|
||||
return self._open
|
||||
def open(self):
|
||||
self._open = True
|
||||
|
||||
class DenonMixerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.m = DenonMixer()
|
||||
self.m._device = DenonMixerDeviceMock()
|
||||
|
||||
def test_volume_set_to_min(self):
|
||||
self.m.volume = 0
|
||||
self.assertEqual(self.m.volume, 0)
|
||||
|
||||
def test_volume_set_to_max(self):
|
||||
self.m.volume = 100
|
||||
self.assertEqual(self.m.volume, 99)
|
||||
|
||||
def test_volume_set_to_below_min_results_in_min(self):
|
||||
self.m.volume = -10
|
||||
self.assertEqual(self.m.volume, 0)
|
||||
|
||||
def test_volume_set_to_above_max_results_in_max(self):
|
||||
self.m.volume = 110
|
||||
self.assertEqual(self.m.volume, 99)
|
||||
|
||||
def test_reopen_device(self):
|
||||
self.m._device._open = False
|
||||
self.m.volume = 10
|
||||
self.assertTrue(self.m._device._open)
|
||||
Loading…
Reference in New Issue
Block a user