Problem combining ROS2 Action and Tkinter GUI

asked 2022-06-17 04:22:39 -0500

brownbrowny gravatar image

updated 2023-04-27 14:27:57 -0500

130s gravatar image

Dear Community,

I am currently working with a UR10e robot and want to enable a experimental Pick and Place application by offering a UI. For that I wrote some Python Code based on TKinter, basically an object-oriented approach.
For every bigger part of the UI I am using separate classes. I am quite happy with the GUI but for communicating with the Robot there is already another ROS2 Node with an Action Server waiting for requests.
Therefore I need to somehow equip one of the TKinter classes with an Action Client which then allows to send requests and receive feedback. The PlanningFrame stores all necessary information for forming the goal_msg for the action request. Therefore it would be very convenient to have the Action Client somewhere near this class.


My main looks like this:

def main(args=None):
  rclpy.init(args=args)    
  gui = GUIWindow()
  gui.mainloop()
  rclpy.shutdown()

  if __name__ == '__main__':
    main()

Where GUIWindow() is my top-level tk.tk class containing all sub tk classes as childs.

So far I have tried to simply add to my TKinter class the action client like:

class PlanningFrame(ttk.Frame):
   def __init__(self, parent, controller):
       self.controller = controller
       ttk.Frame.__init__(self, parent)

       ...
       self.action_client = ActionClient(self, PlanTrajectory, 'trajectory_planner')

Obviously this will not work, as self is the ttk.Frame here and not a rclpy.node.Node resulting in following:

AttributeError: 'ActionClient' object has no attribute '_client_handle'

So I though separating UI and ROS stuff would be a good idea and it basically works:

class PlanningFrame(ttk.Frame):
   def __init__(self, parent, controller):
       self.controller = controller
       ttk.Frame.__init__(self, parent)

       ...
       self.comm_node = CommunicationNode()

class CommunicationNode(rclpy.node.Node):
   def __init__(self):
    super().__init__('communication_node')
    self.action_client = ActionClient(self, PlanTrajectory, 'trajectory_planner')

   def send_goal(self, goal_msg):
       print("send_goal()")
       self.action_client.wait_for_server(5)

       self._send_goal_future = self.action_client.send_goal_async(goal_msg)

       self._send_goal_future.add_done_callback(self.goal_response_callback)

   def goal_response_callback(self, future):
       print("goal_response_callback()")
       goal_handle = future.result()
       if not goal_handle.accepted:
           self.get_logger().info('Goal rejected :(')
           return

       self.get_logger().info('Goal accepted :)')

       self._get_result_future = goal_handle.get_result_async()
       self._get_result_future.add_done_callback(self.get_result_callback)

   def get_result_callback(self, future):
       print("get_result_callback()")
       result = future.result().result
       self.get_logger().info('Result: {0}'.format(result.sequence))
       rclpy.shutdown()

BUT the feedback and response functions are not being called. I guess as there is no spin() being called, it only executes once or something and the Action Server responses get lost. Spinning blocks, so calling it would cause the GUI not to start at all.

How to combine ROS2 Actions and a TKinter application properly? Do I have to use different threads? How to communicate between them?

Later in my application I also need to have an Service Server in the Tkinter App where I need to be able to receive requests after sending the action goal and let the user input someting. Same problem I guess...

edit retag flag offensive close merge delete

Comments

I spin a node in thread and save it in the global to make node and tk widget can communicate to each other. Here is my case: https://github.com/pepelepew71/ws-my_...

pepelepew71 gravatar image pepelepew71  ( 2023-02-25 07:53:00 -0500 )edit