7 import sys, os, string, re, pwd, signal, math, syslog
8 import logging, logging.handlers
9 from traceback import format_tb
11 from time import time, sleep, mktime, localtime
12 from popen2 import popen2
13 from LATClient import LATClient, LATClientException
14 from SerialClient import SerialClient, SerialClientException
15 from VendingMachine import VendingMachine, VendingException
16 from MessageKeeper import MessageKeeper
17 from HorizScroll import HorizScroll
18 from random import random, seed
19 from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
20 from SnackConfig import get_snacks, get_snack
22 from posix import geteuid
25 This vending machine software brought to you by:
30 and a collective of hungry alpacas.
34 For a good time call +61 8 6488 3901
49 STATE_DOOR_OPENING = 2
50 STATE_DOOR_CLOSING = 3
53 STATE_GET_SELECTION = 6
54 STATE_GRANDFATHER_CLOCK = 7
59 class DispenseDatabaseException(Exception): pass
61 class DispenseDatabase:
62 def __init__(self, vending_machine, host, name, user, password):
63 self.vending_machine = vending_machine
64 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
65 self.db.query('LISTEN vend_requests')
67 def process_requests(self):
68 logging.debug('database processing')
69 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
71 outstanding = self.db.query(query).getresult()
72 except (pg.error,), db_err:
73 raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
74 for (id, slot) in outstanding:
75 (worked, code, string) = self.vending_machine.vend(slot)
76 logging.debug (str((worked, code, string)))
78 query = 'SELECT vend_success(%s)'%id
79 self.db.query(query).getresult()
81 query = 'SELECT vend_failed(%s)'%id
82 self.db.query(query).getresult()
84 def handle_events(self):
85 notifier = self.db.getnotify()
86 while notifier is not None:
87 self.process_requests()
88 notify = self.db.getnotify()
90 def scroll_options(username, mk, welcome = False):
92 msg = [(center('WELCOME'), False, TEXT_SPEED),
93 (center(username), False, TEXT_SPEED)]
96 choices = ' '*10+'CHOICES: '
98 coke_machine = file('/home/other/coke/coke_contents')
99 cokes = coke_machine.readlines()
106 (slot_num, price, slot_name) = c.split(' ', 2)
107 if slot_name == 'dead': continue
108 choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
110 # we don't want to print snacks for now since it'll be too large
111 # and there's physical bits of paper in the machine anyway - matt
113 # snacks = get_snacks()
117 # for slot, ( name, price ) in snacks.items():
118 # choices += '%s8-%s (%sc) ' % ( slot, name, price )
120 choices += '55-DOOR '
121 choices += 'OR ANOTHER SNACK. '
122 choices += '99 TO READ AGAIN. '
123 choices += 'CHOICE? '
124 msg.append((choices, False, None))
129 info = pwd.getpwuid(uid)
131 logging.info('getting pin for uid %d: user not in password file'%uid)
133 if info.pw_dir == None: return False
134 pinfile = os.path.join(info.pw_dir, '.pin')
138 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
141 logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
142 os.chmod(pinfile, 0600)
146 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
148 pinstr = f.readline()
150 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
151 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
155 def has_good_pin(uid):
156 return get_pin(uid) != None
158 def verify_user_pin(uid, pin):
159 if get_pin(uid) == pin:
160 info = pwd.getpwuid(uid)
161 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
164 logging.info('refused pin for uid %d'%(uid))
170 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
171 choice = int(random()*len(messages))
172 msg = messages[choice]
173 left = range(len(msg))
174 for i in range(len(msg)):
175 if msg[i] == ' ': left.remove(i)
179 for i in range(0, len(msg)):
185 s += chr(int(random()*26)+ord('A'))
194 return ' '*((LEN-len(str))/2)+str
205 StringIdler(v, text="Kill 'em all", repeat=False),
206 GrayIdler(v,one="*",zero="-"),
207 StringIdler(v, text=CREDITS),
208 GrayIdler(v,one="/",zero="\\"),
210 GrayIdler(v,one="X",zero="O"),
211 FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
212 GrayIdler(v,one="*",zero="-",reorder=1),
213 StringIdler(v, text=str(math.pi) + " "),
215 GrayIdler(v,one="/",zero="\\",reorder=1),
216 StringIdler(v, text=str(math.e) + " "),
217 GrayIdler(v,one="X",zero="O",reorder=1),
218 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),
219 PipeIdler(v, "/usr/bin/getent", "passwd"),
228 def reset_idler(v, vstatus, t = None):
230 idler = GreetingIdler(v, t)
231 vstatus.time_of_next_idlestep = time()+idler.next()
232 vstatus.time_of_next_idler = None
233 vstatus.time_to_autologout = None
234 vstatus.change_state(STATE_IDLE, 1)
239 average_affinity = 10 # guessing here...
241 if idler and idler.__class__ != GreetingIdler:
242 iiindex = idlers.index(idler)
246 move = int(random()*len(idlers)*average_affinity) + 1
251 idler = idlers[iiindex]
252 move -= idler.affinity()
256 def idle_step(vstatus):
260 vstatus.time_of_next_idler = time() + 30
261 nextidle = idler.next()
263 nextidle = IDLE_SPEED
264 vstatus.time_of_next_idlestep = time()+nextidle
267 def __init__(self,v):
268 self.state_table = {}
269 self.state = STATE_IDLE
272 self.mk = MessageKeeper(v)
276 self.cur_selection = ''
277 self.time_to_autologout = None
279 self.last_timeout_refresh = None
281 def change_state(self,newstate,newcounter=None):
282 if self.state != newstate:
283 #print "Changing state from: ",
287 self.state = newstate
289 if newcounter is not None and self.counter != newcounter:
290 #print "Changing counter from: ",
294 self.counter = newcounter
298 def handle_tick_event(event, params, v, vstatus):
299 # don't care right now.
302 def handle_switch_event(event, params, v, vstatus):
303 # don't care right now.
307 def do_nothing(state, event, params, v, vstatus):
308 print "doing nothing (s,e,p)", state, " ", event, " ", params
311 def handle_getting_uid_idle(state, event, params, v, vstatus):
312 # don't care right now.
315 def handle_getting_pin_idle(state, event, params, v, vstatus):
316 # don't care right now.
319 def handle_get_selection_idle(state, event, params, v, vstatus):
320 # don't care right now.
322 ### State logging out ..
323 if vstatus.time_to_autologout != None:
324 time_left = vstatus.time_to_autologout - time()
325 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
326 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
327 vstatus.last_timeout_refresh = int(time_left)
328 vstatus.cur_selection = ''
330 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
331 vstatus.time_to_autologout = None
332 vstatus.cur_user = ''
334 vstatus.cur_selection = ''
336 reset_idler(v, vstatus)
338 ### State fully logged out ... reset variables
339 if vstatus.time_to_autologout and not vstatus.mk.done():
340 vstatus.time_to_autologout = None
341 if vstatus.cur_user == '' and vstatus.time_to_autologout:
342 vstatus.time_to_autologout = None
345 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
347 vstatus.time_to_autologout = time() + 15
348 vstatus.last_timeout_refresh = None
350 ## FIXME - this may need to be elsewhere.....
352 vstatus.mk.update_display()
356 def handle_get_selection_key(state, event, params, v, vstatus):
358 if len(vstatus.cur_selection) == 0:
361 vstatus.cur_user = ''
362 vstatus.cur_selection = ''
364 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
365 reset_idler(v, vstatus, 2)
367 vstatus.cur_selection += chr(key + ord('0'))
368 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
369 vstatus.time_to_autologout = None
370 elif len(vstatus.cur_selection) == 1:
372 vstatus.cur_selection = ''
373 vstatus.time_to_autologout = None
374 scroll_options(vstatus.username, vstatus.mk)
377 vstatus.cur_selection += chr(key + ord('0'))
378 make_selection(v,vstatus)
379 vstatus.cur_selection = ''
380 vstatus.time_to_autologout = time() + 8
381 vstatus.last_timeout_refresh = None
383 def make_selection(v, vstatus):
384 # should use sudo here
385 if vstatus.cur_selection == '55':
386 vstatus.mk.set_message('OPENSESAME')
387 logging.info('dispensing a door for %s'%vstatus.username)
389 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
391 ret = os.system('dispense door')
393 logging.info('door opened')
394 vstatus.mk.set_message(center('DOOR OPEN'))
396 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
397 vstatus.mk.set_message(center('BAD DOOR'))
399 elif vstatus.cur_selection == '81':
401 elif vstatus.cur_selection == '99':
402 scroll_options(vstatus.username, vstatus.mk)
403 vstatus.cur_selection = ''
405 elif vstatus.cur_selection[1] == '8':
406 v.display('GOT DRINK?')
407 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
408 v.display('SEEMS NOT')
410 v.display('GOT DRINK!')
412 # first see if it's a named slot
414 price, shortname, name = get_snack( vstatus.cur_selection )
416 price, shortname, name = get_snack( '--' )
417 dollarprice = "$%.2f" % ( price / 100.0 )
418 v.display(vstatus.cur_selection+' - %s'%dollarprice)
419 exitcode = os.system('su - "%s" -c "dispense give oday %d"'%(vstatus.username, price)) >> 8
421 # magic dispense syslog service
422 syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, vstatus.cur_selection, vstatus.username))
423 v.vend(vstatus.cur_selection)
424 v.display('THANK YOU')
426 syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, vstatus.cur_selection, vstatus.username, exitcode))
427 v.display('NO MONEY?')
431 def handle_getting_pin_key(state, event, params, v, vstatus):
432 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
434 if len(vstatus.cur_pin) < PIN_LENGTH:
436 if vstatus.cur_pin == '':
437 vstatus.cur_user = ''
438 reset_idler(v, vstatus)
442 vstatus.mk.set_message('PIN: ')
444 vstatus.cur_pin += chr(key + ord('0'))
445 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
446 if len(vstatus.cur_pin) == PIN_LENGTH:
447 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
450 vstatus.cur_selection = ''
451 vstatus.change_state(STATE_GET_SELECTION)
452 scroll_options(vstatus.username, vstatus.mk, True)
456 vstatus.mk.set_messages(
457 [(center('BAD PIN'), False, 1.0),
458 (center('SORRY'), False, 0.5)])
459 vstatus.cur_user = ''
462 reset_idler(v, vstatus, 2)
467 def handle_getting_uid_key(state, event, params, v, vstatus):
468 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
471 # complicated key handling here:
475 if len(vstatus.cur_user) <8:
477 vstatus.cur_user = ''
479 reset_idler(v, vstatus)
481 vstatus.cur_user += chr(key + ord('0'))
482 #logging.info('dob: '+vstatus.cur_user)
483 if len(vstatus.cur_user) > 5:
484 vstatus.mk.set_message('>'+vstatus.cur_user)
486 vstatus.mk.set_message('UID: '+vstatus.cur_user)
489 # Easter egg for nikita's birthday -- DGB
490 if len(vstatus.cur_user) == 8:
491 if vstatus.cur_user != "07051980":
492 vstatus.mk.set_messages(
493 [(' '*9+'ONE MORE TRY NiKiTa'+' '*10, False, 3)])
494 vstatus.cur_user = ''
495 reset_idler(v, vstatus, 3)
499 vstatus.mk.set_messages(
500 [(center(' GUILD MAILBOX NUMBER 64 '), False, 20),
501 (center(' GUILD MAILBOX NUMBER 64 '), False, 20),
502 (center(' GUILD MAILBOX NUMBER 64 '), False, 20),
503 (center(' GUILD MAILBOX NUMBER 64 '), False, 20)])
506 vstatus.cur_user = ''
508 #reset_idler(v, vstatus, 10)
509 reset_idler(v, vstatus, 2)
511 # End easter egg part 1
512 if len(vstatus.cur_user) == 5:
513 uid = int(vstatus.cur_user)
515 # Easter egg for nikita's birthday -- DGB
516 if vstatus.cur_user == '07051':
518 vstatus.cur_user = ''
519 reset_idler(v, vstatus)
521 # vstatus.cur_user += chr(key + ord('0'))
522 logging.info(' == 5 dob: '+vstatus.cur_user)
523 vstatus.mk.set_message('>'+vstatus.cur_user)
525 # end easter egg part 2
528 logging.info('user '+vstatus.cur_user+' has a bad PIN')
534 Welcome to Picklevision Sytems, Sunnyvale, CA
536 Greetings Professor Falken.
541 Shall we play a game?
544 Please choose from the following menu:
551 6. Toxic and Biochemical Warfare
552 7. Global Thermonuclear War
556 Wouldn't you prefer a nice game of chess?
558 """.replace('\n',' ')
559 vstatus.mk.set_messages([(pfalken, False, 10)])
560 vstatus.cur_user = ''
563 reset_idler(v, vstatus, 10)
567 if not has_good_pin(uid):
568 logging.info('user '+vstatus.cur_user+' has a bad PIN')
569 vstatus.mk.set_messages(
570 [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
571 vstatus.cur_user = ''
574 reset_idler(v, vstatus, 3)
580 vstatus.mk.set_message('PIN: ')
581 logging.info('need pin for user %s'%vstatus.cur_user)
582 vstatus.change_state(STATE_GETTING_PIN)
586 def handle_idle_key(state, event, params, v, vstatus):
587 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
592 vstatus.cur_user = ''
593 reset_idler(v, vstatus)
596 vstatus.change_state(STATE_GETTING_UID)
597 run_handler(event, key, v, vstatus)
600 def handle_idle_tick(state, event, params, v, vstatus):
602 if vstatus.mk.done():
605 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
606 vstatus.time_of_next_idler = time() + 30
611 vstatus.mk.update_display()
613 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
614 run_handler(event, params, v, vstatus)
617 def beep_on(when, before=0):
618 start = int(when - before)
622 if now >= start and now <= end:
626 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
627 ### check for interesting times
630 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
631 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
632 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
633 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
635 hourfromnow = localtime(time() + 3600)
637 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
638 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
639 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
641 ## check for X seconds to the hour
642 ## if case, update counter to 2
643 if beep_on(onthehour,15) \
644 or beep_on(halfhour,0) \
645 or beep_on(quarterhour,0) \
646 or beep_on(threequarterhour,0) \
647 or beep_on(fivetothehour,0):
648 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
649 run_handler(event, params, v, vstatus)
651 vstatus.change_state(STATE_IDLE)
653 def handle_grandfather_tick(state, event, params, v, vstatus):
657 ### we live in interesting times
660 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
661 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
662 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
663 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
665 hourfromnow = localtime(time() + 3600)
667 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
668 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
669 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
672 #print "when it fashionable to wear a onion on your hip"
674 if beep_on(onthehour,15):
676 next_hour=((hourfromnow[3] + 11) % 12) + 1
677 if onthehour - time() < next_hour and onthehour - time() > 0:
682 msg.append(("DING!", False, None))
684 msg.append((" DING!", False, None))
685 elif int(onthehour - time()) == 0:
687 msg.append((" BONG!", False, None))
688 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
689 elif beep_on(halfhour,0):
692 msg.append((" HALFHOUR ", False, 50))
693 elif beep_on(quarterhour,0):
696 msg.append((" QTR HOUR ", False, 50))
697 elif beep_on(threequarterhour,0):
700 msg.append((" 3 QTR HR ", False, 50))
701 elif beep_on(fivetothehour,0):
704 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
708 ## check for X seconds to the hour
711 vstatus.mk.set_messages(msg)
714 vstatus.mk.update_display()
715 ## if no longer case, return to idle
717 ## change idler to be clock
718 if go_idle and vstatus.mk.done():
719 vstatus.change_state(STATE_IDLE,1)
721 def handle_door_idle(state, event, params, v, vstatus):
722 # don't care right now.
725 def handle_door_event(state, event, params, v, vstatus):
726 if params == 0: #door open
727 vstatus.change_state(STATE_DOOR_OPENING)
728 logging.warning("Entering open door mode")
729 v.display("-FEED ME-")
731 vstatus.cur_user = ''
733 elif params == 1: #door closed
734 vstatus.change_state(STATE_DOOR_CLOSING)
735 reset_idler(v, vstatus, 3)
737 logging.warning('Leaving open door mode')
738 v.display("-YUM YUM!-")
740 def return_to_idle(state,event,params,v,vstatus):
741 reset_idler(v, vstatus)
743 def create_state_table(vstatus):
744 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
745 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
746 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
748 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
749 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
750 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
752 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
753 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
754 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
756 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
757 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
758 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
760 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
761 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
762 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
764 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
765 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
766 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
768 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
769 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
770 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
771 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
772 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
773 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
775 def get_state_table_handler(vstatus, state, event, counter):
776 return vstatus.state_table[(state,event,counter)]
778 def time_to_next_update(vstatus):
779 idle_update = vstatus.time_of_next_idlestep - time()
780 if not vstatus.mk.done() and vstatus.mk.next_update is not None:
781 mk_update = vstatus.mk.next_update - time()
782 if mk_update < idle_update:
783 idle_update = mk_update
786 def run_forever(rfh, wfh, options, cf):
787 v = VendingMachine(rfh, wfh)
788 vstatus = VendState(v)
789 create_state_table(vstatus)
791 logging.debug('PING is ' + str(v.ping()))
793 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
796 reset_idler(v, vstatus)
798 # This main loop was hideous and the work of the devil.
799 # This has now been fixed (mostly) - mtearle
802 # notes for later surgery
803 # (event, counter, ' ')
807 # ( return state - not currently implemented )
813 except DispenseDatabaseException, e:
814 logging.error('Database error: '+str(e))
817 timeout = time_to_next_update(vstatus)
818 e = v.next_event(timeout)
821 run_handler(event, params, v, vstatus)
823 # logging.debug('Got event: ' + repr(e))
826 def run_handler(event, params, v, vstatus):
827 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
829 handler(vstatus.state, event, params, v, vstatus)
831 def connect_to_vend(options, cf):
834 logging.info('Connecting to vending machine using LAT')
835 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
836 rfh, wfh = latclient.get_fh()
837 elif options.use_serial:
838 # Open vending machine via serial.
839 logging.info('Connecting to vending machine using serial')
840 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
841 rfh,wfh = serialclient.get_fh()
843 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
844 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
846 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
847 sock.connect((options.host, options.port))
848 rfh = sock.makefile('r')
849 wfh = sock.makefile('w')
854 from optparse import OptionParser
856 op = OptionParser(usage="%prog [OPTION]...")
857 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')
858 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
859 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
860 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
861 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
862 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
863 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
864 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
865 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
866 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
867 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
868 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
869 options, args = op.parse_args()
872 op.error('extra command line arguments: ' + ' '.join(args))
877 'DBServer': ('Database', 'Server'),
878 'DBName': ('Database', 'Name'),
879 'DBUser': ('VendingMachine', 'DBUser'),
880 'DBPassword': ('VendingMachine', 'DBPassword'),
882 'ServiceName': ('VendingMachine', 'ServiceName'),
883 'ServicePassword': ('VendingMachine', 'Password'),
885 'ServerName': ('DecServer', 'Name'),
886 'ConnectPassword': ('DecServer', 'ConnectPassword'),
887 'PrivPassword': ('DecServer', 'PrivPassword'),
890 class VendConfigFile:
891 def __init__(self, config_file, options):
893 cp = ConfigParser.ConfigParser()
896 for option in options:
897 section, name = options[option]
898 value = cp.get(section, name)
899 self.__dict__[option] = value
901 except ConfigParser.Error, e:
902 raise SystemExit("Error reading config file "+config_file+": " + str(e))
904 def create_pid_file(name):
906 pid_file = file(name, 'w')
907 pid_file.write('%d\n'%os.getpid())
910 logging.warning('unable to write to pid file '+name+': '+str(e))
913 def do_nothing(signum, stack):
914 signal.signal(signum, do_nothing)
915 def stop_server(signum, stack): raise KeyboardInterrupt
916 signal.signal(signal.SIGHUP, do_nothing)
917 signal.signal(signal.SIGTERM, stop_server)
918 signal.signal(signal.SIGINT, stop_server)
920 options = parse_args()
921 config_opts = VendConfigFile(options.config_file, config_options)
922 if options.daemon: become_daemon()
923 set_up_logging(options)
924 if options.pid_file != '': create_pid_file(options.pid_file)
926 return options, config_opts
928 def clean_up_nicely(options, config_opts):
929 if options.pid_file != '':
931 os.unlink(options.pid_file)
932 logging.debug('Removed pid file '+options.pid_file)
933 except OSError: pass # if we can't delete it, meh
935 def set_up_logging(options):
936 logger = logging.getLogger()
938 if not options.daemon:
939 stderr_logger = logging.StreamHandler(sys.stderr)
940 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
941 logger.addHandler(stderr_logger)
943 if options.log_file != '':
945 file_logger = logging.FileHandler(options.log_file)
946 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
947 logger.addHandler(file_logger)
949 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
951 if options.syslog != None:
952 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
953 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
954 logger.addHandler(sys_logger)
957 logger.setLevel(logging.WARNING)
958 elif options.verbose:
959 logger.setLevel(logging.DEBUG)
961 logger.setLevel(logging.INFO)
964 dev_null = file('/dev/null')
965 fd = dev_null.fileno()
974 raise SystemExit('failed to fork: '+str(e))
976 def do_vend_server(options, config_opts):
979 rfh, wfh = connect_to_vend(options, config_opts)
980 except (SerialClientException, socket.error), e:
981 (exc_type, exc_value, exc_traceback) = sys.exc_info()
983 logging.error("Connection error: "+str(exc_type)+" "+str(e))
984 logging.info("Trying again in 5 seconds.")
989 run_forever(rfh, wfh, options, config_opts)
990 except VendingException:
991 logging.error("Connection died, trying again...")
992 logging.info("Trying again in 5 seconds.")
995 if __name__ == '__main__':
996 options, config_opts = set_stuff_up()
999 logging.warning('Starting Vend Server')
1000 do_vend_server(options, config_opts)
1001 logging.error('Vend Server finished unexpectedly, restarting')
1002 except KeyboardInterrupt:
1003 logging.info("Killed by signal, cleaning up")
1004 clean_up_nicely(options, config_opts)
1005 logging.warning("Vend Server stopped")
1010 (exc_type, exc_value, exc_traceback) = sys.exc_info()
1011 tb = format_tb(exc_traceback, 20)
1014 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
1015 logging.critical("Message: " + str(exc_value))
1016 logging.critical("Traceback:")
1018 for line in event.split('\n'):
1019 logging.critical(' '+line)
1020 logging.critical("This message should be considered a bug in the Vend Server.")
1021 logging.critical("Please report this to someone who can fix it.")
1023 logging.warning("Trying again anyway (might not help, but hey...)")