Page 1 of 1

Serial Control Cambridge Audio 340R

Posted: Fri Dec 23, 2011 8:52 pm
by cafe.racer
Hi, utter noob, so be gentle. No experience of EventGhost, or Python.

I have started a plugin for Serial Control of the 340R Receiver which is based on the plugin for the 840A that's included with EventGhost. The Protocol is the same, but the commands are different.

I cannot for the life of me get the Receiver to "act" on the commands, however.

It's talking to EventGhost okay, when I turn the volume knob, or select sources/input etc on the Receiver, the event is triggered in event ghost.

But "sending" commands does not work - the receiver does not respond. I have controled this amp before (but not from this PC), so unless the commands aren't actually as they appear (I worry about the "/r" or carriage return that's needed).

Any pointers?

Re: Serial Control Cambridge Audio 340R

Posted: Sat Dec 24, 2011 3:08 pm
by cafe.racer

Code: Select all

#
# CambridgeAudioAzur540RSerial V0.1
# ================
# Based on work by Pavel Ivanov aka Johnson, <[email protected]>
# Public Domain
#
# Revision history:
# -----------------
# 0.1 - (23.12.11)	initial

import eg

help = '''\
Plugin based on Pavel Ivanov's plugin for the 840A adapted for the Azur 340R/540R.
'''
 
eg.RegisterPlugin(
	name = 'CambridgeAudio Azur 340R/540R Serial',
	author = 'cafe.racer',
	version = '0.1',
	kind = 'external',
	description = 'Control Cambridge Audio 340R/540R via RS232',
	help = help,
	canMultiLoad = True,
	createMacrosOnAdd = True,
)


import threading
import time
import re


class conf:
	FirstVolumeRepeatPause = 0.3
	NextVolumeRepeatPause = 0.110


class current:
	Volume = 45
	Mute = False


cmdList = (
('Power', None, None, None),
	('PowerOn', 'Power On', '1,01,1', None),
	('PowerOff', 'Power Off', '1,01,0', None),
('Volume', None, None, None),
	('MuteOff', 'Mute Off', '1,12,0', None),
	('MuteOn', 'Mute On', '1,12,1', None),
	('VolumeGoto', 'Goto Volume x (00-50, 50=0dB)', '1,13,x', '00-50'),
	('VolumeUp', 'Volume Up', '1,02', None),
	('VolumeDown', 'Volume Down', '1,03', None),
('Parameters', None, None, None),
	('BassUp', 'Bass Up', '1,04', None),
	('BassDown', 'Bass Down', '1,05', None),
	('TrebleUp', 'Treble Up', '1,06', None),
	('TrebleDown', 'Treble Down', '1,07', None),
	('SubOn', 'Sub On', '1,08', None),
	('SubOff', 'Sub Off', '1,09', None),
	('SetLFEtrim', 'Set LFE trim (0 = 0db, 1 = -1db to -10 = -10dB)', '1,10,x', '00-10'),
	('MuteOn', 'Mute On', '1,11,01', None),
	('MuteOff', 'Mute Off', '1,11,00', None),
	('DynamicRangeSet', 'Dynamic Range Set (0 - 0/4, 1 - 1/4, 2 - 2/4, 3 - 3/4, 4 - 4/4)', '1,12,x', '00-04'),
('Sources', None, None, None),
	('InputSelect', 'Input Select (1 - DVD, 2 - Video 1, 3-Tuner, 4 - Video2, 5 - Video3, 6 - Tape/MD/CDR, 7 - CD/Aux, 8 - 6.1 Direct In)', '2,01,x', '01-08'),
	('InputDVD', 'DVD', '2,01,01', None),
	('InputVideo1', 'Video 1', '2,01,02/r', None),
	('InputVideo2', 'Video 2', '2,01,04/r', None),
	('InputVideo3', 'Video 3', '2,01,05', None),
	('InputTuner', 'Tuner', '2,01,03', None),
	('InputTape', 'Tape/MD/CDR', '2,01,06', None),
	('InputCD', 'CD/Aux', '2,01,07', None),
	('InputDirect', '6.1 Direct In', '2,01,08', None),
	('InputUp', 'Next Source Up', '2,02', None),
	('InputDown', 'Next Source Down', '2,03', None),
	('InputMode', 'Set Input Mode (00=Analogue 01=Digital)', '2,04,x', '00-01'),
('Audio Processing', None, None, None),
	('SetAudiotoStereo', 'Set Audio to Stereo', '4,01,00', None),
	('SetAudiotoStereoSub', 'Set Audio to Stereo + Sub', '4,01,01', None),
	('SetPLIINeo', 'Set PLII/Neo', '4,02', None),
	('SetDDDTS', 'Set DD/DTS', '4,03', None),
	('GetCurrentDSPmode', 'Get Current PLII/Neo/DSP mode', '4,04', None),
	('GetcurrentDDDTSmode', 'Get current DD/DTS mode', '4,05', None),
	('SetDSPCycles', 'Set DSP Cycles', '4,06', None),
('Version', None, None, None),
	('SoftwareVersionGet', 'Get Software Version', '5,01,', None),
	('ProtocolVersionGet', 'Get Protocol Version', '5,02,', None),
('Tuner', None, None, None),
	('TunerStereo', 'Tuner Stereo', '3,01,00', None),
	('TunerMono', 'Tuner Mono', '3,01,01', None),
	('FrequencyUp', 'Frequency Up', '3,02', None),
	('FrequencyDown', 'Frequency Down', '3,03', None),
	('Up', 'Up', '3,04', None),
	('Down', 'Down', '3,05', None),
	('TunerMode', 'Tuner Mode', '3,06', None),
	('TunerSearchStop', 'Tuner Search Stop', '3,07', None),
	('TunerAutoSearch', 'Tuner Auto Search', '3,08', None),
	('TunerBand', 'Tuner Band', '3,09', None),
	('TunerPTYSelect', 'Tuner PTY Select', '3,10', None),
	('StoreStation', 'Store Station', '3,11', None),
	('GetPreset', 'Get Preset', '3,12', None),
	('TunerDisplyMode', 'Tuner Disply Mode', '3,13', None),
	('GetFrequency', 'Get Frequency', '3,14', None),
	('GetStationName', 'Get Station Name', '3,15', None),
	('GetPTY', 'Get PTY', '3,16', None),
	('StopPTYSelect', 'Stop PTY Select', '3,17', None),
(None,None,None,None),
)

EventList = {
	'11':{
		'content':'Error',
		'01':'Command Group Unknown',
		'02':'Command Number in Group Unnown',
		'03':'Command Data Error'
	},
	'6':{
		'content':'Amplifier',
		'01':'Power State',
		'02':'Volume Up',
		'03':'Volume Down',
		'04':'Bass Up',
		'05':'Bass Down',
		'06':'Treble Up',
		'07':'Treble Down',
		'08':'Sub On',
		'09':'Sub Off',
		'10':'Set LFE trim',
		'11':'Mute On State',
		'12':'Dynamic Range Set',
		'13':'OSD On',
		'14':'OSD Off',
		'15':'OSD Up',
		'16':'OSD Down',
		'17':'OSD Left',
		'18':'OSD Right',
		'19':'OSD Enter'
	},
	'7':{
		'content':'Source',
		'01':'Input Select',
		'02':'Next Input Up',
		'03':'Next Input Down',
		'04':'Input Mode'
	},
	'8':{
		'content':'Tuner',
		'01':'Tuner Stereo/Mono',
		'02':'Frequency Up',
		'03':'Frequency Down',
		'04':'Up',
		'05':'Down',
		'06':'Tuner Mode',
		'07':'Tuner Search Stop',
		'08':'Tuner Auto Search',
		'09':'Tuner Band',
		'10':'Tuner PTY Select',
		'11':'Store Station',
		'12':'Get Preset',
		'13':'Tuner Diaply Mode',
		'14':'Get Frequency',
		'15':'Get Station Name',
		'16':'Get PTY',
		'17':'Stop PTY Select'
	},
	'9':{
		'content':'Audio Processing',
		'01':'Set Audio to Stereo',
		'02':'Set PLII/Neo/DSP',
		'03':'DD/DTS',
		'04':'Get current PLII/Neo/DSP mode',
		'05':'Get current DD/DTS Mode'
	},
	'10':{
		'content':'Version',
		'01':'Software Version',
		'02':'Protocol Version'
	}

}

class CmdAction(eg.ActionClass):
	'''Base class for all argumentless actions'''

	def __call__(self):
		self.plugin.serialThread.SuspendReadEvents()
		self.plugin.serialThread.Write('#' + self.cmd + chr(13))
		self.plugin.serialThread.ResumeReadEvents()



class ValueAction(eg.ActionWithStringParameter):
	'''Base class for all actions with adjustable argument'''

	def __call__(self, data):
		self.plugin.serialThread.SuspendReadEvents()
		self.plugin.serialThread.Write('#' + self.cmdLeft + str(data) + self.cmdRight + chr(13))
		self.plugin.serialThread.ResumeReadEvents()



class Raw(eg.ActionWithStringParameter):
	name = 'Send Raw command'

	def __call__(self, data):
		self.plugin.serialThread.SuspendReadEvents()
		self.plugin.serialThread.Write(str(data) + chr(13))
		self.plugin.serialThread.ResumeReadEvents()



class VolumeAction(eg.ActionClass):
	def __call__(self):
		self.VolumeThread = threading.Thread(
			target=self.VolumeThreadLoop, 
			name="VolumeThread", 
			args=(eg.event, current.Volume, self.increment,)
		)
		self.VolumeThread.start()


	def VolumeThreadLoop(self, event, vol, increment):
		firstLoop = True
		while not event.isEnded:
			vol += increment
			if (vol < 1) or (vol > 96): break
			self.plugin.serialThread.SuspendReadEvents()
			self.plugin.serialThread.Write('#1,02,' + format(vol, '02d') + chr(13))
			self.plugin.serialThread.ResumeReadEvents()
			if firstLoop:
				time.sleep(conf.FirstVolumeRepeatPause)
				firstLoop = False
			else:
				time.sleep(conf.NextVolumeRepeatPause)



class ToggleMuteAction(eg.ActionClass):
	def __call__(self):
		self.plugin.serialThread.SuspendReadEvents()
		self.plugin.serialThread.Write('#1,11,' + str(int(not current.Mute)) + chr(13))
		self.plugin.serialThread.ResumeReadEvents()



class CambridgeAudioSerial(eg.PluginClass):

	def __init__(self):
		self.serialThread = eg.SerialThread()

		group = self
		for cmd_name, cmd_text, cmd_cmd, cmd_rangespec in cmdList:
			if cmd_text is None:
				# New subgroup, or back up
				if cmd_name is None:
					group = self
				else:
					group = self.AddGroup(cmd_name)
				if cmd_name == 'Volume': groupVolume = group
			elif cmd_rangespec is not None:
				# Command with argument
				actionName, paramDescr = cmd_text.split('(')
				actionName = actionName.strip()
				paramDescr = paramDescr[:-1]
				_cmdLeft, _cmdRight = cmd_cmd.split('x')

				class Action(ValueAction):
					name = actionName
					cmd = cmd_cmd
					parameterDescription = 'Value: (%s)' % paramDescr
					cmdLeft = _cmdLeft
					cmdRight = _cmdRight
				Action.__name__ = cmd_name
				group.AddAction(Action)
			else:
				# Argumentless command
				class Action(CmdAction):
					name = cmd_text
					cmd = cmd_cmd
				Action.__name__ = cmd_name
				group.AddAction(Action)

		group.AddAction(Raw)

		class Action(VolumeAction):
			name = 'Volume Up until event end'
			increment = 1
		Action.__name__ = 'VolumeUpUntilEnd'
		groupVolume.AddAction(Action)

		class Action(VolumeAction):
			name = 'Volume Down until event end'
			increment = -1
		Action.__name__ = 'VolumeDownUntilEnd'
		groupVolume.AddAction(Action)

		class Action(ToggleMuteAction):
			name = 'Toggle Mute'
		Action.__name__ = 'ToggleMute'
		groupVolume.AddAction(Action)



	def __start__(self, port):
		self.port = port
		self.serialThread.parmre = re.compile('#(\d),(\d\d),(([^,]+)?(,(.*))?)??$')
		self.serialThread.SetReadEventCallback(self.OnReceive)
		self.serialThread.Open(port, 9600, '8N1')
		self.serialThread.SetRts(0)
		self.serialThread.Start()
		current.Volume = 45


	def __stop__(self):
		self.serialThread.Close()


	def Configure(self, port=0):
		panel = eg.ConfigPanel(self)
		portCtrl = panel.SerialPortChoice(port)
		panel.AddLine('Port:', portCtrl)
		while panel.Affirmed():
			panel.SetResult(portCtrl.GetValue())


	def OnReceive(self, serial):
		buffer = ''
		while True:
			ch = serial.Read(1, 0.1)
			if (ch == ''): return		# nothing received inside timeout, possibly indicates erroneous data

			if (ch != '\r'):
				buffer += ch
				continue

			m = self.serialThread.parmre.match(buffer)
			if (m is not None) and (m.group(1) in EventList) and (m.group(2) in EventList[m.group(1)]):

				if (EventList[m.group(1)][m.group(2)] == 'VolumeChanged'):
					current.Volume = int(m.group(3))
				elif (EventList[m.group(1)][m.group(2)] == 'MuteStateChanged'):
					current.Mute = bool(int(m.group(3)))

				self.TriggerEvent(EventList[m.group(1)]['content'] + '.' + EventList[m.group(1)][m.group(2)], payload = m.group(3))
			else:
				self.TriggerEvent('Unknown(' + buffer + ')')
			return

Re: Serial Control Cambridge Audio 340R

Posted: Thu Dec 29, 2011 5:26 pm
by cafe.racer
Anyone? Please?

Anyway I could test the serial port?

Re: Serial Control Cambridge Audio 340R

Posted: Fri Dec 30, 2011 2:30 pm
by vicx
I have used this in the past to monitor what I am sending.

http://com0com.sourceforge.net/

Re: Serial Control Cambridge Audio 340R

Posted: Tue Jan 03, 2012 9:27 pm
by cafe.racer
Thanks for that. Useful tool, but it didn't seem to help. Searching around for some help on com0com I found RealTerm, which also doesn't help me, but is useful in that it does exactly what EventGhost, the serial plugin, "my" plugin, the original Cambridge audio plugin do. Makes me think it's not the plugin code, but something else.

I will see about dragging another PC near the amp, see if that will work.

Another Question - my amp will reply with "#7,01,2" (Group 7 - replies from Source Commands, Command Number 1 - Input Select, Command Data 2 - Video 1 Selected) .

The original plugin would translate the first two "chunks", but I can't see a Command Reply of three chunks in that code, and I'm struggling to extend my plugin to translate the third chunk.

If I'm not making sense, currently my plugin has the event

Code: Select all

CambridgeAudioSerial.Source.Input Select '02'
, I want it to have

Code: Select all

CambridgeAudioSerial.Source.Input Select.Video 1
so I can DO something with those events.

Re: Serial Control Cambridge Audio 340R

Posted: Fri Jan 06, 2012 3:33 pm
by cafe.racer
I tried a serial loopback test - nothing.

I tried shorting the jumpers 2 and 3 and a loopback - still nothing, so I bit the bullet and bought a USB - 2 x Serial cable, and now the commands work.

BUT reading from the port is random - it seems to drop characters :evil: