root / trunk / swipe / tooltron.py @ 224
History | View | Annotate | Download (9.25 KB)
1 | 140 | bneuman | #!/usr/bin/python
|
---|---|---|---|
2 | |||
3 | 139 | kwoo | """
|
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 | 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 re |
41 | import sys |
||
42 | 119 | bneuman | import MySQLdb |
43 | import getpass |
||
44 | 144 | bneuman | from time import * |
45 | 224 | bneuman | sys.path.append('../common')
|
46 | 222 | bneuman | import common |
47 | |||
48 | 143 | bneuman | keypadTimeout = 11 #in seconds |
49 | 119 | bneuman | |
50 | 143 | bneuman | #this table maps which keypad button to press for each tool. This
|
51 | #list should probably be printed next to the swipe box
|
||
52 | 130 | bneuman | tools = { |
53 | 150 | bneuman | 'Bandsaw':[],
|
54 | 'DrillPress':['3','8'], |
||
55 | 130 | bneuman | 'Mill':['4'], |
56 | 150 | bneuman | 'Lathe':[],
|
57 | 143 | bneuman | #HACK: since the saw isn't used I use it for testing random boards
|
58 | 150 | 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 | '1':11, |
||
67 | '2':12, |
||
68 | '3':13, |
||
69 | '4':14, |
||
70 | '5':15, |
||
71 | '6':16, |
||
72 | '7':17, |
||
73 | '8':18, |
||
74 | '9':19, |
||
75 | '0':13 |
||
76 | } |
||
77 | 130 | bneuman | |
78 | 128 | bneuman | #################################################
|
79 | #this code spawns a thread which always puts the last card id in the
|
||
80 | #lastid variable.
|
||
81 | #################################################
|
||
82 | import threading |
||
83 | import time |
||
84 | |||
85 | lastid = None
|
||
86 | idready = False
|
||
87 | |||
88 | cv = threading.Condition() |
||
89 | |||
90 | class idThread ( threading.Thread ): |
||
91 | |||
92 | def run ( self ): |
||
93 | global lastid
|
||
94 | global idready
|
||
95 | |||
96 | try:
|
||
97 | while True: |
||
98 | l = raw_input()
|
||
99 | cv.acquire() |
||
100 | id = re.search('%([0-9]*)=.*', l)
|
||
101 | if id != None: |
||
102 | lastid = id
|
||
103 | print "!!! setting idready" |
||
104 | idready = True
|
||
105 | cv.notify() |
||
106 | cv.release() |
||
107 | |||
108 | except EOFError: |
||
109 | cv.acquire() |
||
110 | idready = True
|
||
111 | lastid=None
|
||
112 | cv.notify() |
||
113 | cv.release() |
||
114 | #end thread
|
||
115 | ###############################################
|
||
116 | |||
117 | 145 | bneuman | flog = open("tooltron.log", "a") |
118 | 144 | bneuman | |
119 | def logMessage(str): |
||
120 | flog.write(strftime("%Y.%m.%d %H:%M:%S ") + str + "\n") |
||
121 | 146 | bneuman | flog.flush() |
122 | 144 | bneuman | |
123 | 145 | bneuman | logMessage("----- SERVER STARTED -----")
|
124 | |||
125 | 141 | bneuman | def keypad2toolID(t): |
126 | 145 | bneuman | if t in ids: |
127 | return ids[t]
|
||
128 | else:
|
||
129 | return -1 |
||
130 | 141 | bneuman | |
131 | 144 | bneuman | def reverseTool(tn): |
132 | for t in tools: |
||
133 | if tn in tools[t]: |
||
134 | return t
|
||
135 | |||
136 | 145 | bneuman | return 'invalid' |
137 | 144 | bneuman | |
138 | 145 | bneuman | #keep count of successive non-acks
|
139 | toolFails = tools.copy() |
||
140 | for t in toolFails: |
||
141 | toolFails[t] = 0
|
||
142 | |||
143 | 128 | bneuman | def get_last_id(): |
144 | global idready
|
||
145 | global lastid
|
||
146 | |||
147 | got = None
|
||
148 | |||
149 | print ">>> locking for last_id" |
||
150 | |||
151 | cv.acquire() |
||
152 | while not idready: |
||
153 | print ">>> wait" |
||
154 | cv.wait() |
||
155 | |||
156 | print ">>> idready set" |
||
157 | |||
158 | got = lastid |
||
159 | idready = False
|
||
160 | cv.release() |
||
161 | print ">>> done" |
||
162 | |||
163 | return got
|
||
164 | |||
165 | def clear_id(): |
||
166 | global idready
|
||
167 | global lastid
|
||
168 | |||
169 | print "=== locking to clear idready flag" |
||
170 | cv.acquire() |
||
171 | idready = False
|
||
172 | lastid = None
|
||
173 | cv.release() |
||
174 | print "=== done" |
||
175 | |||
176 | return
|
||
177 | |||
178 | 222 | bneuman | if len(sys.argv) < 2: |
179 | print "usage: tooltron.py /path/to/bus/device" |
||
180 | exit(-1) |
||
181 | 128 | bneuman | |
182 | 222 | bneuman | if not common.initBus(sys.argv[1]): |
183 | 213 | bneuman | print "usage: tooltron.py /path/to/bus/device" |
184 | 99 | bneuman | else:
|
185 | |||
186 | 119 | bneuman | pw = getpass.getpass("mysql password: ")
|
187 | db = MySQLdb.connect(host="roboclub8.frc.ri.cmu.edu", user="tooltron", passwd=pw, db="civicrm") |
||
188 | print "connected, now accepting input" |
||
189 | 144 | bneuman | logMessage("connected to database")
|
190 | 119 | bneuman | |
191 | 130 | bneuman | #start the id scan thread AFTER the getpass prompt
|
192 | idThread().start() |
||
193 | |||
194 | 119 | bneuman | cursor = db.cursor() |
195 | |||
196 | qry = "SELECT tools_6 FROM civicrm_value_roboclub_info_2 WHERE card_number_1 = "
|
||
197 | 218 | bneuman | |
198 | 120 | bneuman | def sendTool(t): |
199 | 141 | bneuman | tn = keypad2toolID(t) |
200 | 222 | bneuman | common.sendTool(tn) |
201 | 120 | bneuman | |
202 | 221 | bneuman | # This function eats the rest of the packet where data is what we have so far
|
203 | def flushPacket(data): |
||
204 | while len(data) < 6: |
||
205 | data += bus.read(1)
|
||
206 | |||
207 | plen = data[4]
|
||
208 | if plen > 0: |
||
209 | bus.read(ord(plen))
|
||
210 | |||
211 | |||
212 | 99 | bneuman | while True: |
213 | 128 | bneuman | |
214 | id = get_last_id() |
||
215 | 99 | bneuman | |
216 | if id != None: |
||
217 | 125 | bneuman | print "\n-----------\nid# ", id.group(1) |
218 | 99 | bneuman | |
219 | 222 | bneuman | common.sendKeyRequest() |
220 | 213 | bneuman | startTime = time.time() |
221 | 119 | bneuman | |
222 | cursor.execute(qry + id.group(1)) |
||
223 | |||
224 | result = cursor.fetchall() |
||
225 | |||
226 | acl = [] |
||
227 | |||
228 | for r in result: |
||
229 | tls = r[0].split("\x01") |
||
230 | for t in tls: |
||
231 | if t != '': |
||
232 | try:
|
||
233 | 140 | bneuman | acl.extend (tools[t]) |
234 | 119 | bneuman | except KeyError: |
235 | 125 | bneuman | #this doesn't really matter
|
236 | pass
|
||
237 | 119 | bneuman | |
238 | print "user has access to:", acl |
||
239 | 125 | bneuman | |
240 | 146 | bneuman | user = ""
|
241 | 145 | bneuman | |
242 | 146 | bneuman | #query for name
|
243 | qry2 = "SELECT civicrm_contact.display_name \
|
||
244 | FROM civicrm_value_roboclub_info_2 \
|
||
245 | INNER JOIN (civicrm_contact) \
|
||
246 | ON civicrm_value_roboclub_info_2.entity_id = civicrm_contact.id \
|
||
247 | WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";" |
||
248 | |||
249 | cursor.execute(qry2) |
||
250 | result = cursor.fetchall() |
||
251 | |||
252 | if len(result) > 0: |
||
253 | user += result[0][0] |
||
254 | |||
255 | qry2 = "SELECT civicrm_email.email \
|
||
256 | FROM civicrm_value_roboclub_info_2 \
|
||
257 | INNER JOIN (civicrm_email) \
|
||
258 | ON civicrm_email.contact_id = civicrm_value_roboclub_info_2.entity_id \
|
||
259 | WHERE civicrm_value_roboclub_info_2.card_number_1 = " + id.group(1) + ";" |
||
260 | |||
261 | cursor.execute(qry2) |
||
262 | result = cursor.fetchall() |
||
263 | |||
264 | if len(result) > 0: |
||
265 | user += " ("+result[0][0]+")" |
||
266 | |||
267 | if user == "": |
||
268 | user = str(id.group(1)) |
||
269 | |||
270 | print user
|
||
271 | |||
272 | 213 | bneuman | |
273 | 222 | bneuman | resp = None
|
274 | while resp==None: |
||
275 | resp = common.readKey() |
||
276 | 213 | bneuman | |
277 | 143 | bneuman | #no lock since we are just reading and we can afford to
|
278 | #miss a loop
|
||
279 | if idready == True: |
||
280 | #if someone swipes, break out of the loop
|
||
281 | print "**swipe during read, bailing out" |
||
282 | 144 | bneuman | logMessage("user swipped during keypad read")
|
283 | 143 | bneuman | break
|
284 | if time.time() - startTime > keypadTimeout:
|
||
285 | print "KEYPAD TIMEOUT!" |
||
286 | 144 | bneuman | logMessage("keypad timed out")
|
287 | 143 | bneuman | break
|
288 | 99 | bneuman | |
289 | 213 | bneuman | #if we have a valid response
|
290 | 150 | bneuman | #sometimes the FTDI chip seems to give a zero as a string when power is reset
|
291 | 222 | bneuman | if resp != None and ord(resp) != 0 and resp != common.TT_TIMEOUT: |
292 | 99 | bneuman | |
293 | 145 | bneuman | toolName = reverseTool(resp) |
294 | 150 | bneuman | toolNameLong = toolName + " (k="+str(resp)+"("+str(ord(resp))+"), t="+str(keypad2toolID(resp))+")" |
295 | print "request:",resp,"(",ord(resp),") ",toolNameLong |
||
296 | 119 | bneuman | |
297 | 143 | bneuman | if acl.count(resp) > 0: |
298 | 222 | bneuman | common.sendAck(2)
|
299 | 143 | bneuman | sendTool(resp) |
300 | print "ACCESS GRANTED" |
||
301 | 146 | bneuman | logMessage(user+" ACCESSED tool "+toolNameLong)
|
302 | 222 | bneuman | if common.checkAck(resp):
|
303 | 143 | bneuman | print "ACK" |
304 | 145 | bneuman | toolFails[toolName] = 0
|
305 | 143 | bneuman | else:
|
306 | print "NO ACK!!!!" |
||
307 | 145 | bneuman | logMessage("tool "+toolNameLong+" did not ACK") |
308 | toolFails[toolName] += 1
|
||
309 | 222 | bneuman | if toolFails[toolName] > common.MAX_TOOL_FAILS:
|
310 | 145 | bneuman | #TODO: send email
|
311 | logMessage("WARNING: tool "+toolNameLong+
|
||
312 | " has failed to ACK "+
|
||
313 | 222 | bneuman | str(common.MAX_TOOL_FAILS)+" times in a row.") |
314 | 120 | bneuman | |
315 | 143 | bneuman | else:
|
316 | 222 | bneuman | common.sendNack(2)
|
317 | 143 | bneuman | print "ACCESS DENIED!!" |
318 | 146 | bneuman | logMessage(user + " DENIED on tool "+toolNameLong)
|
319 | 125 | bneuman | |
320 | 143 | bneuman | clear_id() |
321 | |||
322 | 222 | bneuman | elif resp == None or ord(resp) == 0: #if we get noise, send timeout to reset and sync states |
323 | keypad.write(common.TT_TIMEOUT) |
||
324 | 150 | bneuman | print "ERROR: got strange byte, timing out" |
325 | logMessage("ERROR: got 0 as a byte, sending timeout (cardbox power issue?)")
|
||
326 | 128 | bneuman | else:
|
327 | break |