Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 150

History | View | Annotate | Download (10.4 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 serial
43
import MySQLdb
44
import getpass
45
from time import *
46

    
47
keypad = []
48
keypadTimeout = 11 #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
TT_GET_KEY = 'k'
80
TT_ACK     = 'a'
81
TT_NACK    = 'n'
82
TT_TO      = 'f'
83
TT_TIMEOUT = 't'
84

    
85
BAUD_RATE = 9600
86

    
87
#fails until a warning is sent
88
MAX_TOOL_FAILS = 5
89

    
90
#################################################
91
#this code spawns a thread which always puts the last card id in the
92
#lastid variable.
93
#################################################
94
import threading
95
import time
96

    
97
lastid = None
98
idready = False
99

    
100
cv = threading.Condition()
101

    
102
class idThread ( threading.Thread ):
103

    
104
   def run ( self ):
105
       global lastid
106
       global idready
107

    
108
       try:
109
           while True:
110
               l = raw_input()
111
               cv.acquire()
112
               id = re.search('%([0-9]*)=.*', l)
113
               if id != None:
114
                   lastid = id
115
                   print "!!! setting idready"
116
                   idready = True
117
                   cv.notify()
118
               cv.release()
119

    
120
       except EOFError:
121
           cv.acquire()
122
           idready = True
123
           lastid=None
124
           cv.notify()
125
           cv.release()
126
#end thread
127
###############################################
128

    
129
flog = open("tooltron.log", "a")
130

    
131
def logMessage(str):
132
   flog.write(strftime("%Y.%m.%d %H:%M:%S ") + str + "\n")
133
   flog.flush()
134

    
135
logMessage("----- SERVER STARTED -----")
136

    
137
def keypad2toolID(t):
138
   if t in ids:
139
      return ids[t]
140
   else:
141
      return -1
142

    
143
def reverseTool(tn):
144
   for t in tools:
145
      if tn in tools[t]:
146
         return t
147

    
148
   return 'invalid'
149

    
150
#keep count of successive non-acks
151
toolFails = tools.copy()
152
for t in toolFails:
153
   toolFails[t] = 0
154

    
155
def get_last_id():
156
    global idready
157
    global lastid
158

    
159
    got = None
160

    
161
    print ">>> locking for last_id"
162

    
163
    cv.acquire()
164
    while not idready:
165
        print ">>> wait"
166
        cv.wait()
167

    
168
    print ">>> idready set"
169

    
170
    got = lastid
171
    idready = False
172
    cv.release()
173
    print ">>> done"
174

    
175
    return got
176

    
177
def clear_id():
178
    global idready
179
    global lastid
180

    
181
    print "=== locking to clear idready flag"
182
    cv.acquire()
183
    idready = False
184
    lastid = None
185
    cv.release()
186
    print "=== done"
187

    
188
    return
189

    
190

    
191
if len(sys.argv) < 3:
192
    print "usage: tooltron.py /path/to/keypad/device /path/to/tool/bus/device"
193

    
194
else:
195

    
196
    pw = getpass.getpass("mysql password: ")
197
    db = MySQLdb.connect(host="roboclub8.frc.ri.cmu.edu", user="tooltron", passwd=pw, db="civicrm")
198
    print "connected, now accepting input"
199
    logMessage("connected to database")
200

    
201
    #start the id scan thread AFTER the getpass prompt
202
    idThread().start()
203

    
204
    cursor = db.cursor()
205

    
206
    qry = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
207

    
208
    # user has 10 seconds to press a key, after that the keypad device
209
    # should send a timeout packet. This is a non-blocking serial
210
    # device so we can re-transmit the key request when the user
211
    # swipes a card instead of being stuck on a blocking read call
212
    keypad = serial.Serial(sys.argv[1], BAUD_RATE, timeout = 0) 
213
    keypad.flushInput()
214
    print keypad
215

    
216
    bus = serial.Serial(sys.argv[2], BAUD_RATE, timeout = 2)
217
    bus.flushInput()
218
    print bus
219
    
220
    # ^ <src> <dest> <data>
221
    def sendTool(t):
222
        tn = keypad2toolID(t)
223
        msg = '^' + chr(1) + chr(tn) + 'O' + chr(1 ^ tn ^ ord('O'))
224
        print "seding power to tool ID",tn
225
        bus.write(msg)
226
        return
227

    
228
    #returns [src, data] or [] on error
229
    def readTool():
230
        ret = [0,0,0]
231

    
232
        if bus.read(1) == '^':
233
            ret[0] = bus.read(1)
234
            ret[1] = bus.read(1)
235
            ret[2] = bus.read(1)
236
            x = bus.read(1)
237

    
238
            print "got packet",ret
239

    
240
            if chr(ord(ret[0]) ^ ord(ret[1]) ^ ord(ret[2])) == x:
241
                return ret
242
            else:
243
                print "xor fail. got", x, "should have been got",(ord(ret[0]) ^ ord(ret[1]) ^ ord(ret[2]))
244

    
245
        return []
246

    
247
    def checkAck(t):
248
        tn = keypad2toolID(t)
249
        m = readTool()
250

    
251
        if m== []:
252
            return False
253

    
254

    
255
        return m[0] == chr(tn) and m[1] == chr(1) and m[2] == 'A'
256

    
257

    
258
    while True:
259

    
260
        id = get_last_id()
261
        
262
        if id != None:
263
            print "\n-----------\nid# ", id.group(1)
264

    
265

    
266
            print "sending key request"
267
            keypad.flushInput()
268
            keypad.write(TT_GET_KEY)
269

    
270
            cursor.execute(qry + id.group(1))
271

    
272
            result = cursor.fetchall()
273

    
274
            acl = []
275

    
276
            for r in result:
277
                tls = r[0].split("\x01")
278
                for t in tls:
279
                    if t != '':
280
                        try:
281
                            acl.extend (tools[t])
282
                        except KeyError:
283
                            #this doesn't really matter
284
                            pass
285

    
286
            print "user has access to:", acl
287

    
288
            user = ""
289

    
290
            #query for name
291
            qry2 = "SELECT civicrm_contact.display_name \
292
                   FROM civicrm_value_roboclub_info_2 \
293
                   INNER JOIN (civicrm_contact) \
294
                   ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
295
                   WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";"
296

    
297
            cursor.execute(qry2)
298
            result = cursor.fetchall()
299
            
300
            if len(result) > 0:
301
               user += result[0][0]
302

    
303
            qry2 = "SELECT civicrm_email.email \
304
                   FROM civicrm_value_roboclub_info_2 \
305
                   INNER JOIN (civicrm_email) \
306
                   ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
307
                   WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";"
308

    
309
            cursor.execute(qry2)
310
            result = cursor.fetchall()
311
            
312
            if len(result) > 0:
313
               user += " ("+result[0][0]+")"
314

    
315
            if user == "":
316
               user = str(id.group(1))
317

    
318
            print user
319

    
320
            startTime = time.time();
321
            resp = ""
322
            while resp=="":
323
               resp = keypad.read(1)
324
               #no lock since we are just reading and we can afford to
325
               #miss a loop
326
               if idready == True:
327
                  #if someone swipes, break out of the loop
328
                  print "**swipe during read, bailing out"
329
                  logMessage("user swipped during keypad read")
330
                  break
331
               if time.time() - startTime > keypadTimeout:
332
                  print "KEYPAD TIMEOUT!"
333
                  logMessage("keypad timed out")
334
                  break
335

    
336
            #if we have a vaid response
337
            #sometimes the FTDI chip seems to give a zero as a string when power is reset
338
            if resp != "" and ord(resp) != 0 and resp != TT_TIMEOUT:
339

    
340
               toolName = reverseTool(resp)
341
               toolNameLong = toolName + " (k="+str(resp)+"("+str(ord(resp))+"), t="+str(keypad2toolID(resp))+")"
342
               print "request:",resp,"(",ord(resp),") ",toolNameLong
343

    
344
               if acl.count(resp) > 0:
345
                  keypad.write(TT_ACK)
346
                  sendTool(resp)
347
                  print "ACCESS GRANTED"
348
                  logMessage(user+" ACCESSED tool "+toolNameLong)
349
                  if checkAck(resp):
350
                     print "ACK"
351
                     toolFails[toolName] = 0
352
                  else:
353
                     print "NO ACK!!!!"
354
                     logMessage("tool "+toolNameLong+" did not ACK")
355
                     toolFails[toolName] += 1
356
                     if toolFails[toolName] > MAX_TOOL_FAILS:
357
                        #TODO: send email
358
                        logMessage("WARNING: tool "+toolNameLong+
359
                                   " has failed to ACK "+
360
                                   str(MAX_TOOL_FAILS)+" times in a row.")
361

    
362
               else:
363
                  keypad.write(TT_NACK)
364
                  print "ACCESS DENIED!!"
365
                  logMessage(user + " DENIED on tool "+toolNameLong)
366

    
367
               clear_id()
368

    
369
        elif resp == 0 or ord(resp) == 0: #if we get noise, send timeout to reset and sync states
370
           keypad.write(TT_TIMEOUT);
371
           print "ERROR: got strange byte, timing out"
372
           logMessage("ERROR: got 0 as a byte, sending timeout (cardbox power issue?)")
373
        else:
374
            break