diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index 594dfc4b..8f7cd2fc 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -4,7 +4,7 @@ from threading import Lock from serial import Serial from mopidy.mixers import BaseMixer -from mopidy.settings import MIXER_PORT +from mopidy.settings import MIXER_EXT_PORT logger = logging.getLogger(u'mopidy.mixers.denon') @@ -16,6 +16,15 @@ class DenonMixer(BaseMixer): 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. + + **Dependencies** + + - pyserial (python-serial on Debian/Ubuntu) + + **Settings** + + - :attr:`mopidy.settings.default.MIXER_EXT_PORT` -- Example: + ``/dev/ttyUSB0`` """ def __init__(self): @@ -23,7 +32,7 @@ class DenonMixer(BaseMixer): Connects using the serial specifications from Denon's RS-232 Protocol specification: 9600bps 8N1. """ - self._device = Serial(port=MIXER_PORT, timeout=0.2) + self._device = Serial(port=MIXER_EXT_PORT, timeout=0.2) self._levels = ['99'] + ["%(#)02d" % {'#': v} for v in range(0, 99)] self._volume = 0 self._lock = Lock() diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index 6e95624f..e8c8e47b 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -3,7 +3,8 @@ from serial import Serial from multiprocessing import Pipe, Process from mopidy.mixers import BaseMixer -from mopidy.settings import MIXER_PORT +from mopidy.settings import (MIXER_EXT_PORT, MIXER_EXT_SOURCE, + MIXER_EXT_SPEAKERS_A, MIXER_EXT_SPEAKERS_B) logger = logging.getLogger('mopidy.mixers.nad') @@ -24,6 +25,19 @@ class NadMixer(BaseMixer): recalibrate the mixer, set the volume to 0 through Mopidy. This will reset the amplifier to a known state, including powering on the device, selecting the configured speakers and input sources. + + **Dependencies** + + - pyserial (python-serial on Debian/Ubuntu) + + **Settings** + + - :attr:`mopidy.settings.default.MIXER_EXT_PORT` -- + Example: ``/dev/ttyUSB0`` + - :attr:`mopidy.settings.default.MIXER_EXT_SOURCE` -- Example: ``Aux`` + - :attr:`mopidy.settings.default.MIXER_EXT_SPEAKERS_A` -- Example: ``On`` + - :attr:`mopidy.settings.default.MIXER_EXT_SPEAKERS_B` -- Example: ``Off`` + """ def __init__(self): @@ -42,26 +56,25 @@ class NadMixer(BaseMixer): class NadTalker(Process): - #: Timeout in seconds used for read/write operations. - #: - #: If you set the timeout too low, the reads will never get complete - #: confirmations and calibration will decrease volume forever. If you set - #: the timeout too high, stuff takes more time. + """ + Independent process which does the communication with the NAD device. + + Since the communication is done in an independent process, Mopidy won't + block other requests while doing rather time consuming work like + calibrating the NAD device's volume. + """ + + # Timeout in seconds used for read/write operations. + # If you set the timeout too low, the reads will never get complete + # confirmations and calibration will decrease volume forever. If you set + # the timeout too high, stuff takes more time. 0.2s seems like a good value + # for NAD C 355BEE. TIMEOUT = 0.2 - #: Number of volume levels the device supports - NUM_STEPS = 40 + # Number of volume levels the device supports. 40 for NAD C 355BEE. + VOLUME_LEVELS = 40 - #: The amplifier source to use - SOURCE = 'Aux' - - #: State of speakers A - SPEAKERS_A = 'On' - - #: State of speakers B - SPEAKERS_B = 'Off' - - #: Volume in range [0..NUM_STEPS]. :class:`None` before calibration. + # Volume in range 0..VOLUME_LEVELS. :class:`None` before calibration. _nad_volume = None def __init__(self, pipe=None): @@ -79,12 +92,9 @@ class NadTalker(Process): self._set_device_to_known_state() def _open_connection(self): - """ - Opens serial connection to the device. - - Communication settings: 115200 bps 8N1 - """ - self._device = Serial(port=MIXER_PORT, baudrate=115200, + # Opens serial connection to the device. + # Communication settings: 115200 bps 8N1 + self._device = Serial(port=MIXER_EXT_PORT, baudrate=115200, timeout=self.TIMEOUT) self._get_device_model() @@ -106,17 +116,17 @@ class NadTalker(Process): self._command_device('Main.Power', 'On') def _select_speakers(self): - while self._ask_device('Main.SpeakerA') != self.SPEAKERS_A: - logger.info(u'Setting speakers A "%s"', self.SPEAKERS_A) - self._command_device('Main.SpeakerA', self.SPEAKERS_A) - while self._ask_device('Main.SpeakerB') != self.SPEAKERS_B: - logger.info(u'Setting speakers B "%s"', self.SPEAKERS_B) - self._command_device('Main.SpeakerB', self.SPEAKERS_B) + while self._ask_device('Main.SpeakerA') != MIXER_EXT_SPEAKERS_A: + logger.info(u'Setting speakers A "%s"', MIXER_EXT_SPEAKERS_A) + self._command_device('Main.SpeakerA', MIXER_EXT_SPEAKERS_A) + while self._ask_device('Main.SpeakerB') != MIXER_EXT_SPEAKERS_B: + logger.info(u'Setting speakers B "%s"', MIXER_EXT_SPEAKERS_B) + self._command_device('Main.SpeakerB', MIXER_EXT_SPEAKERS_B) def _select_input_source(self): - while self._ask_device('Main.Source') != self.SOURCE: - logger.info(u'Selecting input source "%s"', self.SOURCE) - self._command_device('Main.Source', self.SOURCE) + while self._ask_device('Main.Source') != MIXER_EXT_SOURCE: + logger.info(u'Selecting input source "%s"', MIXER_EXT_SOURCE) + self._command_device('Main.Source', MIXER_EXT_SOURCE) def _unmute(self): while self._ask_device('Main.Mute') != 'Off': @@ -132,13 +142,11 @@ class NadTalker(Process): self._readline() def _calibrate_volume(self): - """ - The NAD C 355BEE amplifier has 40 different volume levels. We have no - way of asking on which level we are. Thus, we must calibrate the mixer - by decreasing the volume 39 times. - """ + # The NAD C 355BEE amplifier has 40 different volume levels. We have no + # way of asking on which level we are. Thus, we must calibrate the + # mixer by decreasing the volume 39 times. logger.info(u'Calibrating NAD amplifier') - steps_left = self.NUM_STEPS - 1 + steps_left = self.VOLUME_LEVELS - 1 while steps_left: if self._decrease_volume(): steps_left -= 1 @@ -146,12 +154,10 @@ class NadTalker(Process): logger.info(u'Done calibrating NAD amplifier') def _set_volume(self, volume): - """ - Increase or decrease the amplifier volume until it matches the given - target volume. - """ + # Increase or decrease the amplifier volume until it matches the given + # target volume. logger.debug(u'Setting volume to %d' % volume) - target_nad_volume = int(round(volume * self.NUM_STEPS / 100.0)) + target_nad_volume = int(round(volume * self.VOLUME_LEVELS / 100.0)) if self._nad_volume is None: return # Calibration needed while target_nad_volume > self._nad_volume: @@ -162,30 +168,26 @@ class NadTalker(Process): self._nad_volume -= 1 def _increase_volume(self): + # Increase volume. Returns :class:`True` if confirmed by device. self._write('Main.Volume+') return self._readline() == 'Main.Volume+' def _decrease_volume(self): + # Decrease volume. Returns :class:`True` if confirmed by device. self._write('Main.Volume-') return self._readline() == 'Main.Volume-' def _write(self, data): - """ - Write data to device. - - Prepends and appends a newline to the data, as recommended by the NAD - documentation. - """ + # Write data to device. Prepends and appends a newline to the data, as + # recommended by the NAD documentation. if not self._device.isOpen(): self._device.open() self._device.write('\n%s\n' % data) logger.debug('Write: %s', data) def _readline(self): - """ - Read line from device. The result is stripped for leading and trailing - whitespace. - """ + # Read line from device. The result is stripped for leading and + # trailing whitespace. if not self._device.isOpen(): self._device.open() result = self._device.readline(eol='\n').strip() diff --git a/mopidy/settings/default.py b/mopidy/settings/default.py index 7f6d1d88..709d5c9a 100644 --- a/mopidy/settings/default.py +++ b/mopidy/settings/default.py @@ -25,7 +25,7 @@ BACKENDS = ( #: the format. CONSOLE_LOG_FORMAT = u'%(levelname)-8s %(asctime)s [%(threadName)s] %(name)s\n %(message)s' -#: Sound mixer to use. +#: Sound mixer to use. See :mod:`mopidy.mixers` for all available mixers. #: #: Default on Linux:: #: @@ -38,25 +38,32 @@ 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 +#: External mixers only. Which port the mixer is connected to. +#: +#: This must point to the device port like ``/dev/ttyUSB0``. +#: *Default:* :class:`None` +MIXER_EXT_PORT = None + +#: External mixers only. What input source the external mixer should use. +#: +#: Example: ``Aux``. *Default:* :class:`None` +MIXER_EXT_SOURCE = None + +#: External mixers only. What state Speakers A should be in. +#: +#: *Default:* :class:`None`. +MIXER_EXT_SPEAKERS_A = None + +#: External mixers only. What state Speakers B should be in. +#: +#: *Default:* :class:`None`. +MIXER_EXT_SPEAKERS_B = None #: Which address Mopidy should bind to. Examples: #: