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>
|
* Stein Magnus Jodal <stein.magnus@jodal.no>
|
||||||
* Johannes Knutsen <johannes@knutseninfo.no>
|
* Johannes Knutsen <johannes@knutseninfo.no>
|
||||||
* Thomas Adamcik <adamcik@samfundet.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::
|
#: Default on other operating systems::
|
||||||
#:
|
#:
|
||||||
#: MIXER = u'mopidy.mixers.dummy.DummyMixer'
|
#: 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'
|
MIXER = u'mopidy.mixers.dummy.DummyMixer'
|
||||||
if sys.platform == 'linux2':
|
if sys.platform == 'linux2':
|
||||||
MIXER = u'mopidy.mixers.alsa.AlsaMixer'
|
MIXER = u'mopidy.mixers.alsa.AlsaMixer'
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == 'darwin':
|
||||||
MIXER = u'mopidy.mixers.osa.OsaMixer'
|
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:
|
#: Which address Mopidy should bind to. Examples:
|
||||||
#:
|
#:
|
||||||
#: ``localhost``
|
#: ``localhost``
|
||||||
|
|||||||
@ -10,6 +10,7 @@ def main():
|
|||||||
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
|
os.path.abspath(os.path.join(os.path.dirname(__file__), '../')))
|
||||||
r = CoverageTestRunner()
|
r = CoverageTestRunner()
|
||||||
r.add_pair('mopidy/mixers/dummy.py', 'tests/mixers/dummytest.py')
|
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/models.py', 'tests/modelstest.py')
|
||||||
r.add_pair('mopidy/mpd/handler.py', 'tests/mpd/handlertest.py')
|
r.add_pair('mopidy/mpd/handler.py', 'tests/mpd/handlertest.py')
|
||||||
r.run()
|
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