Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 284

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 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
   #HACK: since the saw isn't used I use it for testing random boards
57
   'ChopMiterSaw':['1','2','5','6','7','9']
58
}
59

    
60
#this table maps keypad button numbers to tool IDs. The default is
61
#that the tool id is 10+the keypad number, but this would change if
62
#boards were swapped out. The tool ID number should be printed on the
63
#tool board, but needs to be hand coded into the toolbox code
64
ids = {
65
   '1':13,
66
   '2':18,
67
   '3':11,
68
   '4':17,
69
   '5':14,
70
   '6':10,
71
   '7':10,
72
   '8':10,
73
   '9':10,
74
   '0':10
75
}
76

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

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

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

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

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

    
106
   return 'invalid'
107

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

    
113

    
114
################################################################################
115
# Main:
116

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

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

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

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

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

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

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

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

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

    
157
   while True:
158

    
159
      x = common.readTransaction()
160

    
161
      print "\n"
162

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

    
168
      [key, cardnum] = x
169

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

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

    
176
      print "running queries..."
177

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

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

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

    
190
      result = cursor.fetchall()
191

    
192
      acl = []
193

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

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

    
206
      user = ""
207

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

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

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

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

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

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

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

    
237
      print "[[[",user,"]]]"
238

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

    
244
      # semester start dates
245
      aug20 = datetime.date(today.year, 8, 20) # Fall semester
246
      may21 = datetime.date(today.year, 5, 21) # summer
247
      jan1  = datetime.date(today.year, 1,  1) # Spring semester
248
      # if they don't fall after those two, its fall
249

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

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

    
279

    
280
      cursor.execute(qry3)
281
      result = cursor.fetchall()
282

    
283
      if len(result) == 0 or result[0][0] == 0 or result[0][0] == "NULL":
284
         print "DENIED: user has not paid recently enough!"
285
         if len(result[0]) >= 2:
286
            print "date_paid: ",result[0][1]
287
            logMessage(user + " has not paid since " + result[0][1] + ", revoking access!")
288
         else:
289
            logMessage(user + " has not paid, revoking access!")
290
            #acl = [] #TODO: uncomment!
291

    
292

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

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

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