use "give oday $price" so we can prune dispense
[uccvend-vendserver.git] / sql-edition / servers / VendServer.py
index 8942855..0b45a40 100755 (executable)
@@ -4,18 +4,20 @@
 USE_DB = 0
 
 import ConfigParser
 USE_DB = 0
 
 import ConfigParser
-import sys, os, string, re, pwd, signal
+import sys, os, string, re, pwd, signal, math, syslog
 import logging, logging.handlers
 from traceback import format_tb
 if USE_DB: import pg
 import logging, logging.handlers
 from traceback import format_tb
 if USE_DB: import pg
-from time import time, sleep
+from time import time, sleep, mktime, localtime
 from popen2 import popen2
 from LATClient import LATClient, LATClientException
 from popen2 import popen2
 from LATClient import LATClient, LATClientException
+from SerialClient import SerialClient, SerialClientException
 from VendingMachine import VendingMachine, VendingException
 from MessageKeeper import MessageKeeper
 from HorizScroll import HorizScroll
 from random import random, seed
 from VendingMachine import VendingMachine, VendingException
 from MessageKeeper import MessageKeeper
 from HorizScroll import HorizScroll
 from random import random, seed
-from Idler import TrainIdler,GrayIdler,StringIdler
+from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
+from SnackConfig import get_snacks, get_snack
 import socket
 from posix import geteuid
 
 import socket
 from posix import geteuid
 
@@ -26,14 +28,33 @@ Mark Tearle
 Nick Bannon
 Cameron Patrick
 and a collective of hungry alpacas.
 Nick Bannon
 Cameron Patrick
 and a collective of hungry alpacas.
+
+
+
+For a good time call +61 8 6488 3901
+
+
+
 """
 
 """
 
-GREETING = 'UCC SNACKS'
 PIN_LENGTH = 4
 
 DOOR = 1
 SWITCH = 2
 KEY = 3
 PIN_LENGTH = 4
 
 DOOR = 1
 SWITCH = 2
 KEY = 3
+TICK = 4
+
+
+STATE_IDLE = 1
+STATE_DOOR_OPENING = 2
+STATE_DOOR_CLOSING = 3
+STATE_GETTING_UID = 4
+STATE_GETTING_PIN = 5
+STATE_GET_SELECTION = 6
+STATE_GRANDFATHER_CLOCK = 7
+
+TEXT_SPEED = 0.8
+IDLE_SPEED = 0.05
 
 class DispenseDatabaseException(Exception): pass
 
 
 class DispenseDatabaseException(Exception): pass
 
@@ -68,8 +89,8 @@ class DispenseDatabase:
 
 def scroll_options(username, mk, welcome = False):
        if welcome:
 
 def scroll_options(username, mk, welcome = False):
        if welcome:
-               msg = [(center('WELCOME'), False, 0.8),
-                          (center(username), False, 0.8)]
+               msg = [(center('WELCOME'), False, TEXT_SPEED),
+                          (center(username), False, TEXT_SPEED)]
        else:
                msg = []
        choices = ' '*10+'CHOICES: '
        else:
                msg = []
        choices = ' '*10+'CHOICES: '
@@ -84,9 +105,20 @@ def scroll_options(username, mk, welcome = False):
                c = c.strip()
                (slot_num, price, slot_name) = c.split(' ', 2)
                if slot_name == 'dead': continue
                c = c.strip()
                (slot_num, price, slot_name) = c.split(' ', 2)
                if slot_name == 'dead': continue
-               choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
+               choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
+
+#      we don't want to print snacks for now since it'll be too large
+#      and there's physical bits of paper in the machine anyway - matt
+#      try:
+#              snacks = get_snacks()
+#      except:
+#              snacks = {}
+#
+#      for slot, ( name, price ) in snacks.items():
+#              choices += '%s8-%s (%sc) ' % ( slot, name, price )
+
        choices += '55-DOOR '
        choices += '55-DOOR '
-       choices += 'OR A SNACK. '
+       choices += 'OR ANOTHER SNACK. '
        choices += '99 TO READ AGAIN. '
        choices += 'CHOICE?   '
        msg.append((choices, False, None))
        choices += '99 TO READ AGAIN. '
        choices += 'CHOICE?   '
        msg.append((choices, False, None))
@@ -106,8 +138,8 @@ def get_pin(uid):
                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 not found in home directory'%uid)
                return None
        if s.st_mode & 077:
-               logging.info('getting pin for uid %d: .pin has wrong permissions'%uid)
-               return None
+               logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
+               os.chmod(pinfile, 0600)
        try:
                f = file(pinfile)
        except IOError:
        try:
                f = file(pinfile)
        except IOError:
@@ -132,19 +164,6 @@ def verify_user_pin(uid, pin):
                logging.info('refused pin for uid %d'%(uid))
                return None
 
                logging.info('refused pin for uid %d'%(uid))
                return None
 
-def door_open_mode(v):
-       logging.warning("Entering open door mode")
-       v.display("-FEED  ME-")
-       while True:
-               e = v.next_event()
-               if e == None: break
-               (event, params) = e
-               if event == DOOR:
-                       if params == 1: # door closed
-                               logging.warning('Leaving open door mode')
-                               v.display("-YUM YUM!-")
-                               sleep(1)
-                               return
 
 def cookie(v):
        seed(time())
 
 def cookie(v):
        seed(time())
@@ -178,46 +197,570 @@ def center(str):
 
 idlers = []
 idler = None
 
 idlers = []
 idler = None
+
 def setup_idlers(v):
        global idlers, idler
        idlers = [
 def setup_idlers(v):
        global idlers, idler
        idlers = [
-               StringIdler(v),
+                GrayIdler(v),
+               StringIdler(v, text="Kill 'em all", repeat=False),
+                GrayIdler(v,one="*",zero="-"),
                StringIdler(v, text=CREDITS),
                StringIdler(v, text=CREDITS),
-               GrayIdler(v),
+                GrayIdler(v,one="/",zero="\\"),
+               ClockIdler(v),
+                GrayIdler(v,one="X",zero="O"),
+               FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
+                GrayIdler(v,one="*",zero="-",reorder=1),
+               StringIdler(v, text=str(math.pi) + "            "),
+               ClockIdler(v),
+                GrayIdler(v,one="/",zero="\\",reorder=1),
+               StringIdler(v, text=str(math.e) + "            "),
+                GrayIdler(v,one="X",zero="O",reorder=1),
+               StringIdler(v, text="    I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 [now closed? - MSH] - and order as Quinn - I am getting really hungry", repeat=False),
+               PipeIdler(v, "/usr/bin/getent", "passwd"),
+               FortuneIdler(v),
+               ClockIdler(v),
+               StringIdler(v),
                TrainIdler(v),
                TrainIdler(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),
                ]
                ]
-       idler = choose_idler()
+       disabled = [
+               ]
+
+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)
 
 def choose_idler():
 
 def choose_idler():
-       global idler
-       idler = idlers[int(random()*len(idlers))]
+       global idlers, idler
+       iiindex = 0
+       average_affinity = 10 # guessing here...
+
+       if idler and idler.__class__ != GreetingIdler:
+               iiindex = idlers.index(idler)
+
+       iilen = len(idlers)
+
+       move = int(random()*len(idlers)*average_affinity) + 1
+
+       while move >= 0:
+               iiindex += 1
+               iiindex %= iilen
+               idler = idlers[iiindex]
+               move -= idler.affinity()
+
        idler.reset()
 
        idler.reset()
 
-def idle_step():
+def idle_step(vstatus):
        global idler
        global idler
-       idler.next()
+       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
+
+class VendState:
+       def __init__(self,v):
+               self.state_table = {}
+               self.state = STATE_IDLE
+               self.counter = 0
+
+               self.mk = MessageKeeper(v)
+               self.cur_user = ''
+               self.cur_pin = ''
+               self.username = ''
+               self.cur_selection = ''
+               self.time_to_autologout = None
+
+               self.last_timeout_refresh = None
+
+       def change_state(self,newstate,newcounter=None):
+               if self.state != newstate:
+                       #print "Changing state from: ", 
+                       #print self.state,
+                       #print " to ", 
+                       #print newstate 
+                       self.state = newstate
+
+               if newcounter is not None and self.counter != newcounter:
+                       #print "Changing counter from: ", 
+                       #print self.counter,
+                       #print " to ", 
+                       #print newcounter 
+                       self.counter = newcounter
+
+
+
+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
+
+
+def do_nothing(state, event, params, v, vstatus):
+       print "doing nothing (s,e,p)", state, " ", event, " ", params
+       pass
+
+def handle_getting_uid_idle(state, event, params, v, vstatus):
+       # don't care right now.
+       pass
+
+def handle_getting_pin_idle(state, event, params, v, vstatus):
+       # don't care right now.
+       pass
+
+def handle_get_selection_idle(state, event, params, v, vstatus):
+       # 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 = ''
+                       
+               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
+
+       ## FIXME - this may need to be elsewhere.....
+       # need to check
+       vstatus.mk.update_display()
+
+
+
+def handle_get_selection_key(state, event, params, v, vstatus):
+       key = params
+       if len(vstatus.cur_selection) == 0:
+               if key == 11:
+                       vstatus.cur_pin = ''
+                       vstatus.cur_user = ''
+                       vstatus.cur_selection = ''
+                       
+                       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)
+                       return
+               else:
+                       vstatus.cur_selection += chr(key + ord('0'))
+                       make_selection(v,vstatus)
+                       vstatus.cur_selection = ''
+                       vstatus.time_to_autologout = time() + 8
+                       vstatus.last_timeout_refresh = None
+
+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('su - "%s" -c "dispense door"'%vstatus.username)
+               else:
+                       ret = os.system('dispense door')
+               if ret == 0:
+                       logging.info('door opened')
+                       vstatus.mk.set_message(center('DOOR OPEN'))
+               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 == '91':
+               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('su - "%s" -c "dispense %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('su - "%s" -c "dispense give oday %d"'%(vstatus.username, price))
+               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))
+                       v.vend(vstatus.cur_selection)
+                       v.display('THANK YOU')
+               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('NO MONEY?')
+       sleep(1)
+
+
+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)
+
+                               return
+                       vstatus.cur_pin = ''
+                       vstatus.mk.set_message('PIN: ')
+                       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)
+                               return
+                       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)
+
+                               return
+
+
+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) < 5:
+               if key == 11:
+                       vstatus.cur_user = ''
+
+                       reset_idler(v, vstatus)
+                       return
+
+               vstatus.cur_user += chr(key + ord('0'))
+               vstatus.mk.set_message('UID: '+vstatus.cur_user)
+
+       if len(vstatus.cur_user) == 5:
+               uid = int(vstatus.cur_user)
+               if uid == 0:
+                       logging.info('user '+vstatus.cur_user+' has a bad PIN')
+                       pfalken="""
+CARRIER DETECTED
+
+CONNECT 128000
+
+Welcome to Picklevision Sytems, Sunnyvale, CA
+
+Greetings Professor Falken.
+
+
+
+
+Shall we play a game?
+
+
+Please choose from the following menu:
+
+1. Tic-Tac-Toe
+2. Chess
+3. Checkers
+4. Backgammon
+5. Poker
+6. Toxic and Biochemical Warfare
+7. Global Thermonuclear War
+
+7 [ENTER]
+
+Wouldn't you prefer a nice game of chess?
+
+""".replace('\n','    ')
+                       vstatus.mk.set_messages([(pfalken, False, 10)])
+                       vstatus.cur_user = ''
+                       vstatus.cur_pin = ''
+                       
+                       reset_idler(v, vstatus, 10)
+
+                       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)
+
+                       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
+
+
+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)
+
+
+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)
+
+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)
+
+def handle_grandfather_tick(state, event, params, v, vstatus):
+       go_idle = 1
+
+       msg = []
+       ### we live in 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]])
+
+
+       #print "when it fashionable to wear a onion on your hip"
+
+       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)
+
+                       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:
+               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)
+
+def handle_door_idle(state, event, params, v, vstatus):
+       # don't care right now.
+       pass
+
+def handle_door_event(state, event, params, v, vstatus):
+       if params == 1:  #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 == 0:  #door closed
+               vstatus.change_state(STATE_DOOR_CLOSING)
+               reset_idler(v, vstatus, 3)
+
+               logging.warning('Leaving open door mode')
+               v.display("-YUM YUM!-")
+
+def return_to_idle(state,event,params,v,vstatus):
+       reset_idler(v, vstatus)
+
+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_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_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_GETTING_UID,TICK,1)] = handle_getting_uid_idle
+       vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
+       vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
+
+       vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
+       vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
+       vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
+
+       vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
+       vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
+       vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
+
+       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)] = do_nothing
+       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
+       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
+       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
+
+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)
 
 def run_forever(rfh, wfh, options, cf):
        v = VendingMachine(rfh, wfh)
+       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)
        logging.debug('PING is ' + str(v.ping()))
 
        if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
-       cur_user = ''
-       cur_pin = ''
-       cur_selection = ''
 
 
-       mk = MessageKeeper(v)
-       mk.set_message(GREETING)
-       time_to_autologout = None
        setup_idlers(v)
        setup_idlers(v)
-       time_to_idle = None
-       last_timeout_refresh = None
+       reset_idler(v, vstatus)
+
+       # This main loop was hideous and the work of the devil.
+       # This has now been fixed (mostly) - mtearle
+       #
+       #
+       # notes for later surgery
+       #   (event, counter, ' ')
+       #        V
+       #   d[      ] = (method)
+       #
+       # ( return state - not currently implemented )
 
        while True:
                if USE_DB:
 
        while True:
                if USE_DB:
@@ -226,170 +769,32 @@ def run_forever(rfh, wfh, options, cf):
                        except DispenseDatabaseException, e:
                                logging.error('Database error: '+str(e))
 
                        except DispenseDatabaseException, e:
                                logging.error('Database error: '+str(e))
 
-               if time_to_autologout != None:
-                       time_left = time_to_autologout - time()
-                       if time_left < 6 and (last_timeout_refresh is None or last_timeout_refresh > time_left):
-                               mk.set_message('LOGOUT: '+str(int(time_left)))
-                               last_timeout_refresh = int(time_left)
-                               cur_selection = ''
-
-               if time_to_autologout != None and time_to_autologout - time() <= 0:
-                       time_to_autologout = None
-                       cur_user = ''
-                       cur_pin = ''
-                       cur_selection = ''
-                       mk.set_message(GREETING)
-
-               if time_to_autologout and not mk.done(): time_to_autologout = None
-               if cur_user == '' and time_to_autologout: time_to_autologout = None
-               if len(cur_pin) == PIN_LENGTH and mk.done() and time_to_autologout == None:
-                       # start autologout
-                       time_to_autologout = time() + 15
-
-               if time_to_idle == None and cur_user == '':
-                       time_to_idle = time() + 5
-                       choose_idler()
-               if time_to_idle is not None and cur_user != '': time_to_idle = None
-               if time_to_idle is not None and time() > time_to_idle: idle_step()
-               if time_to_idle is not None and time() > time_to_idle + 300:
-                       time_to_idle = time()
-                       choose_idler()
-
-               mk.update_display()
-
-               e = v.next_event(0)
-               if e == None:
-                       e = v.next_event(0.05)
-                       if e == None:
-                               continue
-               time_to_idle = None
+
+               timeout = time_to_next_update(vstatus)
+               e = v.next_event(timeout)
                (event, params) = e
                (event, params) = e
-               logging.debug('Got event: ' + repr(e))
-               if event == DOOR:
-                       if params == 0:
-                               door_open_mode(v);
-                               cur_user = ''
-                               cur_pin = ''
-                               mk.set_message(GREETING)
-               elif event == SWITCH:
-                       # don't care right now.
-                       pass
-               elif event == KEY:
-                       key = params
-                       # complicated key handling here:
-                       if len(cur_user) < 5:
-                               if key == 11:
-                                       cur_user = ''
-                                       mk.set_message(GREETING)
-                                       continue
-                               cur_user += chr(key + ord('0'))
-                               mk.set_message('UID: '+cur_user)
-                               if len(cur_user) == 5:
-                                       uid = int(cur_user)
-                                       if not has_good_pin(uid):
-                                               logging.info('user '+cur_user+' has a bad PIN')
-                                               #mk.set_messages(
-                                                       #[(center('INVALID'), False, 0.7),
-                                                        #(center('PIN'), False, 0.7),
-                                                        #(center('SETUP'), False, 1.0),
-                                                        #(GREETING, False, None)])
-                                               mk.set_messages(
-                                                       [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
-                                                        (GREETING, False, None)])
-                                               cur_user = ''
-                                               cur_pin = ''
-                                               continue
-                                       cur_pin = ''
-                                       mk.set_message('PIN: ')
-                                       logging.info('need pin for user %s'%cur_user)
-                                       continue
-                       elif len(cur_pin) < PIN_LENGTH:
-                               if key == 11:
-                                       if cur_pin == '':
-                                               cur_user = ''
-                                               mk.set_message(GREETING)
-                                               continue
-                                       cur_pin = ''
-                                       mk.set_message('PIN: ')
-                                       continue
-                               cur_pin += chr(key + ord('0'))
-                               mk.set_message('PIN: '+'X'*len(cur_pin))
-                               if len(cur_pin) == PIN_LENGTH:
-                                       username = verify_user_pin(int(cur_user), int(cur_pin))
-                                       if username:
-                                               v.beep(0, False)
-                                               cur_selection = ''
-                                               scroll_options(username, mk, True)
-                                               continue
-                                       else:
-                                               v.beep(40, False)
-                                               mk.set_messages(
-                                                       [(center('BAD PIN'), False, 1.0),
-                                                        (center('SORRY'), False, 0.5),
-                                                        (GREETING, False, None)])
-                                               cur_user = ''
-                                               cur_pin = ''
-                                               continue
-                       elif len(cur_selection) == 0:
-                               if key == 11:
-                                       cur_pin = ''
-                                       cur_user = ''
-                                       cur_selection = ''
-                                       mk.set_messages(
-                                               [(center('BYE!'), False, 1.5),
-                                                (GREETING, False, None)])
-                                       continue
-                               cur_selection += chr(key + ord('0'))
-                               mk.set_message('SELECT: '+cur_selection)
-                               time_to_autologout = None
-                       elif len(cur_selection) == 1:
-                               if key == 11:
-                                       cur_selection = ''
-                                       time_to_autologout = None
-                                       scroll_options(username, mk)
-                                       continue
-                               else:
-                                       cur_selection += chr(key + ord('0'))
-                                       #make_selection(cur_selection)
-                                       # XXX this should move somewhere else:
-                                       if cur_selection == '55':
-                                               mk.set_message('OPENSESAME')
-                                               logging.info('dispensing a door for %s'%username)
-                                               if geteuid() == 0:
-                                                       ret = os.system('su - "%s" -c "dispense door"'%username)
-                                               else:
-                                                       ret = os.system('dispense door')
-                                               if ret == 0:
-                                                       logging.info('door opened')
-                                                       mk.set_message(center('DOOR OPEN'))
-                                               else:
-                                                       logging.warning('user %s tried to dispense a bad door'%username)
-                                                       mk.set_message(center('BAD DOOR'))
-                                               sleep(1)
-                                       elif cur_selection == '91':
-                                               cookie(v)
-                                       elif cur_selection == '99':
-                                               scroll_options(username, mk)
-                                               cur_selection = ''
-                                               continue
-                                       elif cur_selection[1] == '8':
-                                               v.display('GOT COKE?')
-                                               os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0]))
-                                       else:
-                                               v.display('HERES A '+cur_selection)
-                                               v.vend(cur_selection)
-                                       sleep(0.5)
-                                       v.display('THANK YOU')
-                                       sleep(0.5)
-                                       cur_selection = ''
-                                       time_to_autologout = time() + 8
+
+               run_handler(event, params, v, vstatus)
+
+#              logging.debug('Got event: ' + repr(e))
+
+
+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)
 
 def connect_to_vend(options, cf):
 
 def connect_to_vend(options, cf):
-       # Open vending machine via LAT?
+
        if 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()
        if 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()
+       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))
        else:
                #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
                logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
@@ -406,7 +811,9 @@ def parse_args():
 
        op = OptionParser(usage="%prog [OPTION]...")
        op.add_option('-f', '--config-file', default='/etc/dispense/servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf')
 
        op = OptionParser(usage="%prog [OPTION]...")
        op.add_option('-f', '--config-file', default='/etc/dispense/servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf')
-       op.add_option('--virtualvend', action='store_false', default=True, dest='use_lat', help='use the virtual vending server instead of LAT')
+       op.add_option('--serial', action='store_true', default=True, 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)')
        op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
        op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
        op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
        op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
        op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
@@ -526,7 +933,7 @@ def do_vend_server(options, config_opts):
        while True:
                try:
                        rfh, wfh = connect_to_vend(options, config_opts)
        while True:
                try:
                        rfh, wfh = connect_to_vend(options, config_opts)
-               except (LATClientException, socket.error), e:
+               except (SerialClientException, socket.error), e:
                        (exc_type, exc_value, exc_traceback) = sys.exc_info()
                        del exc_traceback
                        logging.error("Connection error: "+str(exc_type)+" "+str(e))
                        (exc_type, exc_value, exc_traceback) = sys.exc_info()
                        del exc_traceback
                        logging.error("Connection error: "+str(exc_type)+" "+str(e))

UCC git Repository :: git.ucc.asn.au