Page 1 of 1

Control Emotiva XMC-1 audio processor

Posted: Saturday 17 December 2016 16:05
by RogerSch
I have a audio surround processor from Emotiva the XMC-1: https://emotiva.com/products/pres-and-pros/xmc-1
Image

This surround processor can also be controlled via the Ethernet port. For this the vendor has created a document which describes the "Emotiva Network Remote Control protocol". Here is this document.
XMC-1_network_interface_description.docx
(49.24 KiB) Downloaded 271 times
Now I want to control some basic functions via Domoticz. My big question is; can anybody give me some guidance what the best / easiest approach is to enhance Domoticz with functionality to control the XMC-1? Are there already solutions out there which make use of a comparable UDP protocol implementation which could be used as a basis?

Thanks!

Re: Control Emotiva XMC-1 audio processor

Posted: Saturday 17 December 2016 16:23
by Number8
@ RogerSch: lucky you!
Here the code I have developed in Python. Beware that this code is based on previous firmware version (3.x). You may have to adapt if your unit runs v4. I have developed the very basic functions, based on that it would not be difficult to add some other functions (like volume up/down for instance). Python has been chosen because it implements the socket lib which is fairly easy to use.

This code sends the power On/Off command to XMC-1 using iTach IP2IR because I don't want to let the unit in video standby mode due to power drain (35W). So in this specific case, I use the IR in plug at the backside of the unit.

Code: Select all

#!/usr/bin/env python
import socket
import sys

power = sys.argv[1]

TCP_IPADDRESS = "192.168.21.239"
TCP_PORT = 4998
BUFFER_SIZE = 1000
if power == "OFF":
    MESSAGE = 'sendir,1:1,1,38000,1,69,341,170,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,64,21,21,21,21,21,64,21,64,21,64,21,64,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,64,21,64,21,64,21,64,21,64,21,64,21,64,21,64,21,21,21,1517,341,85,21,3655\r'
elif power == "ON":
    MESSAGE = 'sendir,1:1,1,38000,1,69,341,170,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,64,21,21,21,21,21,64,21,64,21,64,21,64,21,21,21,21,21,64,21,21,21,21,21,21,21,21,21,64,21,64,21,64,21,21,21,64,21,64,21,64,21,64,21,21,21,21,21,1517,341,85,21,3655\r'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IPADDRESS, TCP_PORT))
s.send(MESSAGE)
data = s.recv(BUFFER_SIZE)
s.close()
if not 'completeir' in data:
    print 'Status', 'Erreur de transmission avec XMC-1'
    
print "received data:", data

This code sends to XMC-1 (and iTach but this is another story) the code to select the desired input

Code: Select all

#!/usr/bin/env python
# -*- coding: latin-1 -*-
import socket
import sys
import string
from time import sleep
import errno
import fcntl, os
#import domoticz

# Preamp codes
#iTACH IP2IR module
CD='sendir,1:2,1,36023,1,1,32,32,32,32,64,32,32,64,32,32,32,32,32,32,64,64,32,32,64,64,32,3225\r'
TUNER='sendir,1:2,1,36023,1,1,32,32,64,32,32,32,32,64,32,32,32,32,64,32,32,64,64,64,32,32,32,3600\r'
PHONO='sendir,1:2,1,36023,1,1,32,32,32,32,64,32,32,64,32,32,32,32,32,32,64,64,64,64,32,32,32,3235\r'
VIDEO='sendir,1:2,1,36023,1,1,32,32,64,32,32,32,32,64,32,32,32,32,32,32,32,32,64,64,64,64,32,3235\r'
AUX='sendir,1:2,1,36023,1,1,32,32,32,32,64,32,32,64,32,32,32,32,64,64,32,32,32,32,64,64,32,3235\r'
TAPE='sendir,1:2,1,36023,1,1,32,32,64,32,32,32,32,64,32,32,32,32,64,64,32,32,32,32,64,32,32,3267\r'
PHASEIN='sendir,1:2,1,36023,1,1,32,32,32,32,64,32,32,64,32,32,32,32,32,32,32,32,64,32,32,32,32,32,32,3267\r'
PHASEOUT='sendir,1:2,1,36023,1,1,32,32,32,32,64,32,32,64,32,32,32,32,32,32,32,32,64,64,32,32,64,3267\r'
MUTE='sendir,1:2,1,36023,1,1,32,32,32,32,64,32,32,64,32,32,32,32,64,32,32,64,32,32,64,64,32,3235\r'
VOLUME_PLUS='sendir,1:2,1,36023,1,1,32,32,64,32,32,32,32,64,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,3235\r'
VOLUME_MOINS='sendir,1:2,2,36023,1,1,32,32,64,32,32,32,32,64,32,32,32,32,32,32,32,32,32,32,32,32,64,32,32,3267\r'

XMC_POWERON='sendir,1:1,1,38000,1,69,341,170,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,64,21,21,21,21,21,64,21,64,21,64,21,64,21,21,21,21,21,64,21,21,21,21,21,21,21,21,21,64,21,64,21,64,21,21,21,64,21,64,21,64,21,64,21,21,21,21,21,1517,341,85,21,3655\r'
XMC_STANDBY='sendir,1:1,1,38000,1,69,341,170,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,64,21,21,21,21,21,64,21,64,21,64,21,64,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,64,21,64,21,64,21,64,21,64,21,64,21,64,21,64,21,21,21,1517,341,85,21,3655\r'

# end of iTach

XMC_COMMAND='<?xml version="1.0" encoding="utf-8"?><emotivaControl><commandName value="commandValue" ack="yes" /></emotivaControl>'
XMC_PING='<?xml version="1.0" encoding="utf-8"?><emotivaPing />'

SLEEPTIME=0.4
audio = sys.argv[1]

IP2IR_IPADDRESS = "192.168.21.239"
IP2IR_PORT = 4998
XMC_IPADDRESS = "192.168.21.229"
XMC_TRANSACTION_PORT_IN = 7000
XMC_TRANSACTION_PORT_RESPONSE = 7001
XMC_CONTROL_PORT = 7002

BUFFER_SIZE = 1000

#row 0 is command
#row 1 is IP address
#row 2 is port
#row 3 is protocol
    
if audio == 'Plex':
    #Set preamp input to Vidéo and XMC input to Input 1
    MESSAGE=[[XMC_POWERON,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"],[XMC_COMMAND,XMC_IPADDRESS,XMC_CONTROL_PORT,"UDP"],[VIDEO,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"]]
    MESSAGE[1][0]=string.replace(MESSAGE[1][0],'commandName','source_1')
    MESSAGE[1][0]=string.replace(MESSAGE[1][0],'commandValue','0')
elif audio == 'DVD':
    #Set preamp input to Vidéo and XMC input to Input 2
    MESSAGE=[[XMC_POWERON,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"],[XMC_COMMAND,XMC_IPADDRESS,XMC_CONTROL_PORT,"UDP"],[VIDEO,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"]]
    MESSAGE[1][0]=string.replace(MESSAGE[1][0],'commandName','source_2')
    MESSAGE[1][0]=string.replace(MESSAGE[1][0],'commandValue','0')
elif audio == 'jRiver':
    #Set preamp input to Audio
    MESSAGE=[[AUX,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"]]
elif audio == 'CD':
    #Set preamp input to CD
    MESSAGE=[[CD,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"]]
elif audio == 'Tourne disque':
    #Set preamp input to Phono
    MESSAGE=[[PHONO,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"]]
elif audio == 'XMC_STANDBY':
    MESSAGE=[[XMC_STANDBY,IP2IR_IPADDRESS,IP2IR_PORT,"TCP"]]

sTCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sUDPreceive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sUDPreceive.bind(('',XMC_TRANSACTION_PORT_RESPONSE))
fcntl.fcntl(sUDPreceive, fcntl.F_SETFL, os.O_NONBLOCK)
#for row in MESSAGE:
#    #for e in row:
#        #print e
#    print row
IPADDRESS_PREVIOUS=""
loop=0
for row in MESSAGE:
    if row[3] == "UDP": # it is XMC by design
        # send a ping and check if unit is On. If not wait until it is ready (PowerOn has been sent before)
        while True:
            # send a ping until a response is given
            sUDP.sendto(XMC_PING, (XMC_IPADDRESS, XMC_TRANSACTION_PORT_IN))
            #print ('ping sent: ', MESSAGE[0][0])
            # wait some time to let the unit answer
            sleep (0.01)
            try:
                data = sUDPreceive.recvfrom(500)
            except socket.error, e:
                err = e.args[0]
                if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
                    sleep(SLEEPTIME)
                    #print 'No data available'
                    loop+=1
                    if loop*SLEEPTIME >= 70:
                        #print('timeout')
                        exit()
                    continue
                else:
                    # a "real" error occurred
                    print e
                    sys.exit(1)
                #TBD implement a timeout of 20 sec in case there is a problem with the unit
            else:
                # unit is ready send the command now
                sUDP.sendto(row[0], (row[1], row[2]))
                # TBD : check if OK
                break
    else: # TCP protocol
        if IPADDRESS_PREVIOUS != row[1]:
            if IPADDRESS_PREVIOUS != "": s.close()
            sTCP.connect((row[1], int(row[2])))
            IPADDRESS_PREVIOUS=row[1]
        #print ("TCP", row[0])
        result = sTCP.send(row[0])
        #print (result)
        data = sTCP.recv(BUFFER_SIZE)
        #print "received data:", data

sUDP.close
sUDPreceive.close
sTCP.close

Re: Control Emotiva XMC-1 audio processor

Posted: Saturday 17 December 2016 18:25
by RogerSch
@Number8

I've been indeed lucky. :D This will give me a good starting point.

I used your python script as a basis and removed everything which I don't need for me very first test program. Just Volume Up and Volume Down function with no error checking etc. Assumption is also that the XMC-1 is powered on.
Here is my code (which is not optimized). In case you see a major flaw then please let me know.

Code: Select all

#!/usr/bin/env python
# -*- coding: latin-1 -*-
import socket
import sys
import string
#import domoticz

XMC_COMMAND='<?xml version="1.0" encoding="utf-8"?><emotivaControl><commandName value="commandValue" ack="no" /></emotivaControl>'

DoThis = sys.argv[1]

XMC_IPADDRESS = "192.168.1.113"
# XMC_TRANSACTION_PORT_IN = 7000
# XMC_TRANSACTION_PORT_RESPONSE = 7001
XMC_CONTROL_PORT = 7002


#row 0 is command
#row 1 is IP address
#row 2 is port
MESSAGE=[XMC_COMMAND,XMC_IPADDRESS,XMC_CONTROL_PORT]
    
if DoThis == 'VolUp':
    #Increase volume by 1 dB
    MESSAGE[0]=string.replace(MESSAGE[0],'commandName','volume')
    MESSAGE[0]=string.replace(MESSAGE[0],'commandValue','1')
	sUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	sUDP.sendto(MESSAGE[0], (MESSAGE[1], MESSAGE[2]))
	sUDP.close
elif DoThis == 'VolDown':
	#Decrease volume by 1 dB
	MESSAGE[0]=string.replace(MESSAGE[0],'commandName','volume')
    MESSAGE[0]=string.replace(MESSAGE[0],'commandValue','-1')
	sUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	sUDP.sendto(MESSAGE[0], (MESSAGE[1], MESSAGE[2]))
	sUDP.close

Top
No I need to figure where to place the python script on my Synology NAS. I've have installed the Python3 package of Synology.

Thanks a lot

Re: Control Emotiva XMC-1 audio processor

Posted: Saturday 17 December 2016 18:34
by Number8
Seems to me that you could give a shot.

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 6:40
by RogerSch
Thanks @Number8.

After correcting some editorial failures like replacing TAB's with white spaces, I can run the Python script successfully from my PC. :D
So a first small step in controlling the XMC-1 from a Python has succeeded.

Now I need to figure out how to link a Python correctly to a virtual switch in Domoticz running on my Synology NAS. My first attempt did succeed. But I'm confident with other examples on the Domoticz forum how to solve this.

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 12:17
by Number8
Glad to read that. As far as Vol up/down in Domoticz is concerned, the only dummy device I see is the switch button, of type selector or maybe better dimmer. What are your thoughts?

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 12:45
by Number8
If the selector switch is used it could have several buttons like: Mute and some fixed levels, with a slider switch beside for more control over the volume level.

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 14:19
by RogerSch
Mmm somehow I got stuck. The following python script works. I can execute it on windows PC and even from a Terminal session logged onto my Synology NAS.

Terminal command which works:

Code: Select all

/volume1/@appstore/DomoticzScripts$ python ControlXMC1.py volume 1
I copied this into to the "Action On" in a virtual switch:

Code: Select all

script://volume1/@appstore/DomoticzScripts$ python ControlXMC1.py volume 1
It doesn't function when I press the Virtual switch.
What am I doing wrong?

Code: Select all

#!/usr/bin/env python
# -*- coding: latin-1 -*-
#
# Simple script to send commands to the Emotiva XMC-1 Surround Processor
# It is based upon XMC-1_network_interface_description.pdf V1.0
# First parameter is the command as defined in the PDF
# Second parameter is the Value to be applied. If no second parameter is present, Value is set to 0
# The fixed IP address of the XMC-1 needs to be defined below
#
# Author: R. Schuncken
# Date: 18 December 2016
# Version 1.0
#
import socket
import sys
import string
#import domoticz

XMC_COMMAND='<?xml version="1.0" encoding="utf-8"?><emotivaControl><commandName value="commandValue" ack="no" /></emotivaControl>'
XMC_IPADDRESS = "192.168.1.113"
# XMC_TRANSACTION_PORT_IN = 7000
# XMC_TRANSACTION_PORT_RESPONSE = 7001
XMC_CONTROL_PORT = 7002
MESSAGE=[XMC_COMMAND,XMC_IPADDRESS,XMC_CONTROL_PORT]

NumArgs = len(sys.argv)
ChangeValue = '0'

if (NumArgs > 1):
    if (NumArgs > 2):
        ChangeValue = sys.argv[2]
    MESSAGE[0]=string.replace(MESSAGE[0],'commandName',sys.argv[1])
    MESSAGE[0]=string.replace(MESSAGE[0],'commandValue',ChangeValue)
    sUDP = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sUDP.sendto(MESSAGE[0], (MESSAGE[1], MESSAGE[2]))
    sUDP.close

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 14:23
by Number8
Did you check the execution bit? And it has to be root

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 15:55
by RogerSch
Execution bit set. Root to be verified but that should not be a problem as I can execute the script from a Telnet session.

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 16:05
by Number8
that should not be a problem as I can execute the script from a Telnet session
Not too sure, I think Domoticz needs the script to be root to be executed by a button. Maybe some other fellows can confirm?

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 16:07
by Number8
script://volume1/@appstore/DomoticzScripts$ python ControlXMC1.py volume 1
Oops beware you need script:/// (triple)

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 17:13
by RogerSch
I tried that also. No luck...

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 17:20
by RogerSch
Number8 wrote:
that should not be a problem as I can execute the script from a Telnet session
Not too sure, I think Domoticz needs the script to be root to be executed by a button. Maybe some other fellows can confirm?
Ownership is currently admin. Can't change it to root with chmod as I get an error message.
Question why is that needed if the file rights are 777.

Re: Control Emotiva XMC-1 audio processor

Posted: Sunday 18 December 2016 17:54
by Number8
script://volume1/@appstore/DomoticzScripts$ python ControlXMC1.py volume 1
This cannot work. You have to call the python exe and make sure it resolves. Look at where it is installed and use an absolute path

Re: Control Emotiva XMC-1 audio processor

Posted: Monday 19 December 2016 19:47
by RogerSch
I finally got it to work but only after defining a LUA script for the switches and in the LUA script executing the python script.

Now I can switch on and off Zone 2 and control volume of Zone 1 and Zone 2... Exactly what I want.

@Number8 thanks for your help.

In case anybody is interested in the final python code and LUA script I'll upload them here. Please let me know.

Re: Control Emotiva XMC-1 audio processor

Posted: Tuesday 20 December 2016 6:01
by Number8
This is what I did here viewtopic.php?f=6&t=14912
This code is just a skeleton to test the launch of a Python script from LUA.
What kind of button did you use for volume up/down?

Re: Control Emotiva XMC-1 audio processor

Posted: Thursday 22 December 2016 5:11
by RogerSch
Number8 wrote:What kind of button did you use for volume up/down?
For the time being just two "Push On" buttons. I couldn't find an existing button (one!) which suits volume up and down well.
I would say there is some improvement possible in controling A/V devices and also on the graphical design of the Domoticz app. But adapting that is a bit too complex for me.

Re: Control Emotiva XMC-1 audio processor

Posted: Thursday 22 December 2016 9:22
by Number8
I would say there is some improvement possible in controling A/V devices
Agree
But adapting that is a bit too complex for me
Same for me ;)

So far I use a selector switch with 8 predefined sound levels, I use an On/Off button for the Mute function, and I will use a dimmer switch in order to set any levels between 0/100. When the user set a predefined sound level, the value of the "dimmer" switch is updated accordingly, however, the slider is not, which seems to be a design flaw at this stage.