7 import sys, os, string, re, pwd
9 from time import time, sleep
10 from popen2 import popen2
11 from LATClient import LATClient, LATClientException
12 from VendingMachine import VendingMachine, VendingException
13 from HorizScroll import HorizScroll
14 from random import random, seed
15 from Idler import TrainIdler,GrayIdler
17 from traceback import print_tb
19 GREETING = 'UCC SNACKS'
26 class DispenseDatabase:
27 def __init__(self, vending_machine, host, name, user, password):
28 self.vending_machine = vending_machine
29 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
30 self.db.query('LISTEN vend_requests')
32 def process_requests(self):
34 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
36 outstanding = self.db.query(query).getresult()
37 except (pg.error,), db_err:
38 sys.stderr.write('Failed to query database: %s\n'%(db_err.strip()))
40 for (id, slot) in outstanding:
41 (worked, code, string) = self.vending_machine.vend(slot)
42 print (worked, code, string)
44 query = 'SELECT vend_success(%s)'%id
45 self.db.query(query).getresult()
47 query = 'SELECT vend_failed(%s)'%id
48 self.db.query(query).getresult()
50 def handle_events(self):
51 notifier = self.db.getnotify()
52 while notifier is not None:
53 self.process_requests()
54 notify = self.db.getnotify()
56 def scroll_options(username, mk, welcome = False):
58 msg = [(center('WELCOME'), False, 0.8),
59 (center(username), False, 0.8)]
62 choices = ' '*10+'CHOICES: '
64 coke_machine = file('/home/other/coke/coke_contents')
65 cokes = coke_machine.readlines()
72 (slot_num, price, slot_name) = c.split(' ', 2)
73 if slot_name == 'dead': continue
74 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
76 choices += 'OR A SNACK. '
77 choices += '99 TO READ AGAIN. '
79 msg.append((choices, False, None))
84 info = pwd.getpwuid(uid)
87 if info.pw_dir == None: return False
88 pinfile = os.path.join(info.pw_dir, '.pin')
101 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
105 def has_good_pin(uid):
106 return get_pin(uid) != None
108 def verify_user_pin(uid, pin):
109 if get_pin(uid) == pin:
110 info = pwd.getpwuid(uid)
115 def door_open_mode(v):
116 print "Entering open door mode"
117 v.display("-FEED ME-")
123 if params == 1: # door closed
124 v.display("-YUM YUM!-")
130 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
131 choice = int(random()*len(messages))
132 msg = messages[choice]
133 left = range(len(msg))
134 for i in range(len(msg)):
135 if msg[i] == ' ': left.remove(i)
139 for i in range(0, len(msg)):
145 s += chr(int(random()*26)+ord('A'))
154 return ' '*((LEN-len(str))/2)+str
157 def __init__(self, vendie):
158 # Each element of scrolling_message should be a 3-tuple of
159 # ('message', True/False if it is to be repeated, time to display)
160 self.scrolling_message = []
162 self.next_update = None
164 def set_message(self, string):
165 self.scrolling_message = [(string, False, None)]
166 self.update_display(True)
168 def set_messages(self, strings):
169 self.scrolling_message = strings
170 self.update_display(True)
172 def update_display(self, forced = False):
173 if not forced and self.next_update != None and time() < self.next_update:
175 if len(self.scrolling_message) > 0:
176 if len(self.scrolling_message[0][0]) > 10:
177 (m, r, t) = self.scrolling_message[0]
179 exp = HorizScroll(m).expand(padding = 0, wraparound = True)
186 del self.scrolling_message[0]
187 self.scrolling_message = a + self.scrolling_message
188 newmsg = self.scrolling_message[0]
189 if newmsg[2] != None:
190 self.next_update = time() + newmsg[2]
192 self.next_update = None
193 self.v.display(self.scrolling_message[0][0])
194 if self.scrolling_message[0][1]:
195 self.scrolling_message.append(self.scrolling_message[0])
196 del self.scrolling_message[0]
199 return len(self.scrolling_message) == 0
201 def run_forever(rfh, wfh, options, cf):
202 v = VendingMachine(rfh, wfh)
203 print 'PING is', v.ping()
205 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
210 mk = MessageKeeper(v)
211 mk.set_message(GREETING)
212 time_to_autologout = None
213 #idler = TrainIdler(v)
214 #idler = GrayIdler(v)
215 idler = GrayIdler(v,one="*",zero="-")
217 last_timeout_refresh = None
220 if USE_DB: db.handle_events()
222 if time_to_autologout != None:
223 time_left = time_to_autologout - time()
224 if time_left < 6 and (last_timeout_refresh is None or last_timeout_refresh > time_left):
225 mk.set_message('LOGOUT: '+str(int(time_left)))
226 last_timeout_refresh = int(time_left)
229 if time_to_autologout != None and time_to_autologout - time() <= 0:
230 time_to_autologout = None
234 mk.set_message(GREETING)
236 if time_to_autologout and not mk.done(): time_to_autologout = None
237 if cur_user == '' and time_to_autologout: time_to_autologout = None
238 if len(cur_pin) == PIN_LENGTH and mk.done() and time_to_autologout == None:
240 time_to_autologout = time() + 15
242 if time_to_idle == None and cur_user == '': time_to_idle = time() + 60
243 if time_to_idle != None and cur_user != '': time_to_idle = None
244 if time_to_idle is not None and time() > time_to_idle: idler.next()
250 e = v.next_event(0.1)
261 mk.set_message(GREETING)
262 elif event == SWITCH:
263 # don't care right now.
267 # complicated key handling here:
268 if len(cur_user) < 5:
271 mk.set_message(GREETING)
273 cur_user += chr(key + ord('0'))
274 mk.set_message('UID: '+cur_user)
275 if len(cur_user) == 5:
277 if not has_good_pin(uid):
279 #[(center('INVALID'), False, 0.7),
280 #(center('PIN'), False, 0.7),
281 #(center('SETUP'), False, 1.0),
282 #(GREETING, False, None)])
284 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
285 (GREETING, False, None)])
290 mk.set_message('PIN: ')
292 elif len(cur_pin) < PIN_LENGTH:
296 mk.set_message(GREETING)
299 mk.set_message('PIN: ')
301 cur_pin += chr(key + ord('0'))
302 mk.set_message('PIN: '+'X'*len(cur_pin))
303 if len(cur_pin) == PIN_LENGTH:
304 username = verify_user_pin(int(cur_user), int(cur_pin))
308 scroll_options(username, mk, True)
313 [(center('BAD PIN'), False, 1.0),
314 (center('SORRY'), False, 0.5),
315 (GREETING, False, None)])
319 elif len(cur_selection) == 0:
325 [(center('BYE!'), False, 1.5),
326 (GREETING, False, None)])
328 cur_selection += chr(key + ord('0'))
329 mk.set_message('SELECT: '+cur_selection)
330 time_to_autologout = None
331 elif len(cur_selection) == 1:
334 time_to_autologout = None
335 scroll_options(username, mk)
338 cur_selection += chr(key + ord('0'))
339 #make_selection(cur_selection)
340 # XXX this should move somewhere else:
341 if cur_selection == '55':
342 mk.set_message('OPENSESAME')
343 ret = os.system('su - "%s" -c "dispense door"'%username)
345 mk.set_message(center('DOOR OPEN'))
347 mk.set_message(center('BAD DOOR'))
349 elif cur_selection == '91':
351 elif cur_selection == '99':
352 scroll_options(username, mk)
355 elif cur_selection[1] == '8':
356 v.display('GOT COKE?')
357 os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0]))
359 v.display('HERES A '+cur_selection)
360 v.vend(cur_selection)
362 v.display('THANK YOU')
365 time_to_autologout = time() + 8
367 def connect_to_vend(options, cf):
368 # Open vending machine via LAT?
370 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
371 rfh, wfh = latclient.get_fh()
373 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
375 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
376 sock.connect((options.host, options.port))
377 rfh = sock.makefile('r')
378 wfh = sock.makefile('w')
383 from optparse import OptionParser
385 op = OptionParser(usage="%prog [OPTION]...")
386 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')
387 op.add_option('-v', '--virtualvend', action='store_false', default=True, dest='use_lat', help='use the virtual vending server instead of LAT')
388 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
389 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
390 options, args = op.parse_args()
393 op.error('extra command line arguments: ' + ' '.join(args))
398 'DBServer': ('Database', 'Server'),
399 'DBName': ('Database', 'Name'),
400 'DBUser': ('VendingMachine', 'DBUser'),
401 'DBPassword': ('VendingMachine', 'DBPassword'),
403 'ServiceName': ('VendingMachine', 'ServiceName'),
404 'ServicePassword': ('VendingMachine', 'Password'),
406 'ServerName': ('DecServer', 'Name'),
407 'ConnectPassword': ('DecServer', 'ConnectPassword'),
408 'PrivPassword': ('DecServer', 'PrivPassword'),
411 class VendConfigFile:
412 def __init__(self, config_file, options):
414 cp = ConfigParser.ConfigParser()
417 for option in options:
418 section, name = options[option]
419 value = cp.get(section, name)
420 self.__dict__[option] = value
422 except ConfigParser.Error, e:
423 print "Error reading config file "+config_file+": " + str(e)
427 if __name__ == '__main__':
429 options = parse_args()
430 config_opts = VendConfigFile(options.config_file, config_options)
434 rfh, wfh = connect_to_vend(options, config_opts)
435 except (LATClientException, socket.error), e:
436 (exc_type, exc_value, exc_traceback) = sys.exc_info()
439 print "Connection error: "+str(exc_type)+" "+str(e)
440 print "Trying again in 5 seconds."
444 run_forever(rfh, wfh, options, config_opts)
445 except VendingException:
447 print "Connection died, trying again..."