NadMixer: Works nicely. Added power, speaker and source support.

This commit is contained in:
Stein Magnus Jodal 2010-03-13 00:18:16 +01:00
parent 560dc9245f
commit 7da3e73dbb

View File

@ -1,6 +1,6 @@
import logging import logging
from serial import Serial from serial import Serial
from multiprocessing import Process, Pipe from multiprocessing import Pipe, Process
from mopidy.mixers import BaseMixer from mopidy.mixers import BaseMixer
from mopidy.settings import MIXER_PORT from mopidy.settings import MIXER_PORT
@ -10,16 +10,18 @@ logger = logging.getLogger('mopidy.mixers.nad')
class NadMixer(BaseMixer): class NadMixer(BaseMixer):
""" """
The NAD mixer was created using a NAD C 355BEE amplifier, but should also The NAD mixer was created using a NAD C 355BEE amplifier, but should also
work with other NAD amplifiers supporting the same RS-232 protocol. The C work with other NAD amplifiers supporting the same RS-232 protocol (v2.x).
355BEE does not give you access to the current volume. It only supports The C 355BEE does not give you access to the current volume. It only
increasing or decreasing the volume one step at the time. Other NAD supports increasing or decreasing the volume one step at the time. Other
amplifiers may support more advanced volume adjustment than what is NAD amplifiers may support more advanced volume adjustment than what is
currently used by this mixer. currently used by this mixer.
Sadly, this means that if you use the remote control to change the volume Sadly, this means that if you use the remote control to change the volume
on the amplifier, Mopidy will no longer report the correct volume. To on the amplifier, Mopidy will no longer report the correct volume. To
recalibrate the mixer, set the volume to 0, and then back again to the recalibrate the mixer, set the volume to 0, and then back again to the
level you want. level you want. This will reset the amplifier to a known state, including
powering on the device, selecting the configured speakers and input
sources.
""" """
def __init__(self): def __init__(self):
@ -33,14 +35,30 @@ class NadMixer(BaseMixer):
def _set_volume(self, volume): def _set_volume(self, volume):
self._volume = volume self._volume = volume
if volume == 0: if volume == 0:
self._pipe.send({'command': 'calibrate'}) self._pipe.send({'command': 'reset_device'})
self._pipe.send({'command': 'set_volume', 'volume': volume}) self._pipe.send({'command': 'set_volume', 'volume': volume})
class NadTalker(Process): 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.
TIMEOUT = 0.2
#: Number of volume levels the device supports #: Number of volume levels the device supports
NUM_STEPS = 40 NUM_STEPS = 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..NUM_STEPS]. :class:`None` before calibration.
_nad_volume = None _nad_volume = None
@ -49,29 +67,63 @@ class NadTalker(Process):
self.pipe = pipe self.pipe = pipe
def run(self): def run(self):
# If you set the timeout too low, the reads will never get complete self._open_connection()
# confirmations and calibration will decrease volume forever. self._set_device_to_known_state()
self._device = Serial(port=MIXER_PORT, baudrate=115200, timeout=0.2)
self._clear()
self._device_model = self._get_device_model()
self._calibrate()
while self.pipe.poll(None): while self.pipe.poll(None):
message = self.pipe.recv() message = self.pipe.recv()
if message['command'] == 'set_volume': if message['command'] == 'set_volume':
self._set_volume(message['volume']) self._set_volume(message['volume'])
elif message['command'] == 'calibrate': elif message['command'] == 'reset_device':
self._calibrate() 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,
timeout=self.TIMEOUT)
self._get_device_model()
def _set_device_to_known_state(self):
self._power_device_on()
self._select_speakers()
self._select_input_source()
self._calibrate_volume()
def _get_device_model(self): def _get_device_model(self):
self._write('Main.Model?') model = self._ask_device('Main.Model')
result = '' logger.info(u'Connected to device of model "%s"' % model)
while len(result) < 2: return model
result = self._readline()
result = result.replace('Main.Model=', '')
logger.info(u'Connected to device of model "%s"' % result)
return result
def _calibrate(self): def _power_device_on(self):
logger.info(u'Powering device on')
while self._ask_device('Main.Power') != 'On':
self._command_device('Main.Power', 'On')
def _select_speakers(self):
logger.info(u'Setting speakers A "%s"', self.SPEAKERS_A)
while self._ask_device('Main.SpeakerA') != self.SPEAKERS_A:
self._command_device('Main.SpeakerA', self.SPEAKERS_A)
logger.info(u'Setting speakers B "%s"', self.SPEAKERS_B)
while self._ask_device('Main.SpeakerB') != self.SPEAKERS_B:
self._command_device('Main.SpeakerB', self.SPEAKERS_B)
def _select_input_source(self):
logger.info(u'Selecting input source "%s"', self.SOURCE)
while self._ask_device('Main.Source') != self.SOURCE:
self._command_device('Main.Source', self.SOURCE)
def _ask_device(self, key):
self._write('%s?' % key)
return self._readline().replace('%s=' % key, '')
def _command_device(self, key, value):
self._write('%s=%s' % (key, value))
self._readline()
def _calibrate_volume(self):
""" """
The NAD C 355BEE amplifier has 40 different volume levels. We have no 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 way of asking on which level we are. Thus, we must calibrate the mixer
@ -83,24 +135,21 @@ class NadTalker(Process):
if self._decrease_volume(): if self._decrease_volume():
steps_left -= 1 steps_left -= 1
self._nad_volume = 0 self._nad_volume = 0
logger.info(u'Calibration of NAD amplifier done') logger.info(u'Done calibrating NAD amplifier')
def _set_volume(self, volume): def _set_volume(self, volume):
logger.debug(u'Setting volume to %d' % volume)
self._set_nad_volume(int(round(volume * self.NUM_STEPS / 100.0)))
def _set_nad_volume(self, target_volume):
""" """
Increase or decrease the amplifier volume until it matches the given Increase or decrease the amplifier volume until it matches the given
target volume. Only calls to increase and decrease that returns target volume.
:class:`True` are counted against the internal volume.
""" """
logger.debug(u'Setting volume to %d' % volume)
target_nad_volume = int(round(volume * self.NUM_STEPS / 100.0))
if self._nad_volume is None: if self._nad_volume is None:
raise Exception(u'Calibration needed') return # Calibration needed
while target_volume > self._nad_volume: while target_nad_volume > self._nad_volume:
if self._increase_volume(): if self._increase_volume():
self._nad_volume += 1 self._nad_volume += 1
while target_volume < self._nad_volume: while target_nad_volume < self._nad_volume:
if self._decrease_volume(): if self._decrease_volume():
self._nad_volume -= 1 self._nad_volume -= 1
@ -112,17 +161,17 @@ class NadTalker(Process):
self._write('Main.Volume-') self._write('Main.Volume-')
return self._readline() == 'Main.Volume-' return self._readline() == 'Main.Volume-'
def _clear(self):
"""Clear input and output buffers."""
self._device.flushInput()
self._device.flushOutput()
def _write(self, data): def _write(self, data):
"""Write and flush data to device.""" """
Write data to device.
Prepends and appends a newline to the data, as recommended by the NAD
documentation.
"""
if not self._device.isOpen(): if not self._device.isOpen():
self._device.open() self._device.open()
self._device.write('\r%s\r' % data) self._device.write('\n%s\n' % data)
self._device.flush() logger.debug('Write: %s', data)
def _readline(self): def _readline(self):
""" """
@ -131,5 +180,7 @@ class NadTalker(Process):
""" """
if not self._device.isOpen(): if not self._device.isOpen():
self._device.open() self._device.open()
result = self._device.readline(eol='\r') result = self._device.readline(eol='\n').strip()
return result.strip() if result:
logger.debug('Read: %s', result)
return result