# 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 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
+from LDAPConnector import get_uid, set_card_id
CREDITS="""
This vending machine software brought to you by:
"""
-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
def scroll_options(username, mk, welcome = False):
if welcome:
+ # Balance checking: crap code, [DAA]'s fault
+ acct = os.popen('dispense acct %s' % username)
+ balance = acct.read()[15:22]
+ acct.close()
+
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: '
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))
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:
def has_good_pin(uid):
return get_pin(uid) != 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 get_pin(uid) == pin:
info = pwd.getpwuid(uid)
- logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
+ if skip_pin_check:
+ 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))
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),
]
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):
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):
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():
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()
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)
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
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?')
+ 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 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):
+ # 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)) >> 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))
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 price_check(v, vstatus):
+ if vstatus.cur_selection[1] == '8':
+ v.display(center('SEE COKE'))
+ 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
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 = ''
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
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 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)
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.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())
+
+ if ((now % 60 % 2) == 0):
+ twiddle(now, v)
+ else:
+ twiddle(now, v, wise=0)
-def handle_door_event(state, event, params, v, vstatus):
- vstatus.time_to_idle = None
- 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 = verify_user_pin(int(vstatus.cur_user), None, True)
+ except ValueError:
+ vstatus.username = None
+ if vstatus.username:
+ v.beep(0, False)
+ vstatus.cur_selection = ''
+ vstatus.change_state(STATE_GET_SELECTION)
+ scroll_options(vstatus.username, vstatus.mk, True)
return
- if not vstatus.time_to_idle:
- vstatus.mk.set_message(GREETING)
- vstatus.change_state(STATE_IDLE)
+ 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
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)
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
#
# ( return state - not currently implemented )
- vstatus.change_state(STATE_IDLE,1)
-
while True:
if USE_DB:
try:
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)
sock.connect((options.host, options.port))
rfh = sock.makefile('r')
wfh = sock.makefile('w')
+ global USE_MIFARE
+ USE_MIFARE = 0
return rfh, wfh