ros2_rust
ros2_rust copied to clipboard
Support for Bevy
-
Tried to add a subscriber node as a system in Bevy but Bevy stopped running (black screen) after the node started spinning
-
Tried multi-threading. It works (bevy and ros node both running), but ros node is unable to update the resource database in Bevy with the new messages.
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:
- Ubuntu 22.04
- ROS2 humble built from binary
- Bevy 0.12
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:
- I have a dedicated startup plugin that initializes a
bevy Resourcewhich is a struct that bundles anrclrs::Contextand anArc<rclrs::Node>. ADefaulttrait 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, callsrclrs::spin_once()with a clone of the node. - In plugins where I need to define, pub/sub/client/services, I define a
bevy Componentstruct with the correspondingrclrsentity. 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!
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 ");
}
@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.
Is it stuck inside the spin_once or elsewhere? I'd recommend putting a sensible timeout in the spin_once.
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.
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 ");
}
@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.