X-Git-Url: https://git.ucc.asn.au/?p=uccvend-vendserver.git;a=blobdiff_plain;f=VendServer%2FVendServer.py;h=860c1c8456d5a7dbf24f8b4c8dd9284965ccda37;hp=2a9a55af613028cd85d9b3a0e32aa0fc9a056b6f;hb=060a73740d793c8f1643554829c90a0dd442ca79;hpb=6c130dc942392ccc8b7ffa0ec2ede5f9327f99e6 diff --git a/VendServer/VendServer.py b/VendServer/VendServer.py index 2a9a55a..860c1c8 100755 --- a/VendServer/VendServer.py +++ b/VendServer/VendServer.py @@ -22,6 +22,7 @@ from SnackConfig import get_snack#, get_snacks import socket from posix import geteuid from LDAPConnector import get_uid,get_uname, set_card_id +from OpenDispense import OpenDispense as Dispense CREDITS=""" This vending machine software brought to you by: @@ -31,7 +32,11 @@ Nick Bannon Cameron Patrick and a collective of hungry alpacas. +The MIFARE card reader bought to you by: +David Adam +Bug Hunting and hardware maintenance by: +Mitchell Pomery For a good time call +61 8 6488 3901 @@ -61,6 +66,43 @@ STATE_GRANDFATHER_CLOCK, TEXT_SPEED = 0.8 IDLE_SPEED = 0.05 +_pin_uid = 0 +_pin_uname = 'root' +_pin_pin = '----' + +_last_card_id = -1 + +idlers = [] +idler = None + +config_options = { + 'DBServer': ('Database', 'Server'), + 'DBName': ('Database', 'Name'), + 'DBUser': ('VendingMachine', 'DBUser'), + 'DBPassword': ('VendingMachine', 'DBPassword'), + + 'ServiceName': ('VendingMachine', 'ServiceName'), + 'ServicePassword': ('VendingMachine', 'Password'), + + 'ServerName': ('DecServer', 'Name'), + 'ConnectPassword': ('DecServer', 'ConnectPassword'), + 'PrivPassword': ('DecServer', 'PrivPassword'), + } + +class VendConfigFile: + def __init__(self, config_file, options): + try: + cp = ConfigParser.ConfigParser() + cp.read(config_file) + + for option in options: + section, name = options[option] + value = cp.get(section, name) + self.__dict__[option] = value + + except ConfigParser.Error, e: + raise SystemExit("Error reading config file "+config_file+": " + str(e)) + class DispenseDatabaseException(Exception): pass class DispenseDatabase: @@ -91,8 +133,36 @@ class DispenseDatabase: while notifier is not None: self.process_requests() notify = self.db.getnotify() +""" +This class manages the current state of the vending machine. +""" +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: + self.state = newstate + + if newcounter is not None and self.counter != newcounter: + self.counter = newcounter +""" +Show information to the user as to what can be dispensed. +""" def scroll_options(username, mk, welcome = False): + # If the user has just logged in, show them their balance if welcome: # Balance checking acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate() @@ -106,7 +176,7 @@ def scroll_options(username, mk, welcome = False): msg = [] choices = ' '*10+'CHOICES: ' - # Get coke contents + # Show what is in the coke machine cokes = [] for i in range(0, 7): args = ('dispense', 'iteminfo', 'coke:%i' % i) @@ -121,27 +191,19 @@ def scroll_options(username, mk, welcome = False): if slot_name == 'dead': continue 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 ) - + # Show the final few options choices += '55-DOOR ' choices += 'OR ANOTHER SNACK. ' choices += '99 TO READ AGAIN. ' choices += 'CHOICE? ' msg.append((choices, False, None)) + # Send it to the display mk.set_messages(msg) -_pin_uid = 0 -_pin_uname = 'root' -_pin_pin = '----' +""" +Verify the users pin +""" def _check_pin(uid, pin): global _pin_uid global _pin_uname @@ -184,6 +246,9 @@ def _check_pin(uid, pin): logging.info("Pin incorrect for %d",uid) return pin == int(pinstr) +""" +Check if the users account has been disabled +""" def acct_is_disabled(name=None): global _pin_uname if name == None: @@ -197,9 +262,15 @@ def acct_is_disabled(name=None): return True return False +""" +Check that the user has a valid pin set +""" def has_good_pin(uid): return _check_pin(uid, None) != None +""" +Verify the users pin. +""" def verify_user_pin(uid, pin, skip_pin_check=False): if skip_pin_check or _check_pin(uid, pin) == True: info = pwd.getpwuid(uid) @@ -215,7 +286,9 @@ def verify_user_pin(uid, pin, skip_pin_check=False): logging.info('refused pin for uid %d'%(uid)) return None - +""" +In here just for fun. +""" def cookie(v): seed(time()) messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY'] @@ -240,44 +313,54 @@ def cookie(v): s += msg[i] v.display(s) +""" +Format text so it will appear centered on the screen. +""" def center(str): LEN = 10 return ' '*((LEN-len(str))/2)+str - - -idlers = [] -idler = None - +""" +Configure the things that will appear on screen whil the machine is idling. +""" def setup_idlers(v): global idlers, idler idlers = [ - GrayIdler(v), + # + GrayIdler(v), + GrayIdler(v,one="*",zero="-"), + GrayIdler(v,one="/",zero="\\"), + GrayIdler(v,one="X",zero="O"), + GrayIdler(v,one="*",zero="-",reorder=1), + GrayIdler(v,one="/",zero="\\",reorder=1), + GrayIdler(v,one="X",zero="O",reorder=1), + # + ClockIdler(v), + ClockIdler(v), + ClockIdler(v), + # + StringIdler(v), # Hello Cruel World StringIdler(v, text="Kill 'em all", repeat=False), - GrayIdler(v,one="*",zero="-"), StringIdler(v, text=CREDITS), - 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), + StringIdler(v, text=" I want some pizza - please call Broadway Pizza on +61 8 9389 8500 - and order as Quinn - I am getting really hungry", repeat=False), # "Hello World" in brainfuck StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."), + # + TrainIdler(v), + # + FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2), + # + PipeIdler(v, "/usr/bin/getent", "passwd"), + FortuneIdler(v,affinity=20), ] disabled = [ ] +""" +Go back to the default idler. +""" def reset_idler(v, vstatus, t = None): global idlers, idler idler = GreetingIdler(v, t) @@ -286,26 +369,41 @@ def reset_idler(v, vstatus, t = None): vstatus.time_to_autologout = None vstatus.change_state(STATE_IDLE, 1) +""" +Change to a random idler. +""" def choose_idler(): 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() + # Implementation of the King Of the Hill algorithm from; + # http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/ + + #def weighted_choice_king(weights): + # total = 0 + # winner = 0 + # for i, w in enumerate(weights): + # total += w + # if random.random() * total < w: + # winner = i + # return winner + # + + total = 0 + winner = None + + for choice in idlers: + weight = choice.affinity() + total += weight + if random() * total < weight: + winner = choice - idler.reset() + idler = winner + if idler: + idler.reset() +""" +Run every step while the machine is idling. +""" def idle_step(vstatus): global idler if idler.finished(): @@ -316,38 +414,9 @@ def idle_step(vstatus): 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 - - - +""" +These next two events trigger no response in the code. +""" def handle_tick_event(event, params, v, vstatus): # don't care right now. pass @@ -356,11 +425,16 @@ def handle_switch_event(event, params, v, vstatus): # don't care right now. pass - +""" +Don't do anything for this event. +""" def do_nothing(state, event, params, v, vstatus): print "doing nothing (s,e,p)", state, " ", event, " ", params pass +""" +These next few entrie tell us to do nothing while we are idling +""" def handle_getting_uid_idle(state, event, params, v, vstatus): # don't care right now. pass @@ -369,7 +443,11 @@ def handle_getting_pin_idle(state, event, params, v, vstatus): # don't care right now. pass +""" +While logged in and waiting for user input, slowly get closer to logging out. +""" def handle_get_selection_idle(state, event, params, v, vstatus): + global _last_card_id # don't care right now. ### ### State logging out .. @@ -385,7 +463,7 @@ def handle_get_selection_idle(state, event, params, v, vstatus): vstatus.cur_user = '' vstatus.cur_pin = '' vstatus.cur_selection = '' - + _last_card_id = -1 reset_idler(v, vstatus) ### State fully logged out ... reset variables @@ -404,16 +482,18 @@ def handle_get_selection_idle(state, event, params, v, vstatus): # need to check vstatus.mk.update_display() - - +""" +Triggered on user input while logged in. +""" def handle_get_selection_key(state, event, params, v, vstatus): + global _last_card_id key = params if len(vstatus.cur_selection) == 0: if key == 11: vstatus.cur_pin = '' vstatus.cur_user = '' vstatus.cur_selection = '' - + _last_card_id = -1 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)]) reset_idler(v, vstatus, 2) return @@ -440,13 +520,15 @@ def handle_get_selection_key(state, event, params, v, vstatus): vstatus.time_to_autologout = None vstatus.last_timeout_refresh = None +""" +Triggered when the user has entered the id of something they would like to purchase. +""" def make_selection(v, vstatus): # should use sudo here if vstatus.cur_selection == '55': vstatus.mk.set_message('OPENSESAME') logging.info('dispensing a door for %s'%vstatus.username) if geteuid() == 0: - #ret = os.system('su - "%s" -c "dispense door"'%vstatus.username) ret = os.system('dispense -u "%s" door'%vstatus.username) else: ret = os.system('dispense door') @@ -477,8 +559,6 @@ def make_selection(v, vstatus): 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 @@ -500,7 +580,9 @@ def make_selection(v, vstatus): v.display('UNK ERROR') sleep(1) - +""" +Find the price of an item. +""" def price_check(v, vstatus): if vstatus.cur_selection[1] == '8': args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0]) @@ -515,7 +597,9 @@ def price_check(v, vstatus): dollarprice = "$%.2f" % ( price / 100.0 ) v.display(vstatus.cur_selection+' - %s'%dollarprice) - +""" +Triggered when the user presses a button while entering their pin. +""" def handle_getting_pin_key(state, event, params, v, vstatus): #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params key = params @@ -551,7 +635,9 @@ def handle_getting_pin_key(state, event, params, v, vstatus): return - +""" +Triggered when the user presses a button while entering their user id. +""" def handle_getting_uid_key(state, event, params, v, vstatus): #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params key = params @@ -651,7 +737,9 @@ Wouldn't you prefer a nice game of chess? vstatus.change_state(STATE_GETTING_PIN) return - +""" +Triggered when a key is pressed and the machine is idling. +""" def handle_idle_key(state, event, params, v, vstatus): #print "handle_idle_key (s,e,p)", state, " ", event, " ", params @@ -665,7 +753,9 @@ def handle_idle_key(state, event, params, v, vstatus): vstatus.change_state(STATE_GETTING_UID) run_handler(event, key, v, vstatus) - +""" +What to do when there is nothing to do. +""" def handle_idle_tick(state, event, params, v, vstatus): ### State idling if vstatus.mk.done(): @@ -683,6 +773,9 @@ def handle_idle_tick(state, event, params, v, vstatus): run_handler(event, params, v, vstatus) sleep(0.05) +""" +Manages the beeps for the grandfather clock +""" def beep_on(when, before=0): start = int(when - before) end = int(when) @@ -787,6 +880,9 @@ def handle_grandfather_tick(state, event, params, v, vstatus): if go_idle and vstatus.mk.done(): vstatus.change_state(STATE_IDLE,1) +""" +What to do when the door is open. +""" def handle_door_idle(state, event, params, v, vstatus): def twiddle(clock,v,wise = 2): if (clock % 4 == 0): @@ -806,7 +902,9 @@ def handle_door_idle(state, event, params, v, vstatus): else: twiddle(now, v, wise=0) - +""" +What to do when the door is opened or closed. +""" def handle_door_event(state, event, params, v, vstatus): if params == 0: #door open vstatus.change_state(STATE_DOOR_OPENING) @@ -822,12 +920,18 @@ def handle_door_event(state, event, params, v, vstatus): logging.warning('Leaving open door mode') v.display("-YUM YUM!-") +""" +Triggered when a user swipes their caed, and the machine is logged out. +""" def handle_mifare_event(state, event, params, v, vstatus): + global _last_card_id card_id = params # Translate card_id into uid. - if card_id == None: + if card_id == None or card_id == _last_card_id: return + _last_card_id = card_id + try: vstatus.cur_user = get_uid(card_id) logging.info('Mapped card id to uid %s'%vstatus.cur_user) @@ -860,17 +964,24 @@ def handle_mifare_event(state, event, params, v, vstatus): (center('SORRY'), False, 0.5)]) vstatus.cur_user = '' vstatus.cur_pin = '' + _last_card_id = -1 reset_idler(v, vstatus, 2) return +""" +Triggered when a user swipes their card and the machine is logged in. +""" def handle_mifare_add_user_event(state, event, params, v, vstatus): - card_id = params + global _last_card_id + card_id = params # Translate card_id into uid. - if card_id == None: + if card_id == None or card_id == _last_card_id: return + _last_card_id = card_id + try: if get_uid(card_id) != None: vstatus.mk.set_messages( @@ -893,6 +1004,9 @@ def handle_mifare_add_user_event(state, event, params, v, vstatus): def return_to_idle(state,event,params,v,vstatus): reset_idler(v, vstatus) +""" +Maps what to do when the state changes. +""" def create_state_table(vstatus): vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key @@ -910,28 +1024,31 @@ def create_state_table(vstatus): 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,DOOR,1)] = handle_door_event vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = handle_mifare_event vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle - vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing + vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = handle_door_event vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = handle_mifare_event vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle - vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing + vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = handle_door_event vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = handle_mifare_add_user_event vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing - vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = handle_door_event + vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = handle_door_event vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = handle_mifare_event +""" +Get what to do on a state change. +""" def get_state_table_handler(vstatus, state, event, counter): return vstatus.state_table[(state,event,counter)] @@ -955,17 +1072,6 @@ def run_forever(rfh, wfh, options, cf): setup_idlers(v) 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: try: @@ -979,14 +1085,14 @@ def run_forever(rfh, wfh, options, cf): 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) +""" +Connect to the machine. +""" def connect_to_vend(options, cf): if options.use_lat: @@ -1011,6 +1117,9 @@ def connect_to_vend(options, cf): return rfh, wfh +""" +Parse arguments from the command line +""" def parse_args(): from optparse import OptionParser @@ -1034,34 +1143,6 @@ def parse_args(): return options -config_options = { - 'DBServer': ('Database', 'Server'), - 'DBName': ('Database', 'Name'), - 'DBUser': ('VendingMachine', 'DBUser'), - 'DBPassword': ('VendingMachine', 'DBPassword'), - - 'ServiceName': ('VendingMachine', 'ServiceName'), - 'ServicePassword': ('VendingMachine', 'Password'), - - 'ServerName': ('DecServer', 'Name'), - 'ConnectPassword': ('DecServer', 'ConnectPassword'), - 'PrivPassword': ('DecServer', 'PrivPassword'), - } - -class VendConfigFile: - def __init__(self, config_file, options): - try: - cp = ConfigParser.ConfigParser() - cp.read(config_file) - - for option in options: - section, name = options[option] - value = cp.get(section, name) - self.__dict__[option] = value - - except ConfigParser.Error, e: - raise SystemExit("Error reading config file "+config_file+": " + str(e)) - def create_pid_file(name): try: pid_file = file(name, 'w') @@ -1155,7 +1236,8 @@ def do_vend_server(options, config_opts): logging.info("Trying again in 5 seconds.") sleep(5) -if __name__ == '__main__': + +def main(argv=None): options, config_opts = set_stuff_up() while True: try: @@ -1185,3 +1267,5 @@ if __name__ == '__main__': sleep(10) logging.warning("Trying again anyway (might not help, but hey...)") +if __name__ == '__main__': + sys.exit(main())