7 import sys, os, string, re, pwd, signal
8 from traceback import print_tb
10 from time import time, sleep
11 from popen2 import popen2
12 from LATClient import LATClient, LATClientException
13 from VendingMachine import VendingMachine, VendingException
14 from HorizScroll import HorizScroll
15 from random import random, seed
16 from Idler import TrainIdler,GrayIdler
18 from posix import geteuid
20 GREETING = 'UCC SNACKS'
27 class DispenseDatabase:
28 def __init__(self, vending_machine, host, name, user, password):
29 self.vending_machine = vending_machine
30 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
31 self.db.query('LISTEN vend_requests')
33 def process_requests(self):
35 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
37 outstanding = self.db.query(query).getresult()
38 except (pg.error,), db_err:
39 sys.stderr.write('Failed to query database: %s\n'%(db_err.strip()))
41 for (id, slot) in outstanding:
42 (worked, code, string) = self.vending_machine.vend(slot)
43 print (worked, code, string)
45 query = 'SELECT vend_success(%s)'%id
46 self.db.query(query).getresult()
48 query = 'SELECT vend_failed(%s)'%id
49 self.db.query(query).getresult()
51 def handle_events(self):
52 notifier = self.db.getnotify()
53 while notifier is not None:
54 self.process_requests()
55 notify = self.db.getnotify()
57 def scroll_options(username, mk, welcome = False):
59 msg = [(center('WELCOME'), False, 0.8),
60 (center(username), False, 0.8)]
63 choices = ' '*10+'CHOICES: '
65 coke_machine = file('/home/other/coke/coke_contents')
66 cokes = coke_machine.readlines()
73 (slot_num, price, slot_name) = c.split(' ', 2)
74 if slot_name == 'dead': continue
75 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
77 choices += 'OR A SNACK. '
78 choices += '99 TO READ AGAIN. '
80 msg.append((choices, False, None))
85 info = pwd.getpwuid(uid)
88 if info.pw_dir == None: return False
89 pinfile = os.path.join(info.pw_dir, '.pin')
100 pinstr = f.readline()
102 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
106 def has_good_pin(uid):
107 return get_pin(uid) != None
109 def verify_user_pin(uid, pin):
110 if get_pin(uid) == pin:
111 info = pwd.getpwuid(uid)
116 def door_open_mode(v):
117 print "Entering open door mode"
118 v.display("-FEED ME-")
124 if params == 1: # door closed
125 v.display("-YUM YUM!-")
131 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
132 choice = int(random()*len(messages))
133 msg = messages[choice]
134 left = range(len(msg))
135 for i in range(len(msg)):
136 if msg[i] == ' ': left.remove(i)
140 for i in range(0, len(msg)):
146 s += chr(int(random()*26)+ord('A'))
155 return ' '*((LEN-len(str))/2)+str
158 def __init__(self, vendie):
159 # Each element of scrolling_message should be a 3-tuple of
160 # ('message', True/False if it is to be repeated, time to display)
161 self.scrolling_message = []
163 self.next_update = None
165 def set_message(self, string):
166 self.scrolling_message = [(string, False, None)]
167 self.update_display(True)
169 def set_messages(self, strings):
170 self.scrolling_message = strings
171 self.update_display(True)
173 def update_display(self, forced = False):
174 if not forced and self.next_update != None and time() < self.next_update:
176 if len(self.scrolling_message) > 0:
177 if len(self.scrolling_message[0][0]) > 10:
178 (m, r, t) = self.scrolling_message[0]
180 exp = HorizScroll(m).expand(padding = 0, wraparound = True)
187 del self.scrolling_message[0]
188 self.scrolling_message = a + self.scrolling_message
189 newmsg = self.scrolling_message[0]
190 if newmsg[2] != None:
191 self.next_update = time() + newmsg[2]
193 self.next_update = None
194 self.v.display(self.scrolling_message[0][0])
195 if self.scrolling_message[0][1]:
196 self.scrolling_message.append(self.scrolling_message[0])
197 del self.scrolling_message[0]
200 return len(self.scrolling_message) == 0
202 def run_forever(rfh, wfh, options, cf):
203 v = VendingMachine(rfh, wfh)
204 print 'PING is', v.ping()
206 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
211 mk = MessageKeeper(v)
212 mk.set_message(GREETING)
213 time_to_autologout = None
214 #idler = TrainIdler(v)
215 #idler = GrayIdler(v)
216 idler = GrayIdler(v,one="*",zero="-")
218 last_timeout_refresh = None
221 if USE_DB: db.handle_events()
223 if time_to_autologout != None:
224 time_left = time_to_autologout - time()
225 if time_left < 6 and (last_timeout_refresh is None or last_timeout_refresh > time_left):
226 mk.set_message('LOGOUT: '+str(int(time_left)))
227 last_timeout_refresh = int(time_left)
230 if time_to_autologout != None and time_to_autologout - time() <= 0:
231 time_to_autologout = None
235 mk.set_message(GREETING)
237 if time_to_autologout and not mk.done(): time_to_autologout = None
238 if cur_user == '' and time_to_autologout: time_to_autologout = None
239 if len(cur_pin) == PIN_LENGTH and mk.done() and time_to_autologout == None:
241 time_to_autologout = time() + 15
243 if time_to_idle == None and cur_user == '': time_to_idle = time() + 60
244 if time_to_idle != None and cur_user != '': time_to_idle = None
245 if time_to_idle is not None and time() > time_to_idle: idler.next()
251 e = v.next_event(0.1)
262 mk.set_message(GREETING)
263 elif event == SWITCH:
264 # don't care right now.
268 # complicated key handling here:
269 if len(cur_user) < 5:
272 mk.set_message(GREETING)
274 cur_user += chr(key + ord('0'))
275 mk.set_message('UID: '+cur_user)
276 if len(cur_user) == 5:
278 if not has_good_pin(uid):
280 #[(center('INVALID'), False, 0.7),
281 #(center('PIN'), False, 0.7),
282 #(center('SETUP'), False, 1.0),
283 #(GREETING, False, None)])
285 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
286 (GREETING, False, None)])
291 mk.set_message('PIN: ')
293 elif len(cur_pin) < PIN_LENGTH:
297 mk.set_message(GREETING)
300 mk.set_message('PIN: ')
302 cur_pin += chr(key + ord('0'))
303 mk.set_message('PIN: '+'X'*len(cur_pin))
304 if len(cur_pin) == PIN_LENGTH:
305 username = verify_user_pin(int(cur_user), int(cur_pin))
309 scroll_options(username, mk, True)
314 [(center('BAD PIN'), False, 1.0),
315 (center('SORRY'), False, 0.5),
316 (GREETING, False, None)])
320 elif len(cur_selection) == 0:
326 [(center('BYE!'), False, 1.5),
327 (GREETING, False, None)])
329 cur_selection += chr(key + ord('0'))
330 mk.set_message('SELECT: '+cur_selection)
331 time_to_autologout = None
332 elif len(cur_selection) == 1:
335 time_to_autologout = None
336 scroll_options(username, mk)
339 cur_selection += chr(key + ord('0'))
340 #make_selection(cur_selection)
341 # XXX this should move somewhere else:
342 if cur_selection == '55':
343 mk.set_message('OPENSESAME')
345 ret = os.system('su - "%s" -c "dispense door"'%username)
347 ret = os.system('dispense door')
349 mk.set_message(center('DOOR OPEN'))
351 mk.set_message(center('BAD DOOR'))
353 elif cur_selection == '91':
355 elif cur_selection == '99':
356 scroll_options(username, mk)
359 elif cur_selection[1] == '8':
360 v.display('GOT COKE?')
361 os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0]))
363 v.display('HERES A '+cur_selection)
364 v.vend(cur_selection)
366 v.display('THANK YOU')
369 time_to_autologout = time() + 8
371 def connect_to_vend(options, cf):
372 # Open vending machine via LAT?
374 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
375 rfh, wfh = latclient.get_fh()
377 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
379 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
380 sock.connect((options.host, options.port))
381 rfh = sock.makefile('r')
382 wfh = sock.makefile('w')
387 from optparse import OptionParser
389 op = OptionParser(usage="%prog [OPTION]...")
390 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')
391 op.add_option('-v', '--virtualvend', action='store_false', default=True, dest='use_lat', help='use the virtual vending server instead of LAT')
392 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
393 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
394 options, args = op.parse_args()
397 op.error('extra command line arguments: ' + ' '.join(args))
402 'DBServer': ('Database', 'Server'),
403 'DBName': ('Database', 'Name'),
404 'DBUser': ('VendingMachine', 'DBUser'),
405 'DBPassword': ('VendingMachine', 'DBPassword'),
407 'ServiceName': ('VendingMachine', 'ServiceName'),
408 'ServicePassword': ('VendingMachine', 'Password'),
410 'ServerName': ('DecServer', 'Name'),
411 'ConnectPassword': ('DecServer', 'ConnectPassword'),
412 'PrivPassword': ('DecServer', 'PrivPassword'),
415 class VendConfigFile:
416 def __init__(self, config_file, options):
418 cp = ConfigParser.ConfigParser()
421 for option in options:
422 section, name = options[option]
423 value = cp.get(section, name)
424 self.__dict__[option] = value
426 except ConfigParser.Error, e:
427 print "Error reading config file "+config_file+": " + str(e)
430 def do_vend_server():
431 options = parse_args()
432 config_opts = VendConfigFile(options.config_file, config_options)
436 rfh, wfh = connect_to_vend(options, config_opts)
437 except (LATClientException, socket.error), e:
438 (exc_type, exc_value, exc_traceback) = sys.exc_info()
441 print "Connection error: "+str(exc_type)+" "+str(e)
442 print "Trying again in 5 seconds."
446 run_forever(rfh, wfh, options, config_opts)
447 except VendingException:
449 print "Connection died, trying again..."
451 if __name__ == '__main__':
452 def do_nothing(signum, stack): pass
453 def stop_server(signum, stack): raise KeyboardInterrupt
454 signal.signal(signal.SIGHUP, do_nothing)
455 signal.signal(signal.SIGTERM, stop_server)
456 signal.signal(signal.SIGINT, stop_server)
460 except KeyboardInterrupt:
461 print "Killed by signal."
464 (exc_type, exc_value, exc_traceback) = sys.exc_info()
466 print "Uh-oh, unhandled " + str(exc_type) + " exception"
467 print "Message: ", str(exc_value)
469 print_tb(exc_traceback)
472 print "This message should be considered a bug in the Vend Server."
473 print "Please report this to someone who can fix it."
475 print "Trying again anyway (might not help, but hey...)"