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
21 from posix import geteuid
24 This vending machine software brought to you by:
29 and a collective of hungry alpacas.
33 For a good time call +61 8 6488 3901
48 STATE_DOOR_OPENING = 2
49 STATE_DOOR_CLOSING = 3
52 STATE_GET_SELECTION = 6
53 STATE_GRANDFATHER_CLOCK = 7
58 class DispenseDatabaseException(Exception): pass
60 class DispenseDatabase:
61 def __init__(self, vending_machine, host, name, user, password):
62 self.vending_machine = vending_machine
63 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
64 self.db.query('LISTEN vend_requests')
66 def process_requests(self):
67 logging.debug('database processing')
68 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
70 outstanding = self.db.query(query).getresult()
71 except (pg.error,), db_err:
72 raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
73 for (id, slot) in outstanding:
74 (worked, code, string) = self.vending_machine.vend(slot)
75 logging.debug (str((worked, code, string)))
77 query = 'SELECT vend_success(%s)'%id
78 self.db.query(query).getresult()
80 query = 'SELECT vend_failed(%s)'%id
81 self.db.query(query).getresult()
83 def handle_events(self):
84 notifier = self.db.getnotify()
85 while notifier is not None:
86 self.process_requests()
87 notify = self.db.getnotify()
89 def scroll_options(username, mk, welcome = False):
91 msg = [(center('WELCOME'), False, TEXT_SPEED),
92 (center(username), False, TEXT_SPEED)]
95 choices = ' '*10+'CHOICES: '
97 coke_machine = file('/home/other/coke/coke_contents')
98 cokes = coke_machine.readlines()
105 (slot_num, price, slot_name) = c.split(' ', 2)
106 if slot_name == 'dead': continue
107 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
109 # we don't want to print snacks for now since it'll be too large
110 # and there's physical bits of paper in the machine anyway - matt
112 # snacks = get_snacks()
116 # for slot, ( name, price ) in snacks.items():
117 # choices += '%s8-%s (%sc) ' % ( slot, name, price )
119 choices += '55-DOOR '
120 choices += 'OR ANOTHER SNACK. '
121 choices += '99 TO READ AGAIN. '
122 choices += 'CHOICE? '
123 msg.append((choices, False, None))
128 info = pwd.getpwuid(uid)
130 logging.info('getting pin for uid %d: user not in password file'%uid)
132 if info.pw_dir == None: return False
133 pinfile = os.path.join(info.pw_dir, '.pin')
137 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
140 logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
141 os.chmod(pinfile, 0600)
145 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
147 pinstr = f.readline()
149 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
150 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
154 def has_good_pin(uid):
155 return get_pin(uid) != None
157 def verify_user_pin(uid, pin):
158 if get_pin(uid) == pin:
159 info = pwd.getpwuid(uid)
160 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
163 logging.info('refused pin for uid %d'%(uid))
169 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
170 choice = int(random()*len(messages))
171 msg = messages[choice]
172 left = range(len(msg))
173 for i in range(len(msg)):
174 if msg[i] == ' ': left.remove(i)
178 for i in range(0, len(msg)):
184 s += chr(int(random()*26)+ord('A'))
193 return ' '*((LEN-len(str))/2)+str
204 StringIdler(v, text="Kill 'em all", repeat=False),
205 GrayIdler(v,one="*",zero="-"),
206 StringIdler(v, text=CREDITS),
207 GrayIdler(v,one="/",zero="\\"),
209 GrayIdler(v,one="X",zero="O"),
210 FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
211 GrayIdler(v,one="*",zero="-",reorder=1),
212 StringIdler(v, text=str(math.pi) + " "),
214 GrayIdler(v,one="/",zero="\\",reorder=1),
215 StringIdler(v, text=str(math.e) + " "),
216 GrayIdler(v,one="X",zero="O",reorder=1),
217 StringIdler(v, text=" I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 - and order as Quinn - I am getting really hungry", repeat=False),
218 PipeIdler(v, "/usr/bin/getent", "passwd"),
227 def reset_idler(v, vstatus, t = None):
229 idler = GreetingIdler(v, t)
230 vstatus.time_of_next_idlestep = time()+idler.next()
231 vstatus.time_of_next_idler = None
232 vstatus.change_state(STATE_IDLE, 1)
237 average_affinity = 10 # guessing here...
239 if idler and idler.__class__ != GreetingIdler:
240 iiindex = idlers.index(idler)
244 move = int(random()*len(idlers)*average_affinity) + 1
249 idler = idlers[iiindex]
250 move -= idler.affinity()
254 def idle_step(vstatus):
258 vstatus.time_of_next_idler = time() + 30
259 nextidle = idler.next()
261 nextidle = IDLE_SPEED
262 vstatus.time_of_next_idlestep = time()+nextidle
265 def __init__(self,v):
266 self.state_table = {}
267 self.state = STATE_IDLE
270 self.mk = MessageKeeper(v)
274 self.cur_selection = ''
275 self.time_to_autologout = None
277 self.last_timeout_refresh = None
279 def change_state(self,newstate,newcounter=None):
280 if self.state != newstate:
281 #print "Changing state from: ",
285 self.state = newstate
287 if newcounter is not None and self.counter != newcounter:
288 #print "Changing counter from: ",
292 self.counter = newcounter
296 def handle_tick_event(event, params, v, vstatus):
297 # don't care right now.
300 def handle_switch_event(event, params, v, vstatus):
301 # don't care right now.
305 def do_nothing(state, event, params, v, vstatus):
306 print "doing nothing (s,e,p)", state, " ", event, " ", params
309 def handle_getting_uid_idle(state, event, params, v, vstatus):
310 # don't care right now.
313 def handle_getting_pin_idle(state, event, params, v, vstatus):
314 # don't care right now.
317 def handle_get_selection_idle(state, event, params, v, vstatus):
318 # don't care right now.
320 ### State logging out ..
321 if vstatus.time_to_autologout != None:
322 time_left = vstatus.time_to_autologout - time()
323 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
324 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
325 vstatus.last_timeout_refresh = int(time_left)
326 vstatus.cur_selection = ''
328 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
329 vstatus.time_to_autologout = None
330 vstatus.cur_user = ''
332 vstatus.cur_selection = ''
334 reset_idler(v, vstatus)
336 ### State fully logged out ... reset variables
337 if vstatus.time_to_autologout and not vstatus.mk.done():
338 vstatus.time_to_autologout = None
339 if vstatus.cur_user == '' and vstatus.time_to_autologout:
340 vstatus.time_to_autologout = None
343 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
345 vstatus.time_to_autologout = time() + 15
346 vstatus.last_timeout_refresh = None
348 ## FIXME - this may need to be elsewhere.....
350 vstatus.mk.update_display()
354 def handle_get_selection_key(state, event, params, v, vstatus):
356 if len(vstatus.cur_selection) == 0:
359 vstatus.cur_user = ''
360 vstatus.cur_selection = ''
362 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
363 reset_idler(v, vstatus, 2)
365 vstatus.cur_selection += chr(key + ord('0'))
366 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
367 vstatus.time_to_autologout = None
368 elif len(vstatus.cur_selection) == 1:
370 vstatus.cur_selection = ''
371 vstatus.time_to_autologout = None
372 scroll_options(vstatus.username, vstatus.mk)
375 vstatus.cur_selection += chr(key + ord('0'))
376 make_selection(v,vstatus)
377 vstatus.cur_selection = ''
378 vstatus.time_to_autologout = time() + 8
379 vstatus.last_timeout_refresh = None
381 def make_selection(v, vstatus):
382 # should use sudo here
383 if vstatus.cur_selection == '55':
384 vstatus.mk.set_message('OPENSESAME')
385 logging.info('dispensing a door for %s'%vstatus.username)
387 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
389 ret = os.system('dispense door')
391 logging.info('door opened')
392 vstatus.mk.set_message(center('DOOR OPEN'))
394 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
395 vstatus.mk.set_message(center('BAD DOOR'))
397 elif vstatus.cur_selection == '91':
399 elif vstatus.cur_selection == '99':
400 scroll_options(vstatus.username, vstatus.mk)
401 vstatus.cur_selection = ''
403 elif vstatus.cur_selection[1] == '8':
404 v.display('GOT COKE?')
405 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
406 v.display('SEEMS NOT')
408 v.display('GOT COKE!')
410 # first see if it's a named slot
412 price, shortname, name = get_snack( vstatus.cur_selection )
414 price, shortname, name = get_snack( '--' )
415 dollarprice = "$%.2f" % ( price / 100.0 )
416 v.display(vstatus.cur_selection+' - %s'%dollarprice)
417 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, shortname)) >> 8) == 0):
418 v.vend(vstatus.cur_selection)
419 v.display('THANK YOU')
421 v.display('NO MONEY?')
425 def handle_getting_pin_key(state, event, params, v, vstatus):
426 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
428 if len(vstatus.cur_pin) < PIN_LENGTH:
430 if vstatus.cur_pin == '':
431 vstatus.cur_user = ''
432 reset_idler(v, vstatus)
436 vstatus.mk.set_message('PIN: ')
438 vstatus.cur_pin += chr(key + ord('0'))
439 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
440 if len(vstatus.cur_pin) == PIN_LENGTH:
441 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
444 vstatus.cur_selection = ''
445 vstatus.change_state(STATE_GET_SELECTION)
446 scroll_options(vstatus.username, vstatus.mk, True)
450 vstatus.mk.set_messages(
451 [(center('BAD PIN'), False, 1.0),
452 (center('SORRY'), False, 0.5)])
453 vstatus.cur_user = ''
456 reset_idler(v, vstatus, 2)
461 def handle_getting_uid_key(state, event, params, v, vstatus):
462 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
464 # complicated key handling here:
465 if len(vstatus.cur_user) < 5:
467 vstatus.cur_user = ''
469 reset_idler(v, vstatus)
472 vstatus.cur_user += chr(key + ord('0'))
473 vstatus.mk.set_message('UID: '+vstatus.cur_user)
475 if len(vstatus.cur_user) == 5:
476 uid = int(vstatus.cur_user)
478 logging.info('user '+vstatus.cur_user+' has a bad PIN')
484 Welcome to Picklevision Sytems, Sunnyvale, CA
486 Greetings Professor Falken.
491 Shall we play a game?
494 Please choose from the following menu:
501 6. Toxic and Biochemical Warfare
502 7. Global Thermonuclear War
506 Wouldn't you prefer a nice game of chess?
508 """.replace('\n',' ')
509 vstatus.mk.set_messages([(pfalken, False, 10)])
510 vstatus.cur_user = ''
513 reset_idler(v, vstatus, 10)
517 if not has_good_pin(uid):
518 logging.info('user '+vstatus.cur_user+' has a bad PIN')
519 vstatus.mk.set_messages(
520 [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
521 vstatus.cur_user = ''
524 reset_idler(v, vstatus, 3)
530 vstatus.mk.set_message('PIN: ')
531 logging.info('need pin for user %s'%vstatus.cur_user)
532 vstatus.change_state(STATE_GETTING_PIN)
536 def handle_idle_key(state, event, params, v, vstatus):
537 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
542 vstatus.cur_user = ''
543 reset_idler(v, vstatus)
546 vstatus.change_state(STATE_GETTING_UID)
547 run_handler(event, key, v, vstatus)
550 def handle_idle_tick(state, event, params, v, vstatus):
552 if vstatus.mk.done():
555 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
556 vstatus.time_of_next_idler = time() + 30
561 vstatus.mk.update_display()
563 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
564 run_handler(event, params, v, vstatus)
567 def beep_on(when, before=0):
568 start = int(when - before)
572 if now >= start and now <= end:
576 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
577 ### check for interesting times
580 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
581 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
582 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
583 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
585 hourfromnow = localtime(time() + 3600)
587 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
588 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
589 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
591 ## check for X seconds to the hour
592 ## if case, update counter to 2
593 if beep_on(onthehour,15) \
594 or beep_on(halfhour,0) \
595 or beep_on(quarterhour,0) \
596 or beep_on(threequarterhour,0) \
597 or beep_on(fivetothehour,0):
598 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
599 run_handler(event, params, v, vstatus)
601 vstatus.change_state(STATE_IDLE)
603 def handle_grandfather_tick(state, event, params, v, vstatus):
607 ### we live in interesting times
610 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
611 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
612 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
613 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
615 hourfromnow = localtime(time() + 3600)
617 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
618 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
619 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
622 #print "when it fashionable to wear a onion on your hip"
624 if beep_on(onthehour,15):
626 next_hour=((hourfromnow[3] + 11) % 12) + 1
627 if onthehour - time() < next_hour and onthehour - time() > 0:
632 msg.append(("DING!", False, None))
634 msg.append((" DING!", False, None))
635 elif int(onthehour - time()) == 0:
637 msg.append((" BONG!", False, None))
638 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
639 elif beep_on(halfhour,0):
642 msg.append((" HALFHOUR ", False, 50))
643 elif beep_on(quarterhour,0):
646 msg.append((" QTR HOUR ", False, 50))
647 elif beep_on(threequarterhour,0):
650 msg.append((" 3 QTR HR ", False, 50))
651 elif beep_on(fivetothehour,0):
654 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
658 ## check for X seconds to the hour
661 vstatus.mk.set_messages(msg)
664 vstatus.mk.update_display()
665 ## if no longer case, return to idle
667 ## change idler to be clock
668 if go_idle and vstatus.mk.done():
669 vstatus.change_state(STATE_IDLE,1)
671 def handle_door_idle(state, event, params, v, vstatus):
672 # don't care right now.
675 def handle_door_event(state, event, params, v, vstatus):
676 if params == 1: #door open
677 vstatus.change_state(STATE_DOOR_OPENING)
678 logging.warning("Entering open door mode")
679 v.display("-FEED ME-")
681 vstatus.cur_user = ''
683 elif params == 0: #door closed
684 vstatus.change_state(STATE_DOOR_CLOSING)
685 reset_idler(v, vstatus, 3)
687 logging.warning('Leaving open door mode')
688 v.display("-YUM YUM!-")
690 def return_to_idle(state,event,params,v,vstatus):
691 reset_idler(v, vstatus)
693 def create_state_table(vstatus):
694 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
695 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
696 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
698 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
699 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
700 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
702 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
703 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
704 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
706 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
707 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
708 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
710 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
711 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
712 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
714 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
715 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
716 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
718 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
719 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
720 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
721 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
722 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
723 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
725 def get_state_table_handler(vstatus, state, event, counter):
726 return vstatus.state_table[(state,event,counter)]
728 def time_to_next_update(vstatus):
729 idle_update = vstatus.time_of_next_idlestep - time()
730 if not vstatus.mk.done() and vstatus.mk.next_update is not None:
731 mk_update = vstatus.mk.next_update - time()
732 if mk_update < idle_update:
733 idle_update = mk_update
736 def run_forever(rfh, wfh, options, cf):
737 v = VendingMachine(rfh, wfh)
738 vstatus = VendState(v)
739 create_state_table(vstatus)
741 logging.debug('PING is ' + str(v.ping()))
743 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
746 reset_idler(v, vstatus)
748 # This main loop was hideous and the work of the devil.
749 # This has now been fixed (mostly) - mtearle
752 # notes for later surgery
753 # (event, counter, ' ')
757 # ( return state - not currently implemented )
763 except DispenseDatabaseException, e:
764 logging.error('Database error: '+str(e))
767 timeout = time_to_next_update(vstatus)
768 e = v.next_event(timeout)
771 run_handler(event, params, v, vstatus)
773 # logging.debug('Got event: ' + repr(e))
776 def run_handler(event, params, v, vstatus):
777 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
779 handler(vstatus.state, event, params, v, vstatus)
781 def connect_to_vend(options, cf):
784 logging.info('Connecting to vending machine using LAT')
785 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
786 rfh, wfh = latclient.get_fh()
787 elif options.use_serial:
788 # Open vending machine via serial.
789 logging.info('Connecting to vending machine using serial')
790 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
791 rfh,wfh = serialclient.get_fh()
793 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
794 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
796 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
797 sock.connect((options.host, options.port))
798 rfh = sock.makefile('r')
799 wfh = sock.makefile('w')
804 from optparse import OptionParser
806 op = OptionParser(usage="%prog [OPTION]...")
807 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')
808 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
809 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
810 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
811 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
812 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
813 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
814 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
815 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
816 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
817 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
818 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
819 options, args = op.parse_args()
822 op.error('extra command line arguments: ' + ' '.join(args))
827 'DBServer': ('Database', 'Server'),
828 'DBName': ('Database', 'Name'),
829 'DBUser': ('VendingMachine', 'DBUser'),
830 'DBPassword': ('VendingMachine', 'DBPassword'),
832 'ServiceName': ('VendingMachine', 'ServiceName'),
833 'ServicePassword': ('VendingMachine', 'Password'),
835 'ServerName': ('DecServer', 'Name'),
836 'ConnectPassword': ('DecServer', 'ConnectPassword'),
837 'PrivPassword': ('DecServer', 'PrivPassword'),
840 class VendConfigFile:
841 def __init__(self, config_file, options):
843 cp = ConfigParser.ConfigParser()
846 for option in options:
847 section, name = options[option]
848 value = cp.get(section, name)
849 self.__dict__[option] = value
851 except ConfigParser.Error, e:
852 raise SystemExit("Error reading config file "+config_file+": " + str(e))
854 def create_pid_file(name):
856 pid_file = file(name, 'w')
857 pid_file.write('%d\n'%os.getpid())
860 logging.warning('unable to write to pid file '+name+': '+str(e))
863 def do_nothing(signum, stack):
864 signal.signal(signum, do_nothing)
865 def stop_server(signum, stack): raise KeyboardInterrupt
866 signal.signal(signal.SIGHUP, do_nothing)
867 signal.signal(signal.SIGTERM, stop_server)
868 signal.signal(signal.SIGINT, stop_server)
870 options = parse_args()
871 config_opts = VendConfigFile(options.config_file, config_options)
872 if options.daemon: become_daemon()
873 set_up_logging(options)
874 if options.pid_file != '': create_pid_file(options.pid_file)
876 return options, config_opts
878 def clean_up_nicely(options, config_opts):
879 if options.pid_file != '':
881 os.unlink(options.pid_file)
882 logging.debug('Removed pid file '+options.pid_file)
883 except OSError: pass # if we can't delete it, meh
885 def set_up_logging(options):
886 logger = logging.getLogger()
888 if not options.daemon:
889 stderr_logger = logging.StreamHandler(sys.stderr)
890 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
891 logger.addHandler(stderr_logger)
893 if options.log_file != '':
895 file_logger = logging.FileHandler(options.log_file)
896 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
897 logger.addHandler(file_logger)
899 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
901 if options.syslog != None:
902 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
903 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
904 logger.addHandler(sys_logger)
907 logger.setLevel(logging.WARNING)
908 elif options.verbose:
909 logger.setLevel(logging.DEBUG)
911 logger.setLevel(logging.INFO)
914 dev_null = file('/dev/null')
915 fd = dev_null.fileno()
924 raise SystemExit('failed to fork: '+str(e))
926 def do_vend_server(options, config_opts):
929 rfh, wfh = connect_to_vend(options, config_opts)
930 except (SerialClientException, socket.error), e:
931 (exc_type, exc_value, exc_traceback) = sys.exc_info()
933 logging.error("Connection error: "+str(exc_type)+" "+str(e))
934 logging.info("Trying again in 5 seconds.")
939 run_forever(rfh, wfh, options, config_opts)
940 except VendingException:
941 logging.error("Connection died, trying again...")
942 logging.info("Trying again in 5 seconds.")
945 if __name__ == '__main__':
946 options, config_opts = set_stuff_up()
949 logging.warning('Starting Vend Server')
950 do_vend_server(options, config_opts)
951 logging.error('Vend Server finished unexpectedly, restarting')
952 except KeyboardInterrupt:
953 logging.info("Killed by signal, cleaning up")
954 clean_up_nicely(options, config_opts)
955 logging.warning("Vend Server stopped")
960 (exc_type, exc_value, exc_traceback) = sys.exc_info()
961 tb = format_tb(exc_traceback, 20)
964 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
965 logging.critical("Message: " + str(exc_value))
966 logging.critical("Traceback:")
968 for line in event.split('\n'):
969 logging.critical(' '+line)
970 logging.critical("This message should be considered a bug in the Vend Server.")
971 logging.critical("Please report this to someone who can fix it.")
973 logging.warning("Trying again anyway (might not help, but hey...)")