diff --git a/VendServer/HorizScroll.py b/VendServer/HorizScroll.py
index 23811f06e7ea0f6a1c60c227b04377450d17203f..9687c8299e03b64c385fc1651724c644cef71d95 100644
--- a/VendServer/HorizScroll.py
+++ b/VendServer/HorizScroll.py
@@ -14,10 +14,9 @@ class HorizScroll:
 			return [self.text]
 
 		if padding == None:
-			padding = len(self.text) / 2 + 1
+			padding = len(self.text) // 2 + 1
 
-		format = "%-" + str(padding) + "." + str(padding) + "s"
-		pad = string.replace(format % " "," ",paddingchar)
+		pad = paddingchar * padding
 		padtext = self.text + pad
 		if not wraparound:
 			numiters = len(self.text) - 10
diff --git a/VendServer/Idler.py b/VendServer/Idler.py
index 493f209124709cb71e9e91e97e56770269488ed5..d3cab072a6f70c64ab7469b7cd319dd3a88c7456 100755
--- a/VendServer/Idler.py
+++ b/VendServer/Idler.py
@@ -62,7 +62,7 @@ class TrainIdler(Idler):
         Idler.__init__(self, v)
         self.idle_state = 0
 
-    def put_shark(self, s, l):
+    def put_shark(self, s: str, l: int):
         if self.s[l] == ' ':
             self.s[l] = s
         elif self.s[l] == 'X':
@@ -79,13 +79,13 @@ class TrainIdler(Idler):
         else:
             self.put_shark('^', 18-shark1)
 
-        shark2 = ((self.idle_state+4) % 36)/2
+        shark2 = ((self.idle_state+4) % 36)//2
         if shark2 < 9:
             self.put_shark('<', shark2)
         else:
             self.put_shark('<', 18-shark2)
 
-        shark3 = ((self.idle_state+7) % 54)/3
+        shark3 = ((self.idle_state+7) % 54)//3
         if shark3 < 9:
             self.put_shark('>', 9-shark3)
         else:
@@ -112,7 +112,7 @@ class TrainIdler(Idler):
                 ptr = i+train3-train3_start-10
                 if ptr >= 0 and ptr < 10: self.s[ptr] = '-'
 
-        self.v.display(string.join(self.s, ''))
+        self.v.display(''.join(self.s))
         self.idle_state += 1
         self.idle_state %= 18*36*54
 
@@ -139,7 +139,7 @@ class OrderMaker:
         j = 0
         res = []
         while i >= 0:
-            a = index/self.factorial[i]
+            a = index // self.factorial[i]
             index %= self.factorial[i]
             res.append(a+1)
             i -= 1
@@ -172,16 +172,16 @@ class GrayIdler(Idler):
         output = self.do_next_state()
         # does the next stage of a dance
         if self.zero:
-            output = string.replace(output, "0", self.zero)
+            output = output.replace("0", self.zero)
         if self.one:
-            output = string.replace(output, "1", self.one)
+            output = output.replace("1", self.one)
         if self.reorder:
             global orderings
             newoutput = ""
             for i in range(0,8):
                 newoutput += output[orderings[self.reorder][i]]
             output = newoutput
-        self.v.display(" %8.8s " % (output))
+        self.v.display(" %8.8s " % (output,))
         self.i = (self.i + 1) % self.size
 
     def do_next_state(self):
@@ -241,14 +241,14 @@ class StringIdler(Idler):
         msg = [("",False, None),(self.text, repeat, IDLER_TEXT_SPEED)]
         self.mk.set_messages(msg)
 
-    def clean_text(self, text):
+    def clean_text(self, text: str):
         # nothing like a bit of good clean text :)
         valid = string.digits \
-            + string.letters \
+            + string.ascii_letters \
             + string.punctuation \
             + " "
         # uppercase it
-        text = string.upper(text)
+        text = text.upper()
         clean = ""
         for char in text:
             if char in valid:
@@ -283,7 +283,7 @@ class FortuneIdler(StringIdler):
         text = "I broke my wookie...."
         if os.access(fortune,os.F_OK|os.X_OK):
             (lines, unused) = Popen((fortune,), close_fds=True, stdout=PIPE).communicate()
-            text = lines.replace('\n', '  ').replace('\r', '')
+            text = lines.decode('utf-8').replace('\n', '  ').replace('\r', '')
         StringIdler.__init__(self, v, text,repeat=False, affinity=affinity)
 
     def reset(self):
@@ -295,7 +295,7 @@ class PipeIdler(StringIdler):
         text = "I ate my cookie...."
         if os.access(command,os.F_OK|os.X_OK):
             (lines, unused) = Popen([command,] + args.split(), close_fds=True, stdout=PIPE).communicate()
-            text = lines.replace('\n', '  ').replace('\r', '')
+            text = lines.decode('utf-8').replace('\n', '  ').replace('\r', '')
         StringIdler.__init__(self, v, text,repeat=False, affinity=affinity)
 
 class FileIdler(StringIdler):
@@ -304,6 +304,6 @@ class FileIdler(StringIdler):
 
         if os.access(thefile,os.F_OK|os.R_OK):
             f = open(thefile,'r')
-            text = string.join(f.readlines())
+            text = "".join(f.readlines())
             f.close()
         StringIdler.__init__(self, v, text,repeat=False, affinity=affinity)
diff --git a/VendServer/MIFAREClient.py b/VendServer/MIFAREClient.py
index 96e19434f90264d921bece4d2f9e424ab55d5d01..8adf2336cc699bd8ec369d692e00af7a8da79ea3 100644
--- a/VendServer/MIFAREClient.py
+++ b/VendServer/MIFAREClient.py
@@ -1,6 +1,5 @@
 from .MIFAREDriver import MIFAREReader, MIFAREException
 from serial import Serial
-from .LDAPConnector import get_uid, set_card_id
 
 class MIFAREClient:
     def __init__(self):
@@ -20,25 +19,3 @@ class MIFAREClient:
             self.reader.set_led(red = False, green = True)
             self.reader.beep(100)
             return card_id
-    
-    def get_card_uid(self):
-        card_id = self.get_card_id()
-        if card_id == None:
-            return None
-        else:
-            return get_uid(card_id)
-    
-    def add_card(self, uid):
-        self.reader.set_led(red = True, green = False)
-        for attempt in range(5):
-            self.reader.beep(50)
-            try:
-                card_id, capacity = self.reader.select_card()
-            except MIFAREException:
-                pass
-            else:
-                set_card_id(uid, card_id)
-                self.reader.set_led(red = False, green = True)
-                return True
-        self.reader.set_led(red = False, green = True)
-        return False
diff --git a/VendServer/OpenDispense.py b/VendServer/OpenDispense.py
index 63f85afa087f9ff49e54839748070f8b74f56021..6ada5be678525c43905f745bb76ee10eadcda1d8 100644
--- a/VendServer/OpenDispense.py
+++ b/VendServer/OpenDispense.py
@@ -15,10 +15,9 @@ import pwd
 import base64
 import socket
 from subprocess import Popen, PIPE
-from .LDAPConnector import get_uid,get_uname, set_card_id
+#from .LDAPConnector import get_uid,get_uname, set_card_id
 
 DISPENSE_ENDPOINT = ("localhost", 11020)
-DISPSRV_MIFARE = True
 
 # A list of cards that should never be registered, and should never log in
 # - Some of these might have been registered before we knew they were duplicates
@@ -36,11 +35,35 @@ class OpenDispense(DispenseInterface):
 	def __init__(self, username=None, secret=False):
 		pass
 
-	def authUserIdPin(self, userId, pin):
+	def authUserIdPin(self, userId: str, pin: str):
 		return self.authUserIdPin_db(userId, pin)
 		#return self.authUserIdPin_file(userId, pin)
 	
-	def authUserIdPin_db(self, userId, pin):
+	def _connect(self, authenticte:bool=True, set_euid:bool=False):
+
+		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
+		try:
+			sock.connect(DISPENSE_ENDPOINT)
+		except ConnectionRefusedError:
+			logging.error("Cannot connect to dispsrv on {}".format(DISPENSE_ENDPOINT,))
+			return None
+		logging.debug('connected to dispsrv')
+		conn = Connection( sock.makefile('rw', encoding='utf-8') )
+		if authenticte:
+			rsp = conn.send_command("AUTHIDENT")
+			if not "200" in rsp:
+				logging.info('Server said no to AUTHIDENT! - %r' % (rsp,))
+				return None
+			logging.debug('authenticated')
+		if set_euid:
+			rsp = conn.send_command("SETEUSER %s" % (self._username,))
+			if not "200" in rsp:
+				logging.info('Server said no to SETEUSER! - %r' % (rsp,))
+				return None
+		
+		return conn
+
+	def authUserIdPin_db(self, userId: str, pin: str):
 		userId = int(userId)
 
 		try:
@@ -50,18 +73,13 @@ class OpenDispense(DispenseInterface):
 			logging.info('getting pin for uid %d: user not in password file'%userId)
 			return False
 		
-		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
-		sock.connect(DISPENSE_ENDPOINT)
-		logging.debug('connected to dispsrv')
-		sockf = sock.makefile()
-		sockf.write("AUTHIDENT\n"); sockf.flush()
-		rsp = sockf.readline()
-		assert "200" in rsp
-		logging.debug('authenticated')
-		sockf.write("PIN_CHECK %s %s\n" % (info.pw_name, pin)); sockf.flush()
-		rsp = sockf.readline()
+		conn = self._connect(authenticte=True)
+		if conn is None:
+			logging.error("getting pin for uid {}: Unable to open connection".format(userId))
+			return False
+		rsp = conn.send_command("PIN_CHECK %s %s" % (info.pw_name, pin,))
 		if not "200" in rsp:
-			logging.info('checking pin for uid %d: Server said no - %r' % (userId, rsp))
+			logging.info('checking pin for uid %d: Server said no (PIN_CHECK) - %r' % (userId, rsp))
 			return False
 		#Login Successful
 		logging.info('accepted pin for uid %d \'%s\'' % (userId, info.pw_name))
@@ -71,7 +89,7 @@ class OpenDispense(DispenseInterface):
 		self._username = info.pw_name
 		return True
 
-	def authUserIdPin_file(self, userId, pin):
+	def authUserIdPin_file(self, userId: str, pin: str):
 		userId = int(userId)
 
 		try:
@@ -116,51 +134,34 @@ class OpenDispense(DispenseInterface):
 	def authMifareCard(self, cardId):
 		self._loggedIn = False
 		self._username = None
-		if DISPSRV_MIFARE:
-			card_base64 = base64.b64encode(cardId)
-
-			if card_base64 in CARD_BLACKLIST:
-				logging.info("Blacklisted card base64:%s" % (card_base64,))
-				return False
-			
-			sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
-			sock.connect(DISPENSE_ENDPOINT)
-			logging.debug('connected to dispsrv')
-			sockf = sock.makefile()
-			sockf.write("AUTHIDENT\n"); sockf.flush()
-			rsp = sockf.readline()
-			assert "200" in rsp
-			logging.debug('authenticated')
-			sockf.write("AUTHCARD %s\n" % (card_base64,)); sockf.flush()
-			rsp = sockf.readline()
-			if not "200" in rsp:
-				logging.info("Rejected card base64:%s" % (card_base64,))
-				return False
-			username = rsp.split('=')[1].strip()
-			logging.info("Accepted card base64:%s for %s" % (card_base64,username,))
+	
+		card_base64 = base64.b64encode(cardId)
 
-			## Check for thier username
-			#try:
-			#	# Get info from the system (by username)
-			#	info = pwd.getpwnam(username)
-			#except KeyError:
-			#	logging.info('getting info for user \'%s\': user not in password file' % (username,))
-			#	return False
-			#self._userid = info.pw_uid
-			self._userid = None
-			self._username = username
-		else:
-			# Get the users ID
-			self._userid = get_uid(cardId)
+		if card_base64 in CARD_BLACKLIST:
+			logging.info("Blacklisted card base64:%s" % (card_base64,))
+			return False
+		
+		conn = self._connect()
+		if conn is None:
+			logging.error("getting username for card {}: Unable to open connection".format(card_base64))
+			return False
+		rsp = conn.send_command("AUTHCARD %s" % (card_base64,))
+		if not rsp.startswith("200 "):
+			logging.info("Rejected card base64:%s" % (card_base64,))
+			return False
+		username = rsp.split('=')[1].strip()
+		logging.info("Accepted card base64:%s for %s" % (card_base64,username,))
 
-			# Check for thier username
-			try:
-				# Get info from the system (by UID)
-				info = pwd.getpwuid(self._userid)
-			except KeyError:
-				logging.info('getting info for uid %d: user not in password file' % (self._userid,))
-				return False
-			self._username = info.pw_name
+		## Get UID for the username (not needed?)
+		#try:
+		#	# Get info from the system (by username)
+		#	info = pwd.getpwnam(username)
+		#except KeyError:
+		#	logging.info('getting info for user \'%s\': user not in password file' % (username,))
+		#	return False
+		#self._userid = info.pw_uid
+		self._userid = None
+		self._username = username
 
 		# If we get this far all is good
 		self._loggedIn = True
@@ -176,34 +177,22 @@ class OpenDispense(DispenseInterface):
 	def addCard(self, cardId):
 		if not self.isLoggedIn():
 			return False
-		if DISPSRV_MIFARE:
-			card_base64 = base64.b64encode(cardId)
-			if card_base64 in CARD_BLACKLIST:
-				logging.info("Blacklisted card base64:%s" % (card_base64,))
-				return False
-			logging.info('Enrolling card base64:%s to uid %s (%s)' % (card_base64, self._userId, self._username))
-			sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
-			sock.connect(DISPENSE_ENDPOINT)
-			sockf = sock.makefile()
-			sockf.write("AUTHIDENT\n")
-			sockf.flush(); rsp = sockf.readline()
-			assert "200" in rsp
-			sockf.write("SETEUSER %s\n" % (self._username,))
-			sockf.flush(); rsp = sockf.readline()
-			assert "200" in rsp
-			sockf.write("CARD_ADD %s\n" % (card_base64,))
-			sockf.flush(); rsp = sockf.readline()
-			if "200" in rsp:
-				return True
-			else:
-				return False
+		
+		card_base64 = base64.b64encode(cardId)
+		if card_base64 in CARD_BLACKLIST:
+			logging.info("Blacklisted card base64:%s" % (card_base64,))
+			return False
+		logging.info('Enrolling card base64:%s to uid %s (%s)' % (card_base64, self._userId, self._username))
+		conn = self._connect(set_euid=True)
+		if conn is None:
+			logging.warn("Enrolling card failed: Unable to connect".format(rsp))
+			return False
+		rsp = conn.send_command("CARD_ADD %s" % (card_base64,))
+		if "200" in rsp:
+			return True
 		else:
-			if get_uid(cardId) != None:
-				return False
-			else:
-				logging.info('Enrolling card %s to uid %s (%s)' % (cardId, self._userId, self._username))
-				set_card_id(self._userId, cardId)
-				return True
+			logging.warn("Enrolling card failed: Response = {}".format(rsp))
+			return False
 
 	def isLoggedIn(self):
 		return self._loggedIn
@@ -214,7 +203,7 @@ class OpenDispense(DispenseInterface):
 	def getBalance(self):
 		# Balance checking
 		if self.isLoggedIn():
-			acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE).communicate()
+			acct, unused = Popen(['dispense', 'acct', self._username], close_fds=True, stdout=PIPE, encoding='utf-8').communicate()
 		else:
 			return None
 		balance = acct[acct.find("$")+1:acct.find("(")].strip()
@@ -224,7 +213,7 @@ class OpenDispense(DispenseInterface):
 		logging.debug("getItemInfo(%s)" % (itemId,))
 		itemId = OpenDispenseMapping.vendingMachineToOpenDispense(itemId)
 		args = ('dispense', 'iteminfo', itemId)
-		info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
+		info, unused = Popen(args, close_fds=True, stdout=PIPE, encoding='utf-8').communicate()
 		m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
 		if m == None:
 			return("dead", 0)
@@ -243,11 +232,14 @@ class OpenDispense(DispenseInterface):
 			#os.system('dispense -u "%s" %s'%(self._username, itemId))
 			return True
 
-	def logOut(self):
-		self._username = ""
-		self._disabled = True
-		self._loggedIn = False
-		self._userId = None
+class Connection(object):
+	def __init__(self, sockf):
+		self.sockf = sockf
+	def send_command(self, command):
+		self.sockf.write(command)
+		self.sockf.write("\n")
+		self.sockf.flush()
+		return self.sockf.readline()
 
 """
 This class abstracts the idea of item numbers.
diff --git a/VendServer/VendServer.py b/VendServer/VendServer.py
index dda160bc3c7c59220ca697edb835dfbedbb92259..46b31418523f2264be1a94f781fe81253bc150a6 100755
--- a/VendServer/VendServer.py
+++ b/VendServer/VendServer.py
@@ -9,7 +9,7 @@ import logging, logging.handlers
 from traceback import format_tb
 from time import time, sleep, mktime, localtime
 from subprocess import Popen, PIPE
-from .LATClient import LATClient, LATClientException
+#from .LATClient import LATClient, LATClientException
 from .SerialClient import SerialClient, SerialClientException
 from .VendingMachine import VendingMachine, VendingException
 from .MessageKeeper import MessageKeeper
@@ -200,7 +200,7 @@ class VendServer():
 	"""
 	def center(self, str):
 		LEN = 10
-		return ' '*((LEN-len(str))/2)+str
+		return ' '*((LEN-len(str))//2)+str
 
 	"""
 	Configure the things that will appear on screen whil the machine is idling.
@@ -645,16 +645,16 @@ class VendServer():
 		### check for interesting times
 		now = localtime()
 
-		quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
-		halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
-		threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
-		fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
+		quarterhour      = mktime((now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]))
+		halfhour         = mktime((now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]))
+		threequarterhour = mktime((now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]))
+		fivetothehour    = mktime((now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]))
 
 		hourfromnow = localtime(time() + 3600)
 		
 		#onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
-		onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
-			0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
+		onthehour = mktime((hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
+			0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]))
 
 		## check for X seconds to the hour
 		## if case, update counter to 2
@@ -923,7 +923,7 @@ Connect to the machine.
 """
 def connect_to_vend(options, cf):
 
-	if options.use_lat:
+	if False and options.use_lat:
 		logging.info('Connecting to vending machine using LAT')
 		latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
 		rfh, wfh = latclient.get_fh()
@@ -965,6 +965,7 @@ def parse_args():
 	op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
 	op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
 	op.add_option('--traceback-file', dest='traceback_file', default='', help='destination to print tracebacks when receiving SIGUSR1')
+	op.add_option('--crash', action='store_true', help="Crash immediately instead of looping")
 	options, args = op.parse_args()
 
 	if len(args) != 0:
@@ -1082,6 +1083,8 @@ def main(argv=None):
 		except SystemExit:
 			break
 		except:
+			if options.crash:
+				raise
 			(exc_type, exc_value, exc_traceback) = sys.exc_info()
 			tb = format_tb(exc_traceback, 20)
 			del exc_traceback