Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] can we pub a ROS topic and receive it in zenoh subscriber? #131

Open
chenxin199305 opened this issue Aug 23, 2024 · 8 comments
Open
Labels
bug Something isn't working

Comments

@chenxin199305
Copy link

Describe the bug

When try the example:

# build the bridge from source
cargo build -p zenoh-bridge-ros1
cd target/debug/
# terminal 1:
./zenoh-bridge-ros1 --with_rosmaster true --ros_master_uri http://localhost:10000
# terminal 2:
./zenoh-bridge-ros1 --with_rosmaster true --ros_master_uri http://localhost:10001
# terminal 3:
ROS_MASTER_URI=http://localhost:10000 rostopic pub /topic std_msgs/String -r 1 test_message
# terminal 4:
ROS_MASTER_URI=http://localhost:10001 rostopic echo /topic

I can not find a way create a zenoh subscriber to listen to the middle zenoh message (using "topic" key). Is there a way to do it? or can you give an example?

To reproduce

as mentioned above.

System info

Ubuntu 20.04

@chenxin199305 chenxin199305 added the bug Something isn't working label Aug 23, 2024
@edouard-fu
Copy link

edouard-fu commented Sep 19, 2024

I've figured this one out from the source code of this ros1-zenoh bridge: the zenoh topic name has some prefixes. I've written the following code in python to convert a ros topic name to the corresponding zenoh topic name, using the rosbags python package:

def get_zenoh_name_of_ros1_topic(ros1_store, topic: str, msg_type: str) -> str:
        # Get md5 and encode msg_type to construct zenoh topic
        msg_type_split = msg_type.split('/')
        msg_type_encoded = '/'.join([msg_type_split[0],msg_type_split[2]]).encode('utf-8').hex()
        md5 = ros1_store.generate_msgdef(msg_type)[1]
        zenoh_topic = '/'.join([msg_type_encoded, md5, topic])

        return zenoh_topic

with a script example that uses this function:

from rosbags.typesys import Stores, get_typestore

ros1_store = get_typestore(Stores.ROS1_NOETIC)

zenoh_topic = get_zenoh_name_of_ros1_topic(ros1_store, topic="/my/poincloud/topic", msg_type="sensor_msgs/msg/PointCloud2")

My example is for the pointcloud2 type of message, but it also works for other standard ROS message definitions. However, note the difference in naming, it is "sensor_msgs/msg/PointCloud2" and NOT "sensor_msgs/PointCloud2".

You can now subscribe to the middle zenoh message with this generated topic name.

@romainreignier
Copy link

Thanks a lot @edouard-fu for the Python snippet.
I had to add a little fix to remove the leading / on the topic name because the '/'.join() adds a second / which is not supported by Zenoh:

-         zenoh_topic = '/'.join([msg_type_encoded, md5, topic])
+         zenoh_topic = '/'.join([msg_type_encoded, md5, topic[1:]])

Using rosbags, I have added the deserialization to your example:

import zenoh, time
from rosbags.typesys import Stores, get_typestore

TOPIC = '/topic'
TYPE = 'std_msgs/msg/String'

def get_zenoh_name_of_ros1_topic(ros1_store, topic: str, msg_type: str) -> str:
    # Get md5 and encode msg_type to construct zenoh topic
    msg_type_split = msg_type.split('/')
    msg_type_encoded = '/'.join([msg_type_split[0],msg_type_split[2]]).encode('utf-8').hex()
    md5 = ros1_store.generate_msgdef(msg_type)[1]
    zenoh_topic = '/'.join([msg_type_encoded, md5, topic[1:]])

    return zenoh_topic


def listener(sample):
    msg = ros1_store.deserialize_ros1(sample.payload, TYPE)
    print(f'ROS1 msg: {msg.data}')


if __name__ == "__main__":
    session = zenoh.open(zenoh.Config())
    ros1_store = get_typestore(Stores.ROS1_NOETIC)
    zenoh_topic = get_zenoh_name_of_ros1_topic(ros1_store, topic=TOPIC, msg_type=TYPE)
    print(f'ROS topic {TOPIC} is converted to Zenoh {zenoh_topic}')
    sub = session.declare_subscriber(zenoh_topic, listener)
    time.sleep(60)

@Carter12s
Copy link

Just chiming in that this is still a usability problem, first day attempting to use Zenoh and this plugin and have been tripping over this for hours.

The examples: https://github.com/eclipse-zenoh/zenoh-plugin-ros1/blob/main/zenoh-plugin-ros1/examples/ros1_sub.rs really make it look like this "should just work".

Would dearly love a config option like "No Mangle Names" or "Proxy Topics Unmangled" that would allow for this to work directly.

@Carter12s
Copy link

I ended up building a similar workaround in Rust to deal with this.

The critical function is:

/// Takes in a regular ros topic and type and returns a zenoh topic mangled in the way the zenoh-ros1-plugin does
fn mangle_topic(topic: &str, type_str: &str, md5sum: &str) -> String {
    // Name mangling stuff!
    // See: https://github.com/eclipse-zenoh/zenoh-plugin-ros1/issues/131
    // Explicit implementation at: https://github.com/eclipse-zenoh/zenoh-plugin-ros1/blob/main/zenoh-plugin-ros1/src/ros_to_zenoh_bridge/topic_utilities.rs
    // Note: the implementation inside of the bridge uses unstable zenoh, duplicating implementation here with stable zenoh instead.

    // Remove leading and trailing slashes in the topic
    let topic = topic.trim_start_matches('/').trim_end_matches("/");
    // Encode the type as hex
    let type_str = hex::encode(type_str.as_bytes());
    format!("{type_str}/{md5sum}/{topic}")
}

I built a wrapper crate that relies on roslibrust's type generation to get the md5 and type string here:
RosLibRust/roslibrust#209

@aaronchongth
Copy link

aaronchongth commented Dec 7, 2024

hi @edouard-fu and @romainreignier, thanks for the examples! May I know what are the steps you did to get it working? I'm not sure if my setup is wrong or I am facing the same issue as @Carter12s mentioned in #208 where the examples are broken

These are the steps I took so far, but the listener has not received any messages,

# terminal 1
roscore

# terminal 2
rostopic pub my_topic -r 1 std_msgs/String "hello there"

# terminal 3, verifies that it is getting published on ros 1
rostopic echo /my_topic

# terminal 4, I see that /my_topic was registered
wget https://github.com/eclipse-zenoh/zenoh-plugin-ros1/releases/download/1.0.0-beta.2/zenoh-plugin-ros1-1.0.0-beta.2-x86_64-unknown-linux-gnu-standalone.zip
unzip zenoh-plugin-ros1-1.0.0-beta.2-x86_64-unknown-linux-gnu-standalone.zip
./zenoh-bridge-ros1

# terminal 5, changed topic to `/my_topic` and message type to `std_msgs/msg/String`
# however listener doesn't receive anything yet
pip3 install eclipse-zenoh==1.0.0b2 rosbags
python3 try_listen.py

It produced the mangled topic at the output, but nothing else

aaronchong@noetic:~$ python3 try_listen.py 
ROS topic /my_topic is converted to Zenoh 7374645f6d7367732f6d73672f537472696e67/992ce8a1687cec8c8bd883ec73ca41d1/my_topic

edit: just in case it's something to do with versions, it'd be nice to know what versions you all were running then. Thanks in advance!

@Carter12s
Copy link

@aaronchongth

Not sure this is your issue? The bridge's default "mode" in zenoh is "client" you can see that here. I think to get the behavior your are looking for you'll want the bridge in "peer" mode so it automatically connects to your python code. This can be achieved by calling ./zenoh-bridge-ros1 --config my_config.json5 with the contents of "my_config.json5" being:

{
  mode: peer,
}

More info about this on the zenoh deployment page

I've also been testing with 1.0.0-beta-2, but I've been installing via apt on ubuntu.

I believe there is some discrepancy between my Rust implementation, and the python implementation above (possible related to versions of the bridge?). I'm not using std_msgs/msg/String as the "type_str" before converting it to utf-8 hex, I'm directly using std_msgs/String

I was able to determine the correct type str to use by running the bridge with default logging: RUST_LOG=debug ./zenoh-bridge-ros which then prints out the various zenoh keys the bridge is publishing on.

You might also want to check out the "snoop_on_discovery" example here. The bridge is publishing some infomation here that announces what topics exist and has their key information. I used this when debugging to verify I was actually talking to the bridge with zenoh and again to double check the mangled topic names were mangled correctly.

@aaronchongth
Copy link

@Carter12s the default mode when using the standalone bridge should be peer actually, https://github.com/eclipse-zenoh/zenoh-plugin-ros1/blob/1.0.0-beta.2/zenoh-bridge-ros1/src/main.rs#L59, unless I start using a config file directly I guess.

aaron@u24:~$ ./zenoh-bridge-ros1 -h | grep mode
   ...
    -m, --mode <MODE>
            The zenoh session mode. [default: peer] [possible values: peer, client]

But overall I generally have a zenoh router running to help with discovery across multiple devices, or if I need to run clients, so I'm at least sure the mode is not the reason.

I was able to determine the correct type str to use by running the bridge with default logging: RUST_LOG=debug ./zenoh-bridge-ros which then prints out the various zenoh keys the bridge is publishing on.

That's a great tip! Thanks, I'll look into it more starting from here to check the mangling of topic names, as well as snoop_on_discovery. Will update here if all goes well. Thanks again!

@aaronchongth
Copy link

Some updates, I struggled with ROS 1 publisher discovery by the bridge (not even the basic rostopic pub was working), but subscriber was working. Tried building from source and debugged the generated mangled zenoh key as well but nothing worked.

Then I created a fresh Ubuntu 20.04 VM, installed ROS 1 noetic natively, and things started to work 🤣

Just in case it is also stumping some folks, some background, I generally use distrobox and was using an image from docker.io/library/ros:noetic-ros-base-focal earlier, and ROS 1 publisher discovery for the bridge was not working.

Now that it's working for me, I found that the folks above are using 0.11.0 before API changes, here are the diffs required to work with eclipse-zenoh==1.0.4, even with the bridge being version 1.0.0beta2

- def listener(sample):
-     msg = ros1_store.deserialize_ros1(sample.payload, TYPE)
+ def listener(sample: zenoh.Sample):
+     msg = ros1_store.deserialize_ros1(sample.payload.to_bytes(), TYPE)
    print(f'ROS1 msg: {msg.data}')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants