root / trunk / swipe / tooltron.py @ 299
History | View | Annotate | Download (10.5 KB)
1 | 140 | bneuman | #!/usr/bin/python
|
---|---|---|---|
2 | |||
3 | 139 | kwoo | """
|
4 | This file is part of Tooltron.
|
||
5 | 283 | bneuman |
|
6 | 139 | kwoo | 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 | 283 | bneuman |
|
11 | 139 | kwoo | 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 | 99 | bneuman | |
22 | 125 | bneuman | ################################################################################
|
23 | 147 | bneuman | ## tooltron.py is the main tooltron server
|
24 | 125 | bneuman | # 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 | 128 | bneuman | # 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 | 125 | bneuman | # email bradneuman@gmail.com if you have any questions
|
37 | ################################################################################
|
||
38 | |||
39 | |||
40 | 99 | bneuman | import sys |
41 | 119 | bneuman | import MySQLdb |
42 | 284 | bneuman | import base64 |
43 | 265 | bneuman | import datetime |
44 | from datetime import date |
||
45 | 144 | bneuman | from time import * |
46 | 224 | bneuman | sys.path.append('../common')
|
47 | 222 | bneuman | import common |
48 | |||
49 | 143 | bneuman | #this table maps which keypad button to press for each tool. This
|
50 | #list should probably be printed next to the swipe box
|
||
51 | 130 | bneuman | tools = { |
52 | 284 | bneuman | 'Bandsaw':['3','4'], |
53 | 'DrillPress':['1','2'], |
||
54 | 'Mill':['5'], |
||
55 | 150 | bneuman | 'Lathe':[],
|
56 | 299 | bneuman | 'Cnc':['6'] |
57 | 143 | bneuman | #HACK: since the saw isn't used I use it for testing random boards
|
58 | 299 | bneuman | #'ChopMiterSaw':['1','2','5','6','7','9']
|
59 | 130 | bneuman | } |
60 | |||
61 | 143 | bneuman | #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 | 284 | bneuman | '1':13, |
67 | '2':18, |
||
68 | '3':11, |
||
69 | '4':17, |
||
70 | '5':14, |
||
71 | 299 | bneuman | '6':12, |
72 | 284 | bneuman | '7':10, |
73 | '8':10, |
||
74 | '9':10, |
||
75 | '0':10 |
||
76 | 143 | bneuman | } |
77 | 130 | bneuman | |
78 | 240 | bneuman | 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 | 241 | bneuman | print flog
|
89 | 144 | bneuman | |
90 | def logMessage(str): |
||
91 | flog.write(strftime("%Y.%m.%d %H:%M:%S ") + str + "\n") |
||
92 | 146 | bneuman | flog.flush() |
93 | 144 | bneuman | |
94 | 145 | bneuman | logMessage("----- SERVER STARTED -----")
|
95 | |||
96 | 141 | bneuman | def keypad2toolID(t): |
97 | 145 | bneuman | if t in ids: |
98 | return ids[t]
|
||
99 | else:
|
||
100 | return -1 |
||
101 | 141 | bneuman | |
102 | 144 | bneuman | def reverseTool(tn): |
103 | for t in tools: |
||
104 | if tn in tools[t]: |
||
105 | return t
|
||
106 | |||
107 | 145 | bneuman | return 'invalid' |
108 | 144 | bneuman | |
109 | 145 | bneuman | #keep count of successive non-acks
|
110 | toolFails = tools.copy() |
||
111 | for t in toolFails: |
||
112 | toolFails[t] = 0
|
||
113 | |||
114 | 128 | bneuman | |
115 | 283 | bneuman | ################################################################################
|
116 | # Main:
|
||
117 | 128 | bneuman | |
118 | 222 | bneuman | if len(sys.argv) < 2: |
119 | print "usage: tooltron.py /path/to/bus/device" |
||
120 | exit(-1) |
||
121 | 128 | bneuman | |
122 | 222 | bneuman | if not common.initBus(sys.argv[1]): |
123 | 213 | bneuman | print "usage: tooltron.py /path/to/bus/device" |
124 | 99 | bneuman | else:
|
125 | |||
126 | 284 | bneuman | # read password
|
127 | try:
|
||
128 | passfile = open('pass', 'r') |
||
129 | 119 | bneuman | |
130 | 284 | bneuman | 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 | 283 | bneuman | cursor = None
|
141 | qry1 = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
|
||
142 | 119 | bneuman | |
143 | 283 | bneuman | # wapper to translate tool number
|
144 | def sendTool(t): |
||
145 | tn = keypad2toolID(t) |
||
146 | common.sendTool(tn) |
||
147 | 120 | bneuman | |
148 | 283 | bneuman | # 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 | 221 | bneuman | |
154 | 283 | bneuman | plen = data[4]
|
155 | if plen > 0: |
||
156 | bus.read(ord(plen))
|
||
157 | 221 | bneuman | |
158 | 283 | bneuman | while True: |
159 | 128 | bneuman | |
160 | 283 | bneuman | x = common.readTransaction() |
161 | 99 | bneuman | |
162 | 283 | bneuman | print "\n" |
163 | 261 | bneuman | |
164 | 283 | bneuman | if x == None: |
165 | print "ERROR: bad transaction" |
||
166 | logMessage("ERROR: got bad packet from cardbox")
|
||
167 | continue
|
||
168 | 261 | bneuman | |
169 | 283 | bneuman | [key, cardnum] = x |
170 | 261 | bneuman | |
171 | 283 | bneuman | print "\--------------------\n",strftime("%Y.%m.%d %H:%M:%S \n") |
172 | print "cardnum:", cardnum |
||
173 | print "key:", key |
||
174 | 262 | bneuman | |
175 | 283 | bneuman | # Start doing the MySQL queries, hope this isn't too slow
|
176 | 261 | bneuman | |
177 | 283 | bneuman | print "running queries..." |
178 | 119 | bneuman | |
179 | 283 | bneuman | if cursor != None: |
180 | cursor.close() |
||
181 | 258 | bneuman | |
182 | 283 | bneuman | #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 | 258 | bneuman | |
188 | 283 | bneuman | print "qry1 (ACL)..." |
189 | cursor.execute(qry1 + cardnum) |
||
190 | 119 | bneuman | |
191 | 283 | bneuman | result = cursor.fetchall() |
192 | 119 | bneuman | |
193 | 283 | bneuman | acl = [] |
194 | 119 | bneuman | |
195 | 283 | bneuman | 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 | 119 | bneuman | |
205 | 283 | bneuman | print "user has access to:", acl |
206 | 125 | bneuman | |
207 | 283 | bneuman | user = ""
|
208 | 145 | bneuman | |
209 | 283 | bneuman | 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 | 146 | bneuman | |
217 | 283 | bneuman | cursor.execute(qry2) |
218 | result = cursor.fetchall() |
||
219 | 230 | bneuman | |
220 | 283 | bneuman | if len(result) > 0: |
221 | user += result[0][0] |
||
222 | 146 | bneuman | |
223 | 283 | bneuman | 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 | 146 | bneuman | |
229 | 283 | bneuman | cursor.execute(qry2) |
230 | result = cursor.fetchall() |
||
231 | 230 | bneuman | |
232 | 283 | bneuman | if len(result) > 0: |
233 | user += " ("+result[0][0]+")" |
||
234 | 146 | bneuman | |
235 | 283 | bneuman | if user == "": |
236 | user = cardnum |
||
237 | 146 | bneuman | |
238 | 283 | bneuman | print "[[[",user,"]]]" |
239 | 146 | bneuman | |
240 | 283 | bneuman | print "qry3 (payment)..." |
241 | # check if member has paid recently
|
||
242 | # first, figure out what semester it is
|
||
243 | today = date.today() |
||
244 | 265 | bneuman | |
245 | 283 | bneuman | # 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 | 297 | bneuman | |
250 | lastAug20 = datetime.date(today.year-1, 8, 20) |
||
251 | lastMay21 = datetime.date(today.year-1, 5, 21) |
||
252 | 283 | bneuman | # if they don't fall after those two, its fall
|
253 | 265 | bneuman | |
254 | 283 | bneuman | 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 | 297 | bneuman | qry3 = "SELECT civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + lastMay21.strftime('%m/%d/%Y') + "', '%m/%d/%Y') , \ |
258 | 283 | bneuman | 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 | 265 | bneuman | |
262 | 283 | bneuman | 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 | 297 | bneuman | qry3 = "SELECT (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + lastAug20.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \ |
266 | 283 | bneuman | 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 | 297 | bneuman | qry3 = "SELECT (civicrm_value_roboclub_info_2.date_paid_10 >= STR_TO_DATE('" + lastMay21.strftime('%m/%d/%Y') + "', '%m/%d/%Y') AND \ |
276 | 283 | bneuman | 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 | 265 | bneuman | |
283 | |||
284 | 283 | bneuman | cursor.execute(qry3) |
285 | result = cursor.fetchall() |
||
286 | 265 | bneuman | |
287 | 283 | bneuman | 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 | 298 | bneuman | acl = [] |
293 | 283 | bneuman | else:
|
294 | logMessage(user + " has not paid, revoking access!")
|
||
295 | 297 | bneuman | acl = [] |
296 | 265 | bneuman | |
297 | |||
298 | 283 | bneuman | toolName = reverseTool(key) |
299 | toolNameLong = toolName + " (k="+str(key)+"("+str(ord(key))+"), t="+str(keypad2toolID(key))+")" |
||
300 | print "request:",key,"(",ord(key),") ",toolNameLong |
||
301 | 265 | bneuman | |
302 | 283 | bneuman | 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 | 265 | bneuman | |
321 | 283 | bneuman | else:
|
322 | common.sendDeny() |
||
323 | print "ACCESS DENIED!!" |
||
324 | logMessage(user + " DENIED on tool "+toolNameLong) |