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
from serial import Serial
from multiprocessing import Process, Pipe
from multiprocessing import Pipe, Process
from mopidy.mixers import BaseMixer
from mopidy.settings import MIXER_PORT
@ -10,16 +10,18 @@ logger = logging.getLogger('mopidy.mixers.nad')
class NadMixer(BaseMixer):
"""
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
355BEE does not give you access to the current volume. It only supports
increasing or decreasing the volume one step at the time. Other NAD
amplifiers may support more advanced volume adjustment than what is
work with other NAD amplifiers supporting the same RS-232 protocol (v2.x).
The C 355BEE does not give you access to the current volume. It only
supports increasing or decreasing the volume one step at the time. Other
NAD amplifiers may support more advanced volume adjustment than what is
currently used by this mixer.
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
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):
@ -33,14 +35,30 @@ class NadMixer(BaseMixer):
def _set_volume(self, volume):
self._volume = volume
if volume == 0:
self._pipe.send({'command': 'calibrate'})
self._pipe.send({'command': 'reset_device'})
self._pipe.send({'command': 'set_volume', 'volume': volume})
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
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.
_nad_volume = None
@ -49,29 +67,63 @@ class NadTalker(Process):
self.pipe = pipe
def run(self):
# If you set the timeout too low, the reads will never get complete
# confirmations and calibration will decrease volume forever.
self._device = Serial(port=MIXER_PORT, baudrate=115200, timeout=0.2)
self._clear()
self._device_model = self._get_device_model()
self._calibrate()
self._open_connection()
self._set_device_to_known_state()
while self.pipe.poll(None):
message = self.pipe.recv()
if message['command'] == 'set_volume':
self._set_volume(message['volume'])
elif message['command'] == 'calibrate':
self._calibrate()
elif message['command'] == 'reset_device':
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):
self._write('Main.Model?')
result = ''
while len(result) < 2:
result = self._readline()
result = result.replace('Main.Model=', '')
logger.info(u'Connected to device of model "%s"' % result)
return result
model = self._ask_device('Main.Model')
logger.info(u'Connected to device of model "%s"' % model)
return model
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
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():
steps_left -= 1
self._nad_volume = 0
logger.info(u'Calibration of NAD amplifier done')
logger.info(u'Done calibrating NAD amplifier')
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
target volume. Only calls to increase and decrease that returns
:class:`True` are counted against the internal volume.
target 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:
raise Exception(u'Calibration needed')
while target_volume > self._nad_volume:
return # Calibration needed
while target_nad_volume > self._nad_volume:
if self._increase_volume():
self._nad_volume += 1
while target_volume < self._nad_volume:
while target_nad_volume < self._nad_volume:
if self._decrease_volume():
self._nad_volume -= 1
@ -112,17 +161,17 @@ class NadTalker(Process):
self._write('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):
"""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():
self._device.open()
self._device.write('\r%s\r' % data)
self._device.flush()
self._device.write('\n%s\n' % data)
logger.debug('Write: %s', data)
def _readline(self):
"""
@ -131,5 +180,7 @@ class NadTalker(Process):
"""
if not self._device.isOpen():
self._device.open()
result = self._device.readline(eol='\r')
return result.strip()
result = self._device.readline(eol='\n').strip()
if result:
logger.debug('Read: %s', result)
return result