Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 299

History | View | Annotate | Download (10.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 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
      acl = []
194

    
195
      for r in result:
196
         tls = r[0].split("\x01")
197
         for t in tls:
198
            if t != '':
199
               try:
200
                  acl.extend (tools[t])
201
               except KeyError:
202
                  #this doesn't really matter
203
                  pass
204

    
205
      print "user has access to:", acl
206

    
207
      user = ""
208

    
209
      print "qry2 (name/email)..."
210
      #query for name
211
      qry2 = "SELECT civicrm_contact.display_name \
212
              FROM civicrm_value_roboclub_info_2 \
213
              INNER JOIN (civicrm_contact) \
214
              ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
215
              WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
216

    
217
      cursor.execute(qry2)
218
      result = cursor.fetchall()
219

    
220
      if len(result) > 0:
221
         user += result[0][0]
222

    
223
      qry2 = "SELECT civicrm_email.email \
224
              FROM civicrm_value_roboclub_info_2 \
225
              INNER JOIN (civicrm_email) \
226
              ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
227
              WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
228

    
229
      cursor.execute(qry2)
230
      result = cursor.fetchall()
231

    
232
      if len(result) > 0:
233
         user += " ("+result[0][0]+")"
234

    
235
      if user == "":
236
         user = cardnum
237

    
238
      print "[[[",user,"]]]"
239

    
240
      print "qry3 (payment)..."
241
      # check if member has paid recently 
242
      # first, figure out what semester it is
243
      today = date.today()
244

    
245
      # semester start dates
246
      aug20 = datetime.date(today.year, 8, 20) # Fall semester
247
      may21 = datetime.date(today.year, 5, 21) # summer
248
      jan1  = datetime.date(today.year, 1,  1) # Spring semester
249

    
250
      lastAug20 = datetime.date(today.year-1, 8, 20)
251
      lastMay21 = datetime.date(today.year-1, 5, 21)
252
      # if they don't fall after those two, its fall
253

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

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

    
283

    
284
      cursor.execute(qry3)
285
      result = cursor.fetchall()
286

    
287
      if len(result) == 0 or result[0][0] == 0 or result[0][0] == "NULL":
288
         print "DENIED: user has not paid recently enough!"
289
         if len(result[0]) >= 2:
290
            print "date_paid: ",result[0][1]
291
            logMessage(user + " has not paid since " + result[0][1] + ", revoking access!")
292
            acl = []
293
         else:
294
            logMessage(user + " has not paid, revoking access!")
295
            acl = []
296

    
297

    
298
      toolName = reverseTool(key)
299
      toolNameLong = toolName + " (k="+str(key)+"("+str(ord(key))+"), t="+str(keypad2toolID(key))+")"
300
      print "request:",key,"(",ord(key),") ",toolNameLong
301

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

    
321
      else:
322
         common.sendDeny()
323
         print "ACCESS DENIED!!"
324
         logMessage(user + " DENIED on tool "+toolNameLong)