ROS Resources: Documentation | Support | Discussion Forum | Service Status | Q&A answers.ros.org

Dynamically subscribing to nodes in ROS2 after rospy.spin

I and at least 1 other person and several other people online ran into this issue where if you create_subscription after rclpy.spin (say in a thread) it will not execute any callbacks.

I googled and googled and found no answers. ROS1 to ROS2 migration documentation is still in a pretty sad state unfortunately since in ROS1 this stuff just works. Then I pulled my hair out for 3 hours and figured out a voodoo black magic answer. Here’s the answer, in case more people go googling and hopefully stumble upon this:

  • Create at least one subscriber before rclpy.spin(). It can be a dummy subscriber and doesn’t need to actually do anything

  • After self.create_subscription(...), add a self.guards line. It looks weird like that on a line by itself, I have no idea what it does, but this magic incantation works. If you use the non-classy way to do it, i.e. node.create_subscription(...), then it will be node.guards.

Yes, literally like that.

class MyFooNode(Node):
    def __init__(self)
        super().__init__(node_name = "my_foo_node")
        self.create_subscriber(...) # any old dummy subscriber, won't work without a dummy
        threading.Thread(target = some_thread, daemon = True).start()

    def some_thread(self):
        # Dynamically subscribe to 10 topics
        for i in range(10):
            time.sleep(10)
            self.create_subscription(
                String,
                "/some_dynamic_topic_%d" % i,
                functools.partial(on_message, topic_num = i) # they took away the args= feature of ROS1
                10
            )
            self.guards # MAGIC !! won't call the callback without this!

    def on_message(msg, topic_num = 0):
        print("Received a message on topic number %d:" % topic_num, msg)

def main(args=None):
    # boilerplate verbose ros2 stuff
    rclpy.init(args=args)
    node = MyFooNode()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

If someone can can explain why this magic works I’d love it, but until then just posting a solution online for the other people googling for this!

This is essentially a question, so I think it belongs on answers.ros.org, please post it there. Even if you’re providing a workaround, the best thing to do is to ask a question on answers.ros.org and then answer it yourself.

If you post it on answers.ros.org, please comment here with a link back to the question on answers.ros.org and I’ll try to answer it.

Please try to use answers.ros.org first in the future, see Support - ROS Wiki

2 Likes