7 import sys, os, string, re, pwd, signal, math
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.change_state(STATE_IDLE, 1)
238 average_affinity = 10 # guessing here...
240 if idler and idler.__class__ != GreetingIdler:
241 iiindex = idlers.index(idler)
245 move = int(random()*len(idlers)*average_affinity) + 1
250 idler = idlers[iiindex]
251 move -= idler.affinity()
255 def idle_step(vstatus):
259 vstatus.time_of_next_idler = time() + 30
260 nextidle = idler.next()
262 nextidle = IDLE_SPEED
263 vstatus.time_of_next_idlestep = time()+nextidle
266 def __init__(self,v):
267 self.state_table = {}
268 self.state = STATE_IDLE
271 self.mk = MessageKeeper(v)
275 self.cur_selection = ''
276 self.time_to_autologout = None
278 self.last_timeout_refresh = None
280 def change_state(self,newstate,newcounter=None):
281 if self.state != newstate:
282 #print "Changing state from: ",
286 self.state = newstate
288 if newcounter is not None and self.counter != newcounter:
289 #print "Changing counter from: ",
293 self.counter = newcounter
297 def handle_tick_event(event, params, v, vstatus):
298 # don't care right now.
301 def handle_switch_event(event, params, v, vstatus):
302 # don't care right now.
306 def do_nothing(state, event, params, v, vstatus):
307 print "doing nothing (s,e,p)", state, " ", event, " ", params
310 def handle_getting_uid_idle(state, event, params, v, vstatus):
311 # don't care right now.
314 def handle_getting_pin_idle(state, event, params, v, vstatus):
315 # don't care right now.
318 def handle_get_selection_idle(state, event, params, v, vstatus):
319 # don't care right now.
321 ### State logging out ..
322 if vstatus.time_to_autologout != None:
323 time_left = vstatus.time_to_autologout - time()
324 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
325 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
326 vstatus.last_timeout_refresh = int(time_left)
327 vstatus.cur_selection = ''
329 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
330 vstatus.time_to_autologout = None
331 vstatus.cur_user = ''
333 vstatus.cur_selection = ''
335 reset_idler(v, vstatus)
337 ### State fully logged out ... reset variables
338 if vstatus.time_to_autologout and not vstatus.mk.done():
339 vstatus.time_to_autologout = None
340 if vstatus.cur_user == '' and vstatus.time_to_autologout:
341 vstatus.time_to_autologout = None
344 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
346 vstatus.time_to_autologout = time() + 15
347 vstatus.last_timeout_refresh = None
349 ## FIXME - this may need to be elsewhere.....
351 vstatus.mk.update_display()
355 def handle_get_selection_key(state, event, params, v, vstatus):
357 if len(vstatus.cur_selection) == 0:
360 vstatus.cur_user = ''
361 vstatus.cur_selection = ''
363 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
364 reset_idler(v, vstatus, 2)
366 vstatus.cur_selection += chr(key + ord('0'))
367 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
368 vstatus.time_to_autologout = None
369 elif len(vstatus.cur_selection) == 1:
371 vstatus.cur_selection = ''
372 vstatus.time_to_autologout = None
373 scroll_options(vstatus.username, vstatus.mk)
376 vstatus.cur_selection += chr(key + ord('0'))
377 make_selection(v,vstatus)
378 vstatus.cur_selection = ''
379 vstatus.time_to_autologout = time() + 8
380 vstatus.last_timeout_refresh = None
382 def make_selection(v, vstatus):
383 # should use sudo here
384 if vstatus.cur_selection == '55':
385 vstatus.mk.set_message('OPENSESAME')
386 logging.info('dispensing a door for %s'%vstatus.username)
388 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
390 ret = os.system('dispense door')
392 logging.info('door opened')
393 vstatus.mk.set_message(center('DOOR OPEN'))
395 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
396 vstatus.mk.set_message(center('BAD DOOR'))
398 elif vstatus.cur_selection == '91':
400 elif vstatus.cur_selection == '99':
401 scroll_options(vstatus.username, vstatus.mk)
402 vstatus.cur_selection = ''
404 elif vstatus.cur_selection[1] == '8':
405 v.display('GOT COKE?')
406 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
407 v.display('SEEMS NOT')
409 v.display('GOT COKE!')
411 # first see if it's a named slot
413 price, shortname, name = get_snack( vstatus.cur_selection )
415 price, shortname, name = get_snack( '--' )
416 dollarprice = "$%.2f" % ( price / 100.0 )
417 v.display(vstatus.cur_selection+' - %s'%dollarprice)
418 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, shortname)) >> 8) == 0):
419 v.vend(vstatus.cur_selection)
420 v.display('THANK YOU')
422 v.display('NO MONEY?')
426 def handle_getting_pin_key(state, event, params, v, vstatus):
427 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
429 if len(vstatus.cur_pin) < PIN_LENGTH:
431 if vstatus.cur_pin == '':
432 vstatus.cur_user = ''
433 reset_idler(v, vstatus)
437 vstatus.mk.set_message('PIN: ')
439 vstatus.cur_pin += chr(key + ord('0'))
440 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
441 if len(vstatus.cur_pin) == PIN_LENGTH:
442 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
445 vstatus.cur_selection = ''
446 vstatus.change_state(STATE_GET_SELECTION)
447 scroll_options(vstatus.username, vstatus.mk, True)
451 vstatus.mk.set_messages(
452 [(center('BAD PIN'), False, 1.0),
453 (center('SORRY'), False, 0.5)])
454 vstatus.cur_user = ''
457 reset_idler(v, vstatus, 2)
462 def handle_getting_uid_key(state, event, params, v, vstatus):
463 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
465 # complicated key handling here:
466 if len(vstatus.cur_user) < 5:
468 vstatus.cur_user = ''
470 reset_idler(v, vstatus)
473 vstatus.cur_user += chr(key + ord('0'))
474 vstatus.mk.set_message('UID: '+vstatus.cur_user)
476 if len(vstatus.cur_user) == 5:
477 uid = int(vstatus.cur_user)
479 logging.info('user '+vstatus.cur_user+' has a bad PIN')
485 Welcome to Picklevision Sytems, Sunnyvale, CA
487 Greetings Professor Falken.
492 Shall we play a game?
495 Please choose from the following menu:
502 6. Toxic and Biochemical Warfare
503 7. Global Thermonuclear War
507 Wouldn't you prefer a nice game of chess?
509 """.replace('\n',' ')
510 vstatus.mk.set_messages([(pfalken, False, 10)])
511 vstatus.cur_user = ''
514 reset_idler(v, vstatus, 10)
518 if not has_good_pin(uid):
519 logging.info('user '+vstatus.cur_user+' has a bad PIN')
520 vstatus.mk.set_messages(
521 [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
522 vstatus.cur_user = ''
525 reset_idler(v, vstatus, 3)
531 vstatus.mk.set_message('PIN: ')
532 logging.info('need pin for user %s'%vstatus.cur_user)
533 vstatus.change_state(STATE_GETTING_PIN)
537 def handle_idle_key(state, event, params, v, vstatus):
538 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
543 vstatus.cur_user = ''
544 reset_idler(v, vstatus)
547 vstatus.change_state(STATE_GETTING_UID)
548 run_handler(event, key, v, vstatus)
551 def handle_idle_tick(state, event, params, v, vstatus):
553 if vstatus.mk.done():
556 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
557 vstatus.time_of_next_idler = time() + 30
562 vstatus.mk.update_display()
564 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
565 run_handler(event, params, v, vstatus)
568 def beep_on(when, before=0):
569 start = int(when - before)
573 if now >= start and now <= end:
577 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
578 ### check for interesting times
581 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
582 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
583 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
584 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
586 hourfromnow = localtime(time() + 3600)
588 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
589 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
590 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
592 ## check for X seconds to the hour
593 ## if case, update counter to 2
594 if beep_on(onthehour,15) \
595 or beep_on(halfhour,0) \
596 or beep_on(quarterhour,0) \
597 or beep_on(threequarterhour,0) \
598 or beep_on(fivetothehour,0):
599 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
600 run_handler(event, params, v, vstatus)
602 vstatus.change_state(STATE_IDLE)
604 def handle_grandfather_tick(state, event, params, v, vstatus):
608 ### we live in interesting times
611 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
612 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
613 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
614 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
616 hourfromnow = localtime(time() + 3600)
618 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
619 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
620 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
623 #print "when it fashionable to wear a onion on your hip"
625 if beep_on(onthehour,15):
627 next_hour=((hourfromnow[3] + 11) % 12) + 1
628 if onthehour - time() < next_hour and onthehour - time() > 0:
633 msg.append(("DING!", False, None))
635 msg.append((" DING!", False, None))
636 elif int(onthehour - time()) == 0:
638 msg.append((" BONG!", False, None))
639 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
640 elif beep_on(halfhour,0):
643 msg.append((" HALFHOUR ", False, 50))
644 elif beep_on(quarterhour,0):
647 msg.append((" QTR HOUR ", False, 50))
648 elif beep_on(threequarterhour,0):
651 msg.append((" 3 QTR HR ", False, 50))
652 elif beep_on(fivetothehour,0):
655 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
659 ## check for X seconds to the hour
662 vstatus.mk.set_messages(msg)
665 vstatus.mk.update_display()
666 ## if no longer case, return to idle
668 ## change idler to be clock
669 if go_idle and vstatus.mk.done():
670 vstatus.change_state(STATE_IDLE,1)
672 def handle_door_idle(state, event, params, v, vstatus):
673 # don't care right now.
676 def handle_door_event(state, event, params, v, vstatus):
677 if params == 1: #door open
678 vstatus.change_state(STATE_DOOR_OPENING)
679 logging.warning("Entering open door mode")
680 v.display("-FEED ME-")
682 vstatus.cur_user = ''
684 elif params == 0: #door closed
685 vstatus.change_state(STATE_DOOR_CLOSING)
686 reset_idler(v, vstatus, 3)
688 logging.warning('Leaving open door mode')
689 v.display("-YUM YUM!-")
691 def return_to_idle(state,event,params,v,vstatus):
692 reset_idler(v, vstatus)
694 def create_state_table(vstatus):
695 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
696 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
697 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
699 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
700 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
701 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
703 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
704 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
705 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
707 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
708 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
709 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
711 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
712 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
713 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
715 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
716 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
717 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
719 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
720 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
721 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
722 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
723 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
724 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
726 def get_state_table_handler(vstatus, state, event, counter):
727 return vstatus.state_table[(state,event,counter)]
729 def time_to_next_update(vstatus):
730 idle_update = vstatus.time_of_next_idlestep - time()
731 if not vstatus.mk.done() and vstatus.mk.next_update is not None:
732 mk_update = vstatus.mk.next_update - time()
733 if mk_update < idle_update:
734 idle_update = mk_update
737 def run_forever(rfh, wfh, options, cf):
738 v = VendingMachine(rfh, wfh)
739 vstatus = VendState(v)
740 create_state_table(vstatus)
742 logging.debug('PING is ' + str(v.ping()))
744 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
747 reset_idler(v, vstatus)
749 # This main loop was hideous and the work of the devil.
750 # This has now been fixed (mostly) - mtearle
753 # notes for later surgery
754 # (event, counter, ' ')
758 # ( return state - not currently implemented )
764 except DispenseDatabaseException, e:
765 logging.error('Database error: '+str(e))
768 timeout = time_to_next_update(vstatus)
769 e = v.next_event(timeout)
772 run_handler(event, params, v, vstatus)
774 # logging.debug('Got event: ' + repr(e))
777 def run_handler(event, params, v, vstatus):
778 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
780 handler(vstatus.state, event, params, v, vstatus)
782 def connect_to_vend(options, cf):
785 logging.info('Connecting to vending machine using LAT')
786 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
787 rfh, wfh = latclient.get_fh()
788 elif options.use_serial:
789 # Open vending machine via serial.
790 logging.info('Connecting to vending machine using serial')
791 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
792 rfh,wfh = serialclient.get_fh()
794 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
795 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
797 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
798 sock.connect((options.host, options.port))
799 rfh = sock.makefile('r')
800 wfh = sock.makefile('w')
805 from optparse import OptionParser
807 op = OptionParser(usage="%prog [OPTION]...")
808 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')
809 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
810 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
811 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
812 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
813 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
814 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
815 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
816 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
817 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
818 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
819 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
820 options, args = op.parse_args()
823 op.error('extra command line arguments: ' + ' '.join(args))
828 'DBServer': ('Database', 'Server'),
829 'DBName': ('Database', 'Name'),
830 'DBUser': ('VendingMachine', 'DBUser'),
831 'DBPassword': ('VendingMachine', 'DBPassword'),
833 'ServiceName': ('VendingMachine', 'ServiceName'),
834 'ServicePassword': ('VendingMachine', 'Password'),
836 'ServerName': ('DecServer', 'Name'),
837 'ConnectPassword': ('DecServer', 'ConnectPassword'),
838 'PrivPassword': ('DecServer', 'PrivPassword'),
841 class VendConfigFile:
842 def __init__(self, config_file, options):
844 cp = ConfigParser.ConfigParser()
847 for option in options:
848 section, name = options[option]
849 value = cp.get(section, name)
850 self.__dict__[option] = value
852 except ConfigParser.Error, e:
853 raise SystemExit("Error reading config file "+config_file+": " + str(e))
855 def create_pid_file(name):
857 pid_file = file(name, 'w')
858 pid_file.write('%d\n'%os.getpid())
861 logging.warning('unable to write to pid file '+name+': '+str(e))
864 def do_nothing(signum, stack):
865 signal.signal(signum, do_nothing)
866 def stop_server(signum, stack): raise KeyboardInterrupt
867 signal.signal(signal.SIGHUP, do_nothing)
868 signal.signal(signal.SIGTERM, stop_server)
869 signal.signal(signal.SIGINT, stop_server)
871 options = parse_args()
872 config_opts = VendConfigFile(options.config_file, config_options)
873 if options.daemon: become_daemon()
874 set_up_logging(options)
875 if options.pid_file != '': create_pid_file(options.pid_file)
877 return options, config_opts
879 def clean_up_nicely(options, config_opts):
880 if options.pid_file != '':
882 os.unlink(options.pid_file)
883 logging.debug('Removed pid file '+options.pid_file)
884 except OSError: pass # if we can't delete it, meh
886 def set_up_logging(options):
887 logger = logging.getLogger()
889 if not options.daemon:
890 stderr_logger = logging.StreamHandler(sys.stderr)
891 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
892 logger.addHandler(stderr_logger)
894 if options.log_file != '':
896 file_logger = logging.FileHandler(options.log_file)
897 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
898 logger.addHandler(file_logger)
900 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
902 if options.syslog != None:
903 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
904 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
905 logger.addHandler(sys_logger)
908 logger.setLevel(logging.WARNING)
909 elif options.verbose:
910 logger.setLevel(logging.DEBUG)
912 logger.setLevel(logging.INFO)
915 dev_null = file('/dev/null')
916 fd = dev_null.fileno()
925 raise SystemExit('failed to fork: '+str(e))
927 def do_vend_server(options, config_opts):
930 rfh, wfh = connect_to_vend(options, config_opts)
931 except (SerialClientException, socket.error), e:
932 (exc_type, exc_value, exc_traceback) = sys.exc_info()
934 logging.error("Connection error: "+str(exc_type)+" "+str(e))
935 logging.info("Trying again in 5 seconds.")
940 run_forever(rfh, wfh, options, config_opts)
941 except VendingException:
942 logging.error("Connection died, trying again...")
943 logging.info("Trying again in 5 seconds.")
946 if __name__ == '__main__':
947 options, config_opts = set_stuff_up()
950 logging.warning('Starting Vend Server')
951 do_vend_server(options, config_opts)
952 logging.error('Vend Server finished unexpectedly, restarting')
953 except KeyboardInterrupt:
954 logging.info("Killed by signal, cleaning up")
955 clean_up_nicely(options, config_opts)
956 logging.warning("Vend Server stopped")
961 (exc_type, exc_value, exc_traceback) = sys.exc_info()
962 tb = format_tb(exc_traceback, 20)
965 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
966 logging.critical("Message: " + str(exc_value))
967 logging.critical("Traceback:")
969 for line in event.split('\n'):
970 logging.critical(' '+line)
971 logging.critical("This message should be considered a bug in the Vend Server.")
972 logging.critical("Please report this to someone who can fix it.")
974 logging.warning("Trying again anyway (might not help, but hey...)")