Project

General

Profile

Statistics
| Revision:

root / trunk / swipe / tooltron.py @ 283

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

    
49
keypadTimeout = 12 #in seconds
50

    
51
#this table maps which keypad button to press for each tool. This
52
#list should probably be printed next to the swipe box
53
tools = {
54
   'Bandsaw':[],
55
   'DrillPress':['3','8'],
56
   'Mill':['4'],
57
   'Lathe':[],
58
   #HACK: since the saw isn't used I use it for testing random boards
59
   'ChopMiterSaw':['1','2','5','6','7','9']
60
}
61

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

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

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

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

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

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

    
108
   return 'invalid'
109

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

    
115

    
116
################################################################################
117
# Main:
118

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

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

    
127
   pw = getpass.getpass("mysql password: ")
128

    
129
   cursor = None
130
   qry1 = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
131

    
132
   # wapper to translate tool number
133
   def sendTool(t):
134
      tn = keypad2toolID(t)
135
      common.sendTool(tn)
136

    
137
   # This function eats the rest of the packet where data is what we have so far
138
   # TODO: do I still need this?
139
   def flushPacket(data):
140
      while len(data) < 6:
141
         data += bus.read(1)
142

    
143
      plen = data[4]
144
      if plen > 0:
145
         bus.read(ord(plen))
146

    
147
   while True:
148

    
149
      x = common.readTransaction()
150

    
151
      print "\n"
152

    
153
      if x == None:
154
         print "ERROR: bad transaction"
155
         logMessage("ERROR: got bad packet from cardbox")
156
         continue
157

    
158
      [key, cardnum] = x
159

    
160
      print "\--------------------\n",strftime("%Y.%m.%d %H:%M:%S \n")
161
      print "cardnum:", cardnum
162
      print "key:", key
163

    
164
      # Start doing the MySQL queries, hope this isn't too slow
165

    
166
      print "running queries..."
167

    
168
      if cursor != None:
169
         cursor.close()
170

    
171
      #TODO: handle timeouts / slow connections!
172
      db = MySQLdb.connect(host="roboclub8.frc.ri.cmu.edu", user="tooltron", passwd=pw, db="civicrm")
173
      print "connected to mysql server"
174
      cursor = db.cursor()
175
      print "got db cursor"
176

    
177
      print "qry1 (ACL)..."
178
      cursor.execute(qry1 + cardnum)
179

    
180
      result = cursor.fetchall()
181

    
182
      acl = []
183

    
184
      for r in result:
185
         tls = r[0].split("\x01")
186
         for t in tls:
187
            if t != '':
188
               try:
189
                  acl.extend (tools[t])
190
               except KeyError:
191
                  #this doesn't really matter
192
                  pass
193

    
194
      print "user has access to:", acl
195

    
196
      user = ""
197

    
198
      print "qry2 (name/email)..."
199
      #query for name
200
      qry2 = "SELECT civicrm_contact.display_name \
201
              FROM civicrm_value_roboclub_info_2 \
202
              INNER JOIN (civicrm_contact) \
203
              ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
204
              WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
205

    
206
      cursor.execute(qry2)
207
      result = cursor.fetchall()
208

    
209
      if len(result) > 0:
210
         user += result[0][0]
211

    
212
      qry2 = "SELECT civicrm_email.email \
213
              FROM civicrm_value_roboclub_info_2 \
214
              INNER JOIN (civicrm_email) \
215
              ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
216
              WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
217

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

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

    
224
      if user == "":
225
         user = cardnum
226

    
227
      print "[[[",user,"]]]"
228

    
229
      print "qry3 (payment)..."
230
      # check if member has paid recently 
231
      # first, figure out what semester it is
232
      today = date.today()
233

    
234
      # semester start dates
235
      aug20 = datetime.date(today.year, 8, 20) # Fall semester
236
      may21 = datetime.date(today.year, 5, 21) # summer
237
      jan1  = datetime.date(today.year, 1,  1) # Spring semester
238
      # if they don't fall after those two, its fall
239

    
240
      if today > aug20:
241
         print "its fall semester!"
242
         #its fall, so they must have paid after may21 (either full year of half) to be paid
243
         qry3 = "SELECT civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + may21.strftime('%m/%d/%Y') + "', '%m/%d/%Y') ,  \
244
                 DATE_FORMAT(civicrm_value_roboclub_info_2.date_paid_10, '%M %d %Y') \
245
                 FROM civicrm_value_roboclub_info_2 \
246
                 WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
247

    
248
      elif today > may21:
249
         print "its summer!"
250
         #its summer, so they need to have paid either full year since aug20 or one semester since jan 1
251
         qry3 = "SELECT (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + aug20.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \
252
                         civicrm_value_roboclub_info_2.amount_paid_11 = 25) OR \
253
                        (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + jan1.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \
254
                         civicrm_value_roboclub_info_2.amount_paid_11 = 15) , \
255
                 DATE_FORMAT(civicrm_value_roboclub_info_2.date_paid_10, '%M %d %Y') \
256
                 FROM civicrm_value_roboclub_info_2 \
257
                 WHERE civicrm_value_roboclub_info_2.card_number_1 = " + cardnum + ";"
258
      else:
259
         print "its spring!"
260
         #its spring, so they must have either paid full after aug20 or one semester since jan 1 (this is the same as summer)
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

    
269

    
270
      cursor.execute(qry3)
271
      result = cursor.fetchall()
272

    
273
      if len(result) == 0 or result[0][0] == 0 or result[0][0] == "NULL":
274
         print "DENIED: user has not paid recently enough!"
275
         if len(result[0]) >= 2:
276
            print "date_paid: ",result[0][1]
277
            logMessage(user + " has not paid since " + result[0][1] + ", revoking access!")
278
         else:
279
            logMessage(user + " has not paid, revoking access!")
280
            #acl = [] #TODO: uncomment!
281

    
282

    
283
      toolName = reverseTool(key)
284
      toolNameLong = toolName + " (k="+str(key)+"("+str(ord(key))+"), t="+str(keypad2toolID(key))+")"
285
      print "request:",key,"(",ord(key),") ",toolNameLong
286

    
287
      if acl.count(key) > 0:
288
         common.sendGrant()
289
         #time.sleep(0.5) #TODO: do I still need this?
290
         sendTool(key)
291
         print "ACCESS GRANTED"
292
         logMessage(user+" ACCESSED tool "+toolNameLong)
293
         if common.checkAck(key):
294
            print "ACK"
295
            toolFails[toolName] = 0
296
         else:
297
            print "NO ACK!!!!"
298
            logMessage("tool "+toolNameLong+" did not ACK")
299
            toolFails[toolName] += 1
300
            if toolFails[toolName] > common.MAX_TOOL_FAILS:
301
               #TODO: send email
302
               logMessage("WARNING: tool "+toolNameLong+
303
                          " has failed to ACK "+
304
                          str(common.MAX_TOOL_FAILS)+" times in a row.")
305

    
306
      else:
307
         common.sendDeny()
308
         print "ACCESS DENIED!!"
309
         logMessage(user + " DENIED on tool "+toolNameLong)