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)
108 choices += '55-DOOR '
109 choices += 'OR A SNACK. '
110 choices += '99 TO READ AGAIN. '
111 choices += 'CHOICE? '
112 msg.append((choices, False, None))
117 info = pwd.getpwuid(uid)
119 logging.info('getting pin for uid %d: user not in password file'%uid)
121 if info.pw_dir == None: return False
122 pinfile = os.path.join(info.pw_dir, '.pin')
126 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
129 logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
130 os.chmod(pinfile, 0600)
134 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
136 pinstr = f.readline()
138 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
139 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
143 def has_good_pin(uid):
144 return get_pin(uid) != None
146 def verify_user_pin(uid, pin):
147 if get_pin(uid) == pin:
148 info = pwd.getpwuid(uid)
149 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
152 logging.info('refused pin for uid %d'%(uid))
158 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
159 choice = int(random()*len(messages))
160 msg = messages[choice]
161 left = range(len(msg))
162 for i in range(len(msg)):
163 if msg[i] == ' ': left.remove(i)
167 for i in range(0, len(msg)):
173 s += chr(int(random()*26)+ord('A'))
182 return ' '*((LEN-len(str))/2)+str
193 StringIdler(v, text="Kill 'em all", repeat=False),
194 GrayIdler(v,one="*",zero="-"),
195 StringIdler(v, text=CREDITS),
196 GrayIdler(v,one="/",zero="\\"),
198 GrayIdler(v,one="X",zero="O"),
199 FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
200 GrayIdler(v,one="*",zero="-",reorder=1),
201 StringIdler(v, text=str(math.pi) + " "),
203 GrayIdler(v,one="/",zero="\\",reorder=1),
204 StringIdler(v, text=str(math.e) + " "),
205 GrayIdler(v,one="X",zero="O",reorder=1),
206 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),
207 PipeIdler(v, "/usr/bin/getent", "passwd"),
216 def reset_idler(v, vstatus, t = None):
218 idler = GreetingIdler(v, t)
219 vstatus.time_of_next_idlestep = time()+idler.next()
220 vstatus.time_of_next_idler = None
221 vstatus.change_state(STATE_IDLE, 1)
226 average_affinity = 10 # guessing here...
228 if idler and idler.__class__ != GreetingIdler:
229 iiindex = idlers.index(idler)
233 move = int(random()*len(idlers)*average_affinity) + 1
238 idler = idlers[iiindex]
239 move -= idler.affinity()
243 def idle_step(vstatus):
247 vstatus.time_of_next_idler = time() + 30
248 nextidle = idler.next()
250 nextidle = IDLE_SPEED
251 vstatus.time_of_next_idlestep = time()+nextidle
254 def __init__(self,v):
255 self.state_table = {}
256 self.state = STATE_IDLE
259 self.mk = MessageKeeper(v)
263 self.cur_selection = ''
264 self.time_to_autologout = None
266 self.last_timeout_refresh = None
268 def change_state(self,newstate,newcounter=None):
269 if self.state != newstate:
270 #print "Changing state from: ",
274 self.state = newstate
276 if newcounter is not None and self.counter != newcounter:
277 #print "Changing counter from: ",
281 self.counter = newcounter
285 def handle_tick_event(event, params, v, vstatus):
286 # don't care right now.
289 def handle_switch_event(event, params, v, vstatus):
290 # don't care right now.
294 def do_nothing(state, event, params, v, vstatus):
295 print "doing nothing (s,e,p)", state, " ", event, " ", params
298 def handle_getting_uid_idle(state, event, params, v, vstatus):
299 # don't care right now.
302 def handle_getting_pin_idle(state, event, params, v, vstatus):
303 # don't care right now.
306 def handle_get_selection_idle(state, event, params, v, vstatus):
307 # don't care right now.
309 ### State logging out ..
310 if vstatus.time_to_autologout != None:
311 time_left = vstatus.time_to_autologout - time()
312 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
313 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
314 vstatus.last_timeout_refresh = int(time_left)
315 vstatus.cur_selection = ''
317 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
318 vstatus.time_to_autologout = None
319 vstatus.cur_user = ''
321 vstatus.cur_selection = ''
323 reset_idler(v, vstatus)
325 ### State fully logged out ... reset variables
326 if vstatus.time_to_autologout and not vstatus.mk.done():
327 vstatus.time_to_autologout = None
328 if vstatus.cur_user == '' and vstatus.time_to_autologout:
329 vstatus.time_to_autologout = None
332 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
334 vstatus.time_to_autologout = time() + 15
335 vstatus.last_timeout_refresh = None
337 ## FIXME - this may need to be elsewhere.....
339 vstatus.mk.update_display()
343 def handle_get_selection_key(state, event, params, v, vstatus):
345 if len(vstatus.cur_selection) == 0:
348 vstatus.cur_user = ''
349 vstatus.cur_selection = ''
351 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
352 reset_idler(v, vstatus, 2)
354 vstatus.cur_selection += chr(key + ord('0'))
355 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
356 vstatus.time_to_autologout = None
357 elif len(vstatus.cur_selection) == 1:
359 vstatus.cur_selection = ''
360 vstatus.time_to_autologout = None
361 scroll_options(vstatus.username, vstatus.mk)
364 vstatus.cur_selection += chr(key + ord('0'))
365 make_selection(v,vstatus)
366 vstatus.cur_selection = ''
367 vstatus.time_to_autologout = time() + 8
368 vstatus.last_timeout_refresh = None
370 def make_selection(v, vstatus):
371 # should use sudo here
372 if vstatus.cur_selection == '55':
373 vstatus.mk.set_message('OPENSESAME')
374 logging.info('dispensing a door for %s'%vstatus.username)
376 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
378 ret = os.system('dispense door')
380 logging.info('door opened')
381 vstatus.mk.set_message(center('DOOR OPEN'))
383 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
384 vstatus.mk.set_message(center('BAD DOOR'))
386 elif vstatus.cur_selection == '91':
388 elif vstatus.cur_selection == '99':
389 scroll_options(vstatus.username, vstatus.mk)
390 vstatus.cur_selection = ''
392 elif vstatus.cur_selection[1] == '8':
393 v.display('GOT COKE?')
394 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
395 v.display('SEEMS NOT')
397 v.display('GOT COKE!')
399 v.display(vstatus.cur_selection+' - $1.00')
400 if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
401 v.vend(vstatus.cur_selection)
402 v.display('THANK YOU')
404 v.display('NO MONEY?')
408 def handle_getting_pin_key(state, event, params, v, vstatus):
409 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
411 if len(vstatus.cur_pin) < PIN_LENGTH:
413 if vstatus.cur_pin == '':
414 vstatus.cur_user = ''
415 reset_idler(v, vstatus)
419 vstatus.mk.set_message('PIN: ')
421 vstatus.cur_pin += chr(key + ord('0'))
422 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
423 if len(vstatus.cur_pin) == PIN_LENGTH:
424 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
427 vstatus.cur_selection = ''
428 vstatus.change_state(STATE_GET_SELECTION)
429 scroll_options(vstatus.username, vstatus.mk, True)
433 vstatus.mk.set_messages(
434 [(center('BAD PIN'), False, 1.0),
435 (center('SORRY'), False, 0.5)])
436 vstatus.cur_user = ''
439 reset_idler(v, vstatus, 2)
444 def handle_getting_uid_key(state, event, params, v, vstatus):
445 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
447 # complicated key handling here:
448 if len(vstatus.cur_user) < 5:
450 vstatus.cur_user = ''
452 reset_idler(v, vstatus)
455 vstatus.cur_user += chr(key + ord('0'))
456 vstatus.mk.set_message('UID: '+vstatus.cur_user)
458 if len(vstatus.cur_user) == 5:
459 uid = int(vstatus.cur_user)
461 logging.info('user '+vstatus.cur_user+' has a bad PIN')
467 Welcome to Picklevision Sytems, Sunnyvale, CA
469 Greetings Professor Falken.
474 Shall we play a game?
477 Please choose from the following menu:
484 6. Toxic and Biochemical Warfare
485 7. Global Thermonuclear War
489 Wouldn't you prefer a nice game of chess?
491 """.replace('\n',' ')
492 vstatus.mk.set_messages([(pfalken, False, 10)])
493 vstatus.cur_user = ''
496 reset_idler(v, vstatus, 10)
500 if not has_good_pin(uid):
501 logging.info('user '+vstatus.cur_user+' has a bad PIN')
502 vstatus.mk.set_messages(
503 [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
504 vstatus.cur_user = ''
507 reset_idler(v, vstatus, 3)
513 vstatus.mk.set_message('PIN: ')
514 logging.info('need pin for user %s'%vstatus.cur_user)
515 vstatus.change_state(STATE_GETTING_PIN)
519 def handle_idle_key(state, event, params, v, vstatus):
520 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
525 vstatus.cur_user = ''
526 reset_idler(v, vstatus)
529 vstatus.change_state(STATE_GETTING_UID)
530 run_handler(event, key, v, vstatus)
533 def handle_idle_tick(state, event, params, v, vstatus):
535 if vstatus.mk.done():
538 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
539 vstatus.time_of_next_idler = time() + 30
544 vstatus.mk.update_display()
546 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
547 run_handler(event, params, v, vstatus)
550 def beep_on(when, before=0):
551 start = int(when - before)
555 if now >= start and now <= end:
559 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
560 ### check for interesting times
563 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
564 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
565 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
566 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
568 hourfromnow = localtime(time() + 3600)
570 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
571 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
572 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
574 ## check for X seconds to the hour
575 ## if case, update counter to 2
576 if beep_on(onthehour,15) \
577 or beep_on(halfhour,0) \
578 or beep_on(quarterhour,0) \
579 or beep_on(threequarterhour,0) \
580 or beep_on(fivetothehour,0):
581 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
582 run_handler(event, params, v, vstatus)
584 vstatus.change_state(STATE_IDLE)
586 def handle_grandfather_tick(state, event, params, v, vstatus):
590 ### we live in interesting times
593 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
594 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
595 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
596 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
598 hourfromnow = localtime(time() + 3600)
600 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
601 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
602 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
605 #print "when it fashionable to wear a onion on your hip"
607 if beep_on(onthehour,15):
609 next_hour=((hourfromnow[3] + 11) % 12) + 1
610 if onthehour - time() < next_hour and onthehour - time() > 0:
615 msg.append(("DING!", False, None))
617 msg.append((" DING!", False, None))
618 elif int(onthehour - time()) == 0:
620 msg.append((" BONG!", False, None))
621 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
622 elif beep_on(halfhour,0):
625 msg.append((" HALFHOUR ", False, 50))
626 elif beep_on(quarterhour,0):
629 msg.append((" QTR HOUR ", False, 50))
630 elif beep_on(threequarterhour,0):
633 msg.append((" 3 QTR HR ", False, 50))
634 elif beep_on(fivetothehour,0):
637 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
641 ## check for X seconds to the hour
644 vstatus.mk.set_messages(msg)
647 vstatus.mk.update_display()
648 ## if no longer case, return to idle
650 ## change idler to be clock
651 if go_idle and vstatus.mk.done():
652 vstatus.change_state(STATE_IDLE,1)
654 def handle_door_idle(state, event, params, v, vstatus):
655 # don't care right now.
658 def handle_door_event(state, event, params, v, vstatus):
659 if params == 1: #door open
660 vstatus.change_state(STATE_DOOR_OPENING)
661 logging.warning("Entering open door mode")
662 v.display("-FEED ME-")
664 vstatus.cur_user = ''
666 elif params == 0: #door closed
667 vstatus.change_state(STATE_DOOR_CLOSING)
668 reset_idler(v, vstatus, 3)
670 logging.warning('Leaving open door mode')
671 v.display("-YUM YUM!-")
673 def return_to_idle(state,event,params,v,vstatus):
674 reset_idler(v, vstatus)
676 def create_state_table(vstatus):
677 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
678 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
679 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
681 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
682 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
683 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
685 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
686 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
687 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
689 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
690 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
691 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
693 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
694 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
695 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
697 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
698 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
699 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
701 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
702 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
703 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
704 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
705 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
706 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
708 def get_state_table_handler(vstatus, state, event, counter):
709 return vstatus.state_table[(state,event,counter)]
711 def time_to_next_update(vstatus):
712 idle_update = vstatus.time_of_next_idlestep - time()
713 if not vstatus.mk.done() and vstatus.mk.next_update is not None:
714 mk_update = vstatus.mk.next_update - time()
715 if mk_update < idle_update:
716 idle_update = mk_update
719 def run_forever(rfh, wfh, options, cf):
720 v = VendingMachine(rfh, wfh)
721 vstatus = VendState(v)
722 create_state_table(vstatus)
724 logging.debug('PING is ' + str(v.ping()))
726 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
729 reset_idler(v, vstatus)
731 # This main loop was hideous and the work of the devil.
732 # This has now been fixed (mostly) - mtearle
735 # notes for later surgery
736 # (event, counter, ' ')
740 # ( return state - not currently implemented )
746 except DispenseDatabaseException, e:
747 logging.error('Database error: '+str(e))
750 timeout = time_to_next_update(vstatus)
751 e = v.next_event(timeout)
754 run_handler(event, params, v, vstatus)
756 # logging.debug('Got event: ' + repr(e))
759 def run_handler(event, params, v, vstatus):
760 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
762 handler(vstatus.state, event, params, v, vstatus)
764 def connect_to_vend(options, cf):
767 logging.info('Connecting to vending machine using LAT')
768 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
769 rfh, wfh = latclient.get_fh()
770 elif options.use_serial:
771 # Open vending machine via serial.
772 logging.info('Connecting to vending machine using serial')
773 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
774 rfh,wfh = serialclient.get_fh()
776 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
777 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
779 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
780 sock.connect((options.host, options.port))
781 rfh = sock.makefile('r')
782 wfh = sock.makefile('w')
787 from optparse import OptionParser
789 op = OptionParser(usage="%prog [OPTION]...")
790 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')
791 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
792 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
793 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
794 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
795 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
796 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
797 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
798 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
799 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
800 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
801 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
802 options, args = op.parse_args()
805 op.error('extra command line arguments: ' + ' '.join(args))
810 'DBServer': ('Database', 'Server'),
811 'DBName': ('Database', 'Name'),
812 'DBUser': ('VendingMachine', 'DBUser'),
813 'DBPassword': ('VendingMachine', 'DBPassword'),
815 'ServiceName': ('VendingMachine', 'ServiceName'),
816 'ServicePassword': ('VendingMachine', 'Password'),
818 'ServerName': ('DecServer', 'Name'),
819 'ConnectPassword': ('DecServer', 'ConnectPassword'),
820 'PrivPassword': ('DecServer', 'PrivPassword'),
823 class VendConfigFile:
824 def __init__(self, config_file, options):
826 cp = ConfigParser.ConfigParser()
829 for option in options:
830 section, name = options[option]
831 value = cp.get(section, name)
832 self.__dict__[option] = value
834 except ConfigParser.Error, e:
835 raise SystemExit("Error reading config file "+config_file+": " + str(e))
837 def create_pid_file(name):
839 pid_file = file(name, 'w')
840 pid_file.write('%d\n'%os.getpid())
843 logging.warning('unable to write to pid file '+name+': '+str(e))
846 def do_nothing(signum, stack):
847 signal.signal(signum, do_nothing)
848 def stop_server(signum, stack): raise KeyboardInterrupt
849 signal.signal(signal.SIGHUP, do_nothing)
850 signal.signal(signal.SIGTERM, stop_server)
851 signal.signal(signal.SIGINT, stop_server)
853 options = parse_args()
854 config_opts = VendConfigFile(options.config_file, config_options)
855 if options.daemon: become_daemon()
856 set_up_logging(options)
857 if options.pid_file != '': create_pid_file(options.pid_file)
859 return options, config_opts
861 def clean_up_nicely(options, config_opts):
862 if options.pid_file != '':
864 os.unlink(options.pid_file)
865 logging.debug('Removed pid file '+options.pid_file)
866 except OSError: pass # if we can't delete it, meh
868 def set_up_logging(options):
869 logger = logging.getLogger()
871 if not options.daemon:
872 stderr_logger = logging.StreamHandler(sys.stderr)
873 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
874 logger.addHandler(stderr_logger)
876 if options.log_file != '':
878 file_logger = logging.FileHandler(options.log_file)
879 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
880 logger.addHandler(file_logger)
882 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
884 if options.syslog != None:
885 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
886 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
887 logger.addHandler(sys_logger)
890 logger.setLevel(logging.WARNING)
891 elif options.verbose:
892 logger.setLevel(logging.DEBUG)
894 logger.setLevel(logging.INFO)
897 dev_null = file('/dev/null')
898 fd = dev_null.fileno()
907 raise SystemExit('failed to fork: '+str(e))
909 def do_vend_server(options, config_opts):
912 rfh, wfh = connect_to_vend(options, config_opts)
913 except (SerialClientException, socket.error), e:
914 (exc_type, exc_value, exc_traceback) = sys.exc_info()
916 logging.error("Connection error: "+str(exc_type)+" "+str(e))
917 logging.info("Trying again in 5 seconds.")
922 run_forever(rfh, wfh, options, config_opts)
923 except VendingException:
924 logging.error("Connection died, trying again...")
925 logging.info("Trying again in 5 seconds.")
928 if __name__ == '__main__':
929 options, config_opts = set_stuff_up()
932 logging.warning('Starting Vend Server')
933 do_vend_server(options, config_opts)
934 logging.error('Vend Server finished unexpectedly, restarting')
935 except KeyboardInterrupt:
936 logging.info("Killed by signal, cleaning up")
937 clean_up_nicely(options, config_opts)
938 logging.warning("Vend Server stopped")
943 (exc_type, exc_value, exc_traceback) = sys.exc_info()
944 tb = format_tb(exc_traceback, 20)
947 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
948 logging.critical("Message: " + str(exc_value))
949 logging.critical("Traceback:")
951 for line in event.split('\n'):
952 logging.critical(' '+line)
953 logging.critical("This message should be considered a bug in the Vend Server.")
954 logging.critical("Please report this to someone who can fix it.")
956 logging.warning("Trying again anyway (might not help, but hey...)")