From 15173eaaa20442f08c2d32fca37825e5c3bcb3e6 Mon Sep 17 00:00:00 2001
From: Mitchell Pomery <bob_george33@hotmail.com>
Date: Tue, 10 Mar 2015 21:50:33 +0800
Subject: [PATCH] Created VendServer Class

---
 VendServer/VendServer.py | 1753 +++++++++++++++++++-------------------
 1 file changed, 870 insertions(+), 883 deletions(-)

diff --git a/VendServer/VendServer.py b/VendServer/VendServer.py
index 860c1c8..d6a07a0 100755
--- a/VendServer/VendServer.py
+++ b/VendServer/VendServer.py
@@ -66,14 +66,6 @@ STATE_GRANDFATHER_CLOCK,
 TEXT_SPEED = 0.8
 IDLE_SPEED = 0.05
 
-_pin_uid = 0
-_pin_uname = 'root'
-_pin_pin = '----'
-
-_last_card_id = -1
-
-idlers = []
-idler = None
 
 config_options = {
 	'DBServer': ('Database', 'Server'),
@@ -158,937 +150,931 @@ class VendState:
 		if newcounter is not None and self.counter != newcounter:
 			self.counter = newcounter
 
-"""
-Show information to the user as to what can be dispensed.
-"""
-def scroll_options(username, mk, welcome = False):
-	# If the user has just logged in, show them their balance
-	if welcome:
-		# Balance checking
-		acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate()
+class VendServer():
+
+	v = None
+	vstatus = None
+	state = None
+	event = None
+	idlers = []
+	idler = None
+
+	_pin_uid = 0
+	_pin_uname = 'root'
+	_pin_pin = '----'
+	_last_card_id = -1
+
+	"""
+	Show information to the user as to what can be dispensed.
+	"""
+	def scroll_options(self, username, mk, welcome = False):
+		# If the user has just logged in, show them their balance
+		if welcome:
+			# Balance checking
+			acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate()
+			# this is fucking appalling
+			balance = acct[acct.find("$")+1:acct.find("(")].strip()
+			
+			msg = [(self.center('WELCOME'), False, TEXT_SPEED),
+				   (self.center(username), False, TEXT_SPEED),
+				   (self.center(balance), False, TEXT_SPEED),]
+		else:
+			msg = []
+		choices = ' '*10+'CHOICES: '
+
+		# Show what is in the coke machine
+		cokes = []
+		for i in range(0, 7):
+			args = ('dispense', 'iteminfo', 'coke:%i' % i)
+			info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
+			m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
+			cents = int(m.group(1))*100 + int(m.group(2))
+			cokes.append('%i %i %s' % (i, cents, m.group(3)));
+
+		for c in cokes:
+			c = c.strip()
+			(slot_num, price, slot_name) = c.split(' ', 2)
+			if slot_name == 'dead': continue
+			choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
+
+		# Show the final few options
+		choices += '55-DOOR '
+		choices += 'OR ANOTHER SNACK. '
+		choices += '99 TO READ AGAIN. '
+		choices += 'CHOICE?   '
+		msg.append((choices, False, None))
+		# Send it to the display
+		mk.set_messages(msg)
+
+
+	"""
+	Verify the users pin
+	"""
+	def _check_pin(self, uid, pin):
+		print "_check_pin('",uid,"',---)"
+		if uid != self._pin_uid:
+			try:
+				info = pwd.getpwuid(uid)
+			except KeyError:
+				logging.info('getting pin for uid %d: user not in password file'%uid)
+				return None
+			if info.pw_dir == None: return False
+			pinfile = os.path.join(info.pw_dir, '.pin')
+			try:
+				s = os.stat(pinfile)
+			except OSError:
+				logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
+				return None
+			if s.st_mode & 077:
+				logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
+				os.chmod(pinfile, 0600)
+			try:
+				f = file(pinfile)
+			except IOError:
+				logging.info('getting pin for uid %d: I cannot read pin file'%uid)
+				return None
+			pinstr = f.readline()
+			f.close()
+			if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
+				logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
+				return None
+			self._pin_uid = uid
+			self._pin_pin = pinstr
+			self._pin_uname = info.pw_name
+		else:
+			pinstr = self._pin_pin
+		if pin == int(pinstr):
+			logging.info("Pin correct for %d",uid)
+		else:
+			logging.info("Pin incorrect for %d",uid)
+		return pin == int(pinstr)
+
+	"""
+	Check if the users account has been disabled
+	"""
+	def acct_is_disabled(self, name=None):
+		if name == None:
+			name = self._pin_uname
+		acct, unused = Popen(['dispense', 'acct', self._pin_uname], close_fds=True, stdout=PIPE).communicate()
 		# this is fucking appalling
-		balance = acct[acct.find("$")+1:acct.find("(")].strip()
-        
-		msg = [(center('WELCOME'), False, TEXT_SPEED),
-			   (center(username), False, TEXT_SPEED),
-			   (center(balance), False, TEXT_SPEED),]
-	else:
-		msg = []
-	choices = ' '*10+'CHOICES: '
-
-	# Show what is in the coke machine
-	cokes = []
-	for i in range(0, 7):
-		args = ('dispense', 'iteminfo', 'coke:%i' % i)
-		info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
-		m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
-		cents = int(m.group(1))*100 + int(m.group(2))
-		cokes.append('%i %i %s' % (i, cents, m.group(3)));
-
-	for c in cokes:
-		c = c.strip()
-		(slot_num, price, slot_name) = c.split(' ', 2)
-		if slot_name == 'dead': continue
-		choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
-
-	# Show the final few options
-	choices += '55-DOOR '
-	choices += 'OR ANOTHER SNACK. '
-	choices += '99 TO READ AGAIN. '
-	choices += 'CHOICE?   '
-	msg.append((choices, False, None))
-	# Send it to the display
-	mk.set_messages(msg)
-
-
-"""
-Verify the users pin
-"""
-def _check_pin(uid, pin):
-	global _pin_uid
-	global _pin_uname
-	global _pin_pin
-	print "_check_pin('",uid,"',---)"
-	if uid != _pin_uid:
-		try:
+		flags = acct[acct.find("(")+1:acct.find(")")].strip()
+		if 'disabled' in flags:
+			return True
+		if 'internal' in flags:
+			return True
+		return False
+
+	"""
+	Check that the user has a valid pin set
+	"""
+	def has_good_pin(self, uid):
+		return self._check_pin(uid, None) != None
+
+	"""
+	Verify the users pin.
+	"""
+	def verify_user_pin(self, uid, pin, skip_pin_check=False):
+		if skip_pin_check or self._check_pin(uid, pin) == True:
 			info = pwd.getpwuid(uid)
-		except KeyError:
-			logging.info('getting pin for uid %d: user not in password file'%uid)
-			return None
-		if info.pw_dir == None: return False
-		pinfile = os.path.join(info.pw_dir, '.pin')
-		try:
-			s = os.stat(pinfile)
-		except OSError:
-			logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
-			return None
-		if s.st_mode & 077:
-			logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
-			os.chmod(pinfile, 0600)
-		try:
-			f = file(pinfile)
-		except IOError:
-			logging.info('getting pin for uid %d: I cannot read pin file'%uid)
-			return None
-		pinstr = f.readline()
-		f.close()
-		if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
-			logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
-			return None
-		_pin_uid = uid
-		_pin_pin = pinstr
-		_pin_uname = info.pw_name
-	else:
-		pinstr = _pin_pin
-	if pin == int(pinstr):
-		logging.info("Pin correct for %d",uid)
-	else:
-		logging.info("Pin incorrect for %d",uid)
-	return pin == int(pinstr)
-
-"""
-Check if the users account has been disabled
-"""
-def acct_is_disabled(name=None):
-	global _pin_uname
-	if name == None:
-		name = _pin_uname
-	acct, unused = Popen(['dispense', 'acct', _pin_uname], close_fds=True, stdout=PIPE).communicate()
-	# this is fucking appalling
-	flags = acct[acct.find("(")+1:acct.find(")")].strip()
-	if 'disabled' in flags:
-		return True
-	if 'internal' in flags:
-		return True
-	return False
-
-"""
-Check that the user has a valid pin set
-"""
-def has_good_pin(uid):
-	return _check_pin(uid, None) != None
-
-"""
-Verify the users pin.
-"""
-def verify_user_pin(uid, pin, skip_pin_check=False):
-	if skip_pin_check or _check_pin(uid, pin) == True:
-		info = pwd.getpwuid(uid)
-		if skip_pin_check:
-			if acct_is_disabled(info.pw_name):
-				logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name))
-				return '-disabled-'
-			logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name))
+			if skip_pin_check:
+				if self.acct_is_disabled(info.pw_name):
+					logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name))
+					return '-disabled-'
+				logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name))
+			else:
+				logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
+			return info.pw_name
 		else:
-			logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
-		return info.pw_name
-	else:
-		logging.info('refused pin for uid %d'%(uid))
-		return None
+			logging.info('refused pin for uid %d'%(uid))
+			return None
 
-"""
-In here just for fun.
-"""
-def cookie(v):
-	seed(time())
-	messages = ['  WASSUP! ', 'PINK FISH ', ' SECRETS ', '  ESKIMO  ', ' FORTUNES ', 'MORE MONEY']
-	choice = int(random()*len(messages))
-	msg = messages[choice]
-	left = range(len(msg))
-	for i in range(len(msg)):
-		if msg[i] == ' ': left.remove(i)
-	reveal = 1
-	while left:
-		s = ''
-		for i in range(0, len(msg)):
-			if i in left:
-				if reveal == 0:
-					left.remove(i)
-					s += msg[i]
+	"""
+	In here just for fun.
+	"""
+	def cookie(self):
+		seed(time())
+		messages = ['  WASSUP! ', 'PINK FISH ', ' SECRETS ', '  ESKIMO  ', ' FORTUNES ', 'MORE MONEY']
+		choice = int(random()*len(messages))
+		msg = messages[choice]
+		left = range(len(msg))
+		for i in range(len(msg)):
+			if msg[i] == ' ': left.remove(i)
+		reveal = 1
+		while left:
+			s = ''
+			for i in range(0, len(msg)):
+				if i in left:
+					if reveal == 0:
+						left.remove(i)
+						s += msg[i]
+					else:
+						s += chr(int(random()*26)+ord('A'))
+					reveal += 1
+					reveal %= 17
 				else:
-					s += chr(int(random()*26)+ord('A'))
-				reveal += 1
-				reveal %= 17
-			else:
-				s += msg[i]
-		v.display(s)
-
-"""
-Format text so it will appear centered on the screen.
-"""
-def center(str):
-	LEN = 10
-	return ' '*((LEN-len(str))/2)+str
-
-"""
-Configure the things that will appear on screen whil the machine is idling.
-"""
-def setup_idlers(v):
-	global idlers, idler
-	idlers = [
-		#
-		GrayIdler(v),
-		GrayIdler(v,one="*",zero="-"),
-		GrayIdler(v,one="/",zero="\\"),
-		GrayIdler(v,one="X",zero="O"),
-		GrayIdler(v,one="*",zero="-",reorder=1),
-		GrayIdler(v,one="/",zero="\\",reorder=1),
-		GrayIdler(v,one="X",zero="O",reorder=1),
-		#
-		ClockIdler(v),
-		ClockIdler(v),
-		ClockIdler(v),
-		#
-		StringIdler(v), # Hello Cruel World
-		StringIdler(v, text="Kill 'em all", repeat=False),
-		StringIdler(v, text=CREDITS),
-		StringIdler(v, text=str(math.pi) + "            "),
-		StringIdler(v, text=str(math.e) + "            "),
-		StringIdler(v, text="    I want some pizza - please call Broadway Pizza on +61 8 9389 8500 - and order as Quinn - I am getting really hungry", repeat=False),
-		# "Hello World" in brainfuck
-		StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
-		#
-		TrainIdler(v),
-		#
-		FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
-		#
-		PipeIdler(v, "/usr/bin/getent", "passwd"),
-		FortuneIdler(v,affinity=20),
-		]
-	disabled = [
-		]
-
-"""
-Go back to the default idler.
-"""
-def reset_idler(v, vstatus, t = None):
-	global idlers, idler
-	idler = GreetingIdler(v, t)
-	vstatus.time_of_next_idlestep = time()+idler.next()
-	vstatus.time_of_next_idler = None
-	vstatus.time_to_autologout = None
-	vstatus.change_state(STATE_IDLE, 1)
-
-"""
-Change to a random idler.
-"""
-def choose_idler():
-	global idlers, idler
-
-	# Implementation of the King Of the Hill algorithm from;
-	# http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/
-
-	#def weighted_choice_king(weights):
-	#	total = 0
-	#	winner = 0
-	#	for i, w in enumerate(weights):
-	#		total += w
-	#		if random.random() * total < w:
-	#			winner = i
-	#	return winner
-	#	
-
-	total = 0
-	winner = None
-	
-	for choice in idlers:
-		weight = choice.affinity()
-		total += weight
-		if random() * total < weight:
-			winner = choice
-
-	idler = winner
-
-	if idler:
-		idler.reset()
-"""
-Run every step while the machine is idling.
-"""
-def idle_step(vstatus):
-	global idler
-	if idler.finished():
-		choose_idler()
-		vstatus.time_of_next_idler = time() + 30
-	nextidle = idler.next()
-	if nextidle is None:
-		nextidle = IDLE_SPEED
-	vstatus.time_of_next_idlestep = time()+nextidle
-
-"""
-These next two events trigger no response in the code.
-"""
-def handle_tick_event(event, params, v, vstatus):
-	# don't care right now.
-	pass
-
-def handle_switch_event(event, params, v, vstatus):
-	# don't care right now.
-	pass
-
-"""
-Don't do anything for this event.
-"""
-def do_nothing(state, event, params, v, vstatus):
-	print "doing nothing (s,e,p)", state, " ", event, " ", params
-	pass
+					s += msg[i]
+			self.v.display(s)
+
+	"""
+	Format text so it will appear centered on the screen.
+	"""
+	def center(self, str):
+		LEN = 10
+		return ' '*((LEN-len(str))/2)+str
+
+	"""
+	Configure the things that will appear on screen whil the machine is idling.
+	"""
+	def setup_idlers(self):
+		self.idlers = [
+			#
+			GrayIdler(self.v),
+			GrayIdler(self.v,one="*",zero="-"),
+			GrayIdler(self.v,one="/",zero="\\"),
+			GrayIdler(self.v,one="X",zero="O"),
+			GrayIdler(self.v,one="*",zero="-",reorder=1),
+			GrayIdler(self.v,one="/",zero="\\",reorder=1),
+			GrayIdler(self.v,one="X",zero="O",reorder=1),
+			#
+			ClockIdler(self.v),
+			ClockIdler(self.v),
+			ClockIdler(self.v),
+			#
+			StringIdler(self.v), # Hello Cruel World
+			StringIdler(self.v, text="Kill 'em all", repeat=False),
+			StringIdler(self.v, text=CREDITS),
+			StringIdler(self.v, text=str(math.pi) + "            "),
+			StringIdler(self.v, text=str(math.e) + "            "),
+			StringIdler(self.v, text="    I want some pizza - please call Broadway Pizza on +61 8 9389 8500 - and order as Quinn - I am getting really hungry", repeat=False),
+			# "Hello World" in brainfuck
+			StringIdler(self.v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
+			#
+			TrainIdler(self.v),
+			#
+			FileIdler(self.v, '/usr/share/common-licenses/GPL-2',affinity=2),
+			#
+			PipeIdler(self.v, "/usr/bin/getent", "passwd"),
+			FortuneIdler(self.v,affinity=20),
+			]
+		disabled = [
+			]
+
+	"""
+	Go back to the default idler.
+	"""
+	def reset_idler(self, t = None):
+		self.idler = GreetingIdler(self.v, t)
+		self.vstatus.time_of_next_idlestep = time()+self.idler.next()
+		self.vstatus.time_of_next_idler = None
+		self.vstatus.time_to_autologout = None
+		self.vstatus.change_state(STATE_IDLE, 1)
+
+	"""
+	Change to a random idler.
+	"""
+	def choose_idler(self):
+
+		# Implementation of the King Of the Hill algorithm from;
+		# http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/
+
+		#def weighted_choice_king(weights):
+		#	total = 0
+		#	winner = 0
+		#	for i, w in enumerate(weights):
+		#		total += w
+		#		if random.random() * total < w:
+		#			winner = i
+		#	return winner
+		#	
+
+		total = 0
+		winner = None
+		
+		for choice in self.idlers:
+			weight = choice.affinity()
+			total += weight
+			if random() * total < weight:
+				winner = choice
+
+		self.idler = winner
+
+		if self.idler:
+			self.idler.reset()
+	"""
+	Run every step while the machine is idling.
+	"""
+	def idle_step(self):
+		if self.idler.finished():
+			self.choose_idler()
+			self.vstatus.time_of_next_idler = time() + 30
+		nextidle = self.idler.next()
+		if nextidle is None:
+			nextidle = IDLE_SPEED
+		self.vstatus.time_of_next_idlestep = time()+nextidle
+
+	"""
+	These next two events trigger no response in the code.
+	"""
+	def handle_tick_event(self, event, params):
+		# don't care right now.
+		pass
 
-"""
-These next few entrie tell us to do nothing while we are idling
-"""
-def handle_getting_uid_idle(state, event, params, v, vstatus):
-	# don't care right now.
-	pass
+	def handle_switch_event(self, event, params):
+		# don't care right now.
+		pass
 
-def handle_getting_pin_idle(state, event, params, v, vstatus):
-	# don't care right now.
-	pass
+	"""
+	Don't do anything for this event.
+	"""
+	def do_nothing(self, event, params):
+		print "doing nothing (s,e,p)", state, " ", event, " ", params
+		pass
 
-"""
-While logged in and waiting for user input, slowly get closer to logging out.
-"""
-def handle_get_selection_idle(state, event, params, v, vstatus):
-	global _last_card_id
-	# don't care right now.
-	###
-	### State logging out ..
-	if vstatus.time_to_autologout != None:
-		time_left = vstatus.time_to_autologout - time()
-		if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
-			vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
-			vstatus.last_timeout_refresh = int(time_left)
-			vstatus.cur_selection = ''
-
-	if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
-		vstatus.time_to_autologout = None
-		vstatus.cur_user = ''
-		vstatus.cur_pin = ''
-		vstatus.cur_selection = ''
-		_last_card_id = -1
-		reset_idler(v, vstatus)
-
-	### State fully logged out ... reset variables
-	if vstatus.time_to_autologout and not vstatus.mk.done(): 
-		vstatus.time_to_autologout = None
-	if vstatus.cur_user == '' and vstatus.time_to_autologout: 
-		vstatus.time_to_autologout = None
-	
-	### State logged in
-	if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
-		# start autologout
-		vstatus.time_to_autologout = time() + 15
-		vstatus.last_timeout_refresh = None
+	"""
+	These next few entrie tell us to do nothing while we are idling
+	"""
+	def handle_getting_uid_idle(self, event, params):
+		# don't care right now.
+		pass
 
-	## FIXME - this may need to be elsewhere.....
-	# need to check
-	vstatus.mk.update_display()
+	def handle_getting_pin_idle(self, event, params):
+		# don't care right now.
+		pass
 
-"""
-Triggered on user input while logged in.
-"""
-def handle_get_selection_key(state, event, params, v, vstatus):
-	global _last_card_id
-	key = params
-	if len(vstatus.cur_selection) == 0:
-		if key == 11:
-			vstatus.cur_pin = ''
-			vstatus.cur_user = ''
-			vstatus.cur_selection = ''
-			_last_card_id = -1
-			vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
-			reset_idler(v, vstatus, 2)
-			return
-		vstatus.cur_selection += chr(key + ord('0'))
-		vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
-		vstatus.time_to_autologout = None
-	elif len(vstatus.cur_selection) == 1:
-		if key == 11:
-			vstatus.cur_selection = ''
-			vstatus.time_to_autologout = None
-			scroll_options(vstatus.username, vstatus.mk)
+	"""
+	While logged in and waiting for user input, slowly get closer to logging out.
+	"""
+	def handle_get_selection_idle(self, event, params):
+		# don't care right now.
+		###
+		### State logging out ..
+		if self.vstatus.time_to_autologout != None:
+			time_left = self.vstatus.time_to_autologout - time()
+			if time_left < 6 and (self.vstatus.last_timeout_refresh is None or self.vstatus.last_timeout_refresh > time_left):
+				self.vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
+				self.vstatus.last_timeout_refresh = int(time_left)
+				self.vstatus.cur_selection = ''
+
+		if self.vstatus.time_to_autologout != None and self.vstatus.time_to_autologout - time() <= 0:
+			self.vstatus.time_to_autologout = None
+			self.vstatus.cur_user = ''
+			self.vstatus.cur_pin = ''
+			self.vstatus.cur_selection = ''
+			self._last_card_id = -1
+			self.reset_idler()
+
+		### State fully logged out ... reset variables
+		if self.vstatus.time_to_autologout and not self.vstatus.mk.done(): 
+			self.vstatus.time_to_autologout = None
+		if self.vstatus.cur_user == '' and self.vstatus.time_to_autologout: 
+			self.vstatus.time_to_autologout = None
+		
+		### State logged in
+		if len(self.vstatus.cur_pin) == PIN_LENGTH and self.vstatus.mk.done() and self.vstatus.time_to_autologout == None:
+			# start autologout
+			self.vstatus.time_to_autologout = time() + 15
+			self.vstatus.last_timeout_refresh = None
+
+		## FIXME - this may need to be elsewhere.....
+		# need to check
+		self.vstatus.mk.update_display()
+
+	"""
+	Triggered on user input while logged in.
+	"""
+	def handle_get_selection_key(self, event, params):
+		key = params
+		if len(self.vstatus.cur_selection) == 0:
+			if key == 11:
+				self.vstatus.cur_pin = ''
+				self.vstatus.cur_user = ''
+				self.vstatus.cur_selection = ''
+				_last_card_id = -1
+				self.vstatus.mk.set_messages([(self.center('BYE!'), False, 1.5)])
+				self.reset_idler(2)
+				return
+			self.vstatus.cur_selection += chr(key + ord('0'))
+			self.vstatus.mk.set_message('SELECT: '+self.vstatus.cur_selection)
+			self.vstatus.time_to_autologout = None
+		elif len(self.vstatus.cur_selection) == 1:
+			if key == 11:
+				self.vstatus.cur_selection = ''
+				self.vstatus.time_to_autologout = None
+				self.scroll_options(self.vstatus.username, self.vstatus.mk)
+				return
+			else:
+				self.vstatus.cur_selection += chr(key + ord('0'))
+				if self.vstatus.cur_user:
+					self.make_selection()
+					self.vstatus.cur_selection = ''
+					self.vstatus.time_to_autologout = time() + 8
+					self.vstatus.last_timeout_refresh = None
+				else:
+					# Price check mode.
+					self.price_check()
+					self.vstatus.cur_selection = ''
+					self.vstatus.time_to_autologout = None
+					self.vstatus.last_timeout_refresh = None
+
+	"""
+	Triggered when the user has entered the id of something they would like to purchase.
+	"""
+	def make_selection(self):
+		# should use sudo here
+		if self.vstatus.cur_selection == '55':
+			self.vstatus.mk.set_message('OPENSESAME')
+			logging.info('dispensing a door for %s'%self.vstatus.username)
+			if geteuid() == 0:
+				ret = os.system('dispense -u "%s" door'%self.vstatus.username)
+			else:
+				ret = os.system('dispense door')
+			if ret == 0:
+				logging.info('door opened')
+				self.vstatus.mk.set_message(self.center('DOOR OPEN'))
+			else:
+				logging.warning('user %s tried to dispense a bad door'%self.vstatus.username)
+				self.vstatus.mk.set_message(self.center('BAD DOOR'))
+			sleep(1)
+		elif self.vstatus.cur_selection == '81':
+			self.cookie()
+		elif self.vstatus.cur_selection == '99':
+			self.scroll_options(self.vstatus.username, self.vstatus.mk)
+			self.vstatus.cur_selection = ''
 			return
-		else:
-			vstatus.cur_selection += chr(key + ord('0'))
-			if vstatus.cur_user:
-				make_selection(v,vstatus)
-				vstatus.cur_selection = ''
-				vstatus.time_to_autologout = time() + 8
-				vstatus.last_timeout_refresh = None
+		elif self.vstatus.cur_selection[1] == '8':
+			self.v.display('GOT DRINK?')
+			if ((os.system('dispense -u "%s" coke:%s'%(self.vstatus.username, self.vstatus.cur_selection[0])) >> 8) != 0):
+				self.v.display('SEEMS NOT')
 			else:
-				# Price check mode.
-				price_check(v,vstatus)
-				vstatus.cur_selection = ''
-				vstatus.time_to_autologout = None
-				vstatus.last_timeout_refresh = None
-
-"""
-Triggered when the user has entered the id of something they would like to purchase.
-"""
-def make_selection(v, vstatus):
-	# should use sudo here
-	if vstatus.cur_selection == '55':
-		vstatus.mk.set_message('OPENSESAME')
-		logging.info('dispensing a door for %s'%vstatus.username)
-		if geteuid() == 0:
-			ret = os.system('dispense -u "%s" door'%vstatus.username)
-		else:
-			ret = os.system('dispense door')
-		if ret == 0:
-			logging.info('door opened')
-			vstatus.mk.set_message(center('DOOR OPEN'))
+				self.v.display('GOT DRINK!')
 		else:
-			logging.warning('user %s tried to dispense a bad door'%vstatus.username)
-			vstatus.mk.set_message(center('BAD DOOR'))
-		sleep(1)
-	elif vstatus.cur_selection == '81':
-		cookie(v)
-	elif vstatus.cur_selection == '99':
-		scroll_options(vstatus.username, vstatus.mk)
-		vstatus.cur_selection = ''
-		return
-	elif vstatus.cur_selection[1] == '8':
-		v.display('GOT DRINK?')
-		if ((os.system('dispense -u "%s" coke:%s'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
-			v.display('SEEMS NOT')
-		else:
-			v.display('GOT DRINK!')
-	else:
-		# first see if it's a named slot
-		try:
-			price, shortname, name = get_snack( vstatus.cur_selection )
-		except:
-			price, shortname, name = get_snack( '--' )
-		dollarprice = "$%.2f" % ( price / 100.0 )
-		v.display(vstatus.cur_selection+' - %s'%dollarprice)
-		exitcode = os.system('dispense -u "%s" snack:%s'%(vstatus.username, vstatus.cur_selection)) >> 8
-		if (exitcode == 0):
-			# magic dispense syslog service
-			syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, vstatus.cur_selection, vstatus.username))
-			(worked, code, string) = v.vend(vstatus.cur_selection)
-			if worked:
-				v.display('THANK YOU')
+			# first see if it's a named slot
+			try:
+				price, shortname, name = get_snack( self.vstatus.cur_selection )
+			except:
+				price, shortname, name = get_snack( '--' )
+			dollarprice = "$%.2f" % ( price / 100.0 )
+			self.v.display(self.vstatus.cur_selection+' - %s'%dollarprice)
+			exitcode = os.system('dispense -u "%s" snack:%s'%(self.vstatus.username, self.vstatus.cur_selection)) >> 8
+			if (exitcode == 0):
+				# magic dispense syslog service
+				syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, self.vstatus.cur_selection, self.vstatus.username))
+				(worked, code, string) = self.v.vend(self.vstatus.cur_selection)
+				if worked:
+					self.v.display('THANK YOU')
+				else:
+					print "Vend Failed:", code, string
+					self.v.display('VEND FAIL')
+			elif (exitcode == 5):	# RV_BALANCE
+				self.v.display('NO MONEY?')
+			elif (exitcode == 4):	# RV_ARGUMENTS (zero give causes arguments)
+				self.v.display('EMPTY SLOT')
+			elif (exitcode == 1):	# RV_BADITEM (Dead slot)
+				self.v.display('EMPTY SLOT')
 			else:
-				print "Vend Failed:", code, string
-				v.display('VEND FAIL')
-		elif (exitcode == 5):	# RV_BALANCE
-			v.display('NO MONEY?')
-		elif (exitcode == 4):	# RV_ARGUMENTS (zero give causes arguments)
-			v.display('EMPTY SLOT')
-		elif (exitcode == 1):	# RV_BADITEM (Dead slot)
-			v.display('EMPTY SLOT')
-		else:
-			syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, vstatus.cur_selection, vstatus.username, exitcode))
-			v.display('UNK ERROR')
-	sleep(1)
-
-"""
-Find the price of an item.
-"""
-def price_check(v, vstatus):
-	if vstatus.cur_selection[1] == '8':
-		args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0])
-		info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
-		dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1)
-	else:
-		# first see if it's a named slot
-		try:
-			price, shortname, name = get_snack( vstatus.cur_selection )
-		except:
-			price, shortname, name = get_snack( '--' )
-		dollarprice = "$%.2f" % ( price / 100.0 )
-	v.display(vstatus.cur_selection+' - %s'%dollarprice)
-
-"""
-Triggered when the user presses a button while entering their pin.
-"""
-def handle_getting_pin_key(state, event, params, v, vstatus):
-	#print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
-	key = params
-	if len(vstatus.cur_pin) < PIN_LENGTH:
-		if key == 11:
-			if vstatus.cur_pin == '':
-				vstatus.cur_user = ''
-				reset_idler(v, vstatus)
+				syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, self.vstatus.cur_selection, self.vstatus.username, exitcode))
+				self.v.display('UNK ERROR')
+		sleep(1)
 
+	"""
+	Find the price of an item.
+	"""
+	def price_check():
+		if self.vstatus.cur_selection[1] == '8':
+			args = ('dispense', 'iteminfo', 'coke:' + self.vstatus.cur_selection[0])
+			info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
+			dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1)
+		else:
+			# first see if it's a named slot
+			try:
+				price, shortname, name = get_snack( self.vstatus.cur_selection )
+			except:
+				price, shortname, name = get_snack( '--' )
+			dollarprice = "$%.2f" % ( price / 100.0 )
+		self.v.display(self.vstatus.cur_selection+' - %s'%dollarprice)
+
+	"""
+	Triggered when the user presses a button while entering their pin.
+	"""
+	def handle_getting_pin_key(self, event, params):
+		key = params
+		if len(self.vstatus.cur_pin) < PIN_LENGTH:
+			if key == 11:
+				if self.vstatus.cur_pin == '':
+					self.vstatus.cur_user = ''
+					slef.reset_idler()
+
+					return
+				self.vstatus.cur_pin = ''
+				self.vstatus.mk.set_message('PIN: ')
 				return
-			vstatus.cur_pin = ''
-			vstatus.mk.set_message('PIN: ')
+			self.vstatus.cur_pin += chr(key + ord('0'))
+			self.vstatus.mk.set_message('PIN: '+'X'*len(self.vstatus.cur_pin))
+			if len(self.vstatus.cur_pin) == PIN_LENGTH:
+				self.vstatus.username = self.verify_user_pin(int(self.vstatus.cur_user), int(self.vstatus.cur_pin))
+				if self.vstatus.username:
+					self.v.beep(0, False)
+					self.vstatus.cur_selection = ''
+					self.vstatus.change_state(STATE_GET_SELECTION)
+					self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
+					return
+				else:
+					self.v.beep(40, False)
+					self.vstatus.mk.set_messages(
+						[(self.center('BAD PIN'), False, 1.0),
+						 (self.center('SORRY'), False, 0.5)])
+					self.vstatus.cur_user = ''
+					self.vstatus.cur_pin = ''
+				
+					self.reset_idler(2)
+
+					return
+
+	"""
+	Triggered when the user presses a button while entering their user id.
+	"""
+	def handle_getting_uid_key(self, event, params):
+		key = params
+		# complicated key handling here:
+
+		if len(self.vstatus.cur_user) == 0 and key == 9:
+			self.vstatus.cur_selection = ''
+			self.vstatus.time_to_autologout = None
+			self.vstatus.mk.set_message('PRICECHECK')
+			sleep(0.5)
+			self.scroll_options('', vstatus.mk)
+			self.vstatus.change_state(STATE_GET_SELECTION)
 			return
-		vstatus.cur_pin += chr(key + ord('0'))
-		vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
-		if len(vstatus.cur_pin) == PIN_LENGTH:
-			vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
-			if vstatus.username:
-				v.beep(0, False)
-				vstatus.cur_selection = ''
-				vstatus.change_state(STATE_GET_SELECTION)
-				scroll_options(vstatus.username, vstatus.mk, True)
+
+		if len(self.vstatus.cur_user) <8:
+			if key == 11:
+				self.vstatus.cur_user = ''
+
+				self.reset_idler()
 				return
+			self.vstatus.cur_user += chr(key + ord('0'))
+			#logging.info('dob: '+vstatus.cur_user)
+			if len(self.vstatus.cur_user) > 5:
+				self.vstatus.mk.set_message('>'+self.vstatus.cur_user)
 			else:
-				v.beep(40, False)
-				vstatus.mk.set_messages(
-					[(center('BAD PIN'), False, 1.0),
-					 (center('SORRY'), False, 0.5)])
-				vstatus.cur_user = ''
-				vstatus.cur_pin = ''
-			
-				reset_idler(v, vstatus, 2)
+				self.vstatus.mk.set_message('UID: '+self.vstatus.cur_user)
+		
+		if len(self.vstatus.cur_user) == 5:
+			uid = int(self.vstatus.cur_user)
 
-				return
+			if uid == 0:
+				logging.info('user '+self.vstatus.cur_user+' has a bad PIN')
+				pfalken="""
+	CARRIER DETECTED
 
-"""
-Triggered when the user presses a button while entering their user id.
-"""
-def handle_getting_uid_key(state, event, params, v, vstatus):
-	#print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
-	key = params
-
-	# complicated key handling here:
-
-	if len(vstatus.cur_user) == 0 and key == 9:
-		vstatus.cur_selection = ''
-		vstatus.time_to_autologout = None
-		vstatus.mk.set_message('PRICECHECK')
-		sleep(0.5)
-		scroll_options('', vstatus.mk)
-		vstatus.change_state(STATE_GET_SELECTION)
-		return
-
-	if len(vstatus.cur_user) <8:
-		if key == 11:
-			vstatus.cur_user = ''
+	CONNECT 128000
 
-			reset_idler(v, vstatus)
-			return
-		vstatus.cur_user += chr(key + ord('0'))
-		#logging.info('dob: '+vstatus.cur_user)
-		if len(vstatus.cur_user) > 5:
-			vstatus.mk.set_message('>'+vstatus.cur_user)
-		else:
-			vstatus.mk.set_message('UID: '+vstatus.cur_user)
-	
-	if len(vstatus.cur_user) == 5:
-		uid = int(vstatus.cur_user)
+	Welcome to Picklevision Sytems, Sunnyvale, CA
 
-		if uid == 0:
-			logging.info('user '+vstatus.cur_user+' has a bad PIN')
-			pfalken="""
-CARRIER DETECTED
+	Greetings Professor Falken.
 
-CONNECT 128000
 
-Welcome to Picklevision Sytems, Sunnyvale, CA
 
-Greetings Professor Falken.
 
+	Shall we play a game?
 
 
+	Please choose from the following menu:
 
-Shall we play a game?
+	1. Tic-Tac-Toe
+	2. Chess
+	3. Checkers
+	4. Backgammon
+	5. Poker
+	6. Toxic and Biochemical Warfare
+	7. Global Thermonuclear War
 
+	7 [ENTER]
 
-Please choose from the following menu:
+	Wouldn't you prefer a nice game of chess?
 
-1. Tic-Tac-Toe
-2. Chess
-3. Checkers
-4. Backgammon
-5. Poker
-6. Toxic and Biochemical Warfare
-7. Global Thermonuclear War
+	""".replace('\n','    ')
+				self.vstatus.mk.set_messages([(pfalken, False, 10)])
+				self.vstatus.cur_user = ''
+				self.vstatus.cur_pin = ''
+				
+				self.reset_idler(10)
 
-7 [ENTER]
+				return
 
-Wouldn't you prefer a nice game of chess?
+			if not self.has_good_pin(uid):
+				logging.info('user '+self.vstatus.cur_user+' has a bad PIN')
+				self.vstatus.mk.set_messages(
+					[(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
+				self.vstatus.cur_user = ''
+				self.vstatus.cur_pin = ''
+				
+				self.reset_idler(3)
 
-""".replace('\n','    ')
-			vstatus.mk.set_messages([(pfalken, False, 10)])
-			vstatus.cur_user = ''
-			vstatus.cur_pin = ''
+				return
 			
-			reset_idler(v, vstatus, 10)
+			if self.acct_is_disabled():
+				logging.info('user '+self.vstatus.cur_user+' is disabled')
+				self.vstatus.mk.set_messages(
+					[(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)])
+				self.vstatus.cur_user = ''
+				self.vstatus.cur_pin = ''
+				
+				self.reset_idler(3)
+				return
 
-			return
 
-		if not has_good_pin(uid):
-			logging.info('user '+vstatus.cur_user+' has a bad PIN')
-			vstatus.mk.set_messages(
-				[(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
-			vstatus.cur_user = ''
-			vstatus.cur_pin = ''
-			
-			reset_idler(v, vstatus, 3)
+			self.vstatus.cur_pin = ''
+			self.vstatus.mk.set_message('PIN: ')
+			logging.info('need pin for user %s'%self.vstatus.cur_user)
+			self.vstatus.change_state(STATE_GETTING_PIN)
+			return
 
+	"""
+	Triggered when a key is pressed and the machine is idling.
+	"""
+	def handle_idle_key(self, event, params):
+		key = params
+		if key == 11:
+			self.vstatus.cur_user = ''
+			self.reset_idler()
 			return
 		
-		if acct_is_disabled():
-			logging.info('user '+vstatus.cur_user+' is disabled')
-			vstatus.mk.set_messages(
-				[(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)])
-			vstatus.cur_user = ''
-			vstatus.cur_pin = ''
-			
-			reset_idler(v, vstatus, 3)
-			return
-
-
-		vstatus.cur_pin = ''
-		vstatus.mk.set_message('PIN: ')
-		logging.info('need pin for user %s'%vstatus.cur_user)
-		vstatus.change_state(STATE_GETTING_PIN)
-		return
-
-"""
-Triggered when a key is pressed and the machine is idling.
-"""
-def handle_idle_key(state, event, params, v, vstatus):
-	#print "handle_idle_key (s,e,p)", state, " ", event, " ", params
-
-	key = params
-
-	if key == 11:
-		vstatus.cur_user = ''
-		reset_idler(v, vstatus)
-		return
-	
-	vstatus.change_state(STATE_GETTING_UID)
-	run_handler(event, key, v, vstatus)
-
-"""
-What to do when there is nothing to do.
-"""
-def handle_idle_tick(state, event, params, v, vstatus):
-	### State idling
-	if vstatus.mk.done():
-		idle_step(vstatus)
-
-	if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
-		vstatus.time_of_next_idler = time() + 30
-		choose_idler()
-	
-	###
-
-	vstatus.mk.update_display()
-
-	vstatus.change_state(STATE_GRANDFATHER_CLOCK)
-	run_handler(event, params, v, vstatus)
-	sleep(0.05)
-
-"""
-Manages the beeps for the grandfather clock
-"""
-def beep_on(when, before=0):
-	start = int(when - before)
-	end = int(when)
-	now = int(time())
-
-	if now >= start and now <= end:
-		return 1
-	return 0
-
-def handle_idle_grandfather_tick(state, event, params, v, vstatus):
-	### 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]])
-
-	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]])
-
-	## check for X seconds to the hour
-	## if case, update counter to 2
-	if beep_on(onthehour,15) \
-		or beep_on(halfhour,0) \
-		or beep_on(quarterhour,0) \
-		or beep_on(threequarterhour,0) \
-		or beep_on(fivetothehour,0):
-		vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
-		run_handler(event, params, v, vstatus)
-	else:
-		vstatus.change_state(STATE_IDLE)
+		self.vstatus.change_state(STATE_GETTING_UID)
+		self.run_handler(event, params)
+
+	"""
+	What to do when there is nothing to do.
+	"""
+	def handle_idle_tick(self, event, params):
+		### State idling
+		if self.vstatus.mk.done():
+			self.idle_step()
+
+		if self.vstatus.time_of_next_idler and time() > self.vstatus.time_of_next_idler:
+			self.vstatus.time_of_next_idler = time() + 30
+			self.choose_idler()
+		
+		###
 
-def handle_grandfather_tick(state, event, params, v, vstatus):
-	go_idle = 1
+		self.vstatus.mk.update_display()
 
-	msg = []
-	### we live in interesting times
-	now = localtime()
+		self.vstatus.change_state(STATE_GRANDFATHER_CLOCK)
+		self.run_handler(event, params)
+		sleep(0.05)
 
-	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]])
+	"""
+	Manages the beeps for the grandfather clock
+	"""
+	def beep_on(self, when, before=0):
+		start = int(when - before)
+		end = int(when)
+		now = int(time())
 
-	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]])
+		if now >= start and now <= end:
+			return 1
+		return 0
 
+	def handle_idle_grandfather_tick(self, event, params):
+		### check for interesting times
+		now = localtime()
 
-	#print "when it fashionable to wear a onion on your hip"
+		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]])
 
-	if beep_on(onthehour,15):
-		go_idle = 0
-		next_hour=((hourfromnow[3] + 11) % 12) + 1
-		if onthehour - time() < next_hour and onthehour - time() > 0:
-			v.beep(0, False)
+		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]])
+
+		## check for X seconds to the hour
+		## if case, update counter to 2
+		if self.beep_on(onthehour,15) \
+			or self.beep_on(halfhour,0) \
+			or self.beep_on(quarterhour,0) \
+			or self.beep_on(threequarterhour,0) \
+			or self.beep_on(fivetothehour,0):
+			self.vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
+			self.run_handler(event, params)
+		else:
+			self.vstatus.change_state(STATE_IDLE)
 
-			t = int(time())
-			if (t % 2) == 0:
-				msg.append(("DING!", False, None))
-			else:
-				msg.append(("     DING!", False, None))
-		elif int(onthehour - time()) == 0:
-			v.beep(255, False)
-			msg.append(("   BONG!", False, None))
-			msg.append(("     IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
-	elif beep_on(halfhour,0):
-		go_idle = 0
-		v.beep(0, False)
-		msg.append((" HALFHOUR ", False, 50))
-	elif beep_on(quarterhour,0):
-		go_idle = 0
-		v.beep(0, False)
-		msg.append((" QTR HOUR ", False, 50))
-	elif beep_on(threequarterhour,0):
-		go_idle = 0
-		v.beep(0, False)
-		msg.append((" 3 QTR HR ", False, 50))
-	elif beep_on(fivetothehour,0):
-		go_idle = 0
-		v.beep(0, False)
-		msg.append(("Quick run to your lectures!  Hurry! Hurry!", False, TEXT_SPEED*4))
-	else:
+	def handle_grandfather_tick(self, event, params):
 		go_idle = 1
-	
-	## check for X seconds to the hour
-
-	if len(msg):
-		vstatus.mk.set_messages(msg)
-		sleep(1)
-
-	vstatus.mk.update_display()
-	## if no longer case, return to idle
 
-	## change idler to be clock
-	if go_idle and vstatus.mk.done():
-		vstatus.change_state(STATE_IDLE,1)
-
-"""
-What to do when the door is open.
-"""
-def handle_door_idle(state, event, params, v, vstatus):
-	def twiddle(clock,v,wise = 2):
-		if (clock % 4 == 0):
-			v.display("-FEED  ME-")
-		elif (clock % 4 == 1+wise):
-			v.display("\\FEED  ME/")
-		elif (clock % 4 == 2):
-			v.display("-FEED  ME-")
-		elif (clock % 4 == 3-wise):
-			v.display("/FEED  ME\\")
-
-	# don't care right now.
-	now = int(time())
-
-	if ((now % 60 % 2) == 0):
-		twiddle(now, v)
-	else:
-		twiddle(now, v, wise=0)
+		msg = []
+		### we live in interesting times
+		now = localtime()
 
-"""
-What to do when the door is opened or closed.
-"""
-def handle_door_event(state, event, params, v, vstatus):
-	if params == 0:  #door open
-		vstatus.change_state(STATE_DOOR_OPENING)
-		logging.warning("Entering open door mode")
-		v.display("-FEED  ME-")
-		#door_open_mode(v);
-		vstatus.cur_user = ''
-		vstatus.cur_pin = ''
-	elif params == 1:  #door closed
-		vstatus.change_state(STATE_DOOR_CLOSING)
-		reset_idler(v, vstatus, 3)
-
-		logging.warning('Leaving open door mode')
-		v.display("-YUM YUM!-")
+		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]])
 
-"""
-Triggered when a user swipes their caed, and the machine is logged out.
-"""
-def handle_mifare_event(state, event, params, v, vstatus):
-	global _last_card_id
-	card_id = params
-	# Translate card_id into uid.
-	if card_id == None or card_id == _last_card_id:
-		return
-
-	_last_card_id = card_id
-	
-	try:
-		vstatus.cur_user = get_uid(card_id)
-		logging.info('Mapped card id to uid %s'%vstatus.cur_user)
-		vstatus.username = get_uname(vstatus.cur_user)
-		if acct_is_disabled(vstatus.username):
-			vstatus.username = '-disabled-'
-	except ValueError:
-		vstatus.username = None
-	if vstatus.username == '-disabled-':
-		v.beep(40, False)
-		vstatus.mk.set_messages(
-			[(center('ACCT DISABLED'), False, 1.0),
-			 (center('SORRY'), False, 0.5)])
-		vstatus.cur_user = ''
-		vstatus.cur_pin = ''
-		vstatus.username = None
-	
-		reset_idler(v, vstatus, 2)
-		return
-	elif vstatus.username:
-		v.beep(0, False)
-		vstatus.cur_selection = ''
-		vstatus.change_state(STATE_GET_SELECTION)
-		scroll_options(vstatus.username, vstatus.mk, True)
-		return
-	else:
-		v.beep(40, False)
-		vstatus.mk.set_messages(
-			[(center('BAD CARD'), False, 1.0),
-			 (center('SORRY'), False, 0.5)])
-		vstatus.cur_user = ''
-		vstatus.cur_pin = ''
-		_last_card_id = -1
-	
-		reset_idler(v, vstatus, 2)
-		return
+		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]])
 
-"""
-Triggered when a user swipes their card and the machine is logged in.
-"""
-def handle_mifare_add_user_event(state, event, params, v, vstatus):
-	global _last_card_id
-	card_id = params
 
-	# Translate card_id into uid.
-	if card_id == None or card_id == _last_card_id:
-		return
+		#print "when it fashionable to wear a onion on your hip"
 
-	_last_card_id = card_id
-	
-	try:
-		if get_uid(card_id) != None:
-			vstatus.mk.set_messages(
-				[(center('ALREADY'), False, 0.5),
-				 (center('ENROLLED'), False, 0.5)])
+		if self.beep_on(onthehour,15):
+			go_idle = 0
+			next_hour=((hourfromnow[3] + 11) % 12) + 1
+			if onthehour - time() < next_hour and onthehour - time() > 0:
+				self.v.beep(0, False)
 
-			# scroll_options(vstatus.username, vstatus.mk)
+				t = int(time())
+				if (t % 2) == 0:
+					msg.append(("DING!", False, None))
+				else:
+					msg.append(("     DING!", False, None))
+			elif int(onthehour - time()) == 0:
+				self.v.beep(255, False)
+				msg.append(("   BONG!", False, None))
+				msg.append(("     IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
+		elif self.beep_on(halfhour,0):
+			go_idle = 0
+			self.v.beep(0, False)
+			msg.append((" HALFHOUR ", False, 50))
+		elif self.beep_on(quarterhour,0):
+			go_idle = 0
+			self.v.beep(0, False)
+			msg.append((" QTR HOUR ", False, 50))
+		elif self.beep_on(threequarterhour,0):
+			go_idle = 0
+			self.v.beep(0, False)
+			msg.append((" 3 QTR HR ", False, 50))
+		elif self.beep_on(fivetothehour,0):
+			go_idle = 0
+			self.v.beep(0, False)
+			msg.append(("Quick run to your lectures!  Hurry! Hurry!", False, TEXT_SPEED*4))
+		else:
+			go_idle = 1
+		
+		## check for X seconds to the hour
+
+		if len(msg):
+			self.vstatus.mk.set_messages(msg)
+			sleep(1)
+
+		self.vstatus.mk.update_display()
+		## if no longer case, return to idle
+
+		## change idler to be clock
+		if go_idle and self.vstatus.mk.done():
+			self.vstatus.change_state(STATE_IDLE,1)
+
+	"""
+	What to do when the door is open.
+	"""
+	def handle_door_idle(self, event, params):
+		def twiddle(clock,v,wise = 2):
+			if (clock % 4 == 0):
+				v.display("-FEED  ME-")
+			elif (clock % 4 == 1+wise):
+				v.display("\\FEED  ME/")
+			elif (clock % 4 == 2):
+				v.display("-FEED  ME-")
+			elif (clock % 4 == 3-wise):
+				v.display("/FEED  ME\\")
+
+		# don't care right now.
+		now = int(time())
+
+		if ((now % 60 % 2) == 0):
+			twiddle(now, self.v)
+		else:
+			twiddle(now, self.v, wise=0)
+
+	"""
+	What to do when the door is opened or closed.
+	"""
+	def handle_door_event(self, event, params):
+		if params == 0:  #door open
+			self.vstatus.change_state(STATE_DOOR_OPENING)
+			logging.warning("Entering open door mode")
+			self.v.display("-FEED  ME-")
+			#door_open_mode(v);
+			self.vstatus.cur_user = ''
+			self.vstatus.cur_pin = ''
+		elif params == 1:  #door closed
+			self.vstatus.change_state(STATE_DOOR_CLOSING)
+			self.reset_idler(3)
+
+			logging.warning('Leaving open door mode')
+			self.v.display("-YUM YUM!-")
+
+	"""
+	Triggered when a user swipes their caed, and the machine is logged out.
+	"""
+	def handle_mifare_event(self, event, params):
+		card_id = params
+		# Translate card_id into uid.
+		if card_id == None or card_id == self._last_card_id:
 			return
-	except ValueError:
-		pass
 
-	logging.info('Enrolling card %s to uid %s (%s)'%(card_id, vstatus.cur_user, vstatus.username))
-	set_card_id(vstatus.cur_user, card_id)
-	vstatus.mk.set_messages(
-		[(center('CARD'), False, 0.5),
-		 (center('ENROLLED'), False, 0.5)])
-
-	# scroll_options(vstatus.username, vstatus.mk)
-
-def return_to_idle(state,event,params,v,vstatus):
-	reset_idler(v, vstatus)
-
-"""
-Maps what to do when the state changes.
-"""
-def create_state_table(vstatus):
-	vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
-	vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
-	vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_IDLE,MIFARE,1)] = handle_mifare_event
-
-	vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
-	vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
-	vstatus.state_table[(STATE_DOOR_OPENING,MIFARE,1)] = do_nothing
-
-	vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
-	vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
-	vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = do_nothing
-
-	vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
-	vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
-	vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = handle_mifare_event
-
-	vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
-	vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
-	vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = handle_mifare_event
-
-	vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
-	vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
-	vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = handle_mifare_add_user_event
-
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = handle_door_event
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = handle_door_event
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
-	vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = handle_mifare_event
-
-"""
-Get what to do on a state change.
-"""
-def get_state_table_handler(vstatus, state, event, counter):
-	return vstatus.state_table[(state,event,counter)]
-
-def time_to_next_update(vstatus):
-	idle_update = vstatus.time_of_next_idlestep - time()
-	if not vstatus.mk.done() and vstatus.mk.next_update is not None:
-		mk_update = vstatus.mk.next_update - time()
-		if mk_update < idle_update:
-			idle_update = mk_update
-	return idle_update
-
-def run_forever(rfh, wfh, options, cf):
-	v = VendingMachine(rfh, wfh, USE_MIFARE)
-	vstatus = VendState(v)
-	create_state_table(vstatus)
-
-	logging.debug('PING is ' + str(v.ping()))
-
-	if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
-
-	setup_idlers(v)
-	reset_idler(v, vstatus)
+		self._last_card_id = card_id
+		
+		try:
+			self.vstatus.cur_user = get_uid(card_id)
+			logging.info('Mapped card id to uid %s'%vstatus.cur_user)
+			self.vstatus.username = get_uname(vstatus.cur_user)
+			if self.acct_is_disabled(self.vstatus.username):
+				self.vstatus.username = '-disabled-'
+		except ValueError:
+			self.vstatus.username = None
+		if self.vstatus.username == '-disabled-':
+			self.v.beep(40, False)
+			self.vstatus.mk.set_messages(
+				[(self.center('ACCT DISABLED'), False, 1.0),
+				 (self.center('SORRY'), False, 0.5)])
+			self.vstatus.cur_user = ''
+			self.vstatus.cur_pin = ''
+			self.vstatus.username = None
+		
+			self.reset_idler(2)
+			return
+		elif self.vstatus.username:
+			self.v.beep(0, False)
+			self.vstatus.cur_selection = ''
+			self.vstatus.change_state(STATE_GET_SELECTION)
+			self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
+			return
+		else:
+			self.v.beep(40, False)
+			self.vstatus.mk.set_messages(
+				[(self.center('BAD CARD'), False, 1.0),
+				 (self.center('SORRY'), False, 0.5)])
+			self.vstatus.cur_user = ''
+			self.vstatus.cur_pin = ''
+			self._last_card_id = -1
+		
+			self.reset_idler(2)
+			return
 
-	while True:
-		if USE_DB:
-			try:
-				db.handle_events()
-			except DispenseDatabaseException, e:
-				logging.error('Database error: '+str(e))
+	"""
+	Triggered when a user swipes their card and the machine is logged in.
+	"""
+	def handle_mifare_add_user_event(self, evnt, params):
+		card_id = params
 
-		timeout = time_to_next_update(vstatus)
-		e = v.next_event(timeout)
-		(event, params) = e
+		# Translate card_id into uid.
+		if card_id == None or card_id == self._last_card_id:
+			return
 
-		run_handler(event, params, v, vstatus)
+		self._last_card_id = card_id
+		
+		try:
+			if get_uid(card_id) != None:
+				self.vstatus.mk.set_messages(
+					[(self.center('ALREADY'), False, 0.5),
+					 (self.center('ENROLLED'), False, 0.5)])
 
-def run_handler(event, params, v, vstatus):
-	handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
-	if handler:
-		handler(vstatus.state, event, params, v, vstatus)
+				# scroll_options(vstatus.username, vstatus.mk)
+				return
+		except ValueError:
+			pass
+
+		logging.info('Enrolling card %s to uid %s (%s)'%(card_id, vstatus.cur_user, vstatus.username))
+		self.set_card_id(self.vstatus.cur_user, card_id)
+		self.vstatus.mk.set_messages(
+			[(self.center('CARD'), False, 0.5),
+			 (self.center('ENROLLED'), False, 0.5)])
+
+		# scroll_options(vstatus.username, vstatus.mk)
+
+	def return_to_idle(self, event, params):
+		self.reset_idler()
+
+	"""
+	Maps what to do when the state changes.
+	"""
+	def create_state_table(self):
+		self.vstatus.state_table[(STATE_IDLE,TICK,1)] = self.handle_idle_tick
+		self.vstatus.state_table[(STATE_IDLE,KEY,1)] = self.handle_idle_key
+		self.vstatus.state_table[(STATE_IDLE,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_IDLE,MIFARE,1)] = self.handle_mifare_event
+
+		self.vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = self.handle_door_idle
+		self.vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = self.do_nothing
+		self.vstatus.state_table[(STATE_DOOR_OPENING,MIFARE,1)] = self.do_nothing
+
+		self.vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = self.return_to_idle
+		self.vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = self.do_nothing
+		self.vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = self.do_nothing
+
+		self.vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = self.handle_getting_uid_idle
+		self.vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = self.handle_getting_uid_key
+		self.vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = self.handle_mifare_event
+
+		self.vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = self.handle_getting_pin_idle
+		self.vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = self.handle_getting_pin_key
+		self.vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = self.handle_mifare_event
+
+		self.vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = self.handle_get_selection_idle
+		self.vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = self.handle_get_selection_key
+		self.vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = self.handle_mifare_add_user_event
+
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = self.handle_idle_grandfather_tick
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = self.handle_grandfather_tick
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = self.handle_door_event
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = self.do_nothing
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = self.do_nothing
+		self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = self.handle_mifare_event
+
+	"""
+	Get what to do on a state change.
+	"""
+	def get_state_table_handler(self, state, event, counter):
+		return self.vstatus.state_table[(state,event,counter)]
+
+	def time_to_next_update(self):
+		idle_update = self.vstatus.time_of_next_idlestep - time()
+		if not self.vstatus.mk.done() and self.vstatus.mk.next_update is not None:
+			mk_update = self.vstatus.mk.next_update - time()
+			if mk_update < idle_update:
+				idle_update = mk_update
+		return idle_update
+
+	def run_forever(self, rfh, wfh, options, cf):
+		self.v = VendingMachine(rfh, wfh, USE_MIFARE)
+		self.vstatus = VendState(self.v)
+		self.create_state_table()
+
+		logging.debug('PING is ' + str(self.v.ping()))
+
+		if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
+
+		self.setup_idlers()
+		self.reset_idler()
+
+		while True:
+			if USE_DB:
+				try:
+					db.handle_events()
+				except DispenseDatabaseException, e:
+					logging.error('Database error: '+str(e))
+
+			timeout = self.time_to_next_update()
+			(event, params) = self.v.next_event(timeout)
+			self.run_handler(event, params)
+
+	def run_handler(self, event, params):
+		handler = self.get_state_table_handler(self.vstatus.state,event,self.vstatus.counter)
+		if handler:
+			handler(event, params)
 
 """
 Connect to the machine.
@@ -1099,11 +1085,11 @@ def connect_to_vend(options, cf):
 		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()
-	elif options.use_serial:
-		# Open vending machine via serial.
-		logging.info('Connecting to vending machine using serial')
-		serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
-		rfh,wfh = serialclient.get_fh()
+	#elif options.use_serial:
+	#	# Open vending machine via serial.
+	#	logging.info('Connecting to vending machine using serial')
+	#	serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
+	#	rfh,wfh = serialclient.get_fh()
 	else:
 		#(rfh, wfh) = popen2('../../virtualvend/vvend.py')
 		logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
@@ -1124,8 +1110,8 @@ def parse_args():
 	from optparse import OptionParser
 
 	op = OptionParser(usage="%prog [OPTION]...")
-	op.add_option('-f', '--config-file', default='/etc/dispense2/servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf')
-	op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
+	op.add_option('-f', '--config-file', default='./servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf')
+	op.add_option('--serial', action='store_true', default=False, dest='use_serial', help='use the serial port')
 	op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
 	op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
 	op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
@@ -1230,7 +1216,8 @@ def do_vend_server(options, config_opts):
 #		run_forever(rfh, wfh, options, config_opts)
 		
 		try:
-			run_forever(rfh, wfh, options, config_opts)
+			vserver = VendServer()
+			vserver.run_forever(rfh, wfh, options, config_opts)
 		except VendingException:
 			logging.error("Connection died, trying again...")
 			logging.info("Trying again in 5 seconds.")
-- 
GitLab