Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 3 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bit-Bots TF Buffer

This is a nearly drop-in replacement for `tf2_ros.Buffer` in Python. It wraps a C++ node that holds the tf buffer and listener. The interface should be the same as the original `tf2_ros.Buffer`, except that we need to pass a reference to the node to the constructor (it is not optional anymore and the order of the arguments is changed therefore).
This is a nearly drop-in replacement for `tf2_ros.Buffer` in Python. It wraps a C++ node that holds the tf buffer and listener. The interface should be the same as the original `tf2_ros.Buffer` and `tf2_ros.TransformListener`, except for e.g. qos settings that are not supported for now.

## Why?

Expand All @@ -18,19 +18,8 @@ In addition to that, this solution also reduces the amount of executor deadlock

## Usage

- Replace `from tf2_ros import Buffer, TransformListener` with `from bitbots_tf_buffer import
Buffer`.
- Remove the `TransformListener` from the code
- Pass a reference to the node to the constructor of `Buffer`:

```python
from bitbots_tf_buffer import Buffer

class MyNode(Node):
def __init__(self):
super().__init__('my_node')
self.tf_buffer = Buffer(self)
```
Replace `from tf2_ros import Buffer, TransformListener` with `from bitbots_tf_buffer import
Buffer, TransformListener`.

## Installation

Expand Down
34 changes: 32 additions & 2 deletions bitbots_tf_buffer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import tf2_ros as tf2

from rclpy.node import Node
from builtin_interfaces.msg import Duration as DurationMsg
from builtin_interfaces.msg import Time as TimeMsg
from geometry_msgs.msg import TransformStamped
Expand All @@ -18,18 +19,32 @@ class Buffer(tf2.BufferCore, tf2.BufferInterface):
It spawns a new node with the suffix "_tf" to handle the C++ side of the ROS communication.
"""

def __init__(self, node, cache_time: Optional[Duration] = None):
def __init__(self, cache_time: Optional[Duration] = None, node: Optional[Node] = None):
if cache_time is None:
cache_time = Duration(seconds=10.0)

tf2.BufferCore.__init__(self, cache_time)
tf2.BufferInterface.__init__(self)

self._impl = CppBuffer(serialize_message(Duration.to_msg(cache_time)), node)
self.cache_time = cache_time
self._impl: Optional[CppBuffer] = None

# If a node is provided, we can set the node directly
if node is not None:
self.set_node(node)

def set_node(self, node: Node):
"""
This API is used instead of the constructor to set the node.
This way we can have a dummy TransformListener and therefore
keep compatibility with the official implementation.
"""
self._impl = CppBuffer(serialize_message(Duration.to_msg(self.cache_time)), node)
Comment on lines +36 to +42
Copy link
Member

Choose a reason for hiding this comment

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

I believe, set_node is a custom name and does not stem from tf2_ros, right?
I don't have a better suggestion, but I have the feeling, the names here are a bit convoluted: set_node, impl, CppBuffer. Maybe we can clean this up a bit ;)

Copy link
Member Author

@Flova Flova May 29, 2025

Choose a reason for hiding this comment

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

Add node ist a custom API. But is does not break the upstream interface.

What do you mean by convoluted?

CppBuffer ist the name of the Cpp library that this python class wraps to add things like types, doc strings etc.

impl holds the reference to the actual implementation. Naming the it impl is common with this pattern. I think TF2 and our move it bindings do it too (tho. I haven't checked).

And add node adds a node to the TF buffer enabling the use of a dummy listener.

I am not sure how to clean it up either.


def lookup_transform(
self, target_frame: str, source_frame: str, time: Time | TimeMsg, timeout: Optional[Duration | DurationMsg] = None
) -> TransformStamped:
assert self._impl is not None, "Buffer has not been initialized with a node. You either need to pass a node to the constructor or have a TransformListener set up."
# Handle timeout as None
timeout = timeout or Duration()
# Call cpp implementation
Expand All @@ -44,6 +59,7 @@ def lookup_transform(
def can_transform(
self, target_frame: str, source_frame: str, time: Time | TimeMsg, timeout: Optional[Duration | DurationMsg] = None
) -> bool:
assert self._impl is not None, "Buffer has not been initialized with a node. You either need to pass a node to the constructor or have a TransformListener set up."
# Handle timeout as None
timeout = timeout or Duration()
# Call cpp implementation
Expand All @@ -53,3 +69,17 @@ def can_transform(
serialize_message(time if isinstance(time, TimeMsg) else Time.to_msg(time)),
serialize_message(timeout if isinstance(timeout, DurationMsg) else Duration.to_msg(timeout)),
)

class TransformListener:
"""
A dummy TransformListener that just sets the node into the C++ Buffer.
This is done for compatibility with the original API implementation.
"""
def __init__(
self,
buffer: Buffer,
node: Node,
*ignored_args,
**ignored_kwargs
) -> None:
buffer.set_node(node)