ESS Expandable Speaker Selector Switch
In editing phase !!! - working copy !!!
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