Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 235

History | View | Annotate | Download (11.5 KB)

1
#!/usr/bin/python
2

    
3
"""
4
  This file is part of Tooltron.
5
 
6
  Tooltron is free software: you can redistribute it and/or modify
7
  it under the terms of the Lesser GNU General Public License as published by
8
  the Free Software Foundation, either version 3 of the License, or
9
  (at your option) any later version.
10
 
11
  Tooltron is distributed in the hope that it will be useful,
12
  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
  Lesser GNU General Public License for more details.
15
  You should have received a copy of the Lesser GNU General Public License
16
  along with Tooltron.  If not, see <http://www.gnu.org/licenses/>.
17

18
  Copyright 2009 Bradford Neuman <bneuman@andrew.cmu.edu>
19

20
"""
21

    
22
################################################################################
23
## tooltron.py is the main tooltron server
24
# It connects to the civicrm database of mysql and sends commands to
25
# the cardbox and tools.  It must be connected over usb to the card
26
# reader, and the two arguments specify the devices to use for the
27
# serial communication to the card and tool boxes.
28
#
29
# This script requires the modules seen below, which may not be
30
# present by default
31
#
32
# Because of a problem with card scans buffering in stdio, there is a
33
# seperate thread which stores the last swiped id. To cleanly exit the
34
# program type C-d
35
#
36
# email bradneuman@gmail.com if you have any questions
37
################################################################################
38

    
39

    
40
import re
41
import sys
42
import MySQLdb
43
import getpass
44
from time import *
45
sys.path.append('../common')
46
import common
47

    
48
keypadTimeout = 31 #in seconds
49

    
50
#this table maps which keypad button to press for each tool. This
51
#list should probably be printed next to the swipe box
52
tools = {
53
   'Bandsaw':[],
54
   'DrillPress':['3','8'],
55
   'Mill':['4'],
56
   'Lathe':[],
57
   #HACK: since the saw isn't used I use it for testing random boards
58
   'ChopMiterSaw':['1','2','5','6','7','9']
59
}
60

    
61
#this table maps keypad button numbers to tool IDs. The default is
62
#that the tool id is 10+the keypad number, but this would change if
63
#boards were swapped out. The tool ID number should be printed on the
64
#tool board, but needs to be hand coded into the toolbox code
65
ids = {
66
   '1':11,
67
   '2':12,
68
   '3':13,
69
   '4':14,
70
   '5':15,
71
   '6':16,
72
   '7':17,
73
   '8':18,
74
   '9':19,
75
   '0':13
76
}
77

    
78
#################################################
79
#this code spawns a thread which always puts the last card id in the
80
#lastid variable.
81
#################################################
82
import threading
83
import time
84
import select
85
import os
86

    
87
lastid = None
88
idready = False
89
threadDone = False
90
threadDoneExcp = None
91

    
92
cv = threading.Condition()
93

    
94
class ctrlDException (Exception):
95
   def __init__(self,str):
96
      Exception.__init__(self)
97
      self.str = str
98

    
99
   def __str__(self):
100
      return "Ctrl-D pressed: " + self.str
101

    
102
class idThread ( threading.Thread ):
103

    
104
   def __init__ (self):
105
      threading.Thread.__init__(self)
106
      self.wpipe = None
107

    
108
   def stop (self):
109
      self.stop_thread = True
110
      if self.wpipe != None:
111
         # write to this pipe so that the select will drop out
112
         self.wpipe.write('!')
113

    
114
   def run (self):
115
       global lastid
116
       global idready
117
       global threadDone
118
       global threadDoneExcp
119
       self.stop_thread = False
120

    
121
       [r,w] = os.pipe()
122
       self.rpipe = os.fdopen(r,'r')
123
       self.wpipe = os.fdopen(w,'w')
124

    
125
       try:
126
          while not self.stop_thread:
127
             # hang until we either get keyboard input or are told to stop
128
             [active,a,b] = select.select([sys.stdin,self.rpipe],[],[])
129
             if not self.stop_thread:
130
                l = raw_input()
131
                cv.acquire()
132
                try:
133
                   id = re.search('%([0-9]*)=.*', l)
134
                   if id != None:
135
                      lastid = id
136
                      print "!!! setting idready"
137
                      idready = True
138
                      cv.notify()
139
                   else:
140
                      ifready = False
141
                finally:
142
                   cv.release()
143
          print "thread stopped"
144

    
145
       except EOFError:
146
           cv.acquire()
147
           idready = True
148
           lastid=None
149
           cv.notify()
150
           cv.release()
151
           threadDoneExcp=ctrlDException("thread exiting cleanly")
152
       except Exception, e:
153
          threadDoneExcp = e #TODO: provide a line number or something?
154
       finally:
155
          print "id thread getting ready to exit..."
156
          threadDone = True
157
          cv.acquire()
158
          idready = False
159
          cv.notify()
160
          cv.release()
161
          print "id thread exiting"
162
          
163
#end thread
164
###############################################
165

    
166
flog = open("tooltron.log", "a")
167

    
168
def logMessage(str):
169
   flog.write(strftime("%Y.%m.%d %H:%M:%S ") + str + "\n")
170
   flog.flush()
171

    
172
logMessage("----- SERVER STARTED -----")
173

    
174
def keypad2toolID(t):
175
   if t in ids:
176
      return ids[t]
177
   else:
178
      return -1
179

    
180
def reverseTool(tn):
181
   for t in tools:
182
      if tn in tools[t]:
183
         return t
184

    
185
   return 'invalid'
186

    
187
#keep count of successive non-acks
188
toolFails = tools.copy()
189
for t in toolFails:
190
   toolFails[t] = 0
191

    
192
def get_last_id():
193
    global idready
194
    global lastid
195
    global threadDoneExcp
196

    
197
    got = None
198

    
199
    print ">>> locking for last_id"
200

    
201
    cv.acquire()
202
    while not idready:
203
       if threadDoneExcp != None:
204
          print "thread has been exited"
205
          raise threadDoneExcp
206

    
207
       print ">>> wait"
208
       cv.wait()
209

    
210
    print ">>> idready set"
211

    
212
    got = lastid
213
    idready = False
214
    cv.release()
215
    print ">>> done"
216

    
217
    return got
218

    
219
def clear_id():
220
    global idready
221
    global lastid
222

    
223
    print "=== locking to clear idready flag"
224
    cv.acquire()
225
    idready = False
226
    lastid = None
227
    cv.release()
228
    print "=== done"
229

    
230
    return
231

    
232
if len(sys.argv) < 2:
233
   print "usage: tooltron.py /path/to/bus/device"
234
   exit(-1)
235

    
236
if not common.initBus(sys.argv[1]):
237
    print "usage: tooltron.py /path/to/bus/device"
238
else:
239

    
240
   pw = getpass.getpass("mysql password: ")
241
   db = MySQLdb.connect(host="roboclub8.frc.ri.cmu.edu", user="tooltron", passwd=pw, db="civicrm")
242
   print "connected, now accepting input"
243
   logMessage("connected to database")
244

    
245
   #start the id scan thread AFTER the getpass prompt
246
   idt = idThread()
247
   idt.start()
248
   
249
   # If there are any uncaught exceptions in the rest of the code, we need to kill the id thread
250
   # so the program as a whole will exit
251
   try:
252
      cursor = db.cursor()
253

    
254
      qry = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
255

    
256
      def sendTool(t):
257
         tn = keypad2toolID(t)
258
         common.sendTool(tn)
259

    
260
      # This function eats the rest of the packet where data is what we have so far
261
      def flushPacket(data):
262
         while len(data) < 6:
263
            data += bus.read(1)
264

    
265
         plen = data[4]
266
         if plen > 0:
267
            bus.read(ord(plen))
268

    
269
      while True:
270
         id = get_last_id()
271

    
272
         if id != None:
273
            print "\n-----------\nid# ", id.group(1)
274

    
275
            common.sendKeyRequest()
276
            startTime = time.time()
277

    
278
            cursor.execute(qry + id.group(1))
279

    
280
            result = cursor.fetchall()
281

    
282
            acl = []
283

    
284
            for r in result:
285
               tls = r[0].split("\x01")
286
               for t in tls:
287
                  if t != '':
288
                     try:
289
                        acl.extend (tools[t])
290
                     except KeyError:
291
                        #this doesn't really matter
292
                        pass
293

    
294
            print "user has access to:", acl
295

    
296
            user = ""
297

    
298
            #query for name
299
            qry2 = "SELECT civicrm_contact.display_name \
300
                    FROM civicrm_value_roboclub_info_2 \
301
                    INNER JOIN (civicrm_contact) \
302
                    ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
303
                    WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";"
304

    
305
            cursor.execute(qry2)
306
            result = cursor.fetchall()
307

    
308
            if len(result) > 0:
309
               user += result[0][0]
310

    
311
            qry2 = "SELECT civicrm_email.email \
312
                    FROM civicrm_value_roboclub_info_2 \
313
                    INNER JOIN (civicrm_email) \
314
                    ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
315
                    WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";"
316

    
317
            cursor.execute(qry2)
318
            result = cursor.fetchall()
319

    
320
            if len(result) > 0:
321
               user += " ("+result[0][0]+")"
322

    
323
            if user == "":
324
               user = str(id.group(1))
325

    
326
            print user
327

    
328

    
329
            resp = None
330
            while resp==None:
331
               try:
332
                  resp = common.readKey()
333
               except TimoutException:
334
                  print "got TIMEOUT from keypad, breaking out of loop"
335
                  break
336

    
337
               #no lock since we are just reading and we can afford to
338
               #miss a loop
339
               if idready == True:
340
                  #if someone swipes, break out of the loop
341
                  print "**swipe during read, bailing out"
342
                  logMessage("user swipped during keypad read")
343
                  break
344
               if time.time() - startTime > keypadTimeout:
345
                  print "KEYPAD TIMEOUT!"
346
                  logMessage("keypad timed out")
347
                  common.sendMessage(2,common.TT_TIMEOUT,'')
348
                  break
349

    
350
               #if we have a valid response
351
               #sometimes the FTDI chip seems to give a zero as a string when power is reset
352
               if resp != None and ord(resp) != 0 and resp != common.TT_TIMEOUT:
353

    
354
                  toolName = reverseTool(resp)
355
                  toolNameLong = toolName + " (k="+str(resp)+"("+str(ord(resp))+"), t="+str(keypad2toolID(resp))+")"
356
                  print "request:",resp,"(",ord(resp),") ",toolNameLong
357

    
358
                  if acl.count(resp) > 0:
359
                     common.sendAck(2)
360
                     sendTool(resp)
361
                     print "ACCESS GRANTED"
362
                     logMessage(user+" ACCESSED tool "+toolNameLong)
363
                     if common.checkAck(resp):
364
                        print "ACK"
365
                        toolFails[toolName] = 0
366
                     else:
367
                        print "NO ACK!!!!"
368
                        logMessage("tool "+toolNameLong+" did not ACK")
369
                        toolFails[toolName] += 1
370
                        if toolFails[toolName] > common.MAX_TOOL_FAILS:
371
                           #TODO: send email
372
                           logMessage("WARNING: tool "+toolNameLong+
373
                                      " has failed to ACK "+
374
                                      str(common.MAX_TOOL_FAILS)+" times in a row.")
375

    
376
                  else:
377
                     common.sendNack(2)
378
                     print "ACCESS DENIED!!"
379
                     logMessage(user + " DENIED on tool "+toolNameLong)
380

    
381
                  clear_id()
382

    
383
               elif resp == common.TT_TIMEOUT:
384
                  print "TIMEOUT sent from keypad"
385
                  continue
386
               elif resp == None or ord(resp) == 0: #if we get noise, send timeout to reset and sync states
387
                  print "ERROR: got strange byte, timing out"
388
                  logMessage("ERROR: got 0 as a byte, sending timeout (cardbox power issue?)")
389
               else:
390
                  break
391
   except ctrlDException:
392
      print "got a ctrl-D, exiting cleanly"
393
   finally:
394
      if not threadDone:
395
         print "stopping id reader thread..."
396
         idt.stop()