ESS Expandable Speaker Selector Switch

From LinuxMCE
Revision as of 04:14, 11 August 2009 by Ray N (Talk | contribs)

Jump to: navigation, search
Ess2468.gif

More info about device  :

This is nice multizone expandable speaker selector swithc, that can be IR or RS232 controlled. Each zone can also have separate control for 11 volume levels. Although recently it's price went significantly up, I still think it's quite usable (I'm yet about to try it in real life). I'll use it for connecting several speakers pairs to Multiroom secondary stereo amplified output on my Marantz SR5600. In this way I'll get nice configurable family home audio system with two audio channels distributed around house and fully controlled by LMCE (I produce second channel also on LMCE Hybrid, by using squeezeslave and another USB sound card)...

Device includes "base" module, to whom you simply attach different modules (they differ on volume control features) for each zone up to 31. Each zone is simply numbered, zone 0 is special zone - sending commands to that zone will have effect on all zones at once.

There is simple RS232 protocol for this device, described here It's pretty simple and ASCII based..

The whole idea of implementation is to have each zone represented by "Zone" child devices (template #1867), adding as much as you have them in your device. By default zone "0" will be represented by device it self - so sending commands to parent device will have effect on all zones. Sending commands only to Zone child devices, will have effect only in target zone. At the start, 1 child zone is added to parent device - I assume there will be at least one zone in system. Then you add more zones to suit your system.


Description of needed templates

I hope that this template will be available with stock LMCE distribution, so you won't have to create that template from scratch. Template is called "ESS Speaker Selector RS232" and was created from scratch but inspired by my work on "SR Marantz Multiroom RS232" template described on wiki.

For template to be functional I've made certain extensions to existing template for "Zone" that I hope will be usefull also to others. Extensions are described here [Marantz_SR5600]

Important device data and parameters for "ESS Speaker Selector RS232" template

Description : ESS Speaker Selector RS232
Implements DCE : yes
Command line : Generic_Serial_Device
Device Category  AV/preamps/... #103
Manufacturer HACS - Home Automation Control Systems

Also have added following parameters :

Device data
Current Data 	                                    Comments 	                                Default Value
#37 COM Port on PC                                                                              /dev/ttyS0 	
#76 COM Port ParityBit/Stop 	                                                                 N81
#78 COM Port BaudRate 	                                                                         B9600
#220 Process Child Commands In Pare                                                              1 (needed to have ability to send all commands to parent) 	
#157 Discrete Volume(bool)  	                                                                 1 					

Settings are factory defaults and should't be changed (9600 baud rate, 8 bits, 1 stop bit)...

'Edit Ruby code' (on template page)

'Determining proper set of inputs'

Device has two inputs for amplified stereo signals (A,B). Then each zone or all together can be switched on/off and set to input A or B. Zone modules can have no volume control, manual control or 11-level volume control controlled from RS232 interface. Each zone has two inputs for amplified signalI've added all inputs that are present on device. There is also a special input called "FM" where I connected Live TV embedded device to be able to control tuner functions (tuning/searching up/down, go to preset stations, etc...)


'Set proper set of commands'

Basically I've added command groups :

Ruby Internal Commands
Simple IO commands (on,off)
Volume Commands (vol+,vol-,set volume)
Inputs  (A,B)


'Add Ruby snippets to commands'

I've created new IR/Codeset group called "ESS Speaker Selector RS232", although I'm not sure how to handle it. Have also edited corresponding Ruby snippets for each command. Basically those are just simple strings that get sent on rs232 (they differ mainly on target zone) to take proper action on device according to received command from LinuxMCE...


More important parts with more Ruby code :

 Power
#193 Off
	SendZoneCommand(cmd) 	

#192 On
	SendZoneCommand(cmd) 	

Inputs
#953 A
	SendZoneCommand(cmd) 	

#954 B
	SendZoneCommand(cmd) 	

Internal
#373 Private Method Listing
  ###########################################################3 Internal Methods
  # To do list :
  # - ???
  #
  #
  # RS232 protocol, ZN=Zone Number (00 is all, 01-31 separate zones)
  # ZN 01 ON
  # ZN 02 OFF
  # ZN 03 A AMP
  # ZN 04 B AMP
  # ZN 07 VOLUME UP
  # ZN 08 VOLUME DOWN
  # ZN 09 SET LEVEL 0
  # ZN 10 SET LEVEL 1
  # ZN 11 SET LEVEL 2
  # ZN 12 SET LEVEL 3
  # ZN 13 SET LEVEL 4
  # ZN 14 SET LEVEL 5
  # ZN 15 SET LEVEL 6
  # ZN 16 SET LEVEL 7
  # ZN 17 SET LEVEL 8
  # ZN 18 SET LEVEL 9
  # ZN 19 SET LEVEL 10
  # ZN 20 SET LEVEL 11
  # ZN 32 ? STATUS
  # ZN 33 ? LEVEL
  # ZN 34 ? ON/OFF
  # ZN 35 ? A/B 
  
  
  def log(line)
           $log = File.open("/var/log/pluto/" + device_.devid_.to_s + "_Generic_Serial_Device.log", "a")
  #	  $log = File.open("/var/log/pluto/ESS.log", "a")
  	  logTime = Time.now
  	  timeStr = logTime.strftime("%d-%m-%Y  %H:%M:%S  ")
  
  	  $log.puts timeStr + "(***):" + line.to_s
            $log.close
  end 	
  
  def Send_RS232_Command(command)
          log("Send_RS232_Command: [" + command+ "]\n")
  #	conn_.Send("[" + command+ "]")
  end
  
  
  def SendZoneCommand(cmd)
  
    log("Got Command with ID: " + cmd.id_.to_s + " from: " + cmd.devidfrom_.to_s + " to: " + cmd.devidto_.to_s + "\n")
    zone = GetZone(cmd.devidto_)
    if( zone >= 0 )
  	        zone_desc=device_.childdevices_[cmd.devidto_].devdata_[186]
  		log("SendZoneCommand: Child Zone " + zone_desc + "[" + zone.to_s + "]\n")
    elsif (cmd.devidto_ == device_.devid_)
  		log("SendZoneCommand: All Zones " + zone.to_s + ". Sending command to all zones !!!")
  		zone=0
  		zone_desc="All Zones"
    else
  		log("SendZoneCommand: Invalid zone " + zone.to_s + ". Send commands only to valid devices!!!")
    end
     
    serial_command = ""
     
    case cmd.id_
       	when 192 #192 is ON 
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",1);
       		Send_RS232_Command(serial_command);
       		temp_cmd = Command.new(cmd.id_, -1001, 1, 2, 48);
       		temp_cmd.params_[10] = "1";
       		SendCommand(temp_cmd);
       		$ZoneStatus[zone.to_s] = "ON";
       		if (zone==0)
       			SetZones("ON")
       		end
       	when 193 #193 is OFF
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",2);
       		Send_RS232_Command(serial_command);
       		temp_cmd = Command.new(cmd.id_, -1001, 1, 2, 48);
       		temp_cmd.params_[10] = "0";
       		SendCommand(temp_cmd);
       		$ZoneStatus[zone.to_s] = "OFF";
       		if (zone==0)
       			SetZones("OFF")
       		end
  
       	when 90 #90 is Volume Down
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",8);
       		Send_RS232_Command(serial_command);
       		$ZoneVolumes[zone.to_s] = $ZoneVolumes[zone.to_s] - 10
       		if ($ZoneVolumes[zone.to_s]<0)
       			$ZoneVolumes[zone.to_s]=0
  		end
       		SetDeviceDataInDB( cmd.devidto_, 158, ($ZoneVolumes[zone.to_s]).to_s ) # 158 = DEVICEDATA_Volume_Level_CONST
       		temp_cmd = Command.new(cmd.devidto_, -1001, 1, 2, 71);
       		temp_cmd.params_[30] = ($ZoneVolumes[zone.to_s]).to_s
       		SendCommand(temp_cmd);
  		
       	when 89 #90 is Volume Up
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",7);
       		Send_RS232_Command(serial_command);
       		$ZoneVolumes[zone.to_s] = $ZoneVolumes[zone.to_s] + 10
       		if ($ZoneVolumes[zone.to_s]>=120)
       			$ZoneVolumes[zone.to_s]=110
  		end
       		SetDeviceDataInDB( cmd.devidto_, 158, ($ZoneVolumes[zone.to_s]).to_s ) # 158 = DEVICEDATA_Volume_Level_CONST
       		temp_cmd = Command.new(cmd.devidto_, -1001, 1, 2, 71);
       		temp_cmd.params_[30] = ($ZoneVolumes[zone.to_s]).to_s
       		SendCommand(temp_cmd);
       	when 313 #313 is Set Volume with Level as Param #76
  		log("313:Set Volume Command: Got Cmd with ID: " + cmd.id_.to_s + " from: " + cmd.devidfrom_.to_s + " to: " + cmd.devidto_.to_s + " | Zone : " + zone.to_s + "\n")
       		level=(cmd.params_[76]).to_i
       		if level >110 
       			level=110
  			log("313:Set Volume Command: Volume too high - truncated to max level of 110\n")
       		elsif level <0
       			level=0
  			log("313:Set Volume Command: Volume too low - truncated to min level of 0\n")
       		end
  		level = (level/10.0).round
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",level+9);
       		Send_RS232_Command(serial_command);
       		$ZoneVolumes[zone.to_s] = (cmd.params_[76]).to_i
       		SetDeviceDataInDB( cmd.devidto_, 158, ($ZoneVolumes[zone.to_s]).to_s ) # 158 = DEVICEDATA_Volume_Level_CONST
       		temp_cmd = Command.new(cmd.devidto_, -1001, 1, 2, 71);
       		temp_cmd.params_[30] = ($ZoneVolumes[zone.to_s]).to_s
       		SendCommand(temp_cmd);
       		if (zone==0)
       			SetVolumes($ZoneVolumes[zone.to_s])
       		end
  
       	when 616 #616 is Select A
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",3);
       		Send_RS232_Command(serial_command);
       	when 617 #617 is Select B
       		serial_command = sprintf("%02d",zone)+sprintf("%02d",4);
       		Send_RS232_Command(serial_command);
       	else
       		log("Yet not implemented: Handler for command: ID " + cmd.id.to_s)
       end
  	
  
  end
  
  
  def GetZone(deviceDestination)
  	if( device_.mapDevice_PortChannel_.has_key?(deviceDestination) and
  		device_.mapDevice_PortChannel_[deviceDestination] != nil and
  		!device_.mapDevice_PortChannel_[deviceDestination].empty? )
  		return device_.mapDevice_PortChannel_[deviceDestination].to_i
  	end
  	
  	return -1
  end
  
  
  def ReceiveReport()
  	@buff = conn_.RecvDelimited("]", 1000) 
  	log("Response from ESS: " + @buff + "\n")
  #	log("Calling DecodeReceivedInfo: " + @buff + "\n")
  #	temp_resp=@buff;
  #	DecodeReceivedInfo(temp_resp)
  #	log("End of DecodeReceivedInfo: " + temp_resp + "\n")
  end
  
  def SetVolumeInZone(level,zone)
  # Sets Normalized Volume Level (0..100) in certain zone 
    normlevel = (level.to_i/10.0).round
    log("SetVolumeInZone: volume: " + level.to_s + " ["+ normlevel.to_s + "] => Zone: " + zone + "\n")
  
    serial_command = sprintf("%02d",zone)+sprintf("%02d",normlevel+9);
    Send_RS232_Command(serial_command);
  #  log("313:Set Volume Command: Got Cmd with ID: " + cmd.id_.to_s + " from: " + cmd.devidfrom_.to_s + " to: " + cmd.devidto_.to_s + " | Zone : " + zone.to_s + "\n")
    $ZoneVolumes[zone.to_s] = level.to_i
  
  end
  
  def SetZonesInitialVolumes()
  	log("####### Set Initial Volume Levels for zones\n")
  	device_.mapDevice_PortChannel_.each do |key, value|
  		volume = device_.childdevices_[key].devdata_[208]
  		log("Zone " + value.to_s + " => " + volume.to_s + "\n")
                sleep 1.0
  		SetVolumeInZone(volume.to_i,value)
    		SetDeviceDataInDB( device_.childdevices_[key].devid_, 158, ($ZoneVolumes[value.to_s]).to_s ) # 158 = DEVICEDATA_Volume_Level_CONST
    	end
  	
  end
  
  
  def SetZones(state)
  	log("####### Setting all zones to " + state + "\n")
  	device_.mapDevice_PortChannel_.each do |key, value|
  		$ZoneStatus[value] = state
  		log("Zone " + value.to_s + " => " + state + "\n")
   	end
  end
  
  def SetVolumes(level_int)
  	log("####### Set Volume Levels for all zones\n")
  	device_.mapDevice_PortChannel_.each do |key, value|
  		log("Zone " + value.to_s + " => " + level_int.to_s + "\n")
  		$ZoneVolumes[value.to_s] = level_int
    		SetDeviceDataInDB( device_.childdevices_[key].devid_, 158, ($ZoneVolumes[value.to_s]).to_s ) # 158 = DEVICEDATA_Volume_Level_CONST
    	end
  end


#351 Process IDLE
		

#350 Process Incoming Data
	ReceiveReport() 	

#355 Process Initialize

 log("Initializing ESS GSD Device\n")

 sleep 1.0
 Send_RS232_Command("0002");  #All Zones OFF
 sleep 1.0
 Send_RS232_Command("0020");  #All Zones Max Volume
 sleep 1.0
 Send_RS232_Command("0003");  #All Zones Select A
 sleep 1.0
 $ZoneVolumes = Hash.new()
 $ZoneStatus  = Hash.new()

 SetZones("OFF")
 SetZonesInitialVolumes()

 log("Initialization of ESS GSD Device finished !!!!\n")




#384 Process Receive Command For Child
	SendZoneCommand(cmd) 	

#356 Process Release
 Send_RS232_Command("0002");  #All Zones OFF

 Send_RS232_Command("0020");  #All Zones Max Volume

 Send_RS232_Command("0003");  #All Zones Select A


 log("Shutting Down ESS GSD Device finished !!!!\n")

Misc
#616 Select A
		

#617 Select B
		

Sound & Volume
#97 Mute
		

#313 Set Volume
	SendZoneCommand(cmd) 	

#90 Vol Down
		

#89 Vol Up