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

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