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 == '91':
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))
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
470 # complicated key handling here:
471 if len(vstatus.cur_user) < 5:
473 vstatus.cur_user = ''
475 reset_idler(v, vstatus)
478 vstatus.cur_user += chr(key + ord('0'))
479 vstatus.mk.set_message('UID: '+vstatus.cur_user)
481 if len(vstatus.cur_user) == 5:
482 uid = int(vstatus.cur_user)
484 logging.info('user '+vstatus.cur_user+' has a bad PIN')
490 Welcome to Picklevision Sytems, Sunnyvale, CA
492 Greetings Professor Falken.
497 Shall we play a game?
500 Please choose from the following menu:
507 6. Toxic and Biochemical Warfare
508 7. Global Thermonuclear War
512 Wouldn't you prefer a nice game of chess?
514 """.replace('\n',' ')
515 vstatus.mk.set_messages([(pfalken, False, 10)])
516 vstatus.cur_user = ''
519 reset_idler(v, vstatus, 10)
523 if not has_good_pin(uid):
524 logging.info('user '+vstatus.cur_user+' has a bad PIN')
525 vstatus.mk.set_messages(
526 [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
527 vstatus.cur_user = ''
530 reset_idler(v, vstatus, 3)
536 vstatus.mk.set_message('PIN: ')
537 logging.info('need pin for user %s'%vstatus.cur_user)
538 vstatus.change_state(STATE_GETTING_PIN)
542 def handle_idle_key(state, event, params, v, vstatus):
543 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
548 vstatus.cur_user = ''
549 reset_idler(v, vstatus)
552 vstatus.change_state(STATE_GETTING_UID)
553 run_handler(event, key, v, vstatus)
556 def handle_idle_tick(state, event, params, v, vstatus):
558 if vstatus.mk.done():
561 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
562 vstatus.time_of_next_idler = time() + 30
567 vstatus.mk.update_display()
569 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
570 run_handler(event, params, v, vstatus)
573 def beep_on(when, before=0):
574 start = int(when - before)
578 if now >= start and now <= end:
582 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
583 ### check for interesting times
586 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
587 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
588 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
589 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
591 hourfromnow = localtime(time() + 3600)
593 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
594 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
595 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
597 ## check for X seconds to the hour
598 ## if case, update counter to 2
599 if beep_on(onthehour,15) \
600 or beep_on(halfhour,0) \
601 or beep_on(quarterhour,0) \
602 or beep_on(threequarterhour,0) \
603 or beep_on(fivetothehour,0):
604 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
605 run_handler(event, params, v, vstatus)
607 vstatus.change_state(STATE_IDLE)
609 def handle_grandfather_tick(state, event, params, v, vstatus):
613 ### we live in interesting times
616 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
617 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
618 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
619 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
621 hourfromnow = localtime(time() + 3600)
623 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
624 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
625 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
628 #print "when it fashionable to wear a onion on your hip"
630 if beep_on(onthehour,15):
632 next_hour=((hourfromnow[3] + 11) % 12) + 1
633 if onthehour - time() < next_hour and onthehour - time() > 0:
638 msg.append(("DING!", False, None))
640 msg.append((" DING!", False, None))
641 elif int(onthehour - time()) == 0:
643 msg.append((" BONG!", False, None))
644 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
645 elif beep_on(halfhour,0):
648 msg.append((" HALFHOUR ", False, 50))
649 elif beep_on(quarterhour,0):
652 msg.append((" QTR HOUR ", False, 50))
653 elif beep_on(threequarterhour,0):
656 msg.append((" 3 QTR HR ", False, 50))
657 elif beep_on(fivetothehour,0):
660 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
664 ## check for X seconds to the hour
667 vstatus.mk.set_messages(msg)
670 vstatus.mk.update_display()
671 ## if no longer case, return to idle
673 ## change idler to be clock
674 if go_idle and vstatus.mk.done():
675 vstatus.change_state(STATE_IDLE,1)
677 def handle_door_idle(state, event, params, v, vstatus):
678 # don't care right now.
681 def handle_door_event(state, event, params, v, vstatus):
682 if params == 1: #door open
683 vstatus.change_state(STATE_DOOR_OPENING)
684 logging.warning("Entering open door mode")
685 v.display("-FEED ME-")
687 vstatus.cur_user = ''
689 elif params == 0: #door closed
690 vstatus.change_state(STATE_DOOR_CLOSING)
691 reset_idler(v, vstatus, 3)
693 logging.warning('Leaving open door mode')
694 v.display("-YUM YUM!-")
696 def return_to_idle(state,event,params,v,vstatus):
697 reset_idler(v, vstatus)
699 def create_state_table(vstatus):
700 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
701 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
702 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
704 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
705 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
706 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
708 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
709 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
710 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
712 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
713 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
714 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
716 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
717 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
718 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
720 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
721 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
722 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
724 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
725 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
726 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
727 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
728 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
729 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
731 def get_state_table_handler(vstatus, state, event, counter):
732 return vstatus.state_table[(state,event,counter)]
734 def time_to_next_update(vstatus):
735 idle_update = vstatus.time_of_next_idlestep - time()
736 if not vstatus.mk.done() and vstatus.mk.next_update is not None:
737 mk_update = vstatus.mk.next_update - time()
738 if mk_update < idle_update:
739 idle_update = mk_update
742 def run_forever(rfh, wfh, options, cf):
743 v = VendingMachine(rfh, wfh)
744 vstatus = VendState(v)
745 create_state_table(vstatus)
747 logging.debug('PING is ' + str(v.ping()))
749 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
752 reset_idler(v, vstatus)
754 # This main loop was hideous and the work of the devil.
755 # This has now been fixed (mostly) - mtearle
758 # notes for later surgery
759 # (event, counter, ' ')
763 # ( return state - not currently implemented )
769 except DispenseDatabaseException, e:
770 logging.error('Database error: '+str(e))
773 timeout = time_to_next_update(vstatus)
774 e = v.next_event(timeout)
777 run_handler(event, params, v, vstatus)
779 # logging.debug('Got event: ' + repr(e))
782 def run_handler(event, params, v, vstatus):
783 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
785 handler(vstatus.state, event, params, v, vstatus)
787 def connect_to_vend(options, cf):
790 logging.info('Connecting to vending machine using LAT')
791 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
792 rfh, wfh = latclient.get_fh()
793 elif options.use_serial:
794 # Open vending machine via serial.
795 logging.info('Connecting to vending machine using serial')
796 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
797 rfh,wfh = serialclient.get_fh()
799 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
800 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
802 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
803 sock.connect((options.host, options.port))
804 rfh = sock.makefile('r')
805 wfh = sock.makefile('w')
810 from optparse import OptionParser
812 op = OptionParser(usage="%prog [OPTION]...")
813 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')
814 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
815 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
816 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
817 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
818 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
819 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
820 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
821 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
822 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
823 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
824 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
825 options, args = op.parse_args()
828 op.error('extra command line arguments: ' + ' '.join(args))
833 'DBServer': ('Database', 'Server'),
834 'DBName': ('Database', 'Name'),
835 'DBUser': ('VendingMachine', 'DBUser'),
836 'DBPassword': ('VendingMachine', 'DBPassword'),
838 'ServiceName': ('VendingMachine', 'ServiceName'),
839 'ServicePassword': ('VendingMachine', 'Password'),
841 'ServerName': ('DecServer', 'Name'),
842 'ConnectPassword': ('DecServer', 'ConnectPassword'),
843 'PrivPassword': ('DecServer', 'PrivPassword'),
846 class VendConfigFile:
847 def __init__(self, config_file, options):
849 cp = ConfigParser.ConfigParser()
852 for option in options:
853 section, name = options[option]
854 value = cp.get(section, name)
855 self.__dict__[option] = value
857 except ConfigParser.Error, e:
858 raise SystemExit("Error reading config file "+config_file+": " + str(e))
860 def create_pid_file(name):
862 pid_file = file(name, 'w')
863 pid_file.write('%d\n'%os.getpid())
866 logging.warning('unable to write to pid file '+name+': '+str(e))
869 def do_nothing(signum, stack):
870 signal.signal(signum, do_nothing)
871 def stop_server(signum, stack): raise KeyboardInterrupt
872 signal.signal(signal.SIGHUP, do_nothing)
873 signal.signal(signal.SIGTERM, stop_server)
874 signal.signal(signal.SIGINT, stop_server)
876 options = parse_args()
877 config_opts = VendConfigFile(options.config_file, config_options)
878 if options.daemon: become_daemon()
879 set_up_logging(options)
880 if options.pid_file != '': create_pid_file(options.pid_file)
882 return options, config_opts
884 def clean_up_nicely(options, config_opts):
885 if options.pid_file != '':
887 os.unlink(options.pid_file)
888 logging.debug('Removed pid file '+options.pid_file)
889 except OSError: pass # if we can't delete it, meh
891 def set_up_logging(options):
892 logger = logging.getLogger()
894 if not options.daemon:
895 stderr_logger = logging.StreamHandler(sys.stderr)
896 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
897 logger.addHandler(stderr_logger)
899 if options.log_file != '':
901 file_logger = logging.FileHandler(options.log_file)
902 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
903 logger.addHandler(file_logger)
905 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
907 if options.syslog != None:
908 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
909 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
910 logger.addHandler(sys_logger)
913 logger.setLevel(logging.WARNING)
914 elif options.verbose:
915 logger.setLevel(logging.DEBUG)
917 logger.setLevel(logging.INFO)
920 dev_null = file('/dev/null')
921 fd = dev_null.fileno()
930 raise SystemExit('failed to fork: '+str(e))
932 def do_vend_server(options, config_opts):
935 rfh, wfh = connect_to_vend(options, config_opts)
936 except (SerialClientException, socket.error), e:
937 (exc_type, exc_value, exc_traceback) = sys.exc_info()
939 logging.error("Connection error: "+str(exc_type)+" "+str(e))
940 logging.info("Trying again in 5 seconds.")
945 run_forever(rfh, wfh, options, config_opts)
946 except VendingException:
947 logging.error("Connection died, trying again...")
948 logging.info("Trying again in 5 seconds.")
951 if __name__ == '__main__':
952 options, config_opts = set_stuff_up()
955 logging.warning('Starting Vend Server')
956 do_vend_server(options, config_opts)
957 logging.error('Vend Server finished unexpectedly, restarting')
958 except KeyboardInterrupt:
959 logging.info("Killed by signal, cleaning up")
960 clean_up_nicely(options, config_opts)
961 logging.warning("Vend Server stopped")
966 (exc_type, exc_value, exc_traceback) = sys.exc_info()
967 tb = format_tb(exc_traceback, 20)
970 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
971 logging.critical("Message: " + str(exc_value))
972 logging.critical("Traceback:")
974 for line in event.split('\n'):
975 logging.critical(' '+line)
976 logging.critical("This message should be considered a bug in the Vend Server.")
977 logging.critical("Please report this to someone who can fix it.")
979 logging.warning("Trying again anyway (might not help, but hey...)")