7021622b3d93b4d3f6918dd0672694ac40ad1b9b
[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 < 6 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                 time_to_idle = None
328                 (event, params) = e
329                 print e
330                 if event == DOOR:
331                         if params == 0:
332                                 door_open_mode(v);
333                                 cur_user = ''
334                                 cur_pin = ''
335                                 mk.set_message(GREETING)
336                 elif event == SWITCH:
337                         # don't care right now.
338                         pass
339                 elif event == KEY:
340                         key = params
341                         # complicated key handling here:
342                         if len(cur_user) < 5:
343                                 if key == 11:
344                                         cur_user = ''
345                                         mk.set_message(GREETING)
346                                         continue
347                                 cur_user += chr(key + ord('0'))
348                                 mk.set_message('UID: '+cur_user)
349                                 if len(cur_user) == 5:
350                                         uid = int(cur_user)
351                                         if not has_good_pin(uid):
352                                                 #mk.set_messages(
353                                                         #[(center('INVALID'), False, 0.7),
354                                                          #(center('PIN'), False, 0.7),
355                                                          #(center('SETUP'), False, 1.0),
356                                                          #(GREETING, False, None)])
357                                                 mk.set_messages(
358                                                         [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
359                                                          (GREETING, False, None)])
360                                                 cur_user = ''
361                                                 cur_pin = ''
362                                                 continue
363                                         cur_pin = ''
364                                         mk.set_message('PIN: ')
365                                         continue
366                         elif len(cur_pin) < PIN_LENGTH:
367                                 if key == 11:
368                                         if cur_pin == '':
369                                                 cur_user = ''
370                                                 mk.set_message(GREETING)
371                                                 continue
372                                         cur_pin = ''
373                                         mk.set_message('PIN: ')
374                                         continue
375                                 cur_pin += chr(key + ord('0'))
376                                 mk.set_message('PIN: '+'X'*len(cur_pin))
377                                 if len(cur_pin) == PIN_LENGTH:
378                                         username = verify_user_pin(int(cur_user), int(cur_pin))
379                                         if username:
380                                                 v.beep(0, False)
381                                                 cur_selection = ''
382                                                 scroll_options(username, mk, True)
383                                                 continue
384                                         else:
385                                                 v.beep(40, False)
386                                                 mk.set_messages(
387                                                         [(center('BAD PIN'), False, 1.0),
388                                                          (center('SORRY'), False, 0.5),
389                                                          (GREETING, False, None)])
390                                                 cur_user = ''
391                                                 cur_pin = ''
392                                                 continue
393                         elif len(cur_selection) == 0:
394                                 if key == 11:
395                                         cur_pin = ''
396                                         cur_user = ''
397                                         cur_selection = ''
398                                         mk.set_messages(
399                                                 [(center('BYE!'), False, 1.5),
400                                                  (GREETING, False, None)])
401                                         continue
402                                 cur_selection += chr(key + ord('0'))
403                                 mk.set_message('SELECT: '+cur_selection)
404                                 time_to_autologout = None
405                         elif len(cur_selection) == 1:
406                                 if key == 11:
407                                         cur_selection = ''
408                                         time_to_autologout = None
409                                         scroll_options(username, mk)
410                                         continue
411                                 else:
412                                         cur_selection += chr(key + ord('0'))
413                                         #make_selection(cur_selection)
414                                         # XXX this should move somewhere else:
415                                         if cur_selection == '55':
416                                                 mk.set_message('OPENSESAME')
417                                                 ret = os.system('su - "%s" -c "dispense door"'%username)
418                                                 if ret == 0:
419                                                         mk.set_message(center('DOOR OPEN'))
420                                                 else:
421                                                         mk.set_message(center('BAD DOOR'))
422                                                 sleep(1)
423                                         elif cur_selection == '91':
424                                                 cookie(v)
425                                         elif cur_selection == '99':
426                                                 scroll_options(username, mk)
427                                                 cur_selection = ''
428                                                 continue
429                                         elif cur_selection[1] == '8':
430                                                 v.display('GOT COKE?')
431                                                 os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0]))
432                                         else:
433                                                 v.display('HERES A '+cur_selection)
434                                                 v.vend(cur_selection)
435                                         sleep(0.5)
436                                         v.display('THANK YOU')
437                                         sleep(0.5)
438                                         cur_selection = ''
439                                         time_to_autologout = time() + 8

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