ROS Resources: Documentation | Support | Discussion Forum | Index | Service Status | ros @ Robotics Stack Exchange
Ask Your Question
0

Calling spin_once( ) to wait for a future to return causes my timer callback to only call once

asked 2023-03-23 06:57:37 -0500

SMCC94 gravatar image

Hello, I have a ros2 callback that I'd like to call once a second through a timer, which makes several asynchronous service calls to check the state of another part of the system. Unfortunately, when I include asynchronous service calls (with a small block to wait for a result) the timer callback still finishes, but then stops running - i.e. it never seems to get called again. The code is below, and I'm using ROS2 version 'Humble'

When I run this code I see all the printed messages up to and including 'plc_monitor_callback finished', but nothing afterwards. The 'read_plc_bool' service is running as expected in another node, and continues to be callable after my monitor node hangs - at the moment it's running in a separate terminal window although the plan is to run these both from a launch file.

I've tried setting up different configurations of callback groups and executors, as well as using synchronous calls to this service, although these attempts don't seem to help.

import time
import rclpy
from rclpy.node import Node
from rclpy.executors import MultiThreadedExecutor
from rclpy.callback_groups import MutuallyExclusiveCallbackGroup, ReentrantCallbackGroup
from container_interfaces.srv import ReadPLCBool, ReadPLCInt, WritePLCBool, WritePLCInt

from plc_module.utils import error_codes, plc_variables

class MonitorNode(Node):

    PLC_POLL_FREQUENCY = 1.0          # How often to poll the PLC to check if a tray has moved into place (in seconds)

    PLC_SERVICES_TIMEOUT = 10       # How long to wait for the plc services to respond to a request (in seconds) - these are pretty simple services so we shouldn't wait this long unless something has gone wrong

    def __init__(self) -> None:

        # Call the constructor for the Node class
        super().__init__('monitor')        

        # Create various callback groups to ensure that the various services are called in a mutually exclusive manner
        self.plc_reader_callbackgroup = MutuallyExclusiveCallbackGroup()

        # Create various clients to interface with the PLC services - if any of these fail to connect the node will not be able to function,
        # so we kill the node immediately if this happens - better than allowing it to run until it tries to access a broken service and crashes
        self._logger.debug("Creating read bool service client")
        self.read_bool_client = self.create_client(ReadPLCBool, 'read_plc_bool', callback_group=self.plc_reader_callbackgroup)
        try:
            self.read_bool_client.wait_for_service(timeout_sec=10)
            self._logger.info("Read bool service client created")
        except:
            self._logger.fatal("'read_plc_bool' service not ready within 10s timeout - is it running?")
            self.destroy_node()
            exit()

        # Create a timer to periodically check the state of the PLC and trigger the other modules if necessary
        self.plc_monitor_timer = self.create_timer(self.PLC_POLL_FREQUENCY, self.plc_monitor_callback)



def plc_monitor_callback(self) -> bool:

        self._logger.info("plc_monitor_callback called")

        request = ReadPLCBool.Request()
        request.data_block = 36
        request.byte_index = 12
        request.bit_index = 1

        future = self.read_bool_client.call_async(request)

        self._logger.info("Made asynchronous call") 

        while rclpy.ok():
            rclpy.spin_once(self)
            if future.done():
                self._logger.info("Asynchronous call finished")
                try:
                    response = future.result()
                except Exception as e:
                    self.get_logger().error("Service call failed %r" % (e,))
                    break
                else:
                    self._logger.info("Got response: {}".format(response.value))
                    break

        self._logger.info("plc_monitor_callback finished ...
(more)
edit retag flag offensive close merge delete

1 Answer

Sort by ยป oldest newest most voted
0

answered 2023-03-23 08:00:21 -0500

SMCC94 gravatar image

I've managed to work around this by removing the block beginning 'while rclpy.ok( ):' and replacing it with the following:

future = self.read_bool_client.call_async(request)    
self._logger.info("Made asynchronous call") 
future.add_done_callback(self.some_new_callback)

Where the new callback takes over the next step in execution - I'd still be interested to hear if there's some way of executing this in a single sequential callback, although this may contravene the general ROS principle of not using synchronous callbacks nested within other callbacks

edit flag offensive delete link more

Question Tools

1 follower

Stats

Asked: 2023-03-23 06:57:37 -0500

Seen: 749 times

Last updated: Mar 23 '23