Skip to content

Conversation

@RCoder01
Copy link
Member

@RCoder01 RCoder01 commented Sep 24, 2025

This PR contains a lot of semi-independent refactors, so I'd like all yall's opinions on which are good and which are not.

Unrelated to planning:

  • Remove out monitoring from launch files
  • Add some more type hinting to mrobosub_lib to appease mypy
  • Remove unused args param from localization main (why was it there to begin with?)
  • Run the autoformatter on some files

mrobosub_planning changes (not necessarily exhaustive, please read through the diff):

  • Move a bunch of stuff from typing types to built-in types (typing.Type to type, typing.Union[A, B] to A | B, typing.Optional[T] to T | None, etc.)
    • Possible because ros2 uses more modern python versions
  • Refactor some of the abstract_state hierarchy to remove duplicate logic between states
  • Update state doc comments to reflect current requirements/usage
  • Add time and state_runtime methods to TimedState so we don't have to keep writing self.io.node.get_clock().now().nanoseconds / 1e9
  • periodic_io.Interface
    • Rename periodic_io.Captain to periodic_io.Interface and make it take in a Node in the constructor instead of being a node itself
    • Make a Captain node class in captain.py
    • Rename State.io_node to State.io to reflect that Interface is not a node
      • If states need to access the node, they can do so with self.io.node
    • Update captain main function to reflect changes to the node
  • Add pitch stuff to periodic_io
    • idk why it wasn't there in ros1 code but added for completeness (even though we never use it in practice)
  • Update zed/bot_cam service calls in Interface to use simpler, synchronous calls
    • I'm like 99% sure it's equivalent to creating a call_async future and immediately calling rclpy.spin_until_future_complete
    • I wanted to factor out activate_bot_cam, activate_zed, deactivate_cameras into one like set_cameras(bot_cam_enable: bool, zed_enable: bool) that each of the three called but the order in which activate_zed/bot_cam call the services is opposite (better to disable the other camera first, before enabling to ensure bandwith is never overloaded)
  • renames periodic_io.py to io_interface.py

Base automatically changed from ros2 to devel October 5, 2025 17:12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like most of the changes to this file are typing changes now that we use Python 3.12. As well as the change from io_node to io holding a node as a member variable.

"""

def __init__(self, node: Node):
self.logger = node.get_logger()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great change!!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I support the name change from periodic_io.py --> io_interface.py.

This file changes from being a node to having a node passed into it. It also adds pitch because it wasn't there before.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than overhauling DoubleTimedState, changes to this file are mainly updating instance of io_node --> io.node and updating the typing to be consistent with Python 3.12.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DoubleTimedState didn't actually change all that much; ForwardAndWait changed from being a subclass of TimedState to being a subclass of DoubleTimedState, which significantly simplified its implementation. To reflect this change, I swapped the order of the two classes in source which confused git, so it looks like both classes changed significantly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood

@imzaynb imzaynb self-requested a review October 7, 2025 23:59
imzaynb
imzaynb previously approved these changes Oct 8, 2025
Copy link
Contributor

@imzaynb imzaynb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Though I do want @alexanderbowler to look at these changes before we merge since he was in charge of migrating this from ROS1.



TransitionMap = Dict[Type[Outcome], Type[State]]
TransitionMap = dict[type[Outcome], type[State]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the recommended way to type python types, I always used tje upercase typing version instead of the lowercase python version

Copy link
Member Author

@RCoder01 RCoder01 Oct 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uppercase version is from the typing module, lowercase just uses the built-in types. There's no longer any reason to import the uppercase names in recent python versions because the built-in types now support the type[generic] syntax.

https://docs.python.org/3/library/typing.html#typing.Dict

"Deprecated since version 3.9"

rate.sleep()


def main() -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does main return None? doesn't it return an int

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesnt?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main returns whatever you want in python

def __init__(self, prev_outcome: Outcome, io: Interface):
super().__init__(prev_outcome, io)
self.io.reset_target_twist()
self.rate = self.io.node.create_rate(50)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am again unsure about the use of rate here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it is fine because stop is special, in a general state we definitely do not want to rate sleep tho

Returns the Outcome from calling handle() on StopState.
"""
rate = self.node.create_rate(hz)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think rate can be used here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it is fine because of the difficulty of refactoring to not use sleep and there is a separate thread spinning in captain.py

Copy link
Member

@HenryLeC HenryLeC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

rate.sleep()


def main() -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main returns whatever you want in python

def __init__(self, prev_outcome: Outcome, io: Interface):
super().__init__(prev_outcome, io)
self.io.reset_target_twist()
self.rate = self.io.node.create_rate(50)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it is fine because stop is special, in a general state we definitely do not want to rate sleep tho

Returns the Outcome from calling handle() on StopState.
"""
rate = self.node.create_rate(hz)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this case it is fine because of the difficulty of refactoring to not use sleep and there is a separate thread spinning in captain.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants