From 4cda4c233477ea413ba8af06df84507ffc050fb9 Mon Sep 17 00:00:00 2001 From: andreas-amlabs Date: Tue, 16 Jan 2018 08:36:38 +0100 Subject: [PATCH 1/3] Wait for the requested response When sending a telnet request and at the same time using the remote control the reponse was sometimes incorrect. This solution is probably not 100% Eg. sending main.volume? while fibbling with the remote does not guarantee that the response is for the main.volume? command, it could be a response from remote control volume changes. But filtering should work for e.g. main.power? while pressing vol+/- on the remote Tested within home-assistant --- README.md | 13 ++++++++++++- nad_receiver/__init__.py | 17 +++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 099d1a0..beca5bb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # nad_receiver -Simple python API to connect to NAD receivers through the RS232 interface, and to the NAD D 7050 amplifier via tcp/ip. For the RS232 interface use the NADReceiver class, for the D 7050 use the D7050 class. +Simple python API to connect to NAD receivers through the RS232 interface, Telnet interface or the TCP/IP interface. +For the RS232 interface use the NADReceiver class +For the Telnet interface use the NADReceiverTelnet class +For the TCP/IP interface use the D7050 class Note that the RS232 interface is only tested with the NAD T748v2. Commands are implemented based on the T748v2. Those commands should work with more NAD receivers. +The Telnet interface is only tested with the NAD T787. The Telnet interface share documentation with the RS232 interface and supports the same commands The supported commands can easily be extended for receivers which support more commands. For more information see the official documentation at http://nadelectronics.com/software @@ -26,6 +30,13 @@ D7050.select_source('Optical 1') D7050.mute() D7050.unmute() D7050.power_off() + +receiver = NADReceiverTelnet(my_nad.local) + +receiver.main_volume('+') # will increase volume with 1 and return new value +receiver.main_volume('-') # will decrease volume with 1 and return new value +receiver.main_volume('=', '-40') # specify dB, will return new value +print(receiver.main_volume('?')) # will return current value ``` supported commands with supported operators for the RS232 interface diff --git a/nad_receiver/__init__.py b/nad_receiver/__init__.py index aef274d..3592d95 100644 --- a/nad_receiver/__init__.py +++ b/nad_receiver/__init__.py @@ -300,12 +300,17 @@ def exec_command(self, domain, function, operator, value=None): # Not possible to test for open Telnet connection # let is raise if any issues - # Yes, for telnet the first \r / \n is recommended only - self.telnet.write((''.join([cmd, '\n']).encode())) - - msg = self.telnet.read_until('\n'.encode(), self.timeout) - msg = msg.decode().strip('\r\n') - #print("NAD reponded with '%s'" % msg) + # For telnet the first \r / \n is recommended only + self.telnet.write((''.join(['\r', cmd, '\n']).encode())) + + found = False + while not found: + msg = self.telnet.read_until('\n'.encode(), self.timeout) + msg = msg.decode().strip('\r\n') + #print("NAD reponded with '%s'" % msg) + # Wait for the response that equals the requested domain.function + if msg.strip().split('=')[0].lower() == '.'.join([domain, function]).lower(): + found = True return msg.strip().split('=')[1] # b'Main.Volume=-12\r will return -12 From c9a411a9cba43b87c9f117824f73926dc95d94a8 Mon Sep 17 00:00:00 2001 From: andreas-amlabs Date: Wed, 17 Jan 2018 23:04:33 +0100 Subject: [PATCH 2/3] Fix for infinite while loop --- nad_receiver/__init__.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/nad_receiver/__init__.py b/nad_receiver/__init__.py index 3592d95..05b1606 100644 --- a/nad_receiver/__init__.py +++ b/nad_receiver/__init__.py @@ -246,7 +246,7 @@ def mute(self): def unmute(self): """Unmute the device.""" self._send(self.CMD_UNMUTE) - + def select_source(self, source): """Select a source from the list of sources.""" status = self.status() @@ -273,8 +273,8 @@ def __init__(self, host, port=23, timeout=DEFAULT_TIMEOUT): self.timeout = timeout # Some versions of the firmware report Main.Model=T787. # some versions do not, we want to clear that line - msg = self.telnet.read_until('\n'.encode(), self.timeout) - #msg.decode().strip().split('=')[1] + self.telnet.read_until('\n'.encode(), self.timeout) + # Could raise eg. EOFError, UnicodeError, let the client handle it def __del__(self): """ @@ -302,15 +302,28 @@ def exec_command(self, domain, function, operator, value=None): # let is raise if any issues # For telnet the first \r / \n is recommended only self.telnet.write((''.join(['\r', cmd, '\n']).encode())) + # Could raise eg. socket.error, UnicodeError, let the client handle it - found = False - while not found: + # Test 3 x buffer is completely empty + # With the default timeout that means a delay at + # about 3+ seconds + loop = 3 + while loop: msg = self.telnet.read_until('\n'.encode(), self.timeout) + # Could raise eg. EOFError, UnicodeError, let the client handle it + + if msg == "": + # Nothing in buffer + loop -= 1 + continue + msg = msg.decode().strip('\r\n') + # Could raise eg. UnicodeError, let the client handle it + #print("NAD reponded with '%s'" % msg) # Wait for the response that equals the requested domain.function if msg.strip().split('=')[0].lower() == '.'.join([domain, function]).lower(): - found = True + # b'Main.Volume=-12\r will return -12 + return msg.strip().split('=')[1] - return msg.strip().split('=')[1] - # b'Main.Volume=-12\r will return -12 + raise RuntimeError('Failed to read response') From f4d3fdf0e2f2d7a25aeb1e1ebc75dbd904657a7b Mon Sep 17 00:00:00 2001 From: andreas-amlabs Date: Thu, 18 Jan 2018 22:39:43 +0100 Subject: [PATCH 3/3] Fix for raise in __init__ Actually it is a good idea never to raise in __init__ --- nad_receiver/__init__.py | 92 ++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/nad_receiver/__init__.py b/nad_receiver/__init__.py index 05b1606..50e3408 100644 --- a/nad_receiver/__init__.py +++ b/nad_receiver/__init__.py @@ -267,20 +267,39 @@ class NADReceiverTelnet(NADReceiver): Known supported model: Nad T787. """ + def _open_connection(self): + if not self.telnet: + try: + self.telnet = telnetlib.Telnet(self.host, self.port, 3) + # Some versions of the firmware report Main.Model=T787. + # some versions do not, we want to clear that line + self.telnet.read_until('\n'.encode(), self.timeout) + # Could raise eg. EOFError, UnicodeError + except: + return False + + return True + + def _close_connection(self): + """ + Close any telnet session + """ + if self.telnet: + self.telnet.close() + def __init__(self, host, port=23, timeout=DEFAULT_TIMEOUT): - """Create Telnet connection.""" - self.telnet = telnetlib.Telnet(host, port, 5) + """Create NADTelnet.""" + self.telnet = None + self.host = host + self.port = port self.timeout = timeout - # Some versions of the firmware report Main.Model=T787. - # some versions do not, we want to clear that line - self.telnet.read_until('\n'.encode(), self.timeout) - # Could raise eg. EOFError, UnicodeError, let the client handle it + # __init__ must never raise def __del__(self): """ Close any telnet session """ - self.telnet.close() + self._close_connection() def exec_command(self, domain, function, operator, value=None): """ @@ -298,32 +317,33 @@ def exec_command(self, domain, function, operator, value=None): else: raise ValueError('Invalid operator provided %s' % operator) - # Not possible to test for open Telnet connection - # let is raise if any issues - # For telnet the first \r / \n is recommended only - self.telnet.write((''.join(['\r', cmd, '\n']).encode())) - # Could raise eg. socket.error, UnicodeError, let the client handle it - - # Test 3 x buffer is completely empty - # With the default timeout that means a delay at - # about 3+ seconds - loop = 3 - while loop: - msg = self.telnet.read_until('\n'.encode(), self.timeout) - # Could raise eg. EOFError, UnicodeError, let the client handle it - - if msg == "": - # Nothing in buffer - loop -= 1 - continue - - msg = msg.decode().strip('\r\n') - # Could raise eg. UnicodeError, let the client handle it - - #print("NAD reponded with '%s'" % msg) - # Wait for the response that equals the requested domain.function - if msg.strip().split('=')[0].lower() == '.'.join([domain, function]).lower(): - # b'Main.Volume=-12\r will return -12 - return msg.strip().split('=')[1] - - raise RuntimeError('Failed to read response') + if self._open_connection(): + # For telnet the first \r / \n is recommended only + self.telnet.write((''.join(['\r', cmd, '\n']).encode())) + # Could raise eg. socket.error, UnicodeError, let the client handle it + + # Test 3 x buffer is completely empty + # With the default timeout that means a delay at + # about 3+ seconds + loop = 3 + while loop: + msg = self.telnet.read_until('\n'.encode(), self.timeout) + # Could raise eg. EOFError, UnicodeError, let the client handle it + + if msg == "": + # Nothing in buffer + loop -= 1 + continue + + msg = msg.decode().strip('\r\n') + # Could raise eg. UnicodeError, let the client handle it + + #print("NAD reponded with '%s'" % msg) + # Wait for the response that equals the requested domain.function + if msg.strip().split('=')[0].lower() == '.'.join([domain, function]).lower(): + # b'Main.Volume=-12\r will return -12 + return msg.strip().split('=')[1] + + raise RuntimeError('Failed to read response') + + raise RuntimeError('Failed to open connection')