7 import sys, os, string, re, pwd, signal, math
8 import logging, logging.handlers
9 from traceback import format_tb
11 from time import time, sleep, mktime, localtime
12 from popen2 import popen2
13 from LATClient import LATClient, LATClientException
14 from SerialClient import SerialClient, SerialClientException
15 from VendingMachine import VendingMachine, VendingException
16 from MessageKeeper import MessageKeeper
17 from HorizScroll import HorizScroll
18 from random import random, seed
19 from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
20 from SnackConfig import get_snacks, get_snack
22 from posix import geteuid
25 This vending machine software brought to you by:
30 and a collective of hungry alpacas.
34 For a good time call +61 8 6488 3901
49 STATE_DOOR_OPENING = 2
50 STATE_DOOR_CLOSING = 3
53 STATE_GET_SELECTION = 6
54 STATE_GRANDFATHER_CLOCK = 7
59 class DispenseDatabaseException(Exception): pass
61 class DispenseDatabase:
62 def __init__(self, vending_machine, host, name, user, password):
63 self.vending_machine = vending_machine
64 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
65 self.db.query('LISTEN vend_requests')
67 def process_requests(self):
68 logging.debug('database processing')
69 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
71 outstanding = self.db.query(query).getresult()
72 except (pg.error,), db_err:
73 raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
74 for (id, slot) in outstanding:
75 (worked, code, string) = self.vending_machine.vend(slot)
76 logging.debug (str((worked, code, string)))
78 query = 'SELECT vend_success(%s)'%id
79 self.db.query(query).getresult()
81 query = 'SELECT vend_failed(%s)'%id
82 self.db.query(query).getresult()
84 def handle_events(self):
85 notifier = self.db.getnotify()
86 while notifier is not None:
87 self.process_requests()
88 notify = self.db.getnotify()
90 def scroll_options(username, mk, welcome = False):
92 msg = [(center('WELCOME'), False, TEXT_SPEED),
93 (center(username), False, TEXT_SPEED)]
96 choices = ' '*10+'CHOICES: '
98 coke_machine = file('/home/other/coke/coke_contents')
99 cokes = coke_machine.readlines()
106 (slot_num, price, slot_name) = c.split(' ', 2)
107 if slot_name == 'dead': continue
108 choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
110 # we don't want to print snacks for now since it'll be too large
111 # and there's physical bits of paper in the machine anyway - matt
113 # snacks = get_snacks()
117 # for slot, ( name, price ) in snacks.items():
118 # choices += '%s8-%s (%sc) ' % ( slot, name, price )
120 choices += '55-DOOR '
121 choices += 'OR ANOTHER SNACK. '
122 choices += '99 TO READ AGAIN. '
123 choices += 'CHOICE? '
124 msg.append((choices, False, None))
129 info = pwd.getpwuid(uid)
131 logging.info('getting pin for uid %d: user not in password file'%uid)
133 if info.pw_dir == None: return False
134 pinfile = os.path.join(info.pw_dir, '.pin')
138 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
141 logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
142 os.chmod(pinfile, 0600)
146 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
148 pinstr = f.readline()
150 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
151 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
155 def has_good_pin(uid):
156 return get_pin(uid) != None
158 def verify_user_pin(uid, pin):
159 if get_pin(uid) == pin:
160 info = pwd.getpwuid(uid)
161 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
164 logging.info('refused pin for uid %d'%(uid))
170 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
171 choice = int(random()*len(messages))
172 msg = messages[choice]
173 left = range(len(msg))
174 for i in range(len(msg)):
175 if msg[i] == ' ': left.remove(i)
179 for i in range(0, len(msg)):
185 s += chr(int(random()*26)+ord('A'))
194 return ' '*((LEN-len(str))/2)+str
205 StringIdler(v, text="Kill 'em all", repeat=False),
206 GrayIdler(v,one="*",zero="-"),
207 StringIdler(v, text=CREDITS),
208 GrayIdler(v,one="/",zero="\\"),
210 GrayIdler(v,one="X",zero="O"),
211 FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
212 GrayIdler(v,one="*",zero="-",reorder=1),
213 StringIdler(v, text=str(math.pi) + " "),
215 GrayIdler(v,one="/",zero="\\",reorder=1),
216 StringIdler(v, text=str(math.e) + " "),
217 GrayIdler(v,one="X",zero="O",reorder=1),
218 StringIdler(v, text=" I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 [now closed? - MSH] - and order as Quinn - I am getting really hungry", repeat=False),
219 PipeIdler(v, "/usr/bin/getent", "passwd"),
228 def reset_idler(v, vstatus, t = None):
230 idler = GreetingIdler(v, t)
231 vstatus.time_of_next_idlestep = time()+idler.next()
232 vstatus.time_of_next_idler = None
233 vstatus.time_to_autologout = None
234 vstatus.change_state(STATE_IDLE, 1)
239 average_affinity = 10 # guessing here...
241 if idler and idler.__class__ != GreetingIdler:
242 iiindex = idlers.index(idler)
246 move = int(random()*len(idlers)*average_affinity) + 1
251 idler = idlers[iiindex]
252 move -= idler.affinity()
256 def idle_step(vstatus):
260 vstatus.time_of_next_idler = time() + 30
261 nextidle = idler.next()
263 nextidle = IDLE_SPEED
264 vstatus.time_of_next_idlestep = time()+nextidle
267 def __init__(self,v):
268 self.state_table = {}
269 self.state = STATE_IDLE
272 self.mk = MessageKeeper(v)
276 self.cur_selection = ''
277 self.time_to_autologout = None
279 self.last_timeout_refresh = None
281 def change_state(self,newstate,newcounter=None):
282 if self.state != newstate:
283 #print "Changing state from: ",
287 self.state = newstate
289 if newcounter is not None and self.counter != newcounter:
290 #print "Changing counter from: ",
294 self.counter = newcounter
298 def handle_tick_event(event, params, v, vstatus):
299 # don't care right now.
302 def handle_switch_event(event, params, v, vstatus):
303 # don't care right now.
307 def do_nothing(state, event, params, v, vstatus):
308 print "doing nothing (s,e,p)", state, " ", event, " ", params
311 def handle_getting_uid_idle(state, event, params, v, vstatus):
312 # don't care right now.
315 def handle_getting_pin_idle(state, event, params, v, vstatus):
316 # don't care right now.
319 def handle_get_selection_idle(state, event, params, v, vstatus):
320 # don't care right now.
322 ### State logging out ..
323 if vstatus.time_to_autologout != None:
324 time_left = vstatus.time_to_autologout - time()
325 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
326 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
327 vstatus.last_timeout_refresh = int(time_left)
328 vstatus.cur_selection = ''
330 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
331 vstatus.time_to_autologout = None
332 vstatus.cur_user = ''
334 vstatus.cur_selection = ''
336 reset_idler(v, vstatus)
338 ### State fully logged out ... reset variables
339 if vstatus.time_to_autologout and not vstatus.mk.done():
340 vstatus.time_to_autologout = None
341 if vstatus.cur_user == '' and vstatus.time_to_autologout:
342 vstatus.time_to_autologout = None
345 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
347 vstatus.time_to_autologout = time() + 15
348 vstatus.last_timeout_refresh = None
350 ## FIXME - this may need to be elsewhere.....
352 vstatus.mk.update_display()
356 def handle_get_selection_key(state, event, params, v, vstatus):
358 if len(vstatus.cur_selection) == 0:
361 vstatus.cur_user = ''
362 vstatus.cur_selection = ''
364 vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
365 reset_idler(v, vstatus, 2)
367 vstatus.cur_selection += chr(key + ord('0'))
368 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
369 vstatus.time_to_autologout = None
370 elif len(vstatus.cur_selection) == 1:
372 vstatus.cur_selection = ''
373 vstatus.time_to_autologout = None
374 scroll_options(vstatus.username, vstatus.mk)
377 vstatus.cur_selection += chr(key + ord('0'))
378 make_selection(v,vstatus)
379 vstatus.cur_selection = ''
380 vstatus.time_to_autologout = time() + 8
381 vstatus.last_timeout_refresh = None
383 def make_selection(v, vstatus):
384 # should use sudo here
385 if vstatus.cur_selection == '55':
386 vstatus.mk.set_message('OPENSESAME')
387 logging.info('dispensing a door for %s'%vstatus.username)
389 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
391 ret = os.system('dispense door')
393 logging.info('door opened')
394 vstatus.mk.set_message(center('DOOR OPEN'))
396 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
397 vstatus.mk.set_message(center('BAD DOOR'))
399 elif vstatus.cur_selection == '91':
401 elif vstatus.cur_selection == '99':
402 scroll_options(vstatus.username, vstatus.mk)
403 vstatus.cur_selection = ''
405 elif vstatus.cur_selection[1] == '8':
406 v.display('GOT COKE?')
407 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
408 v.display('SEEMS NOT')
410 v.display('GOT COKE!')
412 # first see if it's a named slot
414 price, shortname, name = get_snack( vstatus.cur_selection )
416 price, shortname, name = get_snack( '--' )
417 dollarprice = "$%.2f" % ( price / 100.0 )
418 v.display(vstatus.cur_selection+' - %s'%dollarprice)
419 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, shortname)) >> 8) == 0):
420 v.vend(vstatus.cur_selection)
421 v.display('THANK YOU')
423 v.display('NO MONEY?')
427 def handle_getting_pin_key(state, event, params, v, vstatus):
428 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
430 if len(vstatus.cur_pin) < PIN_LENGTH:
432 if vstatus.cur_pin == '':
433 vstatus.cur_user = ''
434 reset_idler(v, vstatus)
438 vstatus.mk.set_message('PIN: ')
440 vstatus.cur_pin += chr(key + ord('0'))
441 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
442 if len(vstatus.cur_pin) == PIN_LENGTH:
443 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
446 vstatus.cur_selection = ''
447 vstatus.change_state(STATE_GET_SELECTION)
448 scroll_options(vstatus.username, vstatus.mk, True)
452 vstatus.mk.set_messages(
453 [(center('BAD PIN'), False, 1.0),
454 (center('SORRY'), False, 0.5)])
455 vstatus.cur_user = ''
458 reset_idler(v, vstatus, 2)
463 def handle_getting_uid_key(state, event, params, v, vstatus):
464 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
466 # complicated key handling here:
467 if len(vstatus.cur_user) < 5:
469 vstatus.cur_user = ''
471 reset_idler(v, vstatus)
474 vstatus.cur_user += chr(key + ord('0'))
475 vstatus.mk.set_message('UID: '+vstatus.cur_user)
477 if len(vstatus.cur_user) == 5:
478 uid = int(vstatus.cur_user)
480 logging.info('user '+vstatus.cur_user+' has a bad PIN')
486 Welcome to Picklevision Sytems, Sunnyvale, CA
488 Greetings Professor Falken.
493 Shall we play a game?
496 Please choose from the following menu:
503 6. Toxic and Biochemical Warfare
504 7. Global Thermonuclear War
508 Wouldn't you prefer a nice game of chess?
510 """.replace('\n',' ')
511 vstatus.mk.set_messages([(pfalken, False, 10)])
512 vstatus.cur_user = ''
515 reset_idler(v, vstatus, 10)
519 if not has_good_pin(uid):
520 logging.info('user '+vstatus.cur_user+' has a bad PIN')
521 vstatus.mk.set_messages(
522 [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
523 vstatus.cur_user = ''
526 reset_idler(v, vstatus, 3)
532 vstatus.mk.set_message('PIN: ')
533 logging.info('need pin for user %s'%vstatus.cur_user)
534 vstatus.change_state(STATE_GETTING_PIN)
538 def handle_idle_key(state, event, params, v, vstatus):
539 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
544 vstatus.cur_user = ''
545 reset_idler(v, vstatus)
548 vstatus.change_state(STATE_GETTING_UID)
549 run_handler(event, key, v, vstatus)
552 def handle_idle_tick(state, event, params, v, vstatus):
554 if vstatus.mk.done():
557 if vstatus.time_of_next_idler and time() > vstatus.time_of_next_idler:
558 vstatus.time_of_next_idler = time() + 30
563 vstatus.mk.update_display()
565 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
566 run_handler(event, params, v, vstatus)
569 def beep_on(when, before=0):
570 start = int(when - before)
574 if now >= start and now <= end:
578 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
579 ### check for interesting times
582 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
583 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
584 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
585 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
587 hourfromnow = localtime(time() + 3600)
589 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
590 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
591 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
593 ## check for X seconds to the hour
594 ## if case, update counter to 2
595 if beep_on(onthehour,15) \
596 or beep_on(halfhour,0) \
597 or beep_on(quarterhour,0) \
598 or beep_on(threequarterhour,0) \
599 or beep_on(fivetothehour,0):
600 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
601 run_handler(event, params, v, vstatus)
603 vstatus.change_state(STATE_IDLE)
605 def handle_grandfather_tick(state, event, params, v, vstatus):
609 ### we live in interesting times
612 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
613 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
614 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
615 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
617 hourfromnow = localtime(time() + 3600)
619 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
620 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
621 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
624 #print "when it fashionable to wear a onion on your hip"
626 if beep_on(onthehour,15):
628 next_hour=((hourfromnow[3] + 11) % 12) + 1
629 if onthehour - time() < next_hour and onthehour - time() > 0:
634 msg.append(("DING!", False, None))
636 msg.append((" DING!", False, None))
637 elif int(onthehour - time()) == 0:
639 msg.append((" BONG!", False, None))
640 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
641 elif beep_on(halfhour,0):
644 msg.append((" HALFHOUR ", False, 50))
645 elif beep_on(quarterhour,0):
648 msg.append((" QTR HOUR ", False, 50))
649 elif beep_on(threequarterhour,0):
652 msg.append((" 3 QTR HR ", False, 50))
653 elif beep_on(fivetothehour,0):
656 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
660 ## check for X seconds to the hour
663 vstatus.mk.set_messages(msg)
666 vstatus.mk.update_display()
667 ## if no longer case, return to idle
669 ## change idler to be clock
670 if go_idle and vstatus.mk.done():
671 vstatus.change_state(STATE_IDLE,1)
673 def handle_door_idle(state, event, params, v, vstatus):
674 # don't care right now.
677 def handle_door_event(state, event, params, v, vstatus):
678 if params == 1: #door open
679 vstatus.change_state(STATE_DOOR_OPENING)
680 logging.warning("Entering open door mode")
681 v.display("-FEED ME-")
683 vstatus.cur_user = ''
685 elif params == 0: #door closed
686 vstatus.change_state(STATE_DOOR_CLOSING)
687 reset_idler(v, vstatus, 3)
689 logging.warning('Leaving open door mode')
690 v.display("-YUM YUM!-")
692 def return_to_idle(state,event,params,v,vstatus):
693 reset_idler(v, vstatus)
695 def create_state_table(vstatus):
696 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
697 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
698 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
700 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
701 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
702 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
704 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
705 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
706 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
708 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
709 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
710 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
712 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
713 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
714 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
716 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
717 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
718 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
720 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
721 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
722 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
723 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
724 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
725 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
727 def get_state_table_handler(vstatus, state, event, counter):
728 return vstatus.state_table[(state,event,counter)]
730 def time_to_next_update(vstatus):
731 idle_update = vstatus.time_of_next_idlestep - time()
732 if not vstatus.mk.done() and vstatus.mk.next_update is not None:
733 mk_update = vstatus.mk.next_update - time()
734 if mk_update < idle_update:
735 idle_update = mk_update
738 def run_forever(rfh, wfh, options, cf):
739 v = VendingMachine(rfh, wfh)
740 vstatus = VendState(v)
741 create_state_table(vstatus)
743 logging.debug('PING is ' + str(v.ping()))
745 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
748 reset_idler(v, vstatus)
750 # This main loop was hideous and the work of the devil.
751 # This has now been fixed (mostly) - mtearle
754 # notes for later surgery
755 # (event, counter, ' ')
759 # ( return state - not currently implemented )
765 except DispenseDatabaseException, e:
766 logging.error('Database error: '+str(e))
769 timeout = time_to_next_update(vstatus)
770 e = v.next_event(timeout)
773 run_handler(event, params, v, vstatus)
775 # logging.debug('Got event: ' + repr(e))
778 def run_handler(event, params, v, vstatus):
779 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
781 handler(vstatus.state, event, params, v, vstatus)
783 def connect_to_vend(options, cf):
786 logging.info('Connecting to vending machine using LAT')
787 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
788 rfh, wfh = latclient.get_fh()
789 elif options.use_serial:
790 # Open vending machine via serial.
791 logging.info('Connecting to vending machine using serial')
792 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
793 rfh,wfh = serialclient.get_fh()
795 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
796 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
798 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
799 sock.connect((options.host, options.port))
800 rfh = sock.makefile('r')
801 wfh = sock.makefile('w')
806 from optparse import OptionParser
808 op = OptionParser(usage="%prog [OPTION]...")
809 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')
810 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
811 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
812 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
813 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
814 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
815 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
816 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
817 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
818 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
819 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
820 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
821 options, args = op.parse_args()
824 op.error('extra command line arguments: ' + ' '.join(args))
829 'DBServer': ('Database', 'Server'),
830 'DBName': ('Database', 'Name'),
831 'DBUser': ('VendingMachine', 'DBUser'),
832 'DBPassword': ('VendingMachine', 'DBPassword'),
834 'ServiceName': ('VendingMachine', 'ServiceName'),
835 'ServicePassword': ('VendingMachine', 'Password'),
837 'ServerName': ('DecServer', 'Name'),
838 'ConnectPassword': ('DecServer', 'ConnectPassword'),
839 'PrivPassword': ('DecServer', 'PrivPassword'),
842 class VendConfigFile:
843 def __init__(self, config_file, options):
845 cp = ConfigParser.ConfigParser()
848 for option in options:
849 section, name = options[option]
850 value = cp.get(section, name)
851 self.__dict__[option] = value
853 except ConfigParser.Error, e:
854 raise SystemExit("Error reading config file "+config_file+": " + str(e))
856 def create_pid_file(name):
858 pid_file = file(name, 'w')
859 pid_file.write('%d\n'%os.getpid())
862 logging.warning('unable to write to pid file '+name+': '+str(e))
865 def do_nothing(signum, stack):
866 signal.signal(signum, do_nothing)
867 def stop_server(signum, stack): raise KeyboardInterrupt
868 signal.signal(signal.SIGHUP, do_nothing)
869 signal.signal(signal.SIGTERM, stop_server)
870 signal.signal(signal.SIGINT, stop_server)
872 options = parse_args()
873 config_opts = VendConfigFile(options.config_file, config_options)
874 if options.daemon: become_daemon()
875 set_up_logging(options)
876 if options.pid_file != '': create_pid_file(options.pid_file)
878 return options, config_opts
880 def clean_up_nicely(options, config_opts):
881 if options.pid_file != '':
883 os.unlink(options.pid_file)
884 logging.debug('Removed pid file '+options.pid_file)
885 except OSError: pass # if we can't delete it, meh
887 def set_up_logging(options):
888 logger = logging.getLogger()
890 if not options.daemon:
891 stderr_logger = logging.StreamHandler(sys.stderr)
892 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
893 logger.addHandler(stderr_logger)
895 if options.log_file != '':
897 file_logger = logging.FileHandler(options.log_file)
898 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
899 logger.addHandler(file_logger)
901 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
903 if options.syslog != None:
904 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
905 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
906 logger.addHandler(sys_logger)
909 logger.setLevel(logging.WARNING)
910 elif options.verbose:
911 logger.setLevel(logging.DEBUG)
913 logger.setLevel(logging.INFO)
916 dev_null = file('/dev/null')
917 fd = dev_null.fileno()
926 raise SystemExit('failed to fork: '+str(e))
928 def do_vend_server(options, config_opts):
931 rfh, wfh = connect_to_vend(options, config_opts)
932 except (SerialClientException, socket.error), e:
933 (exc_type, exc_value, exc_traceback) = sys.exc_info()
935 logging.error("Connection error: "+str(exc_type)+" "+str(e))
936 logging.info("Trying again in 5 seconds.")
941 run_forever(rfh, wfh, options, config_opts)
942 except VendingException:
943 logging.error("Connection died, trying again...")
944 logging.info("Trying again in 5 seconds.")
947 if __name__ == '__main__':
948 options, config_opts = set_stuff_up()
951 logging.warning('Starting Vend Server')
952 do_vend_server(options, config_opts)
953 logging.error('Vend Server finished unexpectedly, restarting')
954 except KeyboardInterrupt:
955 logging.info("Killed by signal, cleaning up")
956 clean_up_nicely(options, config_opts)
957 logging.warning("Vend Server stopped")
962 (exc_type, exc_value, exc_traceback) = sys.exc_info()
963 tb = format_tb(exc_traceback, 20)
966 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
967 logging.critical("Message: " + str(exc_value))
968 logging.critical("Traceback:")
970 for line in event.split('\n'):
971 logging.critical(' '+line)
972 logging.critical("This message should be considered a bug in the Vend Server.")
973 logging.critical("Please report this to someone who can fix it.")
975 logging.warning("Trying again anyway (might not help, but hey...)")