From 4f5dd8039c0e268d02cce68d4bbfc5b82d412d46 Mon Sep 17 00:00:00 2001 From: teixeluis Date: Sun, 10 Oct 2021 21:37:26 +0100 Subject: [PATCH 1/3] Fix in the scope of https://github.com/home-assistant/core/issues/29184 Added connection test and connection retry loop. --- python_telnet_vlc/vlctelnet.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/python_telnet_vlc/vlctelnet.py b/python_telnet_vlc/vlctelnet.py index 6723375..c27837f 100644 --- a/python_telnet_vlc/vlctelnet.py +++ b/python_telnet_vlc/vlctelnet.py @@ -91,11 +91,26 @@ def login(self): # Read until prompt self.tn.read_until(b'> ') + def is_connected(self): + try: + self.tn.write(b'\n') + answer = self.tn.read_until(b'> ') + if answer: + print(f'is_connected: answer obtained. Value: {answer}') + return True + else: + print(f'is_connected: failed to obtain answer. Value: {answer}') + return False + except sockerr: + return False def run_command(self, command): + return self.do_run_command(command, 5) + + def do_send_string(self, string): """Run a command and return a list with the output lines.""" # Put the command in a nice byte-encoded variable - full_command = command.encode('utf-8') + b'\n' - _LOGGER.debug("Sending command: %s", command) + full_command = string.encode('utf-8') + b'\n' + _LOGGER.debug("Sending command: %s", string) # Write out the command to telnet self.tn.write(full_command) # Get the command output, decode it, and split out the junk @@ -111,6 +126,17 @@ def run_command(self, command): # Return the split output of the command return command_output + def do_run_command(self, command, retries): + _LOGGER.debug("do_run_command: retries: %s", retries) + + if self.is_connected(): + return self.do_send_string(command) + else: + if retries > 0: + self.connect() + return self.do_run_command(command, retries - 1) + else: + raise ConnectionError("Could not connect to VLC. Make sure the Telnet interface is enabled and accessible.") # Commands # Block 1 def add(self, xyz): @@ -192,6 +218,9 @@ def clear(self): def status(self): """Current playlist status.""" status_output = self.run_command('status') + _LOGGER.debug("status: status output: %s", status_output) + if status_output is None: + raise ParseError("Could not get status.") if len(status_output) == 3: inputloc = '%20'.join(status_output[0].split(' ')[3:-1]) volume = int(status_output[1].split(' ')[3]) From 99bba011ebd2c8a17564419523af854a56cd0eae Mon Sep 17 00:00:00 2001 From: teixeluis Date: Sun, 10 Oct 2021 23:06:48 +0100 Subject: [PATCH 2/3] Slight code cleanup. Exposing the retries parameter in the constructor. --- python_telnet_vlc/vlctelnet.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/python_telnet_vlc/vlctelnet.py b/python_telnet_vlc/vlctelnet.py index c27837f..9f642fb 100644 --- a/python_telnet_vlc/vlctelnet.py +++ b/python_telnet_vlc/vlctelnet.py @@ -46,11 +46,13 @@ class VLCTelnet(object): """Conection to VLC using Telnet.""" # Non commands - def __init__(self, host="localhost", password="admin", port=4212, connect=True, login=True): + def __init__(self, host="localhost", password="admin", port=4212, connect=True, login=True, retries=5): self.tn = telnetlib.Telnet() self.host = host self.port = port self.password = password + self.retries = retries + if connect: self.connect() # Login to VLC using password provided in the arguments @@ -96,15 +98,16 @@ def is_connected(self): self.tn.write(b'\n') answer = self.tn.read_until(b'> ') if answer: - print(f'is_connected: answer obtained. Value: {answer}') + _LOGGER.debug("is_connected: answer obtained. Value: %s", answer) return True else: - print(f'is_connected: failed to obtain answer. Value: {answer}') + _LOGGER.debug("is_connected: failed to obtain answer.") return False except sockerr: return False + def run_command(self, command): - return self.do_run_command(command, 5) + return self.do_run_command(command, self.retries) def do_send_string(self, string): """Run a command and return a list with the output lines.""" From 690e1d9b3ba2ffe15d0b3c5d4cf76130434087b7 Mon Sep 17 00:00:00 2001 From: teixeluis Date: Mon, 11 Oct 2021 22:22:27 +0100 Subject: [PATCH 3/3] Added timeout as a constructor parameter. Improved error handling. Fixed absence of login during connection retries. --- python_telnet_vlc/vlctelnet.py | 67 ++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/python_telnet_vlc/vlctelnet.py b/python_telnet_vlc/vlctelnet.py index 9f642fb..1750f80 100644 --- a/python_telnet_vlc/vlctelnet.py +++ b/python_telnet_vlc/vlctelnet.py @@ -4,6 +4,7 @@ import logging import telnetlib import re +import time # Error Imports from socket import error as sockerr @@ -46,12 +47,13 @@ class VLCTelnet(object): """Conection to VLC using Telnet.""" # Non commands - def __init__(self, host="localhost", password="admin", port=4212, connect=True, login=True, retries=5): + def __init__(self, host="localhost", password="admin", port=4212, connect=True, login=True, retries=10, timeout=10): self.tn = telnetlib.Telnet() self.host = host self.port = port self.password = password self.retries = retries + self.timeout = timeout if connect: self.connect() @@ -63,7 +65,7 @@ def connect(self): """Connect to VLC.""" # Connect to telnet. try: - self.tn.open(self.host, port=self.port, timeout=10) + self.tn.open(self.host, self.port, self.timeout) except sockerr: raise ConnectionError("Could not connect to VLC. Make sure the Telnet interface is enabled and accessible.") @@ -79,7 +81,7 @@ def login(self): full_command = self.password.encode('utf-8') + b'\n' self.tn.write(full_command) for _ in range(2): - command_output = self.tn.read_until(b'\n', timeout=10).decode('utf-8').strip('\r\n') + command_output = self.tn.read_until(b'\n', self.timeout).decode('utf-8').strip('\r\n') if command_output: # discard empty line once. break _LOGGER.debug("Password response: %s", command_output) @@ -96,14 +98,14 @@ def login(self): def is_connected(self): try: self.tn.write(b'\n') - answer = self.tn.read_until(b'> ') + answer = self.tn.read_until(b'> ', self.timeout) if answer: _LOGGER.debug("is_connected: answer obtained. Value: %s", answer) return True else: _LOGGER.debug("is_connected: failed to obtain answer.") return False - except sockerr: + except Exception: return False def run_command(self, command): @@ -117,7 +119,7 @@ def do_send_string(self, string): # Write out the command to telnet self.tn.write(full_command) # Get the command output, decode it, and split out the junk - command_output = self.tn.read_until(b'> ').decode('utf-8').split('\r\n')[:-1] + command_output = self.tn.read_until(b'> ', self.timeout).decode('utf-8').split('\r\n')[:-1] # Raise command error if VLC does not recognize the command. _LOGGER.debug("Command output: %s", command_output) if command_output: @@ -135,8 +137,21 @@ def do_run_command(self, command, retries): if self.is_connected(): return self.do_send_string(command) else: + _LOGGER.debug("do_run_command: retrying connection. Iteration # %s", retries) + if retries > 0: - self.connect() + try: + self.disconnect() + time.sleep(1) + except Exception: + pass + + try: + self.connect() + self.login() + except Exception: + pass + return self.do_run_command(command, retries - 1) else: raise ConnectionError("Could not connect to VLC. Make sure the Telnet interface is enabled and accessible.") @@ -222,16 +237,44 @@ def status(self): """Current playlist status.""" status_output = self.run_command('status') _LOGGER.debug("status: status output: %s", status_output) + if status_output is None: raise ParseError("Could not get status.") if len(status_output) == 3: - inputloc = '%20'.join(status_output[0].split(' ')[3:-1]) - volume = int(status_output[1].split(' ')[3]) - state = status_output[2].split(' ')[2] + output_inputloc = status_output[0].split(' ') + output_volume = status_output[1].split(' ') + output_status = status_output[2].split(' ') + + if len(output_inputloc) >= 4: + inputloc = '%20'.join(output_inputloc[3:-1]) + else: + raise ParseError("Received malformed status message") + + if len(output_volume) >= 4: + volume = int(output_volume[3]) + else: + raise ParseError("Received malformed status message") + + if len(output_status) >= 3: + state = output_status[2] + else: + raise ParseError("Received malformed status message") + returndict = {'input': inputloc, 'volume': volume, 'state': state} elif len(status_output) == 2: - volume = int(status_output[0].split(' ')[3]) - state = status_output[1].split(' ')[2] + output_volume = status_output[0].split(' ') + output_status = status_output[1].split(' ') + + if len(output_volume) >= 4: + volume = int(output_volume[3]) + else: + raise ParseError("Received malformed status message") + + if len(output_status) >= 3: + state = output_status[2] + else: + raise ParseError("Received malformed status message") + returndict = {'volume': volume, 'state': state} else: raise ParseError("Could not get status.")