Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 86 additions & 11 deletions python_telnet_vlc/vlctelnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
import telnetlib
import re
import time

# Error Imports
from socket import error as sockerr
Expand Down Expand Up @@ -46,11 +47,14 @@ 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=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()
# Login to VLC using password provided in the arguments
Expand All @@ -61,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.")

Expand All @@ -77,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)
Expand All @@ -91,15 +95,31 @@ 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'> ', 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 Exception:
return False

def run_command(self, command):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could we combine run_command and do_run_command? I think we can wrap much of do_run_command in for _ in range(self.retries): ...

Alternatively, maybe_reconnect_and_run_command might be a clearer name for do_run_command?

return self.do_run_command(command, self.retries)

def do_send_string(self, string):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "string" is also the name of a Python module -- maybe "message" is a better name here?

"""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
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:
Expand All @@ -111,6 +131,30 @@ def run_command(self, command):
# Return the split output of the command
return command_output

def do_run_command(self, command, retries):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Add a short doc string?

_LOGGER.debug("do_run_command: retries: %s", 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:
try:
self.disconnect()
time.sleep(1)
except Exception:
pass
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to log the exception here? (or at least that there was one?) If this is a thing that happens commonly, maybe we can tighten it up to the specific Exception that happens here.

(same for line 153)


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.")
# Commands
# Block 1
def add(self, xyz):
Expand Down Expand Up @@ -192,14 +236,45 @@ 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])
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.")
Expand Down