Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 224

History | View | Annotate | Download (9.25 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 = 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
#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

    
85
lastid = None
86
idready = False
87

    
88
cv = threading.Condition()
89

    
90
class idThread ( threading.Thread ):
91

    
92
   def run ( self ):
93
       global lastid
94
       global idready
95

    
96
       try:
97
           while True:
98
               l = raw_input()
99
               cv.acquire()
100
               id = re.search('%([0-9]*)=.*', l)
101
               if id != None:
102
                   lastid = id
103
                   print "!!! setting idready"
104
                   idready = True
105
                   cv.notify()
106
               cv.release()
107

    
108
       except EOFError:
109
           cv.acquire()
110
           idready = True
111
           lastid=None
112
           cv.notify()
113
           cv.release()
114
#end thread
115
###############################################
116

    
117
flog = open("tooltron.log", "a")
118

    
119
def logMessage(str):
120
   flog.write(strftime("%Y.%m.%d %H:%M:%S ") + str + "\n")
121
   flog.flush()
122

    
123
logMessage("----- SERVER STARTED -----")
124

    
125
def keypad2toolID(t):
126
   if t in ids:
127
      return ids[t]
128
   else:
129
      return -1
130

    
131
def reverseTool(tn):
132
   for t in tools:
133
      if tn in tools[t]:
134
         return t
135

    
136
   return 'invalid'
137

    
138
#keep count of successive non-acks
139
toolFails = tools.copy()
140
for t in toolFails:
141
   toolFails[t] = 0
142

    
143
def get_last_id():
144
    global idready
145
    global lastid
146

    
147
    got = None
148

    
149
    print ">>> locking for last_id"
150

    
151
    cv.acquire()
152
    while not idready:
153
        print ">>> wait"
154
        cv.wait()
155

    
156
    print ">>> idready set"
157

    
158
    got = lastid
159
    idready = False
160
    cv.release()
161
    print ">>> done"
162

    
163
    return got
164

    
165
def clear_id():
166
    global idready
167
    global lastid
168

    
169
    print "=== locking to clear idready flag"
170
    cv.acquire()
171
    idready = False
172
    lastid = None
173
    cv.release()
174
    print "=== done"
175

    
176
    return
177

    
178
if len(sys.argv) < 2:
179
   print "usage: tooltron.py /path/to/bus/device"
180
   exit(-1)
181

    
182
if not common.initBus(sys.argv[1]):
183
    print "usage: tooltron.py /path/to/bus/device"
184
else:
185

    
186
    pw = getpass.getpass("mysql password: ")
187
    db = MySQLdb.connect(host="roboclub8.frc.ri.cmu.edu", user="tooltron", passwd=pw, db="civicrm")
188
    print "connected, now accepting input"
189
    logMessage("connected to database")
190

    
191
    #start the id scan thread AFTER the getpass prompt
192
    idThread().start()
193

    
194
    cursor = db.cursor()
195

    
196
    qry = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
197
          
198
    def sendTool(t):
199
        tn = keypad2toolID(t)
200
        common.sendTool(tn)
201

    
202
    # This function eats the rest of the packet where data is what we have so far
203
    def flushPacket(data):
204
       while len(data) < 6:
205
          data += bus.read(1)
206

    
207
       plen = data[4]
208
       if plen > 0:
209
          bus.read(ord(plen))
210
    
211

    
212
    while True:
213

    
214
        id = get_last_id()
215
        
216
        if id != None:
217
            print "\n-----------\nid# ", id.group(1)
218

    
219
            common.sendKeyRequest()
220
            startTime = time.time()
221

    
222
            cursor.execute(qry + id.group(1))
223

    
224
            result = cursor.fetchall()
225

    
226
            acl = []
227

    
228
            for r in result:
229
                tls = r[0].split("\x01")
230
                for t in tls:
231
                    if t != '':
232
                        try:
233
                            acl.extend (tools[t])
234
                        except KeyError:
235
                            #this doesn't really matter
236
                            pass
237

    
238
            print "user has access to:", acl
239

    
240
            user = ""
241

    
242
            #query for name
243
            qry2 = "SELECT civicrm_contact.display_name \
244
                   FROM civicrm_value_roboclub_info_2 \
245
                   INNER JOIN (civicrm_contact) \
246
                   ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
247
                   WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";"
248

    
249
            cursor.execute(qry2)
250
            result = cursor.fetchall()
251
            
252
            if len(result) > 0:
253
               user += result[0][0]
254

    
255
            qry2 = "SELECT civicrm_email.email \
256
                   FROM civicrm_value_roboclub_info_2 \
257
                   INNER JOIN (civicrm_email) \
258
                   ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
259
                   WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";"
260

    
261
            cursor.execute(qry2)
262
            result = cursor.fetchall()
263
            
264
            if len(result) > 0:
265
               user += " ("+result[0][0]+")"
266

    
267
            if user == "":
268
               user = str(id.group(1))
269

    
270
            print user
271

    
272

    
273
            resp = None
274
            while resp==None:
275
               resp = common.readKey()
276

    
277
               #no lock since we are just reading and we can afford to
278
               #miss a loop
279
               if idready == True:
280
                  #if someone swipes, break out of the loop
281
                  print "**swipe during read, bailing out"
282
                  logMessage("user swipped during keypad read")
283
                  break
284
               if time.time() - startTime > keypadTimeout:
285
                  print "KEYPAD TIMEOUT!"
286
                  logMessage("keypad timed out")
287
                  break
288

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

    
293
               toolName = reverseTool(resp)
294
               toolNameLong = toolName + " (k="+str(resp)+"("+str(ord(resp))+"), t="+str(keypad2toolID(resp))+")"
295
               print "request:",resp,"(",ord(resp),") ",toolNameLong
296

    
297
               if acl.count(resp) > 0:
298
                  common.sendAck(2)
299
                  sendTool(resp)
300
                  print "ACCESS GRANTED"
301
                  logMessage(user+" ACCESSED tool "+toolNameLong)
302
                  if common.checkAck(resp):
303
                     print "ACK"
304
                     toolFails[toolName] = 0
305
                  else:
306
                     print "NO ACK!!!!"
307
                     logMessage("tool "+toolNameLong+" did not ACK")
308
                     toolFails[toolName] += 1
309
                     if toolFails[toolName] > common.MAX_TOOL_FAILS:
310
                        #TODO: send email
311
                        logMessage("WARNING: tool "+toolNameLong+
312
                                   " has failed to ACK "+
313
                                   str(common.MAX_TOOL_FAILS)+" times in a row.")
314

    
315
               else:
316
                  common.sendNack(2)
317
                  print "ACCESS DENIED!!"
318
                  logMessage(user + " DENIED on tool "+toolNameLong)
319

    
320
               clear_id()
321

    
322
        elif resp == None or ord(resp) == 0: #if we get noise, send timeout to reset and sync states
323
           keypad.write(common.TT_TIMEOUT)
324
           print "ERROR: got strange byte, timing out"
325
           logMessage("ERROR: got 0 as a byte, sending timeout (cardbox power issue?)")
326
        else:
327
            break