diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..69990be --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,115 @@ +name: Rust + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + strategy: + matrix: + ros_distribution: + - foxy + - galactic + - humble + - rolling + include: + # Foxy Fitzroy (June 2020 - May 2023) + - docker_image: rostooling/setup-ros-docker:ubuntu-focal-ros-foxy-ros-base-latest + ros_distribution: foxy + ros_version: 2 + # Galactic Geochelone (May 2021 - November 2022) + - docker_image: rostooling/setup-ros-docker:ubuntu-focal-ros-galactic-ros-base-latest + ros_distribution: galactic + ros_version: 2 + # Humble Hawksbill (May 2022 - May 2027) + - docker_image: rostooling/setup-ros-docker:ubuntu-jammy-ros-humble-ros-base-latest + ros_distribution: humble + ros_version: 2 + # Rolling Ridley (June 2020 - Present) + - docker_image: rostooling/setup-ros-docker:ubuntu-jammy-ros-rolling-ros-base-latest + ros_distribution: rolling + ros_version: 2 + runs-on: ubuntu-latest + container: + image: ${{ matrix.docker_image }} + steps: + - uses: actions/checkout@v2 + + - name: Search packages in this repository + id: list_packages + run: | + echo ::set-output name=package_list::$(colcon list --names-only) + + - name: Setup ROS environment + uses: ros-tooling/setup-ros@v0.3 + with: + required-ros-distributions: ${{ matrix.ros_distribution }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@1.63.0 + with: + components: clippy, rustfmt + + - name: Install colcon-cargo and colcon-ros-cargo + run: | + sudo pip3 install git+https://github.com/colcon/colcon-cargo.git + sudo pip3 install git+https://github.com/colcon/colcon-ros-cargo.git + + - name: Check formatting of Rust packages + run: | + for path in $(colcon list | awk '$3 == "(ament_cargo)" { print $2 }'); do + cd $path + cargo fmt -- --check + cd - + done + + - name: Install cargo-ament-build + run: | + cargo install --debug cargo-ament-build + + - name: Build and test + id: build + uses: ros-tooling/action-ros-ci@v0.2 + with: + package-name: ${{ steps.list_packages.outputs.package_list }} + target-ros2-distro: ${{ matrix.ros_distribution }} + vcs-repo-file-url: ros2_rust_${{ matrix.ros_distribution }}.repos + + - name: Run clippy on Rust packages + run: | + cd ${{ steps.build.outputs.ros-workspace-directory-name }} + . /opt/ros/${{ matrix.ros_distribution }}/setup.sh + for path in $(colcon list | awk '$3 == "(ament_cargo)" { print $2 }'); do + cd $path + echo "Running clippy in $path" + cargo clippy --all-targets --all-features -- -D warnings + cd - + done + + - name: Run cargo test on Rust packages + run: | + cd ${{ steps.build.outputs.ros-workspace-directory-name }} + . install/setup.sh + for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" && $1 != "examples_rclrs_minimal_client_service" { print $2 }'); do + cd $path + echo "Running cargo test in $path" + cargo test --all-features + cd - + done + + - name: Rustdoc check + run: | + cd ${{ steps.build.outputs.ros-workspace-directory-name }} + . /opt/ros/${{ matrix.ros_distribution }}/setup.sh + for path in $(colcon list | awk '$3 == "(ament_cargo)" && $1 != "examples_rclrs_minimal_pub_sub" && $1 != "examples_rclrs_minimal_client_service" { print $2 }'); do + cd $path + echo "Running rustdoc check in $path" + cargo rustdoc -- -D warnings + cd - + done diff --git a/message_demo/Cargo.toml b/message_demo/Cargo.toml new file mode 100644 index 0000000..75fad4c --- /dev/null +++ b/message_demo/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "examples_rclrs_message_demo" +version = "0.3.0" +authors = ["Nikolai Morin "] +edition = "2021" + +[[bin]] +name = "message_demo" +path = "src/message_demo.rs" + +[dependencies] +anyhow = {version = "1", features = ["backtrace"]} + +[dependencies.rclrs] +version = "0.3" + +[dependencies.rosidl_runtime_rs] +version = "0.3" + +[dependencies.rclrs_example_msgs] +version = "0.3" +features = ["serde"] + +[dependencies.serde_json] +version = "1.0" diff --git a/message_demo/package.xml b/message_demo/package.xml new file mode 100644 index 0000000..c4ac6e6 --- /dev/null +++ b/message_demo/package.xml @@ -0,0 +1,23 @@ + + + + examples_rclrs_message_demo + 0.3.0 + Package containing an example of message-related functionality in rclrs. + Nikolai Morin + Apache License 2.0 + + rclrs + rosidl_runtime_rs + rclrs_example_msgs + + rclrs + rosidl_runtime_rs + rclrs_example_msgs + + + ament_cargo + + diff --git a/message_demo/src/message_demo.rs b/message_demo/src/message_demo.rs new file mode 100644 index 0000000..1f79492 --- /dev/null +++ b/message_demo/src/message_demo.rs @@ -0,0 +1,177 @@ +use std::convert::TryInto; +use std::env; + +use anyhow::{Error, Result}; +use rosidl_runtime_rs::{seq, BoundedSequence, Message, Sequence}; + +fn check_default_values() { + let msg = rclrs_example_msgs::msg::rmw::VariousTypes::default(); + assert!(msg.bool_member); + assert_eq!(msg.int8_member, 1i8); + assert_eq!(msg.uint8_member, 2u8); + assert_eq!(msg.byte_member, 3u8); + assert_eq!(msg.float32_member, 1e-2f32); + assert_eq!(msg.float_array, [1.0, 2.0, 3.0]); + assert_eq!(msg.float_seq_bounded, seq![3 # 4.0, 5.0]); + assert_eq!(msg.float_seq_unbounded, seq![6.0]); + assert_eq!(msg.string_member.to_string(), "Χαίρετε 你好"); + assert_eq!(msg.wstring_member.to_string(), "αντίο σου 再见"); + assert_eq!(msg.bounded_string_member.to_string(), "äöü"); + assert_eq!(msg.bounded_wstring_member.to_string(), "äöü"); + assert_eq!( + msg.string_array.clone().map(|s| s.to_string()), + ["R", "O", "S", "2"].map(String::from) + ); + assert_eq!( + msg.string_seq_bounded, + seq![4 # "R".into(), "O".into(), "S".into(), "2".into()] + ); + assert_eq!( + msg.string_seq_unbounded, + seq!["R".into(), "O".into(), "S".into(), "2".into()] + ); + assert_eq!( + msg.bounded_string_array.clone().map(|s| s.to_string()), + ["R", "O", "S", "2"].map(String::from) + ); + assert_eq!( + msg.bounded_string_seq_bounded, + ["R", "O", "S", "2"] + .into_iter() + .map(|s| s.try_into().unwrap()) + .collect() + ); + assert_eq!( + msg.bounded_string_seq_unbounded, + ["R", "O", "S", "2"] + .into_iter() + .map(|s| s.try_into().unwrap()) + .collect() + ); + assert_eq!(msg.nested_member.effect.to_string(), "discombobulate"); + assert_eq!( + msg.nested_array, + [msg.nested_member.clone(), msg.nested_member.clone()] + ); + assert_eq!(msg.nested_seq_bounded, seq![3 #]); + assert_eq!(msg.nested_seq_unbounded, seq![]); + + // The default instance for the idiomatic type also has the defaults set + let idiomatic_msg = rclrs_example_msgs::msg::VariousTypes::default(); + assert_eq!( + rclrs_example_msgs::msg::VariousTypes::into_rmw_message(std::borrow::Cow::Owned( + idiomatic_msg + )) + .into_owned(), + msg + ); +} + +fn demonstrate_printing() { + let default_msg = rclrs_example_msgs::msg::VariousTypes::default(); + println!("================== Compact debug representation =================="); + println!("{:?}", default_msg); + println!("================== Pretty debug representation ==================="); + println!("{:#?}", default_msg); + // The RMW-native message type has the same output + let default_rmw_msg = rclrs_example_msgs::msg::rmw::VariousTypes::default(); + assert_eq!( + format!("{:?}", default_msg), + format!("{:?}", default_rmw_msg) + ); + assert_eq!( + format!("{:#?}", default_msg), + format!("{:#?}", default_rmw_msg) + ); +} + +fn demonstrate_serde() -> Result<(), Error> { + // When the serde feature is turned on, messages are able to be serialized + // to and deserialized from a variety of formats. Here JSON is used as an + // example. + // Works with RMW-native and idiomatic messages. + let idiomatic_msg = rclrs_example_msgs::msg::VariousTypes::default(); + let rmw_msg = rclrs_example_msgs::msg::rmw::VariousTypes::default(); + println!("================= JSON serialization with Serde =================="); + let idiomatic_serialized = serde_json::to_string_pretty(&idiomatic_msg)?; + let rmw_serialized = serde_json::to_string_pretty(&rmw_msg)?; + assert_eq!(idiomatic_serialized, rmw_serialized); + println!("{}", rmw_serialized); + let idiomatic_deserialized = serde_json::from_str(&idiomatic_serialized)?; + let rmw_deserialized = serde_json::from_str(&rmw_serialized)?; + assert_eq!(idiomatic_msg, idiomatic_deserialized); + assert_eq!(rmw_msg, rmw_deserialized); + Ok(()) +} + +fn demonstrate_sequences() { + // Convenient creation of (bounded) sequences with the seq! macro + // This one has three items and a length bound of 5 + let mut float_seq_bounded = seq![5 # 1.0, 2.0, 3.0]; + // Sequences and bounded sequences have iter(), iter_mut(), and into_iter() + float_seq_bounded + .iter_mut() + .for_each(|n: &mut f32| *n += 1.0); + let float_vec_1: Vec<_> = float_seq_bounded.iter().copied().collect(); + let float_vec_2: Vec<_> = float_seq_bounded.into_iter().collect(); + assert_eq!(float_vec_1, float_vec_2); + // Sequences also implement FromIterator. + let mut int_seq_unbounded: Sequence = [42; 4].into_iter().collect(); + // Bounded sequences will ignore remaining items once the length bound is reached + let mut int_seq_bounded: BoundedSequence = [42; 4].into_iter().collect(); + // Sequences deref to slices + int_seq_bounded[2] = 24; + assert_eq!(int_seq_bounded.last(), Some(&24)); + int_seq_unbounded[2..].copy_from_slice(&int_seq_bounded[1..]); + // New sequences will contain default values – and 0 for primitive types + let seq_with_default_values = Sequence::::new(1); + assert_eq!(seq_with_default_values[0].effect, "discombobulate".into()); +} + +fn demonstrate_pubsub() -> Result<(), Error> { + println!("================== Interoperability demo =================="); + // Demonstrate interoperability between idiomatic and RMW-native message types + let context = rclrs::Context::new(env::args())?; + let mut node = rclrs::create_node(&context, "message_demo")?; + + let idiomatic_publisher = node.create_publisher::( + "topic", + rclrs::QOS_PROFILE_DEFAULT, + )?; + let direct_publisher = node.create_publisher::( + "topic", + rclrs::QOS_PROFILE_DEFAULT, + )?; + + let _idiomatic_subscription = node + .create_subscription::( + "topic", + rclrs::QOS_PROFILE_DEFAULT, + move |_msg: rclrs_example_msgs::msg::VariousTypes| println!("Got idiomatic message!"), + )?; + let _direct_subscription = node + .create_subscription::( + "topic", + rclrs::QOS_PROFILE_DEFAULT, + move |_msg: rclrs_example_msgs::msg::rmw::VariousTypes| { + println!("Got RMW-native message!") + }, + )?; + println!("Sending idiomatic message."); + idiomatic_publisher.publish(rclrs_example_msgs::msg::VariousTypes::default())?; + rclrs::spin_once(&node, None)?; + println!("Sending RMW-native message."); + direct_publisher.publish(rclrs_example_msgs::msg::rmw::VariousTypes::default())?; + rclrs::spin_once(&node, None)?; + + Ok(()) +} + +fn main() -> Result<(), Error> { + check_default_values(); + demonstrate_printing(); + demonstrate_serde()?; + demonstrate_sequences(); + demonstrate_pubsub()?; + Ok(()) +} diff --git a/minimal_client_service/Cargo.toml b/minimal_client_service/Cargo.toml new file mode 100644 index 0000000..2e0dad4 --- /dev/null +++ b/minimal_client_service/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "examples_rclrs_minimal_client_service" +version = "0.3.0" +authors = ["Esteve Fernandez "] +edition = "2021" + +[[bin]] +name = "minimal_client" +path = "src/minimal_client.rs" + +[[bin]] +name = "minimal_client_async" +path = "src/minimal_client_async.rs" + +[[bin]] +name = "minimal_service" +path = "src/minimal_service.rs" + +[dependencies] +anyhow = {version = "1", features = ["backtrace"]} +tokio = { version = "1", features = ["macros", "rt", "rt-multi-thread", "time"] } + +[dependencies.rclrs] +version = "0.3" + +[dependencies.rosidl_runtime_rs] +version = "0.3" + +[dependencies.example_interfaces] +version = "*" diff --git a/minimal_client_service/package.xml b/minimal_client_service/package.xml new file mode 100644 index 0000000..acad2f0 --- /dev/null +++ b/minimal_client_service/package.xml @@ -0,0 +1,23 @@ + + + + examples_rclrs_minimal_client_service + 0.3.0 + Package containing an example of the client-service mechanism in rclrs. + Esteve Fernandez + Apache License 2.0 + + example_interfaces + rclrs + rosidl_runtime_rs + + example_interfaces + rclrs + rosidl_runtime_rs + + + ament_cargo + + diff --git a/minimal_client_service/src/minimal_client.rs b/minimal_client_service/src/minimal_client.rs new file mode 100644 index 0000000..685dbf6 --- /dev/null +++ b/minimal_client_service/src/minimal_client.rs @@ -0,0 +1,32 @@ +use std::env; + +use anyhow::{Error, Result}; + +fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let mut node = rclrs::create_node(&context, "minimal_client")?; + + let client = node.create_client::("add_two_ints")?; + + let request = example_interfaces::srv::AddTwoInts_Request { a: 41, b: 1 }; + + println!("Starting client"); + + std::thread::sleep(std::time::Duration::from_millis(500)); + + client.async_send_request_with_callback( + &request, + move |response: example_interfaces::srv::AddTwoInts_Response| { + println!( + "Result of {} + {} is: {}", + request.a, request.b, response.sum + ); + }, + )?; + + std::thread::sleep(std::time::Duration::from_millis(500)); + + println!("Waiting for response"); + rclrs::spin(&node).map_err(|err| err.into()) +} diff --git a/minimal_client_service/src/minimal_client_async.rs b/minimal_client_service/src/minimal_client_async.rs new file mode 100644 index 0000000..38f55f1 --- /dev/null +++ b/minimal_client_service/src/minimal_client_async.rs @@ -0,0 +1,33 @@ +use std::env; + +use anyhow::{Error, Result}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let mut node = rclrs::create_node(&context, "minimal_client")?; + + let client = node.create_client::("add_two_ints")?; + + println!("Starting client"); + + std::thread::sleep(std::time::Duration::from_millis(500)); + + let request = example_interfaces::srv::AddTwoInts_Request { a: 41, b: 1 }; + + let future = client.call_async(&request); + + println!("Waiting for response"); + + let rclrs_spin = tokio::task::spawn_blocking(move || rclrs::spin(&node)); + + let response = future.await?; + println!( + "Result of {} + {} is: {}", + request.a, request.b, response.sum + ); + + rclrs_spin.await.ok(); + Ok(()) +} diff --git a/minimal_client_service/src/minimal_service.rs b/minimal_client_service/src/minimal_service.rs new file mode 100644 index 0000000..0150aea --- /dev/null +++ b/minimal_client_service/src/minimal_service.rs @@ -0,0 +1,25 @@ +use std::env; + +use anyhow::{Error, Result}; + +fn handle_service( + _request_header: &rclrs::rmw_request_id_t, + request: example_interfaces::srv::AddTwoInts_Request, +) -> example_interfaces::srv::AddTwoInts_Response { + println!("request: {} + {}", request.a, request.b); + example_interfaces::srv::AddTwoInts_Response { + sum: request.a + request.b, + } +} + +fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let mut node = rclrs::create_node(&context, "minimal_service")?; + + let _server = node + .create_service::("add_two_ints", handle_service)?; + + println!("Starting server"); + rclrs::spin(&node).map_err(|err| err.into()) +} diff --git a/minimal_pub_sub/Cargo.toml b/minimal_pub_sub/Cargo.toml new file mode 100644 index 0000000..ce1dd48 --- /dev/null +++ b/minimal_pub_sub/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "examples_rclrs_minimal_pub_sub" +version = "0.3.0" +# This project is not military-sponsored, Jacob's employment contract just requires him to use this email address +authors = ["Esteve Fernandez ", "Nikolai Morin ", "Jacob Hassold "] +edition = "2021" + +[[bin]] +name = "minimal_subscriber" +path = "src/minimal_subscriber.rs" + +[[bin]] +name = "minimal_publisher" +path = "src/minimal_publisher.rs" + +[[bin]] +name = "zero_copy_subscriber" +path = "src/zero_copy_subscriber.rs" + +[[bin]] +name = "zero_copy_publisher" +path = "src/zero_copy_publisher.rs" + +[dependencies] +anyhow = {version = "1", features = ["backtrace"]} + +[dependencies.rclrs] +version = "0.3" + +[dependencies.rosidl_runtime_rs] +version = "0.3" + +[dependencies.std_msgs] +version = "*" + +[package.metadata.ros] +install_to_share = ["launch"] diff --git a/minimal_pub_sub/launch/minimal_pub_sub.launch.xml b/minimal_pub_sub/launch/minimal_pub_sub.launch.xml new file mode 100644 index 0000000..6d0513b --- /dev/null +++ b/minimal_pub_sub/launch/minimal_pub_sub.launch.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/minimal_pub_sub/package.xml b/minimal_pub_sub/package.xml new file mode 100644 index 0000000..9629978 --- /dev/null +++ b/minimal_pub_sub/package.xml @@ -0,0 +1,26 @@ + + + + examples_rclrs_minimal_pub_sub + 0.3.0 + Package containing an example of the publish-subscribe mechanism in rclrs. + Esteve Fernandez + Nikolai Morin + + Jacob Hassold + Apache License 2.0 + + rclrs + rosidl_runtime_rs + std_msgs + + rclrs + rosidl_runtime_rs + std_msgs + + + ament_cargo + + diff --git a/minimal_pub_sub/src/minimal_publisher.rs b/minimal_pub_sub/src/minimal_publisher.rs new file mode 100644 index 0000000..7200869 --- /dev/null +++ b/minimal_pub_sub/src/minimal_publisher.rs @@ -0,0 +1,25 @@ +use std::env; + +use anyhow::{Error, Result}; + +fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let node = rclrs::create_node(&context, "minimal_publisher")?; + + let publisher = + node.create_publisher::("topic", rclrs::QOS_PROFILE_DEFAULT)?; + + let mut message = std_msgs::msg::String::default(); + + let mut publish_count: u32 = 1; + + while context.ok() { + message.data = format!("Hello, world! {}", publish_count); + println!("Publishing: [{}]", message.data); + publisher.publish(&message)?; + publish_count += 1; + std::thread::sleep(std::time::Duration::from_millis(500)); + } + Ok(()) +} diff --git a/minimal_pub_sub/src/minimal_subscriber.rs b/minimal_pub_sub/src/minimal_subscriber.rs new file mode 100644 index 0000000..eb34a91 --- /dev/null +++ b/minimal_pub_sub/src/minimal_subscriber.rs @@ -0,0 +1,23 @@ +use std::env; + +use anyhow::{Error, Result}; + +fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let mut node = rclrs::create_node(&context, "minimal_subscriber")?; + + let mut num_messages: usize = 0; + + let _subscription = node.create_subscription::( + "topic", + rclrs::QOS_PROFILE_DEFAULT, + move |msg: std_msgs::msg::String| { + num_messages += 1; + println!("I heard: '{}'", msg.data); + println!("(Got {} messages so far)", num_messages); + }, + )?; + + rclrs::spin(&node).map_err(|err| err.into()) +} diff --git a/minimal_pub_sub/src/zero_copy_publisher.rs b/minimal_pub_sub/src/zero_copy_publisher.rs new file mode 100644 index 0000000..5e73b5d --- /dev/null +++ b/minimal_pub_sub/src/zero_copy_publisher.rs @@ -0,0 +1,24 @@ +use std::env; + +use anyhow::{Error, Result}; + +fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let node = rclrs::create_node(&context, "minimal_publisher")?; + + let publisher = + node.create_publisher::("topic", rclrs::QOS_PROFILE_DEFAULT)?; + + let mut publish_count: u32 = 1; + + while context.ok() { + let mut message = publisher.borrow_loaned_message()?; + message.data = publish_count; + println!("Publishing: {}", message.data); + message.publish()?; + publish_count += 1; + std::thread::sleep(std::time::Duration::from_millis(500)); + } + Ok(()) +} diff --git a/minimal_pub_sub/src/zero_copy_subscriber.rs b/minimal_pub_sub/src/zero_copy_subscriber.rs new file mode 100644 index 0000000..b71faec --- /dev/null +++ b/minimal_pub_sub/src/zero_copy_subscriber.rs @@ -0,0 +1,23 @@ +use std::env; + +use anyhow::{Error, Result}; + +fn main() -> Result<(), Error> { + let context = rclrs::Context::new(env::args())?; + + let mut node = rclrs::create_node(&context, "minimal_subscriber")?; + + let mut num_messages: usize = 0; + + let _subscription = node.create_subscription::( + "topic", + rclrs::QOS_PROFILE_DEFAULT, + move |msg: rclrs::ReadOnlyLoanedMessage<'_, std_msgs::msg::UInt32>| { + num_messages += 1; + println!("I heard: '{}'", msg.data); + println!("(Got {} messages so far)", num_messages); + }, + )?; + + rclrs::spin(&node).map_err(|err| err.into()) +} diff --git a/republisher_node/Cargo.toml b/republisher_node/Cargo.toml new file mode 100644 index 0000000..e5ad365 --- /dev/null +++ b/republisher_node/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "republisher_node" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rclrs = "0.3" +std_msgs = "*" diff --git a/republisher_node/package.xml b/republisher_node/package.xml new file mode 100644 index 0000000..28fa015 --- /dev/null +++ b/republisher_node/package.xml @@ -0,0 +1,18 @@ + + + + republisher_node + 0.3.0 + Package containing an example Rust node. + Nikolai Morin + Apache License 2.0 + + rclrs + std_msgs + + + ament_cargo + + diff --git a/republisher_node/src/main.rs b/republisher_node/src/main.rs new file mode 100644 index 0000000..20d6263 --- /dev/null +++ b/republisher_node/src/main.rs @@ -0,0 +1,55 @@ +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use std_msgs::msg::String as StringMsg; + +struct RepublisherNode { + node: rclrs::Node, + _subscription: Arc>, + publisher: rclrs::Publisher, + data: Arc>>, +} + +impl RepublisherNode { + fn new(context: &rclrs::Context) -> Result { + let mut node = rclrs::Node::new(context, "republisher")?; + let data = Arc::new(Mutex::new(None)); + let _subscription = { + let data = Arc::clone(&data); + node.create_subscription( + "in_topic", + rclrs::QOS_PROFILE_DEFAULT, + move |msg: StringMsg| { + *data.lock().unwrap() = Some(msg); + }, + )? + }; + let publisher = node.create_publisher("out_topic", rclrs::QOS_PROFILE_DEFAULT)?; + Ok(Self { + node, + _subscription, + publisher, + data, + }) + } + + fn republish(&self) -> Result<(), rclrs::RclrsError> { + if let Some(s) = &*self.data.lock().unwrap() { + self.publisher.publish(s)?; + } + Ok(()) + } +} + +fn main() -> Result<(), rclrs::RclrsError> { + let context = rclrs::Context::new(std::env::args())?; + let republisher = Arc::new(RepublisherNode::new(&context)?); + let republisher_other_thread = Arc::clone(&republisher); + std::thread::spawn(move || -> Result<(), rclrs::RclrsError> { + loop { + std::thread::sleep(Duration::from_millis(1000)); + republisher_other_thread.republish()?; + } + }); + rclrs::spin(&republisher.node) +} diff --git a/ros2_rust_foxy.repos b/ros2_rust_foxy.repos new file mode 100644 index 0000000..2c7a074 --- /dev/null +++ b/ros2_rust_foxy.repos @@ -0,0 +1,29 @@ +repositories: + ros2/common_interfaces: + type: git + url: https://github.com/ros2/common_interfaces.git + version: foxy + ros2/example_interfaces: + type: git + url: https://github.com/ros2/example_interfaces.git + version: foxy + ros2/rcl_interfaces: + type: git + url: https://github.com/ros2/rcl_interfaces.git + version: foxy + ros2/test_interface_files: + type: git + url: https://github.com/ros2/test_interface_files.git + version: foxy + ros2/rosidl_defaults: + type: git + url: https://github.com/ros2/rosidl_defaults.git + version: foxy + ros2/unique_identifier_msgs: + type: git + url: https://github.com/ros2/unique_identifier_msgs.git + version: foxy + ros2_rust: + type: git + url: https://github.com/ros2-rust/ros2_rust.git + version: main diff --git a/ros2_rust_galactic.repos b/ros2_rust_galactic.repos new file mode 100644 index 0000000..ab398d1 --- /dev/null +++ b/ros2_rust_galactic.repos @@ -0,0 +1,29 @@ +repositories: + ros2/common_interfaces: + type: git + url: https://github.com/ros2/common_interfaces.git + version: galactic + ros2/example_interfaces: + type: git + url: https://github.com/ros2/example_interfaces.git + version: galactic + ros2/rcl_interfaces: + type: git + url: https://github.com/ros2/rcl_interfaces.git + version: galactic + ros2/test_interface_files: + type: git + url: https://github.com/ros2/test_interface_files.git + version: galactic + ros2/rosidl_defaults: + type: git + url: https://github.com/ros2/rosidl_defaults.git + version: galactic + ros2/unique_identifier_msgs: + type: git + url: https://github.com/ros2/unique_identifier_msgs.git + version: galactic + ros2_rust: + type: git + url: https://github.com/ros2-rust/ros2_rust.git + version: main diff --git a/ros2_rust_humble.repos b/ros2_rust_humble.repos new file mode 100644 index 0000000..705c08c --- /dev/null +++ b/ros2_rust_humble.repos @@ -0,0 +1,29 @@ +repositories: + ros2/common_interfaces: + type: git + url: https://github.com/ros2/common_interfaces.git + version: humble + ros2/example_interfaces: + type: git + url: https://github.com/ros2/example_interfaces.git + version: humble + ros2/rcl_interfaces: + type: git + url: https://github.com/ros2/rcl_interfaces.git + version: humble + ros2/test_interface_files: + type: git + url: https://github.com/ros2/test_interface_files.git + version: humble + ros2/rosidl_defaults: + type: git + url: https://github.com/ros2/rosidl_defaults.git + version: humble + ros2/unique_identifier_msgs: + type: git + url: https://github.com/ros2/unique_identifier_msgs.git + version: humble + ros2_rust: + type: git + url: https://github.com/ros2-rust/ros2_rust.git + version: main diff --git a/ros2_rust_rolling.repos b/ros2_rust_rolling.repos new file mode 100644 index 0000000..fce6895 --- /dev/null +++ b/ros2_rust_rolling.repos @@ -0,0 +1,33 @@ +repositories: + ros2/common_interfaces: + type: git + url: https://github.com/ros2/common_interfaces.git + version: rolling + ros2/example_interfaces: + type: git + url: https://github.com/ros2/example_interfaces.git + version: rolling + ros2/rcl_interfaces: + type: git + url: https://github.com/ros2/rcl_interfaces.git + version: rolling + ros2/test_interface_files: + type: git + url: https://github.com/ros2/test_interface_files.git + version: rolling + ros2/rosidl_core: + type: git + url: https://github.com/ros2/rosidl_core.git + version: rolling + ros2/rosidl_defaults: + type: git + url: https://github.com/ros2/rosidl_defaults.git + version: rolling + ros2/unique_identifier_msgs: + type: git + url: https://github.com/ros2/unique_identifier_msgs.git + version: rolling + ros2_rust: + type: git + url: https://github.com/ros2-rust/ros2_rust.git + version: main