1
$\begingroup$

I am working with ROS2 in Python. I want to create a single node, subscribed to topics to get the pose of multiple robots. I'd like to be able to adjust to different numbers of robots, and so I'm trying to create subscriptions dynamically, and pass the corresponding topic to each callback so I can determine which topic data has been received from (as in this question) I've created a node initialized with a list of subscription definitions, with a loop to create subscribers, each referencing the same callback as below:

class SubNode(Node):
    def __init__(self,name, subscription_definitions=[('/robot1/pose', Pose), ('/robot2/pose', Pose)]):
        super().__init__(node_name=name)
        for topic_definition in subscription_definitions:
            self.create_subscription(
                topic= topic_definition[0],
                msg_type= topic_definition[1],
                callback= lambda msg: self.listener_callback(msg, topic_definition[0]),
                qos_profile= 10
                )
        
    def listener_callback(self, msg, topic):
        self.get_logger().info(f'SubNode callback heard {msg} on {topic}')

However, when this node receives data, the callback only ever reports the topic as the final entry in the subscription definitions (here /robot2/pose). However, when I hard-code the creation of these subscriptions, as below:

class SubNode(Node):
    def __init__(self,name, subscription_definitions=[('/robot1/pose', Pose), ('/robot2/pose', Pose)]):
        super().__init__(node_name=name)
            
        self.create_subscription(topic_definitions[0][1], topic_definitions[0][0],lambda msg: self.listener_callback(msg, topic_definitions[0][0]),10)
        self.create_subscription(topic_definitions[1][1], topic_definitions[1][0],lambda msg: self.listener_callback(msg, topic_definitions[1][0]),10)
...

It works. What's happening when I loop over the list rather than statically initialize them that makes it fail? I assume it's something to do with an overwriting issue, but I'm stumped.

Any advice is appreciated. I was able to fix the issue by using partial() in the subscription creation, as below (which further makes me think that it has to do with Python overwriting the callback, as partial would create a new object), but I'd like to understand why my initial approach failed.

self.create_subscription(
                topic= topic_definition[0],
                msg_type= topic_definition[1],
                callback= partial(self.listener_callback, topic=topic_definition[0]),
                qos_profile= 10
                )
$\endgroup$

3 Answers 3

3
$\begingroup$

As firthc pointed out (in the comment that is now a wiki answer), it is a "problem" with lambda functions. They create a reference to the environment rather than a copy of the current value of the loop. Here is how it can be solved it using map():

topics = ['/robot1/pose', '/robot2/pose']

self.subs = map(lambda topic_name: self.create_subscription(Pose, topic_name, lambda msg: self.listener_callback(msg, topic_name), 10), topics)

self.subs = list(self.subs)
$\endgroup$
1
  • $\begingroup$ Just a thought: It might be worth quoting the comment in your answer, as comments are (generally) ephemeral in nature and can end up being deleted. So, if you don't wish to lose the context, blockquoting the comment (either partially or in its entirety) could be a good idea... $\endgroup$ Commented Apr 22 at 10:36
2
$\begingroup$

OP's answer from a now-deleted comment on Steve Mecenski's answer:

Hardcoding working without storing the instance may have to do with how I'm wrapping the node later on... I'll test it in isolation. After some more reading, the hard coding vs looping has to do with creating lambda's in loops and their scope; for anyone else who might end up here, this is what answered my question.

$\endgroup$
0
$\begingroup$

You're not storing your subscriptions.

    for topic_definition in subscription_definitions:
        self.my_subs.append(self.create_subscription(
            topic= topic_definition[0],
            msg_type= topic_definition[1],
            callback= lambda msg: self.listener_callback(msg, topic_definition[0]),
            qos_profile= 10
            ))

I can't speak why your hardcoding works, that is odd. I'm actually surprised any of it works without storing the instance to keep it in scope.

$\endgroup$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.