Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 303

History | View | Annotate | Download (10.7 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 sys
41
import MySQLdb
42
import base64
43
import datetime
44
from datetime import date
45
from time import *
46
sys.path.append('../common')
47
import common
48

    
49
#this table maps which keypad button to press for each tool. This
50
#list should probably be printed next to the swipe box
51
tools = {
52
   'Bandsaw':['3','4'],
53
   'DrillPress':['1','2'],
54
   'Mill':['5'],
55
   'Lathe':[],
56
   'Cnc':['6']
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':13,
67
   '2':18,
68
   '3':11,
69
   '4':17,
70
   '5':14,
71
   '6':12,
72
   '7':10,
73
   '8':10,
74
   '9':10,
75
   '0':10
76
}
77

    
78
try:
79
   flog = open("/var/log/tooltron", "a")
80
except IOError as e:
81
   print "\n*********************************************"
82
   print "File /var/log/tooltron could not be opened!"
83
   print e
84
   print "Using local file 'tooltron.log'"
85
   print "*********************************************\n"
86
   flog = open("tooltron.log","a")
87
finally:
88
   print flog
89

    
90
def logMessage(str):
91
   flog.write(strftime("%Y.%m.%d %H:%M:%S ") + str + "\n")
92
   flog.flush()
93

    
94
logMessage("----- SERVER STARTED -----")
95

    
96
def keypad2toolID(t):
97
   if t in ids:
98
      return ids[t]
99
   else:
100
      return -1
101

    
102
def reverseTool(tn):
103
   for t in tools:
104
      if tn in tools[t]:
105
         return t
106

    
107
   return 'invalid'
108

    
109
#keep count of successive non-acks
110
toolFails = tools.copy()
111
for t in toolFails:
112
   toolFails[t] = 0
113

    
114

    
115
################################################################################
116
# Main:
117

    
118
if len(sys.argv) < 2:
119
   print "usage: tooltron.py /path/to/bus/device"
120
   exit(-1)
121

    
122
if not common.initBus(sys.argv[1]):
123
    print "usage: tooltron.py /path/to/bus/device"
124
else:
125

    
126
   # read password
127
   try:
128
      passfile = open('pass', 'r')
129

    
130
   except IOError as e:
131
      print "\========================================"
132
      print "File 'pass' could not be opened!"
133
      print e
134
      print "This file is required for MySQL authentication"
135
      print "\========================================"
136
      exit(-1)
137

    
138
   pw = base64.b64decode(passfile.read())
139

    
140
   cursor = None
141
   qry1 = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
142

    
143
   # wapper to translate tool number
144
   def sendTool(t):
145
      tn = keypad2toolID(t)
146
      common.sendTool(tn)
147

    
148
   # This function eats the rest of the packet where data is what we have so far
149
   # TODO: do I still need this?
150
   def flushPacket(data):
151
      while len(data) < 6:
152
         data += bus.read(1)
153

    
154
      plen = data[4]
155
      if plen > 0:
156
         bus.read(ord(plen))
157

    
158
   while True:
159

    
160
      x = common.readTransaction()
161

    
162
      print "\n"
163

    
164
      if x == None:
165
         print "ERROR: bad transaction"
166
         logMessage("ERROR: got bad packet from cardbox")
167
         continue
168

    
169
      [key, cardnum] = x
170

    
171
      print "\--------------------\n",strftime("%Y.%m.%d %H:%M:%S \n")
172
      print "cardnum:", cardnum
173
      print "key:", key
174

    
175
      # Start doing the MySQL queries, hope this isn't too slow
176

    
177
      print "running queries..."
178

    
179
      if cursor != None:
180
         cursor.close()
181

    
182
      #TODO: handle timeouts / slow connections!
183
      db = MySQLdb.connect(host="roboclub8.frc.ri.cmu.edu", user="tooltron", passwd=pw, db="civicrm")
184
      print "connected to mysql server"
185
      cursor = db.cursor()
186
      print "got db cursor"
187

    
188
      print "qry1 (ACL)..."
189
      cursor.execute(qry1 + cardnum)
190

    
191
      result = cursor.fetchall()
192

    
193
      if len(result) == 0:
194
         print "ERROR: result is empty!"
195
         logMessage("user has no ACLS or server is down...")
196
         continue
197

    
198
      acl = []
199

    
200
      for r in result:
201
         tls = r[0].split("\x01")
202
         for t in tls:
203
            if t != '':
204
               try:
205
                  acl.extend (tools[t])
206
               except KeyError:
207
                  #this doesn't really matter
208
                  pass
209

    
210
      print "user has access to:", acl
211

    
212
      user = ""
213

    
214
      print "qry2 (name/email)..."
215
      #query for name
216
      qry2 = "SELECT civicrm_contact.display_name \
217
              FROM civicrm_value_roboclub_info_2 \
218
              INNER JOIN (civicrm_contact) \
219
              ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
220
              WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
221

    
222
      cursor.execute(qry2)
223
      result = cursor.fetchall()
224

    
225
      if len(result) > 0:
226
         user += result[0][0]
227

    
228
      qry2 = "SELECT civicrm_email.email \
229
              FROM civicrm_value_roboclub_info_2 \
230
              INNER JOIN (civicrm_email) \
231
              ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
232
              WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
233

    
234
      cursor.execute(qry2)
235
      result = cursor.fetchall()
236

    
237
      if len(result) > 0:
238
         user += " ("+result[0][0]+")"
239

    
240
      if user == "":
241
         user = cardnum
242

    
243
      print "[[[",user,"]]]"
244

    
245
      print "qry3 (payment)..."
246
      # check if member has paid recently 
247
      # first, figure out what semester it is
248
      today = date.today()
249

    
250
      # semester start dates
251
      aug20 = datetime.date(today.year, 8, 20) # Fall semester
252
      may21 = datetime.date(today.year, 5, 21) # summer
253
      jan1  = datetime.date(today.year, 1,  1) # Spring semester
254

    
255
      lastAug20 = datetime.date(today.year-1, 8, 20)
256
      lastMay21 = datetime.date(today.year-1, 5, 21)
257
      # if they don't fall after those two, its fall
258

    
259
      if today > aug20:
260
         print "its fall semester!"
261
         #its fall, so they must have paid after may21 (either full year of half) to be paid
262
         qry3 = "SELECT civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + lastMay21.strftime('%m/%d/%Y') + "', '%m/%d/%Y') ,  \
263
                 DATE_FORMAT(civicrm_value_roboclub_info_2.date_paid_10, '%M %d %Y') \
264
                 FROM civicrm_value_roboclub_info_2 \
265
                 WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
266

    
267
      elif today > may21:
268
         print "its summer!"
269
         #its summer, so they need to have paid either full year since aug20 or one semester since jan 1
270
         qry3 = "SELECT (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + lastAug20.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \
271
                         civicrm_value_roboclub_info_2.amount_paid_11 = 25) OR \
272
                        (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + jan1.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \
273
                         civicrm_value_roboclub_info_2.amount_paid_11 = 15) , \
274
                 DATE_FORMAT(civicrm_value_roboclub_info_2.date_paid_10, '%M %d %Y') \
275
                 FROM civicrm_value_roboclub_info_2 \
276
                 WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
277
      else:
278
         print "its spring!"
279
         #its spring, so they must have either paid full after aug20 or one semester since jan 1 (this is the same as summer)
280
         qry3 = "SELECT (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + lastMay21.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \
281
                         civicrm_value_roboclub_info_2.amount_paid_11 = 25) OR \
282
                        (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + jan1.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \
283
                         civicrm_value_roboclub_info_2.amount_paid_11 = 15) , \
284
                 DATE_FORMAT(civicrm_value_roboclub_info_2.date_paid_10, '%M %d %Y') \
285
                 FROM civicrm_value_roboclub_info_2 \
286
                 WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
287

    
288

    
289
      cursor.execute(qry3)
290
      result = cursor.fetchall()
291

    
292
      if len(result) == 0 or result[0][0] == 0 or result[0][0] == "NULL":
293
         print "DENIED: user has not paid recently enough!"
294
         if len(result[0]) >= 2:
295
            print "date_paid: ",result[0][1]
296
            logMessage(user + " has not paid since " + result[0][1] + ", revoking access!")
297
            acl = []
298
         else:
299
            logMessage(user + " has not paid, revoking access!")
300
            acl = []
301

    
302

    
303
      toolName = reverseTool(key)
304
      toolNameLong = toolName + " (k="+str(key)+"("+str(ord(key))+"), t="+str(keypad2toolID(key))+")"
305
      print "request:",key,"(",ord(key),") ",toolNameLong
306

    
307
      if acl.count(key) > 0:
308
         common.sendGrant()
309
         #time.sleep(0.5) #TODO: do I still need this?
310
         sendTool(key)
311
         print "ACCESS GRANTED"
312
         logMessage(user+" ACCESSED tool "+toolNameLong)
313
         if common.checkAck(key):
314
            print "ACK"
315
            toolFails[toolName] = 0
316
         else:
317
            print "NO ACK!!!!"
318
            logMessage("tool "+toolNameLong+" did not ACK")
319
            toolFails[toolName] += 1
320
            if toolFails[toolName] > common.MAX_TOOL_FAILS:
321
               #TODO: send email
322
               logMessage("WARNING: tool "+toolNameLong+
323
                          " has failed to ACK "+
324
                          str(common.MAX_TOOL_FAILS)+" times in a row.")
325

    
326
      else:
327
         common.sendDeny()
328
         print "ACCESS DENIED!!"
329
         logMessage(user + " DENIED on tool "+toolNameLong)