subprocess.Popen, new pin code, disabled users, bugfixes
[uccvend-vendserver.git] / sql-edition / servers / VendServer.py
index 22958bd..2a9a55a 100755 (executable)
@@ -2,23 +2,26 @@
 # vim:ts=4
 
 USE_DB = 0
+USE_MIFARE = 1
 
 import ConfigParser
-import sys, os, string, re, pwd, signal, math
+import sys, os, string, re, pwd, signal, math, syslog
 import logging, logging.handlers
 from traceback import format_tb
 if USE_DB: import pg
 from time import time, sleep, mktime, localtime
-from popen2 import popen2
+from subprocess import Popen, PIPE
 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 Idler import TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
+from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
+from SnackConfig import get_snack#, get_snacks
 import socket
 from posix import geteuid
+from LDAPConnector import get_uid,get_uname, set_card_id
 
 CREDITS="""
 This vending machine software brought to you by:
@@ -36,25 +39,27 @@ For a good time call +61 8 6488 3901
 
 """
 
-GREETING = 'UCC SNACKS'
 PIN_LENGTH = 4
 
 DOOR = 1
 SWITCH = 2
 KEY = 3
 TICK = 4
+MIFARE = 5
 
 
-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
+(
+STATE_IDLE,
+STATE_DOOR_OPENING,
+STATE_DOOR_CLOSING,
+STATE_GETTING_UID,
+STATE_GETTING_PIN,
+STATE_GET_SELECTION,
+STATE_GRANDFATHER_CLOCK,
+) = range(1,8)
 
 TEXT_SPEED = 0.8
-IDLE_SPEED = 0.02
+IDLE_SPEED = 0.05
 
 class DispenseDatabaseException(Exception): pass
 
@@ -89,65 +94,122 @@ class DispenseDatabase:
 
 def scroll_options(username, mk, welcome = False):
        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 = [(center('WELCOME'), False, TEXT_SPEED),
-                          (center(username), False, TEXT_SPEED)]
+                          (center(username), False, TEXT_SPEED),
+                          (center(balance), False, TEXT_SPEED),]
        else:
                msg = []
        choices = ' '*10+'CHOICES: '
-       try:
-               coke_machine = file('/home/other/coke/coke_contents')
-               cokes = coke_machine.readlines()
-               coke_machine.close()
-       except:
-               cokes = []
-               pass
+
+       # Get coke contents
+       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 += '%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 += 'OR A SNACK. '
+       choices += 'OR ANOTHER SNACK. '
        choices += '99 TO READ AGAIN. '
        choices += 'CHOICE?   '
        msg.append((choices, False, None))
        mk.set_messages(msg)
 
-def get_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
-       return int(pinstr)
+_pin_uid = 0
+_pin_uname = 'root'
+_pin_pin = '----'
+
+def _check_pin(uid, pin):
+       global _pin_uid
+       global _pin_uname
+       global _pin_pin
+       print "_check_pin('",uid,"',---)"
+       if uid != _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
+               _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)
+
+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
 
 def has_good_pin(uid):
-       return get_pin(uid) != None
+       return _check_pin(uid, None) != None
 
-def verify_user_pin(uid, pin):
-       if get_pin(uid) == 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)
-               logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
+               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))
+               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))
@@ -197,47 +259,62 @@ def setup_idlers(v):
                 GrayIdler(v,one="/",zero="\\"),
                ClockIdler(v),
                 GrayIdler(v,one="X",zero="O"),
-               FileIdler(v, '/usr/share/common-licenses/GPL-2'),
+               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 - and order as Quinn - I am getting really hungry", repeat=False),
-               PipeIdler(v, "/usr/bin/ypcat", "passwd"),
+               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),
+               # "Hello World" in brainfuck
+               StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
                ]
        disabled = [
                ]
-       idler = choose_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)
 
 def choose_idler():
        global idlers, idler
        iiindex = 0
+       average_affinity = 10 # guessing here...
 
-       if idler:
+       if idler and idler.__class__ != GreetingIdler:
                iiindex = idlers.index(idler)
 
        iilen = len(idlers)
 
-       move = int(random()*len(idlers)) + 1
+       move = int(random()*len(idlers)*average_affinity) + 1
 
        while move >= 0:
-               idler = idlers[( (iiindex + 1) % iilen)]
-               move = move - idler.affinity()
+               iiindex += 1
+               iiindex %= iilen
+               idler = idlers[iiindex]
+               move -= idler.affinity()
 
        idler.reset()
 
-def idle_step():
+def idle_step(vstatus):
        global idler
        if idler.finished():
                choose_idler()
-       sleep(IDLE_SPEED)
-       idler.next()
+               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):
@@ -252,8 +329,6 @@ class VendState:
                self.cur_selection = ''
                self.time_to_autologout = None
 
-               self.time_to_idle = None
-
                self.last_timeout_refresh = None
 
        def change_state(self,newstate,newcounter=None):
@@ -311,10 +386,7 @@ def handle_get_selection_idle(state, event, params, v, vstatus):
                vstatus.cur_pin = ''
                vstatus.cur_selection = ''
                        
-               idle_in(vstatus,2)
-               vstatus.change_state(STATE_IDLE)
-
-               vstatus.mk.set_message(GREETING)
+               reset_idler(v, vstatus)
 
        ### State fully logged out ... reset variables
        if vstatus.time_to_autologout and not vstatus.mk.done(): 
@@ -328,12 +400,6 @@ def handle_get_selection_idle(state, event, params, v, vstatus):
                vstatus.time_to_autologout = time() + 15
                vstatus.last_timeout_refresh = None
 
-       ### State logged out ... after normal logout??
-       # perhaps when logged in?
-       if vstatus.time_to_idle is not None and vstatus.cur_user != '': 
-               vstatus.time_to_idle = None
-
-
        ## FIXME - this may need to be elsewhere.....
        # need to check
        vstatus.mk.update_display()
@@ -348,12 +414,8 @@ def handle_get_selection_key(state, event, params, v, vstatus):
                        vstatus.cur_user = ''
                        vstatus.cur_selection = ''
                        
-                       idle_in(vstatus,2)
-                       vstatus.change_state(STATE_IDLE)
-
-                       vstatus.mk.set_messages(
-                               [(center('BYE!'), False, 1.5),
-                                (GREETING, False, None)])
+                       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)
@@ -366,10 +428,17 @@ def handle_get_selection_key(state, event, params, v, vstatus):
                        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
+                       if vstatus.cur_user:
+                               make_selection(v,vstatus)
+                               vstatus.cur_selection = ''
+                               vstatus.time_to_autologout = time() + 8
+                               vstatus.last_timeout_refresh = None
+                       else:
+                               # Price check mode.
+                               price_check(v,vstatus)
+                               vstatus.cur_selection = ''
+                               vstatus.time_to_autologout = None
+                               vstatus.last_timeout_refresh = None
 
 def make_selection(v, vstatus):
        # should use sudo here
@@ -377,7 +446,8 @@ def make_selection(v, vstatus):
                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)
+                       #ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
+                       ret = os.system('dispense -u "%s" door'%vstatus.username)
                else:
                        ret = os.system('dispense door')
                if ret == 0:
@@ -387,28 +457,65 @@ def make_selection(v, vstatus):
                        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':
+       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 COKE?')
-               if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
+               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 COKE!')
+                       v.display('GOT DRINK!')
        else:
-               v.display(vstatus.cur_selection+' - $1.00')
-               if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
-                       v.vend(vstatus.cur_selection)
-                       v.display('THANK YOU')
-               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" give \>snacksales %d "%s"'%(vstatus.username, price, name)) >> 8
+#              exitcode = os.system('dispense -u "%s" give \>sales\:snack %d "%s"'%(vstatus.username, price, name)) >> 8
+               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')
+                       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)
 
 
+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)
+
+
 def handle_getting_pin_key(state, event, params, v, vstatus):
        #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
        key = params
@@ -416,10 +523,7 @@ def handle_getting_pin_key(state, event, params, v, vstatus):
                if key == 11:
                        if vstatus.cur_pin == '':
                                vstatus.cur_user = ''
-                               vstatus.mk.set_message(GREETING)
-                       
-                               idle_in(vstatus,5)
-                               vstatus.change_state(STATE_IDLE)
+                               reset_idler(v, vstatus)
 
                                return
                        vstatus.cur_pin = ''
@@ -439,13 +543,11 @@ def handle_getting_pin_key(state, event, params, v, vstatus):
                                v.beep(40, False)
                                vstatus.mk.set_messages(
                                        [(center('BAD PIN'), False, 1.0),
-                                        (center('SORRY'), False, 0.5),
-                                        (GREETING, False, None)])
+                                        (center('SORRY'), False, 0.5)])
                                vstatus.cur_user = ''
                                vstatus.cur_pin = ''
                        
-                               idle_in(vstatus,5)
-                               vstatus.change_state(STATE_IDLE)
+                               reset_idler(v, vstatus, 2)
 
                                return
 
@@ -453,33 +555,94 @@ def handle_getting_pin_key(state, event, params, v, vstatus):
 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 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 = ''
-                       vstatus.mk.set_message(GREETING)
 
-                       idle_in(vstatus,5)
-                       vstatus.change_state(STATE_IDLE)
+                       reset_idler(v, vstatus)
                        return
-
                vstatus.cur_user += chr(key + ord('0'))
-               vstatus.mk.set_message('UID: '+vstatus.cur_user)
-
+               #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)
+
+               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'+' '*10, False, 3),
-                                (GREETING, False, None)])
+                               [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
                        vstatus.cur_user = ''
                        vstatus.cur_pin = ''
                        
-                       idle_in(vstatus,5)
-                       vstatus.change_state(STATE_IDLE)
+                       reset_idler(v, vstatus, 3)
 
                        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 = ''
@@ -496,9 +659,7 @@ def handle_idle_key(state, event, params, v, vstatus):
 
        if key == 11:
                vstatus.cur_user = ''
-               vstatus.mk.set_message(GREETING)
-               idle_in(vstatus,5)
-               choose_idler()
+               reset_idler(v, vstatus)
                return
        
        vstatus.change_state(STATE_GETTING_UID)
@@ -506,18 +667,12 @@ def handle_idle_key(state, event, params, v, vstatus):
 
 
 def handle_idle_tick(state, event, params, v, vstatus):
-       ### State logged out ... initiate idler in 5  (first start?)
-       if vstatus.time_to_idle == None and vstatus.cur_user == '':
-               vstatus.time_to_idle = time() + 5
-               choose_idler()
-
        ### State idling
+       if vstatus.mk.done():
+               idle_step(vstatus)
 
-       if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle: 
-               idle_step()
-
-       if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle + 30:
-               vstatus.time_to_idle = time()
+       if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
+               vstatus.time_of_next_idler = time() + 30
                choose_idler()
        
        ###
@@ -633,63 +788,141 @@ def handle_grandfather_tick(state, event, params, v, vstatus):
                vstatus.change_state(STATE_IDLE,1)
 
 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.
-       pass
+       now = int(time())
 
-def handle_door_event(state, event, params, v, vstatus):
-       vstatus.time_to_idle = None
+       if ((now % 60 % 2) == 0):
+               twiddle(now, v)
+       else:
+               twiddle(now, v, wise=0)
 
-       if params == 1:  #door open
+
+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 == 0:  #door closed
+       elif params == 1:  #door closed
                vstatus.change_state(STATE_DOOR_CLOSING)
-               idle_in(vstatus, 5)
+               reset_idler(v, vstatus, 3)
 
                logging.warning('Leaving open door mode')
                v.display("-YUM YUM!-")
 
-def idle_in(vstatus,seconds):
-       vstatus.time_to_idle = time() + seconds
+def handle_mifare_event(state, event, params, v, vstatus):
+       card_id = params
+       # Translate card_id into uid.
+       if card_id == None:
+               return
 
-def return_to_idle(state,event,params,v,vstatus):
-       if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle: 
-               vstatus.mk.set_message(GREETING)
-               vstatus.change_state(STATE_IDLE)
+       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
-       if not vstatus.time_to_idle:
-               vstatus.mk.set_message(GREETING)
-               vstatus.change_state(STATE_IDLE)
+       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 = ''
+       
+               reset_idler(v, vstatus, 2)
                return
 
+def handle_mifare_add_user_event(state, event, params, v, vstatus):
+       card_id = params
+
+       # Translate card_id into uid.
+       if card_id == None:
+               return
+
+       try:
+               if get_uid(card_id) != None:
+                       vstatus.mk.set_messages(
+                               [(center('ALREADY'), False, 0.5),
+                                (center('ENROLLED'), False, 0.5)])
+
+                       # 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))
+       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)
+
 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)] = do_nothing
        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)] = do_nothing
        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)] = do_nothing
        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
@@ -697,12 +930,21 @@ def create_state_table(vstatus):
        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
+       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = handle_mifare_event
 
 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)
+       v = VendingMachine(rfh, wfh, USE_MIFARE)
        vstatus = VendState(v)
        create_state_table(vstatus)
 
@@ -711,8 +953,7 @@ def run_forever(rfh, wfh, options, cf):
        if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
 
        setup_idlers(v)
-       choose_idler()
-       vstatus.mk.set_message(GREETING)
+       reset_idler(v, vstatus)
 
        # This main loop was hideous and the work of the devil.
        # This has now been fixed (mostly) - mtearle
@@ -725,8 +966,6 @@ def run_forever(rfh, wfh, options, cf):
        #
        # ( return state - not currently implemented )
 
-       vstatus.change_state(STATE_IDLE,1)
-
        while True:
                if USE_DB:
                        try:
@@ -734,8 +973,8 @@ def run_forever(rfh, wfh, options, cf):
                        except DispenseDatabaseException, e:
                                logging.error('Database error: '+str(e))
 
-
-               e = v.next_event(0)
+               timeout = time_to_next_update(vstatus)
+               e = v.next_event(timeout)
                (event, params) = e
 
                run_handler(event, params, v, vstatus)
@@ -767,6 +1006,8 @@ def connect_to_vend(options, cf):
                sock.connect((options.host, options.port))
                rfh = sock.makefile('r')
                wfh = sock.makefile('w')
+               global USE_MIFARE
+               USE_MIFARE = 0
                
        return rfh, wfh
 
@@ -774,7 +1015,7 @@ def parse_args():
        from optparse import OptionParser
 
        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('-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('--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')
@@ -905,6 +1146,8 @@ def do_vend_server(options, config_opts):
                        sleep(5)
                        continue
                
+#              run_forever(rfh, wfh, options, config_opts)
+               
                try:
                        run_forever(rfh, wfh, options, config_opts)
                except VendingException:

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