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'),
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/ypcat", "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 reset_idler(v, vstatus)
353 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
355 vstatus.cur_selection += chr(key + ord('0'))
356 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
357 vstatus.time_to_autologout = None
358 elif len(vstatus.cur_selection) == 1:
360 vstatus.cur_selection = ''
361 vstatus.time_to_autologout = None
362 scroll_options(vstatus.username, vstatus.mk)
365 vstatus.cur_selection += chr(key + ord('0'))
366 make_selection(v,vstatus)
367 vstatus.cur_selection = ''
368 vstatus.time_to_autologout = time() + 8
369 vstatus.last_timeout_refresh = None
371 def make_selection(v, vstatus):
372 # should use sudo here
373 if vstatus.cur_selection == '55':
374 vstatus.mk.set_message('OPENSESAME')
375 logging.info('dispensing a door for %s'%vstatus.username)
377 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
379 ret = os.system('dispense door')
381 logging.info('door opened')
382 vstatus.mk.set_message(center('DOOR OPEN'))
384 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
385 vstatus.mk.set_message(center('BAD DOOR'))
387 elif vstatus.cur_selection == '91':
389 elif vstatus.cur_selection == '99':
390 scroll_options(vstatus.username, vstatus.mk)
391 vstatus.cur_selection = ''
393 elif vstatus.cur_selection[1] == '8':
394 v.display('GOT COKE?')
395 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
396 v.display('SEEMS NOT')
398 v.display('GOT COKE!')
400 v.display(vstatus.cur_selection+' - $1.00')
401 if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
402 v.vend(vstatus.cur_selection)
403 v.display('THANK YOU')
405 v.display('NO MONEY?')
409 def handle_getting_pin_key(state, event, params, v, vstatus):
410 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
412 if len(vstatus.cur_pin) < PIN_LENGTH:
414 if vstatus.cur_pin == '':
415 vstatus.cur_user = ''
416 reset_idler(v, vstatus)
420 vstatus.mk.set_message('PIN: ')
422 vstatus.cur_pin += chr(key + ord('0'))
423 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
424 if len(vstatus.cur_pin) == PIN_LENGTH:
425 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
428 vstatus.cur_selection = ''
429 vstatus.change_state(STATE_GET_SELECTION)
430 scroll_options(vstatus.username, vstatus.mk, True)
434 vstatus.mk.set_messages(
435 [(center('BAD PIN'), False, 1.0),
436 (center('SORRY'), False, 0.5)])
437 vstatus.cur_user = ''
440 reset_idler(v, vstatus, 2)
445 def handle_getting_uid_key(state, event, params, v, vstatus):
446 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
448 # complicated key handling here:
449 if len(vstatus.cur_user) < 5:
451 vstatus.cur_user = ''
453 reset_idler(v, vstatus)
456 vstatus.cur_user += chr(key + ord('0'))
457 vstatus.mk.set_message('UID: '+vstatus.cur_user)
459 if len(vstatus.cur_user) == 5:
460 uid = int(vstatus.cur_user)
461 if not has_good_pin(uid):
462 logging.info('user '+vstatus.cur_user+' has a bad PIN')
463 vstatus.mk.set_messages(
464 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3)])
465 vstatus.cur_user = ''
468 reset_idler(v, vstatus, 5)
474 vstatus.mk.set_message('PIN: ')
475 logging.info('need pin for user %s'%vstatus.cur_user)
476 vstatus.change_state(STATE_GETTING_PIN)
480 def handle_idle_key(state, event, params, v, vstatus):
481 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
486 vstatus.cur_user = ''
487 reset_idler(v, vstatus)
490 vstatus.change_state(STATE_GETTING_UID)
491 run_handler(event, key, v, vstatus)
494 def handle_idle_tick(state, event, params, v, vstatus):
498 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
499 vstatus.time_of_next_idler = time() + 30
504 vstatus.mk.update_display()
506 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
507 run_handler(event, params, v, vstatus)
510 def beep_on(when, before=0):
511 start = int(when - before)
515 if now >= start and now <= end:
519 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
520 ### check for interesting times
523 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
524 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
525 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
526 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
528 hourfromnow = localtime(time() + 3600)
530 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
531 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
532 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
534 ## check for X seconds to the hour
535 ## if case, update counter to 2
536 if beep_on(onthehour,15) \
537 or beep_on(halfhour,0) \
538 or beep_on(quarterhour,0) \
539 or beep_on(threequarterhour,0) \
540 or beep_on(fivetothehour,0):
541 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
542 run_handler(event, params, v, vstatus)
544 vstatus.change_state(STATE_IDLE)
546 def handle_grandfather_tick(state, event, params, v, vstatus):
550 ### we live in interesting times
553 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
554 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
555 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
556 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
558 hourfromnow = localtime(time() + 3600)
560 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
561 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
562 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
565 #print "when it fashionable to wear a onion on your hip"
567 if beep_on(onthehour,15):
569 next_hour=((hourfromnow[3] + 11) % 12) + 1
570 if onthehour - time() < next_hour and onthehour - time() > 0:
575 msg.append(("DING!", False, None))
577 msg.append((" DING!", False, None))
578 elif int(onthehour - time()) == 0:
580 msg.append((" BONG!", False, None))
581 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
582 elif beep_on(halfhour,0):
585 msg.append((" HALFHOUR ", False, 50))
586 elif beep_on(quarterhour,0):
589 msg.append((" QTR HOUR ", False, 50))
590 elif beep_on(threequarterhour,0):
593 msg.append((" 3 QTR HR ", False, 50))
594 elif beep_on(fivetothehour,0):
597 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
601 ## check for X seconds to the hour
604 vstatus.mk.set_messages(msg)
607 vstatus.mk.update_display()
608 ## if no longer case, return to idle
610 ## change idler to be clock
611 if go_idle and vstatus.mk.done():
612 vstatus.change_state(STATE_IDLE,1)
614 def handle_door_idle(state, event, params, v, vstatus):
615 # don't care right now.
618 def handle_door_event(state, event, params, v, vstatus):
619 if params == 1: #door open
620 vstatus.change_state(STATE_DOOR_OPENING)
621 logging.warning("Entering open door mode")
622 v.display("-FEED ME-")
624 vstatus.cur_user = ''
626 elif params == 0: #door closed
627 vstatus.change_state(STATE_DOOR_CLOSING)
628 reset_idler(v, vstatus, 3)
630 logging.warning('Leaving open door mode')
631 v.display("-YUM YUM!-")
633 def return_to_idle(state,event,params,v,vstatus):
634 reset_idler(v, vstatus)
636 def create_state_table(vstatus):
637 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
638 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
639 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
641 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
642 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
643 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
645 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
646 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
647 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
649 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
650 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
651 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
653 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
654 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
655 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
657 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
658 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
659 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
661 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
662 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
663 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
664 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
665 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
666 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
668 def get_state_table_handler(vstatus, state, event, counter):
669 return vstatus.state_table[(state,event,counter)]
671 def run_forever(rfh, wfh, options, cf):
672 v = VendingMachine(rfh, wfh)
673 vstatus = VendState(v)
674 create_state_table(vstatus)
676 logging.debug('PING is ' + str(v.ping()))
678 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
681 reset_idler(v, vstatus)
683 # This main loop was hideous and the work of the devil.
684 # This has now been fixed (mostly) - mtearle
687 # notes for later surgery
688 # (event, counter, ' ')
692 # ( return state - not currently implemented )
698 except DispenseDatabaseException, e:
699 logging.error('Database error: '+str(e))
702 e = v.next_event(vstatus.time_of_next_idlestep-time())
705 run_handler(event, params, v, vstatus)
707 # logging.debug('Got event: ' + repr(e))
710 def run_handler(event, params, v, vstatus):
711 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
713 handler(vstatus.state, event, params, v, vstatus)
715 def connect_to_vend(options, cf):
718 logging.info('Connecting to vending machine using LAT')
719 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
720 rfh, wfh = latclient.get_fh()
721 elif options.use_serial:
722 # Open vending machine via serial.
723 logging.info('Connecting to vending machine using serial')
724 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
725 rfh,wfh = serialclient.get_fh()
727 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
728 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
730 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
731 sock.connect((options.host, options.port))
732 rfh = sock.makefile('r')
733 wfh = sock.makefile('w')
738 from optparse import OptionParser
740 op = OptionParser(usage="%prog [OPTION]...")
741 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')
742 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
743 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
744 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
745 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
746 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
747 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
748 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
749 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
750 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
751 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
752 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
753 options, args = op.parse_args()
756 op.error('extra command line arguments: ' + ' '.join(args))
761 'DBServer': ('Database', 'Server'),
762 'DBName': ('Database', 'Name'),
763 'DBUser': ('VendingMachine', 'DBUser'),
764 'DBPassword': ('VendingMachine', 'DBPassword'),
766 'ServiceName': ('VendingMachine', 'ServiceName'),
767 'ServicePassword': ('VendingMachine', 'Password'),
769 'ServerName': ('DecServer', 'Name'),
770 'ConnectPassword': ('DecServer', 'ConnectPassword'),
771 'PrivPassword': ('DecServer', 'PrivPassword'),
774 class VendConfigFile:
775 def __init__(self, config_file, options):
777 cp = ConfigParser.ConfigParser()
780 for option in options:
781 section, name = options[option]
782 value = cp.get(section, name)
783 self.__dict__[option] = value
785 except ConfigParser.Error, e:
786 raise SystemExit("Error reading config file "+config_file+": " + str(e))
788 def create_pid_file(name):
790 pid_file = file(name, 'w')
791 pid_file.write('%d\n'%os.getpid())
794 logging.warning('unable to write to pid file '+name+': '+str(e))
797 def do_nothing(signum, stack):
798 signal.signal(signum, do_nothing)
799 def stop_server(signum, stack): raise KeyboardInterrupt
800 signal.signal(signal.SIGHUP, do_nothing)
801 signal.signal(signal.SIGTERM, stop_server)
802 signal.signal(signal.SIGINT, stop_server)
804 options = parse_args()
805 config_opts = VendConfigFile(options.config_file, config_options)
806 if options.daemon: become_daemon()
807 set_up_logging(options)
808 if options.pid_file != '': create_pid_file(options.pid_file)
810 return options, config_opts
812 def clean_up_nicely(options, config_opts):
813 if options.pid_file != '':
815 os.unlink(options.pid_file)
816 logging.debug('Removed pid file '+options.pid_file)
817 except OSError: pass # if we can't delete it, meh
819 def set_up_logging(options):
820 logger = logging.getLogger()
822 if not options.daemon:
823 stderr_logger = logging.StreamHandler(sys.stderr)
824 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
825 logger.addHandler(stderr_logger)
827 if options.log_file != '':
829 file_logger = logging.FileHandler(options.log_file)
830 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
831 logger.addHandler(file_logger)
833 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
835 if options.syslog != None:
836 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
837 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
838 logger.addHandler(sys_logger)
841 logger.setLevel(logging.WARNING)
842 elif options.verbose:
843 logger.setLevel(logging.DEBUG)
845 logger.setLevel(logging.INFO)
848 dev_null = file('/dev/null')
849 fd = dev_null.fileno()
858 raise SystemExit('failed to fork: '+str(e))
860 def do_vend_server(options, config_opts):
863 rfh, wfh = connect_to_vend(options, config_opts)
864 except (SerialClientException, socket.error), e:
865 (exc_type, exc_value, exc_traceback) = sys.exc_info()
867 logging.error("Connection error: "+str(exc_type)+" "+str(e))
868 logging.info("Trying again in 5 seconds.")
873 run_forever(rfh, wfh, options, config_opts)
874 except VendingException:
875 logging.error("Connection died, trying again...")
876 logging.info("Trying again in 5 seconds.")
879 if __name__ == '__main__':
880 options, config_opts = set_stuff_up()
883 logging.warning('Starting Vend Server')
884 do_vend_server(options, config_opts)
885 logging.error('Vend Server finished unexpectedly, restarting')
886 except KeyboardInterrupt:
887 logging.info("Killed by signal, cleaning up")
888 clean_up_nicely(options, config_opts)
889 logging.warning("Vend Server stopped")
894 (exc_type, exc_value, exc_traceback) = sys.exc_info()
895 tb = format_tb(exc_traceback, 20)
898 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
899 logging.critical("Message: " + str(exc_value))
900 logging.critical("Traceback:")
902 for line in event.split('\n'):
903 logging.critical(' '+line)
904 logging.critical("This message should be considered a bug in the Vend Server.")
905 logging.critical("Please report this to someone who can fix it.")
907 logging.warning("Trying again anyway (might not help, but hey...)")