ros2-rust / ros2_rust

Rust bindings for ROS 2
Apache License 2.0
942 stars 127 forks source link

Support for Bevy #358

Open TanJunKiat opened 9 months ago

TanJunKiat commented 9 months ago

Any help or suggestion will be appreciated and we can try to get it working and improve the Docs or an example for the community.

Setup:

Yadunund commented 9 months ago

Hi there!

I've been using ros2_rust along with bevy (although v0.11) without much problems.

Here's how I integrate ROS 2 interfaces within my bevy application:

  1. I have a dedicated startup plugin that initializes a bevy Resource which is a struct that bundles an rclrs::Context and an Arc<rclrs::Node>. A Default trait for this resource is defined which instantiates the context and node. The same plugin also adds a system which retrieves the above resource, checks if the context is okay and if so, calls rclrs::spin_once() with a clone of the node.
  2. In plugins where I need to define, pub/sub/client/services, I define a bevy Component struct with the corresponding rclrs entity. Then, there are systems that query for the Resource above, initialize the component using the node from the resource, and finally insert the component into the entities.

Hope that helps!

TanJunKiat commented 9 months ago
use bevy::prelude::*;
use std::sync::{Arc, Mutex};
use std_msgs::msg::String as StringMsg;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(NodePlugin)
        .add_systems(Startup, setup)
        .add_systems(Update,print_string)
        .run();
}

fn setup() {
    eprintln!("Bevy initialized.");
}

fn print_string(
    string_node: ResMut<NodeSetting>,
) {
    let s = string_node.data.clone().lock().unwrap().clone().unwrap();
    eprintln!("{}",&s.data);
    eprintln!("Received string.");
}

pub struct NodePlugin;

#[derive(Resource)]
struct NodeSetting {
    node: Arc<rclrs::Node>,
    _subscription: Arc<rclrs::Subscription<StringMsg>>,
    data: Arc<Mutex<Option<StringMsg>>>,
}

impl Plugin for NodePlugin {
    fn build (&self, app: &mut App){
        let context = rclrs::Context::new(std::env::args()).unwrap();
        let node = rclrs::Node::new(&context, "republisher").unwrap();
        let data = Arc::new(Mutex::new(None));
        let data_cb = Arc::clone(&data);
        let string_node = NodeSetting {
            node: node.clone(),
            data: data.clone(),
            _subscription: node.create_subscription(
                "in_topic",
                rclrs::QOS_PROFILE_DEFAULT,
                move |msg: StringMsg| {
                    *data_cb.lock().unwrap() = Some(msg);  // (4)
                },
            ).unwrap(),
        };

        app.insert_resource(string_node)
        .add_systems(Update, spin_node)
        .insert_resource(Time::<Fixed>::from_hz(1.0));
    }
}

fn spin_node(string_node: ResMut<NodeSetting>){
    eprintln!("spinning node ");
    rclrs::spin_once(string_node.node.clone(),None).unwrap();
    eprintln!("exit node ");
}
TanJunKiat commented 9 months ago

@Yadunund thanks for the support.

I tried to create a simple test code to try out what you suggested.

Not sure did I understood the recommendation correctly but my Bevy still gets stuck after the node was spun once.

mxgrey commented 9 months ago

Is it stuck inside the spin_once or elsewhere? I'd recommend putting a sensible timeout in the spin_once.

mxgrey commented 9 months ago

Instead of transferring the subscription data using an Arc<Mutex<Option<T>>> I would recommend creating a mpsc channel. Give the sender to your subscription callback and store the receiver in your resource.

Your subscription callback would simply pass the messages into the sender. Your print_string system would drain the receiver using try_recv.

TanJunKiat commented 9 months ago
use bevy::prelude::*;
use std::{sync::{Arc, Mutex}, time::Duration};
use std_msgs::msg::String as StringMsg;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(NodePlugin)
        .add_systems(Startup, setup)
        .add_systems(Update,print_string)
        .run();
}

fn setup() {
    eprintln!("Bevy initialized.");
}

fn print_string(
    string_node: ResMut<NodeSetting>,
) {
    let guard = string_node.data.lock().unwrap();
    if guard.as_ref() != None{   
        let info = guard.as_ref().unwrap();
        eprintln!("Bevy got message: {}",&info.data);
    }
}

pub struct NodePlugin;

#[derive(Resource)]
struct NodeSetting {
    node: Arc<rclrs::Node>,
    _subscription: Arc<rclrs::Subscription<StringMsg>>,
    data: Arc<Mutex<Option<StringMsg>>>,
}

impl Plugin for NodePlugin {
    fn build (&self, app: &mut App){
        let context = rclrs::Context::new(std::env::args()).unwrap();
        let node = rclrs::Node::new(&context, "republisher").unwrap();
        let data = Arc::new(Mutex::new(None));
        let data_cb = Arc::clone(&data);
        let string_node = NodeSetting {
            node: node.clone(),
            data: data.clone(),
            _subscription: node.create_subscription(
                "in_topic",
                rclrs::QOS_PROFILE_DEFAULT,
                move |msg: StringMsg| {
                    eprintln!("Node received message: {}",msg.data);
                    *data_cb.lock().unwrap() = Some(msg);  // (4)
                },
            ).unwrap(),
        };

        app.insert_resource(string_node)
        .add_systems(Update, spin_node)
        .insert_resource(Time::<Fixed>::from_hz(1.0));
    }
}

fn spin_node(string_node: ResMut<NodeSetting>){
    eprintln!("Entering node ");
    let _action = rclrs::spin_once(string_node.node.clone(),Some(Duration::new(1, 0)));
    eprintln!("Exiting node ");
}
TanJunKiat commented 9 months ago

@mxgrey @Yadunund Awesome guys.

Thank you so much for the help.

I managed to get the message on the bevy resource struct and bevy can print the string as it receives it.