Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 145

History | View | Annotate | Download (8.84 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
## dustmite.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':['2','8'],
54
   'DrillPress':['1','9'],
55
   'Mill':['4'],
56
   'Lathe':['5'],
57
   #HACK: since the saw isn't used I use it for testing random boards
58
   'ChopMiterSaw':['3','7','0']
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

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

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

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

    
147
   return 'invalid'
148

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

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

    
158
    got = None
159

    
160
    print ">>> locking for last_id"
161

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

    
167
    print ">>> idready set"
168

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

    
174
    return got
175

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

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

    
187
    return
188

    
189

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

    
193
else:
194

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

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

    
203
    cursor = db.cursor()
204

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

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

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

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

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

    
237
            print "got packet",ret
238

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

    
244
        return []
245

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

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

    
253

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

    
256

    
257
    while True:
258

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

    
264

    
265
            print "sending key request"
266
            keypad.write(TT_GET_KEY)
267

    
268
            cursor.execute(qry + id.group(1))
269

    
270
            result = cursor.fetchall()
271

    
272
            acl = []
273

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

    
284
            print "user has access to:", acl
285

    
286
            user = "user"
287

    
288
            startTime = time.time();
289
            resp = ""
290
            while resp=="":
291
               resp = keypad.read(1)
292
               #no lock since we are just reading and we can afford to
293
               #miss a loop
294
               if idready == True:
295
                  #if someone swipes, break out of the loop
296
                  print "**swipe during read, bailing out"
297
                  logMessage("user swipped during keypad read")
298
                  break
299
               if time.time() - startTime > keypadTimeout:
300
                  print "KEYPAD TIMEOUT!"
301
                  logMessage("keypad timed out")
302
                  break
303

    
304
            #if we have a vaid response
305
            if resp != "" and resp != TT_TIMEOUT:
306

    
307
               toolName = reverseTool(resp)
308
               toolNameLong = toolName + " (k="+str(resp)+", t="+str(keypad2toolID(resp))+")"
309
               print "request:",resp,toolNameLong
310

    
311
               if acl.count(resp) > 0:
312
                  keypad.write(TT_ACK)
313
                  sendTool(resp)
314
                  print "ACCESS GRANTED"
315
                  logMessage(user+" accessed tool "+toolNameLong)
316
                  if checkAck(resp):
317
                     print "ACK"
318
                     toolFails[toolName] = 0
319
                  else:
320
                     print "NO ACK!!!!"
321
                     logMessage("tool "+toolNameLong+" did not ACK")
322
                     toolFails[toolName] += 1
323
                     if toolFails > MAX_TOOL_FAILS:
324
                        #TODO: send email
325
                        logMessage("WARNING: tool "+toolNameLong+
326
                                   " has failed to ACK "+
327
                                   str(MAX_TOOL_FAILS)+" times in a row.")
328

    
329
               else:
330
                  keypad.write(TT_NACK)
331
                  print "ACCESS DENIED!!"
332
                  logMessage(user + " denied on tool "+toolNameLong)
333

    
334
               clear_id()
335

    
336
        else:
337
            break