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
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'%uid)
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"),
215 idler = choose_idler()
222 iiindex = idlers.index(idler)
226 move = int(random()*len(idlers)) + 1
229 idler = idlers[( (iiindex + 1) % iilen)]
230 move = move - idler.affinity()
241 def __init__(self,v):
242 self.state_table = {}
243 self.state = STATE_IDLE
246 self.mk = MessageKeeper(v)
250 self.cur_selection = ''
251 self.time_to_autologout = None
253 self.time_to_idle = None
255 self.last_timeout_refresh = None
257 def change_state(self,newstate,newcounter=None):
258 if self.state != newstate:
259 #print "Changing state from: ",
263 self.state = newstate
265 if newcounter is not None and self.counter != newcounter:
266 #print "Changing counter from: ",
270 self.counter = newcounter
274 def handle_tick_event(event, params, v, vstatus):
275 # don't care right now.
278 def handle_switch_event(event, params, v, vstatus):
279 # don't care right now.
283 def do_nothing(state, event, params, v, vstatus):
284 print "doing nothing (s,e,p)", state, " ", event, " ", params
287 def handle_getting_uid_idle(state, event, params, v, vstatus):
288 # don't care right now.
291 def handle_getting_pin_idle(state, event, params, v, vstatus):
292 # don't care right now.
295 def handle_get_selection_idle(state, event, params, v, vstatus):
296 # don't care right now.
298 ### State logging out ..
299 if vstatus.time_to_autologout != None:
300 time_left = vstatus.time_to_autologout - time()
301 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
302 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
303 vstatus.last_timeout_refresh = int(time_left)
304 vstatus.cur_selection = ''
306 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
307 vstatus.time_to_autologout = None
308 vstatus.cur_user = ''
310 vstatus.cur_selection = ''
313 vstatus.change_state(STATE_IDLE)
315 vstatus.mk.set_message(GREETING)
317 ### State fully logged out ... reset variables
318 if vstatus.time_to_autologout and not vstatus.mk.done():
319 vstatus.time_to_autologout = None
320 if vstatus.cur_user == '' and vstatus.time_to_autologout:
321 vstatus.time_to_autologout = None
324 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
326 vstatus.time_to_autologout = time() + 15
327 vstatus.last_timeout_refresh = None
329 ### State logged out ... after normal logout??
330 # perhaps when logged in?
331 if vstatus.time_to_idle is not None and vstatus.cur_user != '':
332 vstatus.time_to_idle = None
335 ## FIXME - this may need to be elsewhere.....
337 vstatus.mk.update_display()
341 def handle_get_selection_key(state, event, params, v, vstatus):
343 if len(vstatus.cur_selection) == 0:
346 vstatus.cur_user = ''
347 vstatus.cur_selection = ''
350 vstatus.change_state(STATE_IDLE)
352 vstatus.mk.set_messages(
353 [(center('BYE!'), False, 1.5),
354 (GREETING, False, None)])
356 vstatus.cur_selection += chr(key + ord('0'))
357 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
358 vstatus.time_to_autologout = None
359 elif len(vstatus.cur_selection) == 1:
361 vstatus.cur_selection = ''
362 vstatus.time_to_autologout = None
363 scroll_options(vstatus.username, vstatus.mk)
366 vstatus.cur_selection += chr(key + ord('0'))
367 make_selection(v,vstatus)
368 vstatus.cur_selection = ''
369 vstatus.time_to_autologout = time() + 8
370 vstatus.last_timeout_refresh = None
372 def make_selection(v, vstatus):
373 # should use sudo here
374 if vstatus.cur_selection == '55':
375 vstatus.mk.set_message('OPENSESAME')
376 logging.info('dispensing a door for %s'%vstatus.username)
378 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
380 ret = os.system('dispense door')
382 logging.info('door opened')
383 vstatus.mk.set_message(center('DOOR OPEN'))
385 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
386 vstatus.mk.set_message(center('BAD DOOR'))
388 elif vstatus.cur_selection == '91':
390 elif vstatus.cur_selection == '99':
391 scroll_options(vstatus.username, vstatus.mk)
392 vstatus.cur_selection = ''
394 elif vstatus.cur_selection[1] == '8':
395 v.display('GOT COKE?')
396 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
397 v.display('SEEMS NOT')
399 v.display('GOT COKE!')
401 v.display(vstatus.cur_selection+' - $1.00')
402 if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
403 v.vend(vstatus.cur_selection)
404 v.display('THANK YOU')
406 v.display('NO MONEY?')
410 def handle_getting_pin_key(state, event, params, v, vstatus):
411 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
413 if len(vstatus.cur_pin) < PIN_LENGTH:
415 if vstatus.cur_pin == '':
416 vstatus.cur_user = ''
417 vstatus.mk.set_message(GREETING)
420 vstatus.change_state(STATE_IDLE)
424 vstatus.mk.set_message('PIN: ')
426 vstatus.cur_pin += chr(key + ord('0'))
427 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
428 if len(vstatus.cur_pin) == PIN_LENGTH:
429 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
432 vstatus.cur_selection = ''
433 vstatus.change_state(STATE_GET_SELECTION)
434 scroll_options(vstatus.username, vstatus.mk, True)
438 vstatus.mk.set_messages(
439 [(center('BAD PIN'), False, 1.0),
440 (center('SORRY'), False, 0.5),
441 (GREETING, False, None)])
442 vstatus.cur_user = ''
446 vstatus.change_state(STATE_IDLE)
451 def handle_getting_uid_key(state, event, params, v, vstatus):
452 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
454 # complicated key handling here:
455 if len(vstatus.cur_user) < 5:
457 vstatus.cur_user = ''
458 vstatus.mk.set_message(GREETING)
461 vstatus.change_state(STATE_IDLE)
464 vstatus.cur_user += chr(key + ord('0'))
465 vstatus.mk.set_message('UID: '+vstatus.cur_user)
467 if len(vstatus.cur_user) == 5:
468 uid = int(vstatus.cur_user)
469 if not has_good_pin(uid):
470 logging.info('user '+vstatus.cur_user+' has a bad PIN')
471 vstatus.mk.set_messages(
472 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
473 (GREETING, False, None)])
474 vstatus.cur_user = ''
478 vstatus.change_state(STATE_IDLE)
484 vstatus.mk.set_message('PIN: ')
485 logging.info('need pin for user %s'%vstatus.cur_user)
486 vstatus.change_state(STATE_GETTING_PIN)
490 def handle_idle_key(state, event, params, v, vstatus):
491 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
496 vstatus.cur_user = ''
497 vstatus.mk.set_message(GREETING)
502 vstatus.change_state(STATE_GETTING_UID)
503 run_handler(event, key, v, vstatus)
506 def handle_idle_tick(state, event, params, v, vstatus):
507 ### State logged out ... initiate idler in 5 (first start?)
508 if vstatus.time_to_idle == None and vstatus.cur_user == '':
509 vstatus.time_to_idle = time() + 5
514 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle:
517 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle + 30:
518 vstatus.time_to_idle = time()
523 vstatus.mk.update_display()
525 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
526 run_handler(event, params, v, vstatus)
529 def beep_on(when, before=0):
530 start = int(when - before)
534 if now >= start and now <= end:
538 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
539 ### check for interesting times
542 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
543 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
544 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
545 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
547 hourfromnow = localtime(time() + 3600)
549 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
550 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
551 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
553 ## check for X seconds to the hour
554 ## if case, update counter to 2
555 if beep_on(onthehour,15) \
556 or beep_on(halfhour,0) \
557 or beep_on(quarterhour,0) \
558 or beep_on(threequarterhour,0) \
559 or beep_on(fivetothehour,0):
560 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
561 run_handler(event, params, v, vstatus)
563 vstatus.change_state(STATE_IDLE)
565 def handle_grandfather_tick(state, event, params, v, vstatus):
569 ### we live in interesting times
572 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
573 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
574 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
575 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
577 hourfromnow = localtime(time() + 3600)
579 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
580 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
581 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
584 #print "when it fashionable to wear a onion on your hip"
586 if beep_on(onthehour,15):
588 next_hour=((hourfromnow[3] + 11) % 12) + 1
589 if onthehour - time() < next_hour and onthehour - time() > 0:
594 msg.append(("DING!", False, None))
596 msg.append((" DING!", False, None))
597 elif int(onthehour - time()) == 0:
599 msg.append((" BONG!", False, None))
600 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
601 elif beep_on(halfhour,0):
604 msg.append((" HALFHOUR ", False, 50))
605 elif beep_on(quarterhour,0):
608 msg.append((" QTR HOUR ", False, 50))
609 elif beep_on(threequarterhour,0):
612 msg.append((" 3 QTR HR ", False, 50))
613 elif beep_on(fivetothehour,0):
616 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
620 ## check for X seconds to the hour
623 vstatus.mk.set_messages(msg)
626 vstatus.mk.update_display()
627 ## if no longer case, return to idle
629 ## change idler to be clock
630 if go_idle and vstatus.mk.done():
631 vstatus.change_state(STATE_IDLE,1)
633 def handle_door_idle(state, event, params, v, vstatus):
634 # don't care right now.
637 def handle_door_event(state, event, params, v, vstatus):
638 vstatus.time_to_idle = None
640 if params == 1: #door open
641 vstatus.change_state(STATE_DOOR_OPENING)
642 logging.warning("Entering open door mode")
643 v.display("-FEED ME-")
645 vstatus.cur_user = ''
647 elif params == 0: #door closed
648 vstatus.change_state(STATE_DOOR_CLOSING)
651 logging.warning('Leaving open door mode')
652 v.display("-YUM YUM!-")
654 def idle_in(vstatus,seconds):
655 vstatus.time_to_idle = time() + seconds
657 def return_to_idle(state,event,params,v,vstatus):
658 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle:
659 vstatus.mk.set_message(GREETING)
660 vstatus.change_state(STATE_IDLE)
662 if not vstatus.time_to_idle:
663 vstatus.mk.set_message(GREETING)
664 vstatus.change_state(STATE_IDLE)
667 def create_state_table(vstatus):
668 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
669 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
670 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
672 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
673 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
674 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
676 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
677 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
678 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
680 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
681 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
682 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
684 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
685 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
686 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
688 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
689 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
690 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
692 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
693 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
694 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
695 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
696 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
697 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
699 def get_state_table_handler(vstatus, state, event, counter):
700 return vstatus.state_table[(state,event,counter)]
702 def run_forever(rfh, wfh, options, cf):
703 v = VendingMachine(rfh, wfh)
704 vstatus = VendState(v)
705 create_state_table(vstatus)
707 logging.debug('PING is ' + str(v.ping()))
709 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
711 vstatus.mk.set_message(GREETING)
714 vstatus.mk.set_message("Booted")
717 # This main loop was hideous and the work of the devil.
718 # This has now been fixed (mostly) - mtearle
721 # notes for later surgery
722 # (event, counter, ' ')
726 # ( return state - not currently implemented )
728 vstatus.change_state(STATE_IDLE,1)
734 except DispenseDatabaseException, e:
735 logging.error('Database error: '+str(e))
741 run_handler(event, params, v, vstatus)
743 # logging.debug('Got event: ' + repr(e))
746 def run_handler(event, params, v, vstatus):
747 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
749 handler(vstatus.state, event, params, v, vstatus)
751 def connect_to_vend(options, cf):
754 logging.info('Connecting to vending machine using LAT')
755 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
756 rfh, wfh = latclient.get_fh()
757 elif options.use_serial:
758 # Open vending machine via serial.
759 logging.info('Connecting to vending machine using serial')
760 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
761 rfh,wfh = serialclient.get_fh()
763 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
764 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
766 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
767 sock.connect((options.host, options.port))
768 rfh = sock.makefile('r')
769 wfh = sock.makefile('w')
774 from optparse import OptionParser
776 op = OptionParser(usage="%prog [OPTION]...")
777 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')
778 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
779 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
780 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
781 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
782 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
783 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
784 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
785 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
786 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
787 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
788 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
789 options, args = op.parse_args()
792 op.error('extra command line arguments: ' + ' '.join(args))
797 'DBServer': ('Database', 'Server'),
798 'DBName': ('Database', 'Name'),
799 'DBUser': ('VendingMachine', 'DBUser'),
800 'DBPassword': ('VendingMachine', 'DBPassword'),
802 'ServiceName': ('VendingMachine', 'ServiceName'),
803 'ServicePassword': ('VendingMachine', 'Password'),
805 'ServerName': ('DecServer', 'Name'),
806 'ConnectPassword': ('DecServer', 'ConnectPassword'),
807 'PrivPassword': ('DecServer', 'PrivPassword'),
810 class VendConfigFile:
811 def __init__(self, config_file, options):
813 cp = ConfigParser.ConfigParser()
816 for option in options:
817 section, name = options[option]
818 value = cp.get(section, name)
819 self.__dict__[option] = value
821 except ConfigParser.Error, e:
822 raise SystemExit("Error reading config file "+config_file+": " + str(e))
824 def create_pid_file(name):
826 pid_file = file(name, 'w')
827 pid_file.write('%d\n'%os.getpid())
830 logging.warning('unable to write to pid file '+name+': '+str(e))
833 def do_nothing(signum, stack):
834 signal.signal(signum, do_nothing)
835 def stop_server(signum, stack): raise KeyboardInterrupt
836 signal.signal(signal.SIGHUP, do_nothing)
837 signal.signal(signal.SIGTERM, stop_server)
838 signal.signal(signal.SIGINT, stop_server)
840 options = parse_args()
841 config_opts = VendConfigFile(options.config_file, config_options)
842 if options.daemon: become_daemon()
843 set_up_logging(options)
844 if options.pid_file != '': create_pid_file(options.pid_file)
846 return options, config_opts
848 def clean_up_nicely(options, config_opts):
849 if options.pid_file != '':
851 os.unlink(options.pid_file)
852 logging.debug('Removed pid file '+options.pid_file)
853 except OSError: pass # if we can't delete it, meh
855 def set_up_logging(options):
856 logger = logging.getLogger()
858 if not options.daemon:
859 stderr_logger = logging.StreamHandler(sys.stderr)
860 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
861 logger.addHandler(stderr_logger)
863 if options.log_file != '':
865 file_logger = logging.FileHandler(options.log_file)
866 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
867 logger.addHandler(file_logger)
869 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
871 if options.syslog != None:
872 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
873 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
874 logger.addHandler(sys_logger)
877 logger.setLevel(logging.WARNING)
878 elif options.verbose:
879 logger.setLevel(logging.DEBUG)
881 logger.setLevel(logging.INFO)
884 dev_null = file('/dev/null')
885 fd = dev_null.fileno()
894 raise SystemExit('failed to fork: '+str(e))
896 def do_vend_server(options, config_opts):
899 rfh, wfh = connect_to_vend(options, config_opts)
900 except (SerialClientException, socket.error), e:
901 (exc_type, exc_value, exc_traceback) = sys.exc_info()
903 logging.error("Connection error: "+str(exc_type)+" "+str(e))
904 logging.info("Trying again in 5 seconds.")
909 run_forever(rfh, wfh, options, config_opts)
910 except VendingException:
911 logging.error("Connection died, trying again...")
912 logging.info("Trying again in 5 seconds.")
915 if __name__ == '__main__':
916 options, config_opts = set_stuff_up()
919 logging.warning('Starting Vend Server')
920 do_vend_server(options, config_opts)
921 logging.error('Vend Server finished unexpectedly, restarting')
922 except KeyboardInterrupt:
923 logging.info("Killed by signal, cleaning up")
924 clean_up_nicely(options, config_opts)
925 logging.warning("Vend Server stopped")
930 (exc_type, exc_value, exc_traceback) = sys.exc_info()
931 tb = format_tb(exc_traceback, 20)
934 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
935 logging.critical("Message: " + str(exc_value))
936 logging.critical("Traceback:")
938 for line in event.split('\n'):
939 logging.critical(' '+line)
940 logging.critical("This message should be considered a bug in the Vend Server.")
941 logging.critical("Please report this to someone who can fix it.")
943 logging.warning("Trying again anyway (might not help, but hey...)")