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'%uid)
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)
713 vstatus.mk.set_message(GREETING)
716 vstatus.mk.set_message("Booted")
719 # This main loop was hideous and the work of the devil.
720 # This has now been fixed (mostly) - mtearle
723 # notes for later surgery
724 # (event, counter, ' ')
728 # ( return state - not currently implemented )
730 vstatus.change_state(STATE_IDLE,1)
736 except DispenseDatabaseException, e:
737 logging.error('Database error: '+str(e))
743 run_handler(event, params, v, vstatus)
745 # logging.debug('Got event: ' + repr(e))
748 def run_handler(event, params, v, vstatus):
749 handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
751 handler(vstatus.state, event, params, v, vstatus)
753 def connect_to_vend(options, cf):
756 logging.info('Connecting to vending machine using LAT')
757 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
758 rfh, wfh = latclient.get_fh()
759 elif options.use_serial:
760 # Open vending machine via serial.
761 logging.info('Connecting to vending machine using serial')
762 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
763 rfh,wfh = serialclient.get_fh()
765 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
766 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
768 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
769 sock.connect((options.host, options.port))
770 rfh = sock.makefile('r')
771 wfh = sock.makefile('w')
776 from optparse import OptionParser
778 op = OptionParser(usage="%prog [OPTION]...")
779 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')
780 op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
781 op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
782 op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
783 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
784 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
785 op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
786 op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
787 op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
788 op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
789 op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
790 op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
791 options, args = op.parse_args()
794 op.error('extra command line arguments: ' + ' '.join(args))
799 'DBServer': ('Database', 'Server'),
800 'DBName': ('Database', 'Name'),
801 'DBUser': ('VendingMachine', 'DBUser'),
802 'DBPassword': ('VendingMachine', 'DBPassword'),
804 'ServiceName': ('VendingMachine', 'ServiceName'),
805 'ServicePassword': ('VendingMachine', 'Password'),
807 'ServerName': ('DecServer', 'Name'),
808 'ConnectPassword': ('DecServer', 'ConnectPassword'),
809 'PrivPassword': ('DecServer', 'PrivPassword'),
812 class VendConfigFile:
813 def __init__(self, config_file, options):
815 cp = ConfigParser.ConfigParser()
818 for option in options:
819 section, name = options[option]
820 value = cp.get(section, name)
821 self.__dict__[option] = value
823 except ConfigParser.Error, e:
824 raise SystemExit("Error reading config file "+config_file+": " + str(e))
826 def create_pid_file(name):
828 pid_file = file(name, 'w')
829 pid_file.write('%d\n'%os.getpid())
832 logging.warning('unable to write to pid file '+name+': '+str(e))
835 def do_nothing(signum, stack):
836 signal.signal(signum, do_nothing)
837 def stop_server(signum, stack): raise KeyboardInterrupt
838 signal.signal(signal.SIGHUP, do_nothing)
839 signal.signal(signal.SIGTERM, stop_server)
840 signal.signal(signal.SIGINT, stop_server)
842 options = parse_args()
843 config_opts = VendConfigFile(options.config_file, config_options)
844 if options.daemon: become_daemon()
845 set_up_logging(options)
846 if options.pid_file != '': create_pid_file(options.pid_file)
848 return options, config_opts
850 def clean_up_nicely(options, config_opts):
851 if options.pid_file != '':
853 os.unlink(options.pid_file)
854 logging.debug('Removed pid file '+options.pid_file)
855 except OSError: pass # if we can't delete it, meh
857 def set_up_logging(options):
858 logger = logging.getLogger()
860 if not options.daemon:
861 stderr_logger = logging.StreamHandler(sys.stderr)
862 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
863 logger.addHandler(stderr_logger)
865 if options.log_file != '':
867 file_logger = logging.FileHandler(options.log_file)
868 file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
869 logger.addHandler(file_logger)
871 logger.warning('unable to write to log file '+options.log_file+': '+str(e))
873 if options.syslog != None:
874 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
875 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
876 logger.addHandler(sys_logger)
879 logger.setLevel(logging.WARNING)
880 elif options.verbose:
881 logger.setLevel(logging.DEBUG)
883 logger.setLevel(logging.INFO)
886 dev_null = file('/dev/null')
887 fd = dev_null.fileno()
896 raise SystemExit('failed to fork: '+str(e))
898 def do_vend_server(options, config_opts):
901 rfh, wfh = connect_to_vend(options, config_opts)
902 except (SerialClientException, socket.error), e:
903 (exc_type, exc_value, exc_traceback) = sys.exc_info()
905 logging.error("Connection error: "+str(exc_type)+" "+str(e))
906 logging.info("Trying again in 5 seconds.")
911 run_forever(rfh, wfh, options, config_opts)
912 except VendingException:
913 logging.error("Connection died, trying again...")
914 logging.info("Trying again in 5 seconds.")
917 if __name__ == '__main__':
918 options, config_opts = set_stuff_up()
921 logging.warning('Starting Vend Server')
922 do_vend_server(options, config_opts)
923 logging.error('Vend Server finished unexpectedly, restarting')
924 except KeyboardInterrupt:
925 logging.info("Killed by signal, cleaning up")
926 clean_up_nicely(options, config_opts)
927 logging.warning("Vend Server stopped")
932 (exc_type, exc_value, exc_traceback) = sys.exc_info()
933 tb = format_tb(exc_traceback, 20)
936 logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
937 logging.critical("Message: " + str(exc_value))
938 logging.critical("Traceback:")
940 for line in event.split('\n'):
941 logging.critical(' '+line)
942 logging.critical("This message should be considered a bug in the Vend Server.")
943 logging.critical("Please report this to someone who can fix it.")
945 logging.warning("Trying again anyway (might not help, but hey...)")