split out idlers into Seperate file
[uccvend-vendserver.git] / sql-edition / servers / VendServer.py
1 #!/usr/bin/python
2 # vim:ts=4
3
4 USE_DB = 0
5
6 import sys, os, string, re, pwd
7 if USE_DB: import pg
8 from time import time, sleep
9 from popen2 import popen2
10 from LATClient import LATClient
11 from VendingMachine import VendingMachine
12 from ConfigParser import ConfigParser
13 from HorizScroll import HorizScroll
14 from random import random, seed
15 from Idler import TrainIdler,GrayIdler
16
17 GREETING = 'UCC SNACKS'
18 PIN_LENGTH = 4
19
20 DOOR = 1
21 SWITCH = 2
22 KEY = 3
23
24 class DispenseDatabase:
25         def __init__(self, vending_machine, host, name, user, password):
26                 self.vending_machine = vending_machine
27                 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
28                 self.db.query('LISTEN vend_requests')
29
30         def process_requests(self):
31                 print 'processing'
32                 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
33                 try:
34                         outstanding = self.db.query(query).getresult()
35                 except (pg.error,), db_err:
36                         sys.stderr.write('Failed to query database: %s\n'%(db_err.strip()))
37                         return
38                 for (id, slot) in outstanding:
39                         (worked, code, string) = self.vending_machine.vend(slot)
40                         print (worked, code, string)
41                         if worked:
42                                 query = 'SELECT vend_success(%s)'%id
43                                 self.db.query(query).getresult()
44                         else:
45                                 query = 'SELECT vend_failed(%s)'%id
46                                 self.db.query(query).getresult()
47
48         def handle_events(self):
49                 notifier = self.db.getnotify()
50                 while notifier is not None:
51                         self.process_requests()
52                         notify = self.db.getnotify()
53
54 def scroll_options(username, mk, welcome = False):
55         if welcome:
56                 msg = [(center('WELCOME'), False, 0.8),
57                            (center(username), False, 0.8)]
58         else:
59                 msg = []
60         choices = ' '*10+'CHOICES: '
61         try:
62                 coke_machine = file('/home/other/coke/coke_contents')
63                 cokes = coke_machine.readlines()
64                 coke_machine.close()
65         except:
66                 cokes = []
67                 pass
68         for c in cokes:
69                 c = c.strip()
70                 (slot_num, price, slot_name) = c.split(' ', 2)
71                 if slot_name == 'dead': continue
72                 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
73         choices += '55-DOOR '
74         choices += 'OR A SNACK. '
75         choices += '99 TO READ AGAIN. '
76         choices += 'CHOICE?   '
77         msg.append((choices, False, None))
78         mk.set_messages(msg)
79
80 def get_pin(uid):
81         try:
82                 info = pwd.getpwuid(uid)
83         except KeyError:
84                 return None
85         if info.pw_dir == None: return False
86         pinfile = os.path.join(info.pw_dir, '.pin')
87         try:
88                 s = os.stat(pinfile)
89         except OSError:
90                 return None
91         if s.st_mode & 077:
92                 return None
93         try:
94                 f = file(pinfile)
95         except IOError:
96                 return None
97         pinstr = f.readline()
98         f.close()
99         if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
100                 return None
101         return int(pinstr)
102
103 def has_good_pin(uid):
104         return get_pin(uid) != None
105
106 def verify_user_pin(uid, pin):
107         if get_pin(uid) == pin:
108                 info = pwd.getpwuid(uid)
109                 return info.pw_name
110         else:
111                 return None
112
113 def door_open_mode(vending_machine):
114         print "Entering open door mode"
115         v.display("-FEED  ME-")
116         while True:
117                 e = v.next_event()
118                 if e == None: break
119                 (event, params) = e
120                 if event == DOOR:
121                         if params == 1: # door closed
122                                 v.display("-YUM YUM!-")
123                                 sleep(1)
124                                 return
125
126 def cookie(v):
127         seed(time())
128         messages = ['  WASSUP! ', 'PINK FISH ', ' SECRETS ', '  ESKIMO  ', ' FORTUNES ', 'MORE MONEY']
129         choice = int(random()*len(messages))
130         msg = messages[choice]
131         left = range(len(msg))
132         for i in range(len(msg)):
133                 if msg[i] == ' ': left.remove(i)
134         reveal = 1
135         while left:
136                 s = ''
137                 for i in range(0, len(msg)):
138                         if i in left:
139                                 if reveal == 0:
140                                         left.remove(i)
141                                         s += msg[i]
142                                 else:
143                                         s += chr(int(random()*26)+ord('A'))
144                                 reveal += 1
145                                 reveal %= 17
146                         else:
147                                 s += msg[i]
148                 v.display(s)
149
150 def center(str):
151         LEN = 10
152         return ' '*((LEN-len(str))/2)+str
153
154 class MessageKeeper:
155         def __init__(self, vendie):
156                 # Each element of scrolling_message should be a 3-tuple of
157                 # ('message', True/False if it is to be repeated, time to display)
158                 self.scrolling_message = []
159                 self.v = vendie
160                 self.next_update = None
161
162         def set_message(self, string):
163                 self.scrolling_message = [(string, False, None)]
164                 self.update_display(True)
165
166         def set_messages(self, strings):
167                 self.scrolling_message = strings
168                 self.update_display(True)
169
170         def update_display(self, forced = False):
171                 if not forced and self.next_update != None and time() < self.next_update:
172                         return
173                 if len(self.scrolling_message) > 0:
174                         if len(self.scrolling_message[0][0]) > 10:
175                                 (m, r, t) = self.scrolling_message[0]
176                                 a = []
177                                 exp = HorizScroll(m).expand(padding = 0, wraparound = True)
178                                 if t == None:
179                                         t = 0.1
180                                 else:
181                                         t = t / len(exp)
182                                 for x in exp:
183                                         a.append((x, r, t))
184                                 del self.scrolling_message[0]
185                                 self.scrolling_message = a + self.scrolling_message
186                         newmsg = self.scrolling_message[0]
187                         if newmsg[2] != None:
188                                 self.next_update = time() + newmsg[2]
189                         else:
190                                 self.next_update = None
191                         self.v.display(self.scrolling_message[0][0])
192                         if self.scrolling_message[0][1]:
193                                 self.scrolling_message.append(self.scrolling_message[0])
194                         del self.scrolling_message[0]
195
196         def done(self):
197                 return len(self.scrolling_message) == 0
198
199 if __name__ == '__main__':
200         cp = ConfigParser()
201         cp.read('/etc/dispense/servers.conf')
202         DBServer = cp.get('Database', 'Server')
203         DBName = cp.get('Database', 'Name')
204         DBUser = cp.get('VendingMachine', 'DBUser')
205         DBPassword = cp.get('VendingMachine', 'DBPassword')
206
207         ServiceName = cp.get('VendingMachine', 'ServiceName')
208         ServicePassword = cp.get('VendingMachine', 'Password')
209         # Open vending machine via LAT
210         if 0:
211                 latclient = LATClient(service = ServiceName, password = ServicePassword)
212                 (rfh, wfh) = latclient.get_fh()
213         else:
214                 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
215                 import socket
216                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
217                 sock.connect(('localhost', 5150))
218                 rfh = sock.makefile('r')
219                 wfh = sock.makefile('w')
220         v = VendingMachine(rfh, wfh)
221         print 'PING is', v.ping()
222
223         if USE_DB: db = DispenseDatabase(v, DBServer, DBName, DBUser, DBPassword)
224         cur_user = ''
225         cur_pin = ''
226         cur_selection = ''
227
228         mk = MessageKeeper(v)
229         mk.set_message(GREETING)
230         time_to_autologout = None
231         #idler = TrainIdler(v)
232         #idler = GrayIdler(v)
233         idler = GrayIdler(v,one="*",zero="-")
234         time_to_idle = None
235         last_timeout_refresh = None
236
237         while True:
238                 if USE_DB: db.handle_events()
239
240                 if time_to_autologout != None:
241                         time_left = time_to_autologout - time()
242                         if time_left < 6 and (last_timeout_refresh is None or last_timeout_refresh > time_left):
243                                 mk.set_message('LOGOUT: '+str(int(time_left)))
244                                 last_timeout_refresh = int(time_left)
245                                 cur_selection = ''
246
247                 if time_to_autologout != None and time_to_autologout - time() <= 0:
248                         time_to_autologout = None
249                         cur_user = ''
250                         cur_pin = ''
251                         cur_selection = ''
252                         mk.set_message(GREETING)
253
254                 if time_to_autologout and not mk.done(): time_to_autologout = None
255                 if cur_user == '' and time_to_autologout: time_to_autologout = None
256                 if len(cur_pin) == PIN_LENGTH and mk.done() and time_to_autologout == None:
257                         # start autologout
258                         time_to_autologout = time() + 15
259
260                 if time_to_idle == None and cur_user == '': time_to_idle = time() + 60
261                 if time_to_idle != None and cur_user != '': time_to_idle = None
262                 if time_to_idle is not None and time() > time_to_idle: idler.next()
263
264                 mk.update_display()
265
266                 e = v.next_event(0)
267                 if e == None:
268                         e = v.next_event(0.1)
269                         if e == None:
270                                 continue
271                 time_to_idle = None
272                 (event, params) = e
273                 print e
274                 if event == DOOR:
275                         if params == 0:
276                                 door_open_mode(v);
277                                 cur_user = ''
278                                 cur_pin = ''
279                                 mk.set_message(GREETING)
280                 elif event == SWITCH:
281                         # don't care right now.
282                         pass
283                 elif event == KEY:
284                         key = params
285                         # complicated key handling here:
286                         if len(cur_user) < 5:
287                                 if key == 11:
288                                         cur_user = ''
289                                         mk.set_message(GREETING)
290                                         continue
291                                 cur_user += chr(key + ord('0'))
292                                 mk.set_message('UID: '+cur_user)
293                                 if len(cur_user) == 5:
294                                         uid = int(cur_user)
295                                         if not has_good_pin(uid):
296                                                 #mk.set_messages(
297                                                         #[(center('INVALID'), False, 0.7),
298                                                          #(center('PIN'), False, 0.7),
299                                                          #(center('SETUP'), False, 1.0),
300                                                          #(GREETING, False, None)])
301                                                 mk.set_messages(
302                                                         [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
303                                                          (GREETING, False, None)])
304                                                 cur_user = ''
305                                                 cur_pin = ''
306                                                 continue
307                                         cur_pin = ''
308                                         mk.set_message('PIN: ')
309                                         continue
310                         elif len(cur_pin) < PIN_LENGTH:
311                                 if key == 11:
312                                         if cur_pin == '':
313                                                 cur_user = ''
314                                                 mk.set_message(GREETING)
315                                                 continue
316                                         cur_pin = ''
317                                         mk.set_message('PIN: ')
318                                         continue
319                                 cur_pin += chr(key + ord('0'))
320                                 mk.set_message('PIN: '+'X'*len(cur_pin))
321                                 if len(cur_pin) == PIN_LENGTH:
322                                         username = verify_user_pin(int(cur_user), int(cur_pin))
323                                         if username:
324                                                 v.beep(0, False)
325                                                 cur_selection = ''
326                                                 scroll_options(username, mk, True)
327                                                 continue
328                                         else:
329                                                 v.beep(40, False)
330                                                 mk.set_messages(
331                                                         [(center('BAD PIN'), False, 1.0),
332                                                          (center('SORRY'), False, 0.5),
333                                                          (GREETING, False, None)])
334                                                 cur_user = ''
335                                                 cur_pin = ''
336                                                 continue
337                         elif len(cur_selection) == 0:
338                                 if key == 11:
339                                         cur_pin = ''
340                                         cur_user = ''
341                                         cur_selection = ''
342                                         mk.set_messages(
343                                                 [(center('BYE!'), False, 1.5),
344                                                  (GREETING, False, None)])
345                                         continue
346                                 cur_selection += chr(key + ord('0'))
347                                 mk.set_message('SELECT: '+cur_selection)
348                                 time_to_autologout = None
349                         elif len(cur_selection) == 1:
350                                 if key == 11:
351                                         cur_selection = ''
352                                         time_to_autologout = None
353                                         scroll_options(username, mk)
354                                         continue
355                                 else:
356                                         cur_selection += chr(key + ord('0'))
357                                         #make_selection(cur_selection)
358                                         # XXX this should move somewhere else:
359                                         if cur_selection == '55':
360                                                 mk.set_message('OPENSESAME')
361                                                 ret = os.system('su - "%s" -c "dispense door"'%username)
362                                                 if ret == 0:
363                                                         mk.set_message(center('DOOR OPEN'))
364                                                 else:
365                                                         mk.set_message(center('BAD DOOR'))
366                                                 sleep(1)
367                                         elif cur_selection == '91':
368                                                 cookie(v)
369                                         elif cur_selection == '99':
370                                                 scroll_options(username, mk)
371                                                 cur_selection = ''
372                                                 continue
373                                         elif cur_selection[1] == '8':
374                                                 v.display('GOT COKE?')
375                                                 os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0]))
376                                         else:
377                                                 v.display('HERES A '+cur_selection)
378                                                 v.vend(cur_selection)
379                                         sleep(0.5)
380                                         v.display('THANK YOU')
381                                         sleep(0.5)
382                                         cur_selection = ''
383                                         time_to_autologout = time() + 8

UCC git Repository :: git.ucc.asn.au