Conversation
There was a problem hiding this comment.
Pull request overview
This pull request migrates the gate navigation task from a SMACH-based state machine implementation to a py_trees-based behavior tree architecture. The refactoring introduces a new behaviors module with reusable behavior tree components while maintaining the same high-level gate navigation logic.
Changes:
- Replaced SMACH state machine in
gate.pywith a behavior tree wrapper that delegates to a new modular behavior tree implementation - Created a new
behaviorspackage with actions, conditions, subtrees, and task-specific behavior trees - Added py_trees and py_trees_ros dependencies to the package
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 24 comments.
Show a summary per file
| File | Description |
|---|---|
auv_smach/src/auv_smach/gate.py |
Simplified to wrapper that creates and executes behavior tree instead of defining SMACH states |
auv_smach/src/auv_smach/behaviors/__init__.py |
New module exposing behavior tree components |
auv_smach/src/auv_smach/behaviors/actions.py |
Core behavior implementations (depth control, alignment, path execution, etc.) |
auv_smach/src/auv_smach/behaviors/conditions.py |
Condition behaviors for checking TF availability |
auv_smach/src/auv_smach/behaviors/subtrees.py |
Reusable composite behaviors (search, look around, dynamic path) |
auv_smach/src/auv_smach/behaviors/roll.py |
Roll and yaw maneuver behaviors with pitch correction |
auv_smach/src/auv_smach/behaviors/coin_flip.py |
Coin flip maneuver subtree |
auv_smach/src/auv_smach/behaviors/gate_tree.py |
Main gate task tree construction logic |
auv_smach/package.xml |
Added py_trees dependencies (with one duplicate entry) |
.gitignore |
Added exclusion for personal learning notes |
auv_navigation/auv_mapping/config/slalom.yaml |
Added YAML document separator |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # Start publishing enable signal (background thread) | ||
| self._stop_publishing.clear() | ||
| self._pub_thread = threading.Thread(target=self._publish_enable_loop) | ||
| self._pub_thread.start() |
There was a problem hiding this comment.
Potential race condition: The _stop_publishing Event is cleared on line 113 even though it was already cleared in initialise() on line 85. If the behavior is re-initialized while a thread is still running (didn't finish within the 1-second join timeout in terminate), this could lead to undefined behavior. Additionally, if a thread is orphaned and still running when update() is called again, a new thread will be created while the old one is still publishing, causing double-publishing. Consider checking if _pub_thread is still alive before starting a new one, or ensure proper cleanup.
| <build_export_depend>std_srvs</build_export_depend> | ||
| <exec_depend>auv_navigation</exec_depend> | ||
| <exec_depend>py_trees</exec_depend> | ||
| <exec_depend>py_trees</exec_depend> |
There was a problem hiding this comment.
The py_trees dependency is listed twice in the exec_depend section (lines 58 and 59). Remove one of the duplicate entries.
| <exec_depend>py_trees</exec_depend> |
| def setup(self, timeout=15, **kwargs): | ||
| """Setup ROS connections.""" | ||
| try: | ||
| self.sub_odom = rospy.Subscriber( | ||
| self.odometry_topic, Odometry, self._odom_callback | ||
| ) | ||
| self.sub_kill = rospy.Subscriber( | ||
| self.killswitch_topic, Bool, self._killswitch_callback | ||
| ) | ||
| self.pub_wrench = rospy.Publisher( | ||
| self.wrench_topic, WrenchStamped, queue_size=1 | ||
| ) | ||
| return True | ||
| except Exception as e: | ||
| rospy.logerr(f"[{self.name}] Setup error: {str(e)}") | ||
| return False |
There was a problem hiding this comment.
The subscribers created in the setup() method (lines 54-58) are never unregistered. In behavior trees, behaviors can be setup once and executed multiple times. If the behavior is used in a tree that runs repeatedly, these subscribers will persist and continue receiving callbacks even when the behavior is not active. Consider adding a shutdown() method or cleaning up subscribers in the terminate() method to prevent resource leaks and unwanted callbacks.
| except Exception as e: | ||
| rospy.logerr(f"[{self.name}] Setup error: {e}") | ||
| return False | ||
|
|
There was a problem hiding this comment.
The subscribers created in the setup() method (lines 292-300) for odometry and killswitch are never unregistered. These should be cleaned up when the behavior is no longer needed to prevent resource leaks and unwanted callbacks.
| def terminate(self, new_status): | |
| """ | |
| Cleanup subscribers when the behavior is terminated. | |
| This prevents resource leaks and unwanted callbacks after the | |
| behavior is no longer active. | |
| """ | |
| # Unregister odometry subscriber if it was created | |
| if hasattr(self, "odom_sub") and self.odom_sub is not None: | |
| try: | |
| self.odom_sub.unregister() | |
| except Exception as e: | |
| rospy.logwarn(f"[{self.name}] Error unregistering odom_sub: {e}") | |
| finally: | |
| self.odom_sub = None | |
| # Unregister killswitch subscriber if it was created | |
| if hasattr(self, "killswitch_sub") and self.killswitch_sub is not None: | |
| try: | |
| self.killswitch_sub.unregister() | |
| except Exception as e: | |
| rospy.logwarn(f"[{self.name}] Error unregistering killswitch_sub: {e}") | |
| finally: | |
| self.killswitch_sub = None |
| self.tree = None | ||
| self.behaviour_tree = None |
There was a problem hiding this comment.
The instance variables self.tree (line 32) and self.behaviour_tree (line 33) are initialized to None in init but self.tree is never used. The local variable root is created on line 39 and used throughout the execute method. Either remove the unused self.tree instance variable or assign root to it if it's intended for future use or debugging.
| RotateBehavior, | ||
| SetFrameLookingAtBehavior, | ||
| AlignFrameBehavior, | ||
| CreateFrameAtCurrentPositionBehavior, |
There was a problem hiding this comment.
Import of 'RotateBehavior' is not used.
Import of 'SetFrameLookingAtBehavior' is not used.
Import of 'CreateFrameAtCurrentPositionBehavior' is not used.
| RotateBehavior, | |
| SetFrameLookingAtBehavior, | |
| AlignFrameBehavior, | |
| CreateFrameAtCurrentPositionBehavior, | |
| AlignFrameBehavior, |
| import py_trees | ||
| import rospy | ||
| import math | ||
| import tf2_ros |
There was a problem hiding this comment.
Import of 'tf2_ros' is not used.
| import tf2_ros |
| RotateBehavior( | ||
| name="YawFirst360", | ||
| source_frame="taluy/base_link", | ||
| look_at_frame=yaw_frame, | ||
| rotation_speed=0.5, | ||
| rotation_radian=2 * math.pi, | ||
| timeout=45.0, | ||
| full_rotation=True, | ||
| ) |
There was a problem hiding this comment.
Keyword argument 'full_rotation' is not a supported parameter name of RotateBehavior.init.
| RotateBehavior( | ||
| name="YawSecond360", | ||
| source_frame="taluy/base_link", | ||
| look_at_frame=yaw_frame, | ||
| rotation_speed=0.5, | ||
| rotation_radian=2 * math.pi, | ||
| timeout=45.0, | ||
| full_rotation=True, | ||
| ) |
There was a problem hiding this comment.
Keyword argument 'full_rotation' is not a supported parameter name of RotateBehavior.init.
| except Exception: | ||
| pass |
There was a problem hiding this comment.
'except' clause does nothing but pass and there is no explanatory comment.
| except Exception: | |
| pass | |
| except Exception as e: | |
| rospy.logwarn(f"[{self.name}] Failed to compute pitch from odometry: {e}") |
…NaNs after runtime)" This reverts commit ef06274.
- Reimplement Gate logic using BT wrapper - Implement Coin Flip, Roll, and Yaw maneuvers - Refactor common behaviors - Update dependencies
03fae9c to
b557190
Compare
* test fixes * pre
* bag filename arg * x
* kaydet.py :D * chmod * Update Realsense camera image topic mapping
* add debug feature for frames in gazebo * rename topics to better names * delete vim swp file * Set z position of pose to 0.0 --------- Co-authored-by: Melih Okur <melihokur.okur@gmail.com>
No description provided.