/
car.py
325 lines (269 loc) · 12.8 KB
/
car.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
import threading
import time
import math
import random
from collections import deque
import utils
from cartalk import CarTalker, Message
from collision import Collision, CollisionState
from driver import Driver
from viconclient import ViconClient
# begin debug
# from tests.viconclient_mock import ViconClient
# from tests.driver_mock import Driver
# end debug
class CarState(object):
"""
Car's movment state
"""
IDLE = 0
DRIVING = 1
STOPPED = 2
class Car(object):
"""
Car class:
- main module in the system
- orchestrates collision detection and processing, as well as car-to-car communication
"""
def __init__(self, path=None):
f = open(utils.NAME_FILE, 'r')
car_name = f.read().strip()
f.close()
self.name = car_name
self.frames = deque(maxlen=5) # frame = {'car_name': (x,y,theta,vx,vy,t), ...}
self._state = CarState.IDLE
self._kill = False
self._last_frame_number = 0
self._collision_worker = threading.Thread(target=self._detect_collisions)
self._message_worker = threading.Thread(target=self._process_messages)
self._main_worker = threading.Thread(target=self._process_collisions)
self._collisions = dict() # {'car_name': collision_object ...}
car_ips = dict()
ips = utils.get_car_ips()
for name, ip in ips.iteritems():
if name != self.name:
car_ips[name] = ip
self._vicon_client = ViconClient(self.frames)
self._talker = CarTalker(car_ips)
self._driver = None
if path:
self._driver = Driver(path=path)
else:
self._driver = Driver()
def _detect_collisions(self):
"""
Collision detector:
- detects future collisions between self and other cars
- creates new Collision objects and sends them to the main queue
"""
while not self._kill:
if len(self.frames) == 0:
continue
curr_frame = self.frames[-1]
my_vals = curr_frame[self.name]
# don't waste time on old frames
if self._last_frame_number >= my_vals[-1]:
continue
self._last_frame_number = my_vals[-1]
# +1 makes loop inclusive
for dt in xrange(utils.FRAME_STEP, utils.FRAME_LOOKAHEAD + 1, utils.FRAME_STEP):
my_future_pos = (my_vals[0] + my_vals[3] * dt, my_vals[1] + my_vals[4] * dt)
for car_name, vals in curr_frame.iteritems():
if car_name == self.name:
continue
dx = vals[3] * dt
dy = vals[4] * dt
future_pos = (vals[0] + dx, vals[1] + dy)
if not utils.SAFE_DISTANCE(my_future_pos, future_pos):
if car_name not in self._collisions:
print "NEW COLLISION"
if dt > utils.COLLISION_CRITICAL:
self._collisions[car_name] = Collision(car_name, self._last_frame_number + dt,
utils.CENTER_POINT(my_future_pos, future_pos))
else:
self._collisions[car_name] = Collision(car_name, self._last_frame_number + dt,
utils.CENTER_POINT(my_future_pos, future_pos), critical=True)
else:
# print "REPEAT"
if dt <= utils.COLLISION_CRITICAL:
# print "START CRITICAL"
self._collisions[car_name].lock.acquire()
self._collisions[car_name].critical = True
self._collisions[car_name].lock.release()
print "DETECTED CRITICAL COLLISION", car_name
time.sleep(utils.THREAD_SLEEP)
def _process_messages(self):
"""
Message Processor:
- processes incoming messages from CarTalker
- creates collision objects and adds them to collisions
"""
while not self._kill:
message = self._talker.get_message()
if message is None:
time.sleep(utils.THREAD_SLEEP)
continue
if message.other_name not in self._collisions:
if message.type != Message.ROW:
print "ERROR: Got response for nonexistant collision"
else:
print "NEW MESSAGE RECEIVED"
message.my_name = self.name
c = Collision(message.other_name, message.frame_num, message.location, message=message)
c.state = CollisionState.RECEIVED_MESSAGE
self._collisions[message.other_name] = c
else:
collision = self._collisions[message.other_name]
collision.lock.acquire()
if message.type == Message.ROW:
if collision.state == CollisionState.WAITING:
# we are already waiting, so just send a confirmation that other has ROW
print "ALREADY WAITING"
self._talker.send_message(Message(Message.GO, self.name, message.other_name,
collision.location, collision.frame_num))
elif collision.state == CollisionState.RESOLVED:
# we are already going, so send a stay message
print "ALREADY RESOLVED"
self._talker.send_message(Message(Message.STAY, self.name, message.other_name,
collision.location, collision.frame_num))
elif collision.state == CollisionState.NEW:
# if we haven't already sent a message, set collision to received in interim
collision.state = CollisionState.RECEIVED_MESSAGE
message.my_name = self.name
collision.message = message
print "Got message about unprocessed collision"
elif collision.state == CollisionState.SENT_MESSAGE:
# if collision.message.type == Message.ROW:
print "ALREADY SENT ROW... RESOLVING CONFLICT"
# compare the ROW we sent and the one we received
if collision.priority_val > message.priority_val:
# we win
self._talker.send_message(Message(Message.STAY, self.name, message.other_name,
collision.location, collision.frame_num))
collision.state = CollisionState.RESOLVED
print "RESOLVED DUPLICATED ROW... WE GO"
elif collision.priority_val < message.priority_val:
# we lose
self._talker.send_message(Message(Message.GO, self.name, message.other_name,
collision.location, collision.frame_num))
collision.state = CollisionState.WAITING
print "RESOLVED DUPLICATED ROW... WE WAIT"
else:
# tie, restart process by sending ROW
self._talker.send_message(Message(Message.ROW, self.name, message.other_name,
collision.location, collision.frame_num))
collision.state = CollisionState.SENT_MESSAGE
print "TIED DUPLICATED ROW... RESEND"
collision.message = None
else:
if collision.state == CollisionState.SENT_MESSAGE:
if message.type == Message.STAY:
collision.state = CollisionState.WAITING
collision.message = None
print "WE STAY"
else:
collision.state = CollisionState.RESOLVED
collision.message = None
print "RESOLVED... WE GO"
else:
print "ERROR: got response without having sent a message"
collision.lock.release()
time.sleep(utils.THREAD_SLEEP)
def _process_collisions(self):
"""
Collision Processor:
- main loop
- drives car
- processes Collision objects and decides course of action
- constructs messages to other cars
"""
while not self._kill:
collisions_to_delete = set()
for car_name, collision in self._collisions.iteritems():
# TODO might need this not to block so we can keep moving
collision.lock.acquire()
if collision.critical:
# stop driver, send ROW
self._driver.stop()
self.state = CarState.STOPPED
collision.critical = False
if collision.state == CollisionState.SENT_MESSAGE:
pass # wait for reply
elif collision.state == CollisionState.WAITING:
self._driver.stop() # repeated calls are benign
self.state = CarState.STOPPED
if collision.safe_to_drive(self.frames[-1][car_name]):
print "SAFE TO PROCEED"
collision.state = CollisionState.RESOLVED
elif collision.state == CollisionState.RESOLVED:
print "RESOLVED: going..."
self._driver.go() # repated calls are benign
self._state = CarState.DRIVING
collisions_to_delete.add(car_name)
elif collision.state == CollisionState.NEW:
# send ROW
priority = Car._generate_priority()
self._talker.send_message(Message(Message.ROW, self.name, collision.car_name,
collision.location, collision.frame_num, priority_val=priority))
collision.state = CollisionState.SENT_MESSAGE
collision.priority_val = priority
print "ROW SENT"
elif collision.state == CollisionState.RECEIVED_MESSAGE:
assert collision.message != None
# process collision message
print "RECEIVED MESSAGE NORMAL"
if collision.message.type == Message.ROW:
val = Car._generate_priority()
if collision.message.priority_val > val:
# send GO, wait
self._talker.send_message(Message(Message.GO, self.name, collision.message.other_name,
collision.location, collision.frame_num))
collision.state = CollisionState.WAITING
print "WAIT"
else:
# send WAIT
self._talker.send_message(Message(Message.STAY, self.name, collision.message.other_name,
collision.location, collision.frame_num))
collision.state = CollisionState.RESOLVED
print "RESOLVED... GO"
elif collision.message == Message.GO:
collision.state = CollisionState.RESOLVED
else:
collision.state = CollisionState.WAITING
collision.message = None
collision.lock.release()
for key in collisions_to_delete:
del self._collisions[key]
print "DELETED COLLISION", key
time.sleep(utils.THREAD_SLEEP)
@staticmethod
def _generate_priority():
return random.randint(0, utils.PRIORITY_CEILING)
def start(self):
self._vicon_client.start()
self._talker.start()
self._collision_worker.start()
self._message_worker.start()
self._main_worker.start()
def stop(self):
self._kill = True
self._driver.stop()
self._driver.destroy()
self._vicon_client.stop()
self._talker.stop()
self._collision_worker.join()
self._message_worker.join()
self._main_worker.join()
return True
def main():
car = Car()
car.start()
print "Running..."
try:
while(True):
pass
except KeyboardInterrupt:
pass
car.stop()
if __name__ == '__main__':
main()