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
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 += '%s8-%s (%sc) '%(slot_num, slot_name, price)
109 choices += '55-DOOR '
110 choices += 'OR A SNACK. '
111 choices += '99 TO READ AGAIN. '
112 choices += 'CHOICE? '
113 msg.append((choices, False, None))
118 info = pwd.getpwuid(uid)
120 logging.info('getting pin for uid %d: user not in password file'%uid)
122 if info.pw_dir == None: return False
123 pinfile = os.path.join(info.pw_dir, '.pin')
127 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
130 logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
131 os.chmod(pinfile, 0600)
135 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
137 pinstr = f.readline()
139 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
140 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
144 def has_good_pin(uid):
145 return get_pin(uid) != None
147 def verify_user_pin(uid, pin):
148 if get_pin(uid) == pin:
149 info = pwd.getpwuid(uid)
150 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
153 logging.info('refused pin for uid %d'%(uid))
159 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
160 choice = int(random()*len(messages))
161 msg = messages[choice]
162 left = range(len(msg))
163 for i in range(len(msg)):
164 if msg[i] == ' ': left.remove(i)
168 for i in range(0, len(msg)):
174 s += chr(int(random()*26)+ord('A'))
183 return ' '*((LEN-len(str))/2)+str
194 StringIdler(v, text="Kill 'em all", repeat=False),
195 GrayIdler(v,one="*",zero="-"),
196 StringIdler(v, text=CREDITS),
197 GrayIdler(v,one="/",zero="\\"),
199 GrayIdler(v,one="X",zero="O"),
200 FileIdler(v, '/usr/share/common-licenses/GPL-2'),
201 GrayIdler(v,one="*",zero="-",reorder=1),
202 StringIdler(v, text=str(math.pi) + " "),
204 GrayIdler(v,one="/",zero="\\",reorder=1),
205 StringIdler(v, text=str(math.e) + " "),
206 GrayIdler(v,one="X",zero="O",reorder=1),
207 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),
208 PipeIdler(v, "/usr/bin/ypcat", "passwd"),
216 idler = choose_idler()
223 iiindex = idlers.index(idler)
227 move = int(random()*len(idlers)) + 1
230 idler = idlers[( (iiindex + 1) % iilen)]
231 move = move - idler.affinity()
243 def __init__(self,v):
244 self.state_table = {}
245 self.state = STATE_IDLE
248 self.mk = MessageKeeper(v)
252 self.cur_selection = ''
253 self.time_to_autologout = None
255 self.time_to_idle = None
257 self.last_timeout_refresh = None
259 def change_state(self,newstate,newcounter=None):
260 if self.state != newstate:
261 #print "Changing state from: ",
265 self.state = newstate
267 if newcounter is not None and self.counter != newcounter:
268 #print "Changing counter from: ",
272 self.counter = newcounter
276 def handle_tick_event(event, params, v, vstatus):
277 # don't care right now.
280 def handle_switch_event(event, params, v, vstatus):
281 # don't care right now.
285 def do_nothing(state, event, params, v, vstatus):
286 print "doing nothing (s,e,p)", state, " ", event, " ", params
289 def handle_getting_uid_idle(state, event, params, v, vstatus):
290 # don't care right now.
293 def handle_getting_pin_idle(state, event, params, v, vstatus):
294 # don't care right now.
297 def handle_get_selection_idle(state, event, params, v, vstatus):
298 # don't care right now.
300 ### State logging out ..
301 if vstatus.time_to_autologout != None:
302 time_left = vstatus.time_to_autologout - time()
303 if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
304 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
305 vstatus.last_timeout_refresh = int(time_left)
306 vstatus.cur_selection = ''
308 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
309 vstatus.time_to_autologout = None
310 vstatus.cur_user = ''
312 vstatus.cur_selection = ''
315 vstatus.change_state(STATE_IDLE)
317 vstatus.mk.set_message(GREETING)
319 ### State fully logged out ... reset variables
320 if vstatus.time_to_autologout and not vstatus.mk.done():
321 vstatus.time_to_autologout = None
322 if vstatus.cur_user == '' and vstatus.time_to_autologout:
323 vstatus.time_to_autologout = None
326 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
328 vstatus.time_to_autologout = time() + 15
329 vstatus.last_timeout_refresh = None
331 ### State logged out ... after normal logout??
332 # perhaps when logged in?
333 if vstatus.time_to_idle is not None and vstatus.cur_user != '':
334 vstatus.time_to_idle = 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 = ''
352 vstatus.change_state(STATE_IDLE)
354 vstatus.mk.set_messages(
355 [(center('BYE!'), False, 1.5),
356 (GREETING, False, None)])
358 vstatus.cur_selection += chr(key + ord('0'))
359 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
360 vstatus.time_to_autologout = None
361 elif len(vstatus.cur_selection) == 1:
363 vstatus.cur_selection = ''
364 vstatus.time_to_autologout = None
365 scroll_options(vstatus.username, vstatus.mk)
368 vstatus.cur_selection += chr(key + ord('0'))
369 make_selection(v,vstatus)
370 vstatus.cur_selection = ''
371 vstatus.time_to_autologout = time() + 8
372 vstatus.last_timeout_refresh = None
374 def make_selection(v, vstatus):
375 # should use sudo here
376 if vstatus.cur_selection == '55':
377 vstatus.mk.set_message('OPENSESAME')
378 logging.info('dispensing a door for %s'%vstatus.username)
380 ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
382 ret = os.system('dispense door')
384 logging.info('door opened')
385 vstatus.mk.set_message(center('DOOR OPEN'))
387 logging.warning('user %s tried to dispense a bad door'%vstatus.username)
388 vstatus.mk.set_message(center('BAD DOOR'))
390 elif vstatus.cur_selection == '91':
392 elif vstatus.cur_selection == '99':
393 scroll_options(vstatus.username, vstatus.mk)
394 vstatus.cur_selection = ''
396 elif vstatus.cur_selection[1] == '8':
397 v.display('GOT COKE?')
398 if ((os.system('su - "%s" -c "dispense %s"'%(vstatus.username, vstatus.cur_selection[0])) >> 8) != 0):
399 v.display('SEEMS NOT')
401 v.display('GOT COKE!')
403 v.display(vstatus.cur_selection+' - $1.00')
404 if ((os.system('su - "%s" -c "dispense snack"'%(vstatus.username)) >> 8) == 0):
405 v.vend(vstatus.cur_selection)
406 v.display('THANK YOU')
408 v.display('NO MONEY?')
412 def handle_getting_pin_key(state, event, params, v, vstatus):
413 #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
415 if len(vstatus.cur_pin) < PIN_LENGTH:
417 if vstatus.cur_pin == '':
418 vstatus.cur_user = ''
419 vstatus.mk.set_message(GREETING)
422 vstatus.change_state(STATE_IDLE)
426 vstatus.mk.set_message('PIN: ')
428 vstatus.cur_pin += chr(key + ord('0'))
429 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
430 if len(vstatus.cur_pin) == PIN_LENGTH:
431 vstatus.username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
434 vstatus.cur_selection = ''
435 vstatus.change_state(STATE_GET_SELECTION)
436 scroll_options(vstatus.username, vstatus.mk, True)
440 vstatus.mk.set_messages(
441 [(center('BAD PIN'), False, 1.0),
442 (center('SORRY'), False, 0.5),
443 (GREETING, False, None)])
444 vstatus.cur_user = ''
448 vstatus.change_state(STATE_IDLE)
453 def handle_getting_uid_key(state, event, params, v, vstatus):
454 #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
456 # complicated key handling here:
457 if len(vstatus.cur_user) < 5:
459 vstatus.cur_user = ''
460 vstatus.mk.set_message(GREETING)
463 vstatus.change_state(STATE_IDLE)
466 vstatus.cur_user += chr(key + ord('0'))
467 vstatus.mk.set_message('UID: '+vstatus.cur_user)
469 if len(vstatus.cur_user) == 5:
470 uid = int(vstatus.cur_user)
471 if not has_good_pin(uid):
472 logging.info('user '+vstatus.cur_user+' has a bad PIN')
473 vstatus.mk.set_messages(
474 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
475 (GREETING, False, None)])
476 vstatus.cur_user = ''
480 vstatus.change_state(STATE_IDLE)
486 vstatus.mk.set_message('PIN: ')
487 logging.info('need pin for user %s'%vstatus.cur_user)
488 vstatus.change_state(STATE_GETTING_PIN)
492 def handle_idle_key(state, event, params, v, vstatus):
493 #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
498 vstatus.cur_user = ''
499 vstatus.mk.set_message(GREETING)
504 vstatus.change_state(STATE_GETTING_UID)
505 run_handler(event, key, v, vstatus)
508 def handle_idle_tick(state, event, params, v, vstatus):
509 ### State logged out ... initiate idler in 5 (first start?)
510 if vstatus.time_to_idle == None and vstatus.cur_user == '':
511 vstatus.time_to_idle = time() + 5
516 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle:
519 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle + 30:
520 vstatus.time_to_idle = time()
525 vstatus.mk.update_display()
527 vstatus.change_state(STATE_GRANDFATHER_CLOCK)
528 run_handler(event, params, v, vstatus)
531 def beep_on(when, before=0):
532 start = int(when - before)
536 if now >= start and now <= end:
540 def handle_idle_grandfather_tick(state, event, params, v, vstatus):
541 ### check for interesting times
544 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
545 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
546 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
547 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
549 hourfromnow = localtime(time() + 3600)
551 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
552 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
553 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
555 ## check for X seconds to the hour
556 ## if case, update counter to 2
557 if beep_on(onthehour,15) \
558 or beep_on(halfhour,0) \
559 or beep_on(quarterhour,0) \
560 or beep_on(threequarterhour,0) \
561 or beep_on(fivetothehour,0):
562 vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
563 run_handler(event, params, v, vstatus)
565 vstatus.change_state(STATE_IDLE)
567 def handle_grandfather_tick(state, event, params, v, vstatus):
571 ### we live in interesting times
574 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
575 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
576 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
577 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
579 hourfromnow = localtime(time() + 3600)
581 # onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
582 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
583 0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
586 #print "when it fashionable to wear a onion on your hip"
588 if beep_on(onthehour,15):
590 next_hour=((hourfromnow[3] + 11) % 12) + 1
591 if onthehour - time() < next_hour and onthehour - time() > 0:
596 msg.append(("DING!", False, None))
598 msg.append((" DING!", False, None))
599 elif int(onthehour - time()) == 0:
601 msg.append((" BONG!", False, None))
602 msg.append((" IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
603 elif beep_on(halfhour,0):
606 msg.append((" HALFHOUR ", False, 50))
607 elif beep_on(quarterhour,0):
610 msg.append((" QTR HOUR ", False, 50))
611 elif beep_on(threequarterhour,0):
614 msg.append((" 3 QTR HR ", False, 50))
615 elif beep_on(fivetothehour,0):
618 msg.append(("Quick run to your lectures! Hurry! Hurry!", False, TEXT_SPEED*4))
622 ## check for X seconds to the hour
625 vstatus.mk.set_messages(msg)
628 vstatus.mk.update_display()
629 ## if no longer case, return to idle
631 ## change idler to be clock
632 if go_idle and vstatus.mk.done():
633 vstatus.change_state(STATE_IDLE,1)
635 def handle_door_idle(state, event, params, v, vstatus):
636 # don't care right now.
639 def handle_door_event(state, event, params, v, vstatus):
640 vstatus.time_to_idle = None
642 if params == 1: #door open
643 vstatus.change_state(STATE_DOOR_OPENING)
644 logging.warning("Entering open door mode")
645 v.display("-FEED ME-")
647 vstatus.cur_user = ''
649 elif params == 0: #door closed
650 vstatus.change_state(STATE_DOOR_CLOSING)
653 logging.warning('Leaving open door mode')
654 v.display("-YUM YUM!-")
656 def idle_in(vstatus,seconds):
657 vstatus.time_to_idle = time() + seconds
659 def return_to_idle(state,event,params,v,vstatus):
660 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle:
661 vstatus.mk.set_message(GREETING)
662 vstatus.change_state(STATE_IDLE)
664 if not vstatus.time_to_idle:
665 vstatus.mk.set_message(GREETING)
666 vstatus.change_state(STATE_IDLE)
669 def create_state_table(vstatus):
670 vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
671 vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
672 vstatus.state_table[(STATE_IDLE,DOOR,1)] = handle_door_event
674 vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = handle_door_idle
675 vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = handle_door_event
676 vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = do_nothing
678 vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = return_to_idle
679 vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = handle_door_event
680 vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = do_nothing
682 vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
683 vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
684 vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
686 vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
687 vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
688 vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
690 vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
691 vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
692 vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
694 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
695 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
696 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
697 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
698 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
699 vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
701 def get_state_table_handler(vstatus, state, event, counter):
702 return vstatus.state_table[(state,event,counter)]
704 def run_forever(rfh, wfh, options, cf):
705 v = VendingMachine(rfh, wfh)
706 vstatus = VendState(v)
707 create_state_table(vstatus)
709 logging.debug('PING is ' + str(v.ping()))
711 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
715 vstatus.mk.set_message(GREETING)
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...)")