item pricing works now
[uccvend-vendserver.git] / sql-edition / servers / VendServer.py
index 2adc4e1..9792c56 100755 (executable)
@@ -8,7 +8,7 @@ import sys, os, string, re, pwd, signal, math
 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 SerialClient import SerialClient, SerialClientException
@@ -16,7 +16,8 @@ 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_snacks, get_snack
 import socket
 from posix import geteuid
 
@@ -36,7 +37,6 @@ For a good time call +61 8 6488 3901
 
 """
 
-GREETING = 'UCC SNACKS'
 PIN_LENGTH = 4
 
 DOOR = 1
@@ -51,6 +51,10 @@ 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
 
@@ -85,8 +89,8 @@ class DispenseDatabase:
 
 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: '
@@ -102,8 +106,19 @@ def scroll_options(username, mk, welcome = False):
                (slot_num, price, slot_name) = c.split(' ', 2)
                if slot_name == 'dead': continue
                choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
+
+#      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))
@@ -123,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 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:
@@ -193,7 +208,7 @@ 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),
@@ -201,7 +216,7 @@ def setup_idlers(v):
                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"),
+               PipeIdler(v, "/usr/bin/getent", "passwd"),
                FortuneIdler(v),
                ClockIdler(v),
                StringIdler(v),
@@ -209,30 +224,43 @@ def setup_idlers(v):
                ]
        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.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()
-       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):
@@ -247,10 +275,23 @@ 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):
+               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):
@@ -291,10 +332,7 @@ def handle_get_selection_idle(state, event, params, v, vstatus):
                vstatus.cur_pin = ''
                vstatus.cur_selection = ''
                        
-               idle_in(vstatus,2)
-               vstatus.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(): 
@@ -308,12 +346,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()
@@ -328,12 +360,8 @@ def handle_get_selection_key(state, event, params, v, vstatus):
                        vstatus.cur_user = ''
                        vstatus.cur_selection = ''
                        
-                       idle_in(vstatus,2)
-                       vstatus.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)
@@ -380,8 +408,14 @@ def make_selection(v, vstatus):
                else:
                        v.display('GOT COKE!')
        else:
-               v.display(vstatus.cur_selection+' - $1.00')
-               if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
+               # 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)
+               if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, shortname)) >> 8) == 0):
                        v.vend(vstatus.cur_selection)
                        v.display('THANK YOU')
                else:
@@ -396,10 +430,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.state = STATE_IDLE
+                               reset_idler(v, vstatus)
 
                                return
                        vstatus.cur_pin = ''
@@ -412,20 +443,18 @@ def handle_getting_pin_key(state, event, params, v, vstatus):
                        if vstatus.username:
                                v.beep(0, False)
                                vstatus.cur_selection = ''
-                               vstatus.state = STATE_GET_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),
-                                        (GREETING, False, None)])
+                                        (center('SORRY'), False, 0.5)])
                                vstatus.cur_user = ''
                                vstatus.cur_pin = ''
                        
-                               idle_in(vstatus,5)
-                               vstatus.state = STATE_IDLE
+                               reset_idler(v, vstatus, 2)
 
                                return
 
@@ -437,10 +466,8 @@ def handle_getting_uid_key(state, event, params, v, vstatus):
        if len(vstatus.cur_user) < 5:
                if key == 11:
                        vstatus.cur_user = ''
-                       vstatus.mk.set_message(GREETING)
 
-                       idle_in(vstatus,5)
-                       vstatus.state = STATE_IDLE
+                       reset_idler(v, vstatus)
                        return
 
                vstatus.cur_user += chr(key + ord('0'))
@@ -448,16 +475,54 @@ def handle_getting_uid_key(state, event, params, v, vstatus):
 
        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.state = STATE_IDLE
+                       reset_idler(v, vstatus, 3)
 
                        return
 
@@ -465,7 +530,7 @@ def handle_getting_uid_key(state, event, params, v, vstatus):
                vstatus.cur_pin = ''
                vstatus.mk.set_message('PIN: ')
                logging.info('need pin for user %s'%vstatus.cur_user)
-               vstatus.state = STATE_GETTING_PIN
+               vstatus.change_state(STATE_GETTING_PIN)
                return
 
 
@@ -476,68 +541,155 @@ 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.state = STATE_GETTING_UID
+       vstatus.change_state(STATE_GETTING_UID)
        run_handler(event, key, 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()
        
        ###
 
        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):
-       vstatus.time_to_idle = None
-
        if params == 1:  #door open
-               vstatus.state = STATE_DOOR_OPENING
+               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.state = STATE_DOOR_CLOSING
-               idle_in(vstatus, 5)
+               vstatus.change_state(STATE_DOOR_CLOSING)
+               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 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.state = STATE_IDLE
-               return
-       if not vstatus.time_to_idle:
-               vstatus.mk.set_message(GREETING)
-               vstatus.state = STATE_IDLE
-               return
+       reset_idler(v, vstatus)
 
 def create_state_table(vstatus):
        vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
@@ -564,9 +716,24 @@ def create_state_table(vstatus):
        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)
        vstatus = VendState(v)
@@ -576,11 +743,8 @@ def run_forever(rfh, wfh, options, cf):
 
        if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
 
-       vstatus.mk.set_message(GREETING)
        setup_idlers(v)
-       choose_idler()
-       vstatus.mk.set_message("Booted")
-
+       reset_idler(v, vstatus)
 
        # This main loop was hideous and the work of the devil.
        # This has now been fixed (mostly) - mtearle
@@ -601,10 +765,10 @@ def run_forever(rfh, wfh, options, cf):
                                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
 
-               vstatus.counter=1
                run_handler(event, params, v, vstatus)
 
 #              logging.debug('Got event: ' + repr(e))

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