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 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
39 GREETING = 'UCC SNACKS'
49 STATE_DOOR_OPENING = 2
50 STATE_DOOR_CLOSING = 3
53 STATE_GET_SELECTION = 6
54 STATE_GRANDFATHER_CLOCK = 7
56 class DispenseDatabaseException(Exception): pass
58 class DispenseDatabase:
59 def __init__(self, vending_machine, host, name, user, password):
60 self.vending_machine = vending_machine
61 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
62 self.db.query('LISTEN vend_requests')
64 def process_requests(self):
65 logging.debug('database processing')
66 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
68 outstanding = self.db.query(query).getresult()
69 except (pg.error,), db_err:
70 raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
71 for (id, slot) in outstanding:
72 (worked, code, string) = self.vending_machine.vend(slot)
73 logging.debug (str((worked, code, string)))
75 query = 'SELECT vend_success(%s)'%id
76 self.db.query(query).getresult()
78 query = 'SELECT vend_failed(%s)'%id
79 self.db.query(query).getresult()
81 def handle_events(self):
82 notifier = self.db.getnotify()
83 while notifier is not None:
84 self.process_requests()
85 notify = self.db.getnotify()
87 def scroll_options(username, mk, welcome = False):
89 msg = [(center('WELCOME'), False, 0.8),
90 (center(username), False, 0.8)]
93 choices = ' '*10+'CHOICES: '
95 coke_machine = file('/home/other/coke/coke_contents')
96 cokes = coke_machine.readlines()
103 (slot_num, price, slot_name) = c.split(' ', 2)
104 if slot_name == 'dead': continue
105 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
106 choices += '55-DOOR '
107 choices += 'OR A SNACK. '
108 choices += '99 TO READ AGAIN. '
109 choices += 'CHOICE? '
110 msg.append((choices, False, None))
115 info = pwd.getpwuid(uid)
117 logging.info('getting pin for uid %d: user not in password file'%uid)
119 if info.pw_dir == None: return False
120 pinfile = os.path.join(info.pw_dir, '.pin')
124 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
127 logging.info('getting pin for uid %d: .pin has wrong permissions'%uid)
132 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
134 pinstr = f.readline()
136 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
137 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
141 def has_good_pin(uid):
142 return get_pin(uid) != None
144 def verify_user_pin(uid, pin):
145 if get_pin(uid) == pin:
146 info = pwd.getpwuid(uid)
147 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
150 logging.info('refused pin for uid %d'%(uid))
156 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
157 choice = int(random()*len(messages))
158 msg = messages[choice]
159 left = range(len(msg))
160 for i in range(len(msg)):
161 if msg[i] == ' ': left.remove(i)
165 for i in range(0, len(msg)):
171 s += chr(int(random()*26)+ord('A'))
180 return ' '*((LEN-len(str))/2)+str
191 StringIdler(v, text="Kill 'em all", repeat=False),
192 GrayIdler(v,one="*",zero="-"),
193 StringIdler(v, text=CREDITS),
194 GrayIdler(v,one="/",zero="\\"),
196 GrayIdler(v,one="X",zero="O"),
197 FileIdler(v, '/usr/share/common-licenses/GPL-2'),
198 GrayIdler(v,one="*",zero="-",reorder=1),
199 StringIdler(v, text=str(math.pi) + " "),
201 GrayIdler(v,one="/",zero="\\",reorder=1),
202 StringIdler(v, text=str(math.e) + " "),
203 GrayIdler(v,one="X",zero="O",reorder=1),
204 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),
205 PipeIdler(v, "/usr/bin/ypcat", "passwd"),
213 idler = choose_idler()
220 iiindex = idlers.index(idler)
224 move = int(random()*len(idlers)) + 1
227 idler = idlers[( (iiindex + 1) % iilen)]
228 move = move - idler.affinity()
239 def __init__(self,v):
240 self.state_table = {}
241 self.state = STATE_IDLE
244 self.mk = MessageKeeper(v)
248 self.cur_selection = ''
249 self.time_to_autologout = None
251 self.time_to_idle = None
253 self.last_timeout_refresh = None
257 def handle_tick_event(event, params, v, vstatus):
258 # don't care right now.
261 def handle_switch_event(event, params, v, vstatus):
262 # don't care right now.
266 def do_nothing(state, event, params, v, vstatus):
267 print "doing nothing (s,e,p)", state, " ", event, " ", params
270 def handle_getting_uid_idle(state, event, params, v, vstatus):
271 # don't care right now.
274 def handle_getting_pin_idle(state, event, params, v, vstatus):
275 # don't care right now.
278 def handle_get_selection_idle(state, event, params, v, vstatus):
279 # don't care right now.
281 ### State logging out ..
282 if vstatus.time_to_autologout != None:
283 time_left = vstatus.time_to_autologout - time()
284 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
285 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
286 vstatus.last_timeout_refresh = int(time_left)
287 vstatus.cur_selection = ''
289 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
290 vstatus.time_to_autologout = None
291 vstatus.cur_user = ''
293 vstatus.cur_selection = ''
296 vstatus.state = STATE_IDLE
298 vstatus.mk.set_message(GREETING)
300 ### State fully logged out ... reset variables
301 if vstatus.time_to_autologout and not vstatus.mk.done():
302 vstatus.time_to_autologout = None
303 if vstatus.cur_user == '' and vstatus.time_to_autologout:
304 vstatus.time_to_autologout = None
307 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
309 vstatus.time_to_autologout = time() + 15
310 vstatus.last_timeout_refresh = None
312 ### State logged out ... after normal logout??
313 # perhaps when logged in?
314 if vstatus.time_to_idle is not None and vstatus.cur_user != '':
315 vstatus.time_to_idle = None
318 ## FIXME - this may need to be elsewhere.....
320 vstatus.mk.update_display()
324 def handle_get_selection_key(state, event, params, v, vstatus):
326 if len(vstatus.cur_selection) == 0:
329 vstatus.cur_user = ''
330 vstatus.cur_selection = ''
333 vstatus.state = STATE_IDLE
335 vstatus.mk.set_messages(
336 [(center('BYE!'), False, 1.5),
337 (GREETING, False, None)])
339 vstatus.cur_selection += chr(key + ord('0'))
340 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
341 vstatus.time_to_autologout = None
342 elif len(vstatus.cur_selection) == 1:
344 vstatus.cur_selection = ''
345 vstatus.time_to_autologout = None
346 scroll_options(vstatus.username, vstatus.mk)
349 vstatus.cur_selection += chr(key + ord('0'))
350 make_selection(v,vstatus)
351 vstatus.cur_selection = ''
352 vstatus.time_to_autologout = time() + 8
353 vstatus.last_timeout_refresh = None
355 def make_selection(v, vstatus):
356 # should use sudo here
357 if vstatus.cur_selection == '55':
358 vstatus.mk.set_message('OPENSESAME')
359 logging.info('dispensing a door for %s'%vstatus.username)
361 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
363 ret = os.system('dispense door')
365 logging.info('door opened')
366 vstatus.mk.set_message(center('DOOR OPEN'))
368 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
369 vstatus.mk.set_message(center('BAD DOOR'))
371 elif vstatus.cur_selection == '91':
373 elif vstatus.cur_selection == '99':
374 scroll_options(vstatus.username, vstatus.mk)
375 vstatus.cur_selection = ''
377 elif vstatus.cur_selection[1] == '8':
378 v.display('GOT COKE?')
379 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
380 v.display('SEEMS NOT')
382 v.display('GOT COKE!')
384 v.display(vstatus.cur_selection+' - $1.00')
385 if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
386 v.vend(vstatus.cur_selection)
387 v.display('THANK YOU')
389 v.display('NO MONEY?')
393 def handle_getting_pin_key(state, event, params, v, vstatus):
394 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
396 if len(vstatus.cur_pin) < PIN_LENGTH:
398 if vstatus.cur_pin == '':
399 vstatus.cur_user = ''
400 vstatus.mk.set_message(GREETING)
403 vstatus.state = STATE_IDLE
407 vstatus.mk.set_message('PIN: ')
409 vstatus.cur_pin += chr(key + ord('0'))
410 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
411 if len(vstatus.cur_pin) == PIN_LENGTH:
412 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
415 vstatus.cur_selection = ''
416 vstatus.state = STATE_GET_SELECTION
417 scroll_options(vstatus.username, vstatus.mk, True)
421 vstatus.mk.set_messages(
422 [(center('BAD PIN'), False, 1.0),
423 (center('SORRY'), False, 0.5),
424 (GREETING, False, None)])
425 vstatus.cur_user = ''
429 vstatus.state = STATE_IDLE
434 def handle_getting_uid_key(state, event, params, v, vstatus):
435 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
437 # complicated key handling here:
438 if len(vstatus.cur_user) < 5:
440 vstatus.cur_user = ''
441 vstatus.mk.set_message(GREETING)
444 vstatus.state = STATE_IDLE
447 vstatus.cur_user += chr(key + ord('0'))
448 vstatus.mk.set_message('UID: '+vstatus.cur_user)
450 if len(vstatus.cur_user) == 5:
451 uid = int(vstatus.cur_user)
452 if not has_good_pin(uid):
453 logging.info('user '+vstatus.cur_user+' has a bad PIN')
454 vstatus.mk.set_messages(
455 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
456 (GREETING, False, None)])
457 vstatus.cur_user = ''
461 vstatus.state = STATE_IDLE
467 vstatus.mk.set_message('PIN: ')
468 logging.info('need pin for user %s'%vstatus.cur_user)
469 vstatus.state = STATE_GETTING_PIN
473 def handle_idle_key(state, event, params, v, vstatus):
474 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
479 vstatus.cur_user = ''
480 vstatus.mk.set_message(GREETING)
485 vstatus.state = STATE_GETTING_UID
486 run_handler(event, key, v, vstatus)
489 def handle_idle_tick(state, event, params, v, vstatus):
490 ### State logged out ... initiate idler in 5 (first start?)
491 if vstatus.time_to_idle == None and vstatus.cur_user == '':
492 vstatus.time_to_idle = time() + 5
497 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle:
500 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle + 30:
501 vstatus.time_to_idle = time()
506 vstatus.mk.update_display()
508 vstatus.state = STATE_GRANDFATHER_CLOCK
509 run_handler(event, params, v, vstatus)
511 def beep_on(when, before=0):
512 start = int(when - before)
516 if now >= start and now <= end:
520 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
521 ### check for interesting times
524 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
525 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
526 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
527 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
529 hourfromnow = localtime(time() + 3600)
531 #onthehour = mktime([now[0],now[1],now[2],now[3],27,0,now[6],now[7],now[8]])
532 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
533 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
535 ## check for X seconds to the hour
536 ## if case, update counter to 2
537 if beep_on(onthehour,15) \
538 or beep_on(halfhour,0) \
539 or beep_on(quarterhour,0) \
540 or beep_on(threequarterhour,0) \
541 or beep_on(fivetothehour,0):
542 vstatus.state = STATE_GRANDFATHER_CLOCK
544 run_handler(event, params, v, vstatus)
546 vstatus.state = STATE_IDLE
548 def handle_grandfather_tick(state, event, params, v, vstatus):
552 ### we live in interesting times
555 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
556 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
557 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
558 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
560 hourfromnow = localtime(time() + 3600)
562 #onthehour = mktime([now[0],now[1],now[2],now[3],27,0,now[6],now[7],now[8]])
563 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
564 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
567 #print "when it fashionable to wear a onion on your hip"
569 if beep_on(onthehour,15):
571 next_hour=((hourfromnow[3] + 11) % 12) + 1
572 if onthehour - time() < next_hour and onthehour - time() > 0:
574 msg.append((" DING!", False, None))
575 elif int(onthehour - time()) == 0:
577 msg.append((" DING!", False, None))
578 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, None))
579 elif beep_on(halfhour,0):
582 msg.append((" HALFHOUR ", False, None))
583 elif beep_on(quarterhour,0):
586 msg.append((" QTR HOUR ", False, None))
587 elif beep_on(threequarterhour,0):
590 msg.append((" 3 QTR HOUR ", False, None))
591 elif beep_on(fivetothehour,0):
594 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, None))
598 ## check for X seconds to the hour
600 vstatus.mk.set_messages(msg)
601 vstatus.mk.update_display()
603 ## if no longer case, return to idle
605 ## change idler to be clock
606 if go_idle and vstatus.mk.done():
608 vstatus.state = STATE_IDLE
610 def handle_door_idle(state, event, params, v, vstatus):
611 # don't care right now.
614 def handle_door_event(state, event, params, v, vstatus):
615 vstatus.time_to_idle = None
617 if params == 1: #door open
618 vstatus.state = STATE_DOOR_OPENING
619 logging.warning("Entering open door mode")
620 v.display("-FEED ME-")
622 vstatus.cur_user = ''
624 elif params == 0: #door closed
625 vstatus.state = STATE_DOOR_CLOSING
628 logging.warning('Leaving open door mode')
629 v.display("-YUM YUM!-")
631 def idle_in(vstatus,seconds):
632 vstatus.time_to_idle = time() + seconds
634 def return_to_idle(state,event,params,v,vstatus):
635 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle:
636 vstatus.mk.set_message(GREETING)
637 vstatus.state = STATE_IDLE
639 if not vstatus.time_to_idle:
640 vstatus.mk.set_message(GREETING)
641 vstatus.state = STATE_IDLE
644 def create_state_table(vstatus):
645 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
646 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
647 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
649 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
650 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
651 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
653 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
654 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
655 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
657 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
658 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
659 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
661 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
662 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
663 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
665 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
666 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
667 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
669 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
670 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
671 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
672 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
673 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
674 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
676 def get_state_table_handler(vstatus, state, event, counter):
677 return vstatus.state_table[(state,event,counter)]
679 def run_forever(rfh, wfh, options, cf):
680 v = VendingMachine(rfh, wfh)
681 vstatus = VendState(v)
682 create_state_table(vstatus)
684 logging.debug('PING is ' + str(v.ping()))
686 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
688 vstatus.mk.set_message(GREETING)
691 vstatus.mk.set_message("Booted")
694 # This main loop was hideous and the work of the devil.
695 # This has now been fixed (mostly) - mtearle
698 # notes for later surgery
699 # (event, counter, ' ')
703 # ( return state - not currently implemented )
709 except DispenseDatabaseException, e:
710 logging.error('Database error: '+str(e))
717 run_handler(event, params, v, vstatus)
719 # logging.debug('Got event: ' + repr(e))
722 def run_handler(event, params, v, vstatus):
723 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
725 handler(vstatus.state, event, params, v, vstatus)
727 def connect_to_vend(options, cf):
730 logging.info('Connecting to vending machine using LAT')
731 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
732 rfh, wfh = latclient.get_fh()
733 elif options.use_serial:
734 # Open vending machine via serial.
735 logging.info('Connecting to vending machine using serial')
736 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
737 rfh,wfh = serialclient.get_fh()
739 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
740 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
742 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
743 sock.connect((options.host, options.port))
744 rfh = sock.makefile('r')
745 wfh = sock.makefile('w')
750 from optparse import OptionParser
752 op = OptionParser(usage="%prog [OPTION]...")
753 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')
754 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
755 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
756 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
757 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
758 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
759 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
760 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
761 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
762 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
763 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
764 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
765 options, args = op.parse_args()
768 op.error('extra command line arguments: ' + ' '.join(args))
773 'DBServer': ('Database', 'Server'),
774 'DBName': ('Database', 'Name'),
775 'DBUser': ('VendingMachine', 'DBUser'),
776 'DBPassword': ('VendingMachine', 'DBPassword'),
778 'ServiceName': ('VendingMachine', 'ServiceName'),
779 'ServicePassword': ('VendingMachine', 'Password'),
781 'ServerName': ('DecServer', 'Name'),
782 'ConnectPassword': ('DecServer', 'ConnectPassword'),
783 'PrivPassword': ('DecServer', 'PrivPassword'),
786 class VendConfigFile:
787 def __init__(self, config_file, options):
789 cp = ConfigParser.ConfigParser()
792 for option in options:
793 section, name = options[option]
794 value = cp.get(section, name)
795 self.__dict__[option] = value
797 except ConfigParser.Error, e:
798 raise SystemExit("Error reading config file "+config_file+": " + str(e))
800 def create_pid_file(name):
802 pid_file = file(name, 'w')
803 pid_file.write('%d\n'%os.getpid())
806 logging.warning('unable to write to pid file '+name+': '+str(e))
809 def do_nothing(signum, stack):
810 signal.signal(signum, do_nothing)
811 def stop_server(signum, stack): raise KeyboardInterrupt
812 signal.signal(signal.SIGHUP, do_nothing)
813 signal.signal(signal.SIGTERM, stop_server)
814 signal.signal(signal.SIGINT, stop_server)
816 options = parse_args()
817 config_opts = VendConfigFile(options.config_file, config_options)
818 if options.daemon: become_daemon()
819 set_up_logging(options)
820 if options.pid_file != '': create_pid_file(options.pid_file)
822 return options, config_opts
824 def clean_up_nicely(options, config_opts):
825 if options.pid_file != '':
827 os.unlink(options.pid_file)
828 logging.debug('Removed pid file '+options.pid_file)
829 except OSError: pass # if we can't delete it, meh
831 def set_up_logging(options):
832 logger = logging.getLogger()
834 if not options.daemon:
835 stderr_logger = logging.StreamHandler(sys.stderr)
836 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
837 logger.addHandler(stderr_logger)
839 if options.log_file != '':
841 file_logger = logging.FileHandler(options.log_file)
842 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
843 logger.addHandler(file_logger)
845 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
847 if options.syslog != None:
848 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
849 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
850 logger.addHandler(sys_logger)
853 logger.setLevel(logging.WARNING)
854 elif options.verbose:
855 logger.setLevel(logging.DEBUG)
857 logger.setLevel(logging.INFO)
860 dev_null = file('/dev/null')
861 fd = dev_null.fileno()
870 raise SystemExit('failed to fork: '+str(e))
872 def do_vend_server(options, config_opts):
875 rfh, wfh = connect_to_vend(options, config_opts)
876 except (SerialClientException, socket.error), e:
877 (exc_type, exc_value, exc_traceback) = sys.exc_info()
879 logging.error("Connection error: "+str(exc_type)+" "+str(e))
880 logging.info("Trying again in 5 seconds.")
885 run_forever(rfh, wfh, options, config_opts)
886 except VendingException:
887 logging.error("Connection died, trying again...")
888 logging.info("Trying again in 5 seconds.")
891 if __name__ == '__main__':
892 options, config_opts = set_stuff_up()
895 logging.warning('Starting Vend Server')
896 do_vend_server(options, config_opts)
897 logging.error('Vend Server finished unexpectedly, restarting')
898 except KeyboardInterrupt:
899 logging.info("Killed by signal, cleaning up")
900 clean_up_nicely(options, config_opts)
901 logging.warning("Vend Server stopped")
906 (exc_type, exc_value, exc_traceback) = sys.exc_info()
907 tb = format_tb(exc_traceback, 20)
910 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
911 logging.critical("Message: " + str(exc_value))
912 logging.critical("Traceback:")
914 for line in event.split('\n'):
915 logging.critical(' '+line)
916 logging.critical("This message should be considered a bug in the Vend Server.")
917 logging.critical("Please report this to someone who can fix it.")
919 logging.warning("Trying again anyway (might not help, but hey...)")