Notice: This forum has been recovered from an old backup, so some content, links, and dates may be outdated. The forum is currently read-only while we restore sign-in and registration functionality. Details

If you find this forum valuable and would like to help keep it online, donations to help cover hosting and domain costs are greatly appreciated, but never expected. You can support the forum through Buy Me a Coffee or Ko-fi. Thank you for helping preserve the EventGhost community.

Serial Control Cambridge Audio 340R

Do you have questions about writing plugins or scripts in Python? Meet the coders here.
Post Reply
cafe.racer
Posts: 6
Joined: Fri Dec 23, 2011 8:41 pm

Serial Control Cambridge Audio 340R

Post 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?
cafe.racer
Posts: 6
Joined: Fri Dec 23, 2011 8:41 pm

Re: Serial Control Cambridge Audio 340R

Post 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
cafe.racer
Posts: 6
Joined: Fri Dec 23, 2011 8:41 pm

Re: Serial Control Cambridge Audio 340R

Post by cafe.racer »

Anyone? Please?

Anyway I could test the serial port?
vicx
Posts: 13
Joined: Sun Sep 06, 2009 11:34 am

Re: Serial Control Cambridge Audio 340R

Post by vicx »

I have used this in the past to monitor what I am sending.

http://com0com.sourceforge.net/
cafe.racer
Posts: 6
Joined: Fri Dec 23, 2011 8:41 pm

Re: Serial Control Cambridge Audio 340R

Post 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.
cafe.racer
Posts: 6
Joined: Fri Dec 23, 2011 8:41 pm

Re: Serial Control Cambridge Audio 340R

Post 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:
Post Reply