ME 405 Romi
Loading...
Searching...
No Matches
pathing_plan_task.py
Go to the documentation of this file.
1"""!
2@file pathing_plan_task.py
3@brief Pathing plan task for cooperative scheduler
4
5This file contains a class to create a pathing_plan_task object which is compatible with
6the cotask scheduler to run as a cooperative task.
7
8The pathing_plan object uses a finite state machine design to dictate the motion of
9the Pololu ROMI kit used in ME 405, Fall 2025 to follow the term project course. The task
10sets share flags and goals to order how control task moves the ROMI to reach the 6 checkpoints
11and the two bonus cups on the course. A long list of defines are used at the beginning to make tuning
12path simpler. The state names are used to make adding new states inbetween states easier so that the file
13reads in the order of how ROMI will move. The goal defines are what are tuned for the course. When moving forward
14the task uses the total distance travelled as found by the observer to know when it needs to change states.
15When turning, the task waits for the yaw from the IMU to be within an acceptable range to change states.
16
17@note The intention of the angle goals was to be done in a global sense where 0 is the direction ROMI faces
18 at the start. However, the IMU has a lot of drift as ROMI goes through the course so these values are found
19 experimentally and start to lose meaning when read in the code until you see how ROMI actually moves.
20@note Yaw drift is inconsistent so this path is not consistent do not run ROMI unsupervised.
21
22@author Alex Power, Lucas Heuchert, Erik Heuchert
23@author ChatGPT5 was used for debugging purposes but not to generate this code
24@date 2025-Nov-10 Approximate date of creation of file
25@date 2025-Dec-12 Final alterations made
26"""
27
28# Yaw Goal Defines
29PI = 3.1415
30YAWGOAL1 = (82)*(PI/180)
31YAWGOAL2 = (329)*(PI/180)
32YAWGOAL3 = (90)*(PI/180)
33YAWGOAL4 = (166)*(PI/180)
34YAWGOAL5 = (163.5)*(PI/180)
35YAWGOAL6 = (255)*(PI/180)
36YAWGOAL7 = (345)*(PI/180)
37YAWGOAL8 = (240)*(PI/180)
38
39# State Names in Order
40LINE_FOLLOW_STATE_0 = 0
41CENTROID_OFFSET_STATE = 1
42ALIGNMENT_GOAL_1 = 2
43ALIGNMENT_CONTROL_1 = 3
44FORK = 4
45ALIGNMENT_GOAL_2 = 5
46ALIGNMENT_CONTROL_2 = 6
47FREE_TRAVERSE_1 = 7
48CP2_PIVOT_1 = 29
49CP2_PIVOT_2 = 31
50LINE_FOLLOWING_2 = 8
51ALIGNMENT_GOAL_3 = 9
52ALIGNMENT_CONTROL_3 = 10
53FREE_TRAVERSE_2 = 11
54LINE_FOLLOW_3 = 12
55ALIGNMENT_GOAL_4 = 13
56ALIGNMENT_CONTROL_4 = 14
57FREE_TRAVERSE_3 = 15
58LINE_FOLLOW_GRG = 32
59ALIGNMENT_GOAL_5 = 16
60ALIGNMENT_CONTROL_5 = 17
61FREE_TRAVERSE_4 = 18
62ALIGNMENT_GOAL_6 = 19
63ALIGNMENT_CONTROL_6 = 20
64FREE_TRAVERSE_5 = 21
65WALL = 22
66ALIGNMENT_GOAL_7 = 23
67ALIGNMENT_CONTROL_7 = 24
68CUP = 25
69ALIGNMENT_GOAL_8 = 26
70ALIGNMENT_CONTROL_8 = 27
71FINISH_LINE = 33
72FINISH = 28
73
74STOP = 40
75TESTING = 30
76
77# Total Distance Check Points
78CP1_2_DISTANCE = 1800
79LF2_DIST = 2025
80CUP_CP3_DIST = 2725
81CP3_CP4 = 3150
82CP4_LINE = 3275
83CP4_GARAGE = 3600
84OUT_GARAGE = 3900
85BACK_UP_DIST = OUT_GARAGE + 100
86TO_CUP = 4300
87RUN_TO_LINE = 4500
88FINISH_DIST = 5000
89
90
92 """!
93 @brief Pathing plan task class for ROMI navigation
94
95 This class implements a finite state machine for autonomous navigation of the Pololu ROMI
96 robot through a predefined course with checkpoints and obstacles.
97 """
98
99 def __init__(self, total_dist, automatic_mode, c_state, fwd_ref, piv_ref, yaw, centroid_goal, yaw_goal, bump_on, bump_flg):
100 """!
101 @brief Initialize the pathing_plan_task object
102
103 @param total_dist Float share containing total distance traveled by ROMI (set by observer)
104 @param automatic_mode Integer share flag to enable automatic line following mode
105 @param c_state Integer share flag holding the expected state of control_task
106 @param fwd_ref Integer share holding forward reference speed in mm/s
107 @param piv_ref Float share holding pivoting reference angular speed in rad/s
108 @param yaw Float share containing current yaw from IMU task
109 @param centroid_goal Float share for line following centroid controller set point
110 @param yaw_goal Float share for heading control set point (target angle)
111 @param bump_on Integer share flag to turn on bump sensors
112 @param bump_flg Integer share flag set by bump task when sensors are triggered
113 """
114 self.state = LINE_FOLLOW_STATE_0
115 self.total_dist = total_dist
116 self.automatic_mode = automatic_mode
117 self.c_state = c_state
118 self.fwd_ref = fwd_ref
119 self.yaw = yaw
120 self.yaw_init = 0
121 self.piv_ref = piv_ref
122 self.centroid_goal = centroid_goal
123 self.yaw_goal = yaw_goal
124 self.bump_on = bump_on
125 self.bump_flg = bump_flg
126 self.bump_off = 0
127 self.iteration = 0
128 self.yaw_goals_5 = [YAWGOAL5, EXPYAWGOAL1, EXPYAWGOAL2, EXPYAWGOAL3]
129 self.direction = [1, 1, -1, 1]
130 self.dist = [CP4_GARAGE, 3600, 3800, 4000]
131
132 def wrapper(self, val):
133 """!
134 @brief Wrap a yaw value to the 0-2PI range
135
136 This method ensures yaw values stay within the valid range used by the IMU,
137 preventing negative values or values exceeding 2π.
138
139 @param val The yaw value to be wrapped
140 @return The wrapped yaw value in the range [0, 2π]
141 """
142 if val > 2*PI:
143 return val - 2*PI
144 elif val < 0:
145 return 2*PI + val
146 else:
147 return val
148
149 def line_following(self, final_dist, next_state):
150 """!
151 @brief Configure control task for line following and check progress
152
153 Sets all necessary flags to put control_task into line following mode and monitors
154 whether ROMI has traveled far enough to transition to the next state.
155
156 @param final_dist Distance threshold to compare against total distance traveled
157 @param next_state State number to transition to when distance is reached
158 @return True if ROMI has moved far enough and state is updated, False otherwise
159 """
160 self.automatic_mode.put(1)
161 self.centroid_goal.put(0)
162 if self.total_dist.get() >= final_dist:
163 self.state = next_state
164 return True
165 return False
166
167 def alignment_goal(self, yaw_goal, rot_direction, next_state):
168 """!
169 @brief Configure control task for large pivoting turns
170
171 Sets all necessary flags to put control_task into pivoting mode for making large,
172 inaccurate turns. Transitions to the next state when within 0.5 rad of goal.
173
174 @param yaw_goal Target yaw value to reach
175 @param rot_direction Direction to turn (1 for CCW, -1 for CW)
176 @param next_state State number to transition to when goal is reached
177 @return True if ROMI is within tolerance and state is updated, False otherwise
178 """
179 self.c_state.put(3)
180 self.piv_ref.put(0.75*rot_direction)
181 real_yaw = self.wrapper(self.yaw.get() - self.yaw_init)
182 if real_yaw >= yaw_goal and real_yaw <= ((yaw_goal) + 0.5):
183 self.c_state.put(4)
184 self.piv_ref.put(0)
185 self.yaw_goal.put(self.wrapper(yaw_goal + self.yaw_init))
186 self.state = next_state
187 self.automatic_mode.put(0)
188 return True
189 return False
190
191 def alignment_control(self, next_state):
192 """!
193 @brief Configure control task for precise controlled turning
194
195 Sets all necessary flags for controlled turning mode to achieve precise yaw alignment.
196 Expected to be used after alignment_goal(). Does not set yaw_goal itself.
197
198 @param next_state State number to transition to when alignment is complete
199 @return True if control_task indicates proper alignment, False otherwise
200 """
201 self.c_state.put(7)
202 if self.yaw_goal.get() == 0:
203 self.state = next_state
204 return True
205 return False
206
207 def free_traverse(self, final_dist, next_state, ref):
208 """!
209 @brief Configure control task for forward motion and check progress
210
211 Sets all necessary flags to put control_task into forward motion mode and monitors
212 whether ROMI has traveled far enough to transition to the next state.
213
214 @param final_dist Distance threshold to compare against total distance traveled
215 @param next_state State number to transition to when distance is reached
216 @param ref Reference speed for ROMI to traverse at in mm/s
217 @return True if ROMI has moved far enough and state is updated, False otherwise
218 """
219 self.c_state.put(1)
220 self.fwd_ref.put(ref)
221 if self.total_dist.get() >= final_dist:
222 self.c_state.put(4)
223 self.fwd_ref.put(0)
224 self.state = next_state
225 return True
226 return False
227
228 def run(self):
229 """!
230 @brief Main task generator for cooperative scheduler
231
232 Implements a 34-state finite state machine for autonomous course navigation.
233 Each state executes once in sequence and never returns. The generator yields
234 control after processing each state.
235
236 @details State sequence overview:
237
238 States execute in the following order: LINE_FOLLOW_STATE_0, CENTROID_OFFSET_STATE,
239 ALIGNMENT_GOAL_1, ALIGNMENT_CONTROL_1, FORK, ALIGNMENT_GOAL_2, ALIGNMENT_CONTROL_2,
240 FREE_TRAVERSE_1, CP2_PIVOT_1, CP2_PIVOT_2, LINE_FOLLOWING_2, ALIGNMENT_GOAL_3,
241 ALIGNMENT_CONTROL_3, FREE_TRAVERSE_2, LINE_FOLLOW_3, ALIGNMENT_GOAL_4,
242 ALIGNMENT_CONTROL_4, FREE_TRAVERSE_3, LINE_FOLLOW_GRG, ALIGNMENT_GOAL_5,
243 ALIGNMENT_CONTROL_5, FREE_TRAVERSE_4, ALIGNMENT_GOAL_6, ALIGNMENT_CONTROL_6,
244 FREE_TRAVERSE_5, WALL, ALIGNMENT_GOAL_7, ALIGNMENT_CONTROL_7, CUP, ALIGNMENT_GOAL_8,
245 ALIGNMENT_CONTROL_8, FINISH, FINISH_LINE
246
247 Path overview: Start line following to the Y, move the centroid to the left to keep right,
248 face CP1, drive directly there, face CP2, drive directly there, line follow briefly,
249 turn to CP3, drive there pushing cup out of the way, turn to CP4, drive there and line
250 follow at the end to align, align at garage entrance, drive through, turn towards wall,
251 drive out, hit wall and backup, turn towards cup, drive over it, turn towards finish,
252 drive back to the line, use line following to get into CP6.
253
254 @note Alignments use two-stage process: goal state for large turns at set speeds,
255 then control state for precise alignment using controller
256 @note Yaw drift is inconsistent making the path unreliable - do not run unsupervised
257
258 @return Yields state number after each iteration
259 """
260 while (True):
261
262 #---------------------------------------------------------#
263 # To CP 1 #
264 #---------------------------------------------------------#
265 if self.yaw_init == 0:
266 self.yaw_init = self.yaw.get()
267 print(f"Yaw Offset : {self.yaw_init}")
268
269 if self.state == LINE_FOLLOW_STATE_0:
270 # Follow the line to the fork
271 if self.line_following(550, CENTROID_OFFSET_STATE):
272 print("Going to CENTROID OFFSET STATE")
273 yield 0
274
275 elif self.state == CENTROID_OFFSET_STATE:
276 # Follow right fork
277 self.centroid_goal.put(-0.4)
278 real_yaw = self.wrapper(self.yaw.get() - self.yaw_init)
279 if self.total_dist.get() >= 950:
280 print("Going to ALIGNMENT GOAL 1")
281 self.centroid_goal.put(0)
282 self.automatic_mode.put(0)
283 self.state = ALIGNMENT_GOAL_1
284 yield 1
285
286 elif self.state == ALIGNMENT_GOAL_1:
287 # Align to 90 degrees
288 if self.alignment_goal(YAWGOAL1, -1, ALIGNMENT_CONTROL_1):
289 print("Going to ALIGNMENT CONTROL 1")
290 yield ALIGNMENT_GOAL_1
291 yield 2
292
293 elif self.state == ALIGNMENT_CONTROL_1:
294 # Align to 90 degrees
295 if self.alignment_control(FORK):
296 print("Going to FORK")
297 yield ALIGNMENT_CONTROL_1
298
299 elif self.state == FORK:
300 # Traverse the fork to CP 1
301 if self.free_traverse(1175, ALIGNMENT_GOAL_2, 100):
302 print("Going to ALIGNMENT GOAL 2")
303 yield FORK
304
305 #---------------------------------------------------------#
306 # To CP 2 #
307 #---------------------------------------------------------#
308
309 elif self.state == ALIGNMENT_GOAL_2:
310 # Turn towards CP 2
311 if self.alignment_goal(YAWGOAL2, 1, ALIGNMENT_CONTROL_2):
312 print("Going to ALIGNMENT CONTROL 2")
313 yield ALIGNMENT_GOAL_2
314
315 elif self.state == ALIGNMENT_CONTROL_2:
316 # Controller align to CP 2
317 if self.alignment_control(FREE_TRAVERSE_1):
318 print("Going to FREE_TRAVERSE_1")
319 yield ALIGNMENT_CONTROL_2
320
321 elif self.state == FREE_TRAVERSE_1:
322 # Move to CP 2
323 if self.free_traverse(CP1_2_DISTANCE, CP2_PIVOT_1, 100):
324 print("Going to CP2_PIVOT_1")
325 yield FREE_TRAVERSE_1
326
327 elif self.state == CP2_PIVOT_1:
328 # Turn towards cup
329 if self.alignment_goal(YAWGOAL7, -1, CP2_PIVOT_2):
330 print("Going to CP2_PIVOT_2")
331 yield CP2_PIVOT_1
332
333 elif self.state == CP2_PIVOT_2:
334 # Controller align to cup
335 if self.alignment_control(LINE_FOLLOWING_2):
336 print("Going to LINE_FOLLOWING_2")
337 yield CP2_PIVOT_2
338
339 elif self.state == LINE_FOLLOWING_2:
340 self.automatic_mode.put(1)
341 if self.total_dist.get() >= LF2_DIST:
342 print("Going to ALIGNMENT GOAL 3 / START OF FUNCTION TESTS")
343 self.automatic_mode.put(0)
344 self.state = ALIGNMENT_GOAL_3
345 yield LINE_FOLLOWING_2
346
347 #---------------------------------------------------------#
348 # To CP 3/Cup #
349 #---------------------------------------------------------#
350
351 elif self.state == ALIGNMENT_GOAL_3:
352 # Turn towards cup
353 if self.alignment_goal(YAWGOAL3, -1, ALIGNMENT_CONTROL_3):
354 print("Going to ALIGNMENT CONTROL 3")
355 yield ALIGNMENT_GOAL_3
356
357 elif self.state == ALIGNMENT_CONTROL_3:
358 # Controller align to cup
359 if self.alignment_control(FREE_TRAVERSE_2):
360 print("Going to FREE_TRAVERSE_2")
361 yield ALIGNMENT_CONTROL_3
362
363 elif self.state == FREE_TRAVERSE_2:
364 # Run over cup
365 if self.free_traverse(CUP_CP3_DIST, ALIGNMENT_GOAL_4, 100):
366 print("Going to ALIGNMENT_GOAL_4")
367 yield FREE_TRAVERSE_2
368
369 elif self.state == LINE_FOLLOW_3:
370 if self.line_following(2700, STOP):
371 print("Going to ALIGNMENT GOAL 4")
372 yield LINE_FOLLOW_3
373
374 #---------------------------------------------------------#
375 # To CP 4 #
376 #---------------------------------------------------------#
377
378 elif self.state == ALIGNMENT_GOAL_4:
379 # Turn towards CP4
380 if self.alignment_goal(YAWGOAL4, -1, ALIGNMENT_CONTROL_4):
381 print("Going to ALIGNMENT CONTROL 4")
382 yield ALIGNMENT_GOAL_3
383
384 elif self.state == ALIGNMENT_CONTROL_4:
385 # Controller align to CP4
386 if self.alignment_control(FREE_TRAVERSE_3):
387 print("Going to FREE_TRAVERSE_3")
388 yield ALIGNMENT_CONTROL_4
389
390 elif self.state == FREE_TRAVERSE_3:
391 # Move to CP 4
392 if self.free_traverse(CP3_CP4, LINE_FOLLOW_GRG, 100):
393 print("Going to LINE_FOLLOW_GRG")
394 yield FREE_TRAVERSE_3
395
396 elif self.state == LINE_FOLLOW_GRG:
397 # Line follow a bit to CP 4
398 if self.line_following(CP4_LINE, ALIGNMENT_GOAL_5):
399 print("Going to ALIGNMENT GOAL 5")
400 yield LINE_FOLLOW_GRG
401
402 #---------------------------------------------------------#
403 # To CP 5 #
404 #---------------------------------------------------------#
405
406 elif self.state == ALIGNMENT_GOAL_5:
407 # Align into the garage
408 if self.alignment_goal(YAWGOAL5, 1, ALIGNMENT_CONTROL_5):
409 print("Going to Alignment Control 5")
410 yield ALIGNMENT_GOAL_5
411
412 elif self.state == ALIGNMENT_CONTROL_5:
413 # Refine garage align
414 if self.alignment_control(FREE_TRAVERSE_4):
415 print("Going to FREE_TRAVERSE_4")
416 yield ALIGNMENT_CONTROL_5
417
418 elif self.state == FREE_TRAVERSE_4:
419 # Go through garage
420 if self.free_traverse(CP4_GARAGE, ALIGNMENT_GOAL_6, 50):
421 print("Going to ALIGNMENT GOAL 5")
422 print(f"Total Dist: {self.total_dist.get()}")
423 yield FREE_TRAVERSE_4
424
425 elif self.state == ALIGNMENT_GOAL_6:
426 # Align into the garage
427 if self.alignment_goal(YAWGOAL6, -1, ALIGNMENT_CONTROL_6):
428 print("Going to ALIGNMENT CONTROL 6")
429 yield ALIGNMENT_GOAL_6
430
431 elif self.state == ALIGNMENT_CONTROL_6:
432 # Refine garage align
433 if self.alignment_control(FREE_TRAVERSE_5):
434 print("Going to FREE_TRAVERSE_5")
435 yield ALIGNMENT_CONTROL_6
436
437 elif self.state == FREE_TRAVERSE_5:
438 # Go through garage to CP 5
439 if self.free_traverse(OUT_GARAGE, WALL, 100):
440 print("Going to WALL")
441 yield FREE_TRAVERSE_5
442
443 #---------------------------------------------------------#
444 # To Finish #
445 #---------------------------------------------------------#
446 elif self.state == WALL:
447 # Wall detection and backup using bump sensors
448 self.c_state.put(1)
449 if self.bump_off == 0:
450 self.bump_on.put(1)
451 now = 0
452 if self.bump_flg.get() == 1:
453 print("Bump flag on")
454 self.fwd_ref.put(-100)
455 if now == 0:
456 now = self.total_dist.get()
457 if self.total_dist.get() <= now - 100:
458 self.state = ALIGNMENT_GOAL_7
459 self.bump_on.put(0)
460 self.bump_off = 1
461 else:
462 print("No bump yet")
463 self.fwd_ref.put(100)
464 print(f"Total dist: {self.total_dist.get()}")
465 yield WALL
466
467 elif self.state == ALIGNMENT_GOAL_7:
468 # Align to the second cup
469 if self.alignment_goal(YAWGOAL7, -1, ALIGNMENT_CONTROL_7):
470 print("Going to ALIGNMENT CONTROL 7")
471 yield ALIGNMENT_GOAL_7
472
473 elif self.state == ALIGNMENT_CONTROL_7:
474 # Align to the second cup
475 if self.alignment_control(CUP):
476 print("Going to CUP")
477 yield ALIGNMENT_CONTROL_7
478
479 elif self.state == CUP:
480 # Go to cup
481 if self.free_traverse(TO_CUP, ALIGNMENT_GOAL_8, 100):
482 print("Going to ALIGNMENT GOAL 8")
483 yield CUP
484
485 elif self.state == ALIGNMENT_GOAL_8:
486 # Align to the finish
487 if self.alignment_goal(YAWGOAL8, 1, ALIGNMENT_CONTROL_8):
488 print("Going to ALIGNMENT CONTROL 8")
489 yield ALIGNMENT_GOAL_8
490
491 elif self.state == ALIGNMENT_CONTROL_8:
492 # Refine finish align
493 if self.alignment_control(FINISH):
494 print("Going to FINISH")
495 yield ALIGNMENT_CONTROL_8
496
497 elif self.state == FINISH:
498 # Go to the finish
499 if self.free_traverse(RUN_TO_LINE, FINISH_LINE, 100):
500 print("Going to STOP")
501 yield FINISH
502
503 elif self.state == FINISH_LINE:
504 if self.line_following(FINISH_DIST, STOP):
505 print("FINISHED")
506 yield FINISH_LINE
507
508 elif self.state == STOP:
509 self.c_state.put(4)
510 self.automatic_mode.put(0)
511 yield STOP
512
513 elif self.state == 40:
514 self.c_state.put(3)
515 self.piv_ref.put(0.5)
516 self.automatic_mode.put(0)
517 else:
518 raise ValueError("Not that many states")
519 yield self.state
Pathing plan task class for ROMI navigation.
alignment_control(self, next_state)
Configure control task for precise controlled turning.
__init__(self, total_dist, automatic_mode, c_state, fwd_ref, piv_ref, yaw, centroid_goal, yaw_goal, bump_on, bump_flg)
Initialize the pathing_plan_task object.
run(self)
Main task generator for cooperative scheduler.
alignment_goal(self, yaw_goal, rot_direction, next_state)
Configure control task for large pivoting turns.
free_traverse(self, final_dist, next_state, ref)
Configure control task for forward motion and check progress.
wrapper(self, val)
Wrap a yaw value to the 0-2PI range.
line_following(self, final_dist, next_state)
Configure control task for line following and check progress.