6 import sys, os, string, re, pwd
8 from time import time, sleep
9 from popen2 import popen2
10 from LATClient import LATClient
11 from VendingMachine import VendingMachine
12 from HorizScroll import HorizScroll
13 from random import random, seed
14 from Idler import TrainIdler,GrayIdler
16 GREETING = 'UCC SNACKS'
23 class DispenseDatabase:
24 def __init__(self, vending_machine, host, name, user, password):
25 self.vending_machine = vending_machine
26 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
27 self.db.query('LISTEN vend_requests')
29 def process_requests(self):
31 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
33 outstanding = self.db.query(query).getresult()
34 except (pg.error,), db_err:
35 sys.stderr.write('Failed to query database: %s\n'%(db_err.strip()))
37 for (id, slot) in outstanding:
38 (worked, code, string) = self.vending_machine.vend(slot)
39 print (worked, code, string)
41 query = 'SELECT vend_success(%s)'%id
42 self.db.query(query).getresult()
44 query = 'SELECT vend_failed(%s)'%id
45 self.db.query(query).getresult()
47 def handle_events(self):
48 notifier = self.db.getnotify()
49 while notifier is not None:
50 self.process_requests()
51 notify = self.db.getnotify()
53 def scroll_options(username, mk, welcome = False):
55 msg = [(center('WELCOME'), False, 0.8),
56 (center(username), False, 0.8)]
59 choices = ' '*10+'CHOICES: '
61 coke_machine = file('/home/other/coke/coke_contents')
62 cokes = coke_machine.readlines()
69 (slot_num, price, slot_name) = c.split(' ', 2)
70 if slot_name == 'dead': continue
71 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
73 choices += 'OR A SNACK. '
74 choices += '99 TO READ AGAIN. '
76 msg.append((choices, False, None))
81 info = pwd.getpwuid(uid)
84 if info.pw_dir == None: return False
85 pinfile = os.path.join(info.pw_dir, '.pin')
98 if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
102 def has_good_pin(uid):
103 return get_pin(uid) != None
105 def verify_user_pin(uid, pin):
106 if get_pin(uid) == pin:
107 info = pwd.getpwuid(uid)
112 def door_open_mode(vending_machine):
113 print "Entering open door mode"
114 v.display("-FEED ME-")
120 if params == 1: # door closed
121 v.display("-YUM YUM!-")
127 messages = [' WASSUP! ', 'PINK FISH ', ' SECRETS ', ' ESKIMO ', ' FORTUNES ', 'MORE MONEY']
128 choice = int(random()*len(messages))
129 msg = messages[choice]
130 left = range(len(msg))
131 for i in range(len(msg)):
132 if msg[i] == ' ': left.remove(i)
136 for i in range(0, len(msg)):
142 s += chr(int(random()*26)+ord('A'))
151 return ' '*((LEN-len(str))/2)+str
154 def __init__(self, vendie):
155 # Each element of scrolling_message should be a 3-tuple of
156 # ('message', True/False if it is to be repeated, time to display)
157 self.scrolling_message = []
159 self.next_update = None
161 def set_message(self, string):
162 self.scrolling_message = [(string, False, None)]
163 self.update_display(True)
165 def set_messages(self, strings):
166 self.scrolling_message = strings
167 self.update_display(True)
169 def update_display(self, forced = False):
170 if not forced and self.next_update != None and time() < self.next_update:
172 if len(self.scrolling_message) > 0:
173 if len(self.scrolling_message[0][0]) > 10:
174 (m, r, t) = self.scrolling_message[0]
176 exp = HorizScroll(m).expand(padding = 0, wraparound = True)
183 del self.scrolling_message[0]
184 self.scrolling_message = a + self.scrolling_message
185 newmsg = self.scrolling_message[0]
186 if newmsg[2] != None:
187 self.next_update = time() + newmsg[2]
189 self.next_update = None
190 self.v.display(self.scrolling_message[0][0])
191 if self.scrolling_message[0][1]:
192 self.scrolling_message.append(self.scrolling_message[0])
193 del self.scrolling_message[0]
196 return len(self.scrolling_message) == 0
198 def run_forever(rfh, wfh):
199 v = VendingMachine(rfh, wfh)
200 print 'PING is', v.ping()
202 if USE_DB: db = DispenseDatabase(v, DBServer, DBName, DBUser, DBPassword)
207 mk = MessageKeeper(v)
208 mk.set_message(GREETING)
209 time_to_autologout = None
210 #idler = TrainIdler(v)
211 #idler = GrayIdler(v)
212 idler = GrayIdler(v,one="*",zero="-")
214 last_timeout_refresh = None
217 if USE_DB: db.handle_events()
219 if time_to_autologout != None:
220 time_left = time_to_autologout - time()
221 if time_left < 6 and (last_timeout_refresh is None or last_timeout_refresh > time_left):
222 mk.set_message('LOGOUT: '+str(int(time_left)))
223 last_timeout_refresh = int(time_left)
226 if time_to_autologout != None and time_to_autologout - time() <= 0:
227 time_to_autologout = None
231 mk.set_message(GREETING)
233 if time_to_autologout and not mk.done(): time_to_autologout = None
234 if cur_user == '' and time_to_autologout: time_to_autologout = None
235 if len(cur_pin) == PIN_LENGTH and mk.done() and time_to_autologout == None:
237 time_to_autologout = time() + 15
239 if time_to_idle == None and cur_user == '': time_to_idle = time() + 60
240 if time_to_idle != None and cur_user != '': time_to_idle = None
241 if time_to_idle is not None and time() > time_to_idle: idler.next()
247 e = v.next_event(0.1)
258 mk.set_message(GREETING)
259 elif event == SWITCH:
260 # don't care right now.
264 # complicated key handling here:
265 if len(cur_user) < 5:
268 mk.set_message(GREETING)
270 cur_user += chr(key + ord('0'))
271 mk.set_message('UID: '+cur_user)
272 if len(cur_user) == 5:
274 if not has_good_pin(uid):
276 #[(center('INVALID'), False, 0.7),
277 #(center('PIN'), False, 0.7),
278 #(center('SETUP'), False, 1.0),
279 #(GREETING, False, None)])
281 [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
282 (GREETING, False, None)])
287 mk.set_message('PIN: ')
289 elif len(cur_pin) < PIN_LENGTH:
293 mk.set_message(GREETING)
296 mk.set_message('PIN: ')
298 cur_pin += chr(key + ord('0'))
299 mk.set_message('PIN: '+'X'*len(cur_pin))
300 if len(cur_pin) == PIN_LENGTH:
301 username = verify_user_pin(int(cur_user), int(cur_pin))
305 scroll_options(username, mk, True)
310 [(center('BAD PIN'), False, 1.0),
311 (center('SORRY'), False, 0.5),
312 (GREETING, False, None)])
316 elif len(cur_selection) == 0:
322 [(center('BYE!'), False, 1.5),
323 (GREETING, False, None)])
325 cur_selection += chr(key + ord('0'))
326 mk.set_message('SELECT: '+cur_selection)
327 time_to_autologout = None
328 elif len(cur_selection) == 1:
331 time_to_autologout = None
332 scroll_options(username, mk)
335 cur_selection += chr(key + ord('0'))
336 #make_selection(cur_selection)
337 # XXX this should move somewhere else:
338 if cur_selection == '55':
339 mk.set_message('OPENSESAME')
340 ret = os.system('su - "%s" -c "dispense door"'%username)
342 mk.set_message(center('DOOR OPEN'))
344 mk.set_message(center('BAD DOOR'))
346 elif cur_selection == '91':
348 elif cur_selection == '99':
349 scroll_options(username, mk)
352 elif cur_selection[1] == '8':
353 v.display('GOT COKE?')
354 os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0]))
356 v.display('HERES A '+cur_selection)
357 v.vend(cur_selection)
359 v.display('THANK YOU')
362 time_to_autologout = time() + 8
364 # FIXME: a less ugly way of passing all these options would be nice
365 def connect_to_vend(options, DBServer, DBName, DBUser, DBPassword, ServiceName, ServicePassword):
366 # Open vending machine via LAT
368 latclient = LATClient(service = ServiceName, password = ServicePassword)
369 rfh, wfh = latclient.get_fh()
371 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
373 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
374 sock.connect((options.host, options.port))
375 rfh = sock.makefile('r')
376 wfh = sock.makefile('w')
380 if __name__ == '__main__':
381 from ConfigParser import ConfigParser
382 from optparse import OptionParser
384 op = OptionParser(usage="%prog [OPTION]...")
385 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')
386 op.add_option('-v', '--virtualvend', action='store_false', default=True, dest='use_lat', help='use the virtual vending server instead of LAT')
387 op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
388 op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
389 options, args = op.parse_args()
392 op.error('extra command line arguments: ' + ' '.join(args))
395 cp.read(options.config_file)
396 DBServer = cp.get('Database', 'Server')
397 DBName = cp.get('Database', 'Name')
398 DBUser = cp.get('VendingMachine', 'DBUser')
399 DBPassword = cp.get('VendingMachine', 'DBPassword')
401 ServiceName = cp.get('VendingMachine', 'ServiceName')
402 ServicePassword = cp.get('VendingMachine', 'Password')
405 rfh, wfh = connect_to_vend(options, DBServer, DBName, DBUser, DBPassword, ServiceName, ServicePassword)
407 run_forever(rfh, wfh)
408 except VendingException:
409 print "Connection died, trying again..."