Repository navigation
Action API and example for dora-ros2-bridge #1194
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| *.o | ||
| /build |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| nodes: | ||
| - id: cxx-node-rust-api | ||
| path: build/node_rust_api | ||
| inputs: | ||
| tick: dora/timer/millis/500 | ||
| outputs: | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,109 @@ | ||||||||||||||||||||||
| #include "cxx.h" | ||||||||||||||||||||||
| #include "dora-node-api.h" | ||||||||||||||||||||||
| #include "ros2-bridge/impl.h" | ||||||||||||||||||||||
| #include "ros2-bridge/msg/example_interfaces.h" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #include <chrono> | ||||||||||||||||||||||
| #include <iostream> | ||||||||||||||||||||||
| #include <memory> | ||||||||||||||||||||||
| #include <vector> | ||||||||||||||||||||||
| using namespace std::chrono_literals; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| int main() | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| std::cout << "HELLO FROM C++" << std::endl; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto dora_node = init_dora_node(); | ||||||||||||||||||||||
| auto merged_events = dora_events_into_combined(std::move(dora_node.events)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto service_qos = qos_default(); | ||||||||||||||||||||||
| service_qos.durability = Ros2Durability::Volatile; | ||||||||||||||||||||||
| service_qos.liveliness = Ros2Liveliness::Automatic; | ||||||||||||||||||||||
| service_qos.reliable = true; | ||||||||||||||||||||||
| service_qos.max_blocking_time = 0.1; | ||||||||||||||||||||||
| auto qos = actionqos_default(); | ||||||||||||||||||||||
| qos.goal_service = service_qos; | ||||||||||||||||||||||
| qos.result_service = service_qos; | ||||||||||||||||||||||
| qos.cancel_service = service_qos; | ||||||||||||||||||||||
| qos.feedback_subscription = service_qos; | ||||||||||||||||||||||
| qos.status_subscription = service_qos; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto ros2_context = init_ros2_context(); | ||||||||||||||||||||||
| auto node = ros2_context->new_node("/dora_ros2_bridge", "action_client"); | ||||||||||||||||||||||
| auto client = example_interfaces::create_Fibonacci_action_client( | ||||||||||||||||||||||
| *node, "/", "fibonacci", qos, merged_events); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| client->wait_for_action(node); | ||||||||||||||||||||||
| std::unique_ptr<::rust::Box<::ActionGoalId>> goal_id; | ||||||||||||||||||||||
| for(int count = 0; count < 3;) { | ||||||||||||||||||||||
| auto event = merged_events.next(); | ||||||||||||||||||||||
| if (event.is_dora()) { // Event from Dora | ||||||||||||||||||||||
| auto dora_event = downcast_dora(std::move(event)); | ||||||||||||||||||||||
| auto ty = event_type(dora_event); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (ty == DoraEventType::Input) { | ||||||||||||||||||||||
| auto input = event_as_input(std::move(dora_event)); | ||||||||||||||||||||||
| if (!goal_id) { | ||||||||||||||||||||||
| auto goal = example_interfaces::Fibonacci_Goal{ .order = 10 + count }; | ||||||||||||||||||||||
| goal_id = std::make_unique<::rust::Box<::ActionGoalId>>( | ||||||||||||||||||||||
| client->send_goal(goal)); | ||||||||||||||||||||||
| std::cout << "Goal have been sent" << std::endl; | ||||||||||||||||||||||
| client->request_result(*goal_id); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } else if (ty == DoraEventType::Stop || ty == DoraEventType::AllInputsClosed) { | ||||||||||||||||||||||
| std::cout << "Received stop event" << std::endl; | ||||||||||||||||||||||
| break; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } else if (client->matches_result(event)) { // Result Event from ActionClient | ||||||||||||||||||||||
| std::cout << "Get result event" << std::endl; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto result_event = client->downcast_result(std::move(event)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| std::cout << "Result: "; | ||||||||||||||||||||||
| for(auto& num: result_event->get_result().sequence) { | ||||||||||||||||||||||
| std::cout << num << " "; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| std::cout << std::endl; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto status = result_event->get_status(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| std::cout << "Status: " << static_cast<::std::int16_t>(status) << std::endl; | ||||||||||||||||||||||
| if(status == ::ActionStatusEnum::Succeeded) { | ||||||||||||||||||||||
| std::cout << "Goal succeeded" << std::endl; | ||||||||||||||||||||||
| goal_id = nullptr; | ||||||||||||||||||||||
| count += 1; | ||||||||||||||||||||||
| } else if(status == ::ActionStatusEnum::Aborted) { | ||||||||||||||||||||||
| std::cerr << "Goal aborted" << std::endl; | ||||||||||||||||||||||
| } else if(status == ::ActionStatusEnum::Canceled) { | ||||||||||||||||||||||
| std::cerr << "Goal canceled" << std::endl; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } else if (client->matches_status(event)) { // Feedback Event from ActionClient | ||||||||||||||||||||||
| std::cout << "Get status event" << std::endl; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto status_event = client->downcast_status(std::move(event)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| std::cout << "Status: " << static_cast<::std::int16_t>(status_event->get_status()) << std::endl; | ||||||||||||||||||||||
| } else if (client->matches_feedback(event)) { // Status Event from ActionClient | ||||||||||||||||||||||
| std::cout << "Get feedback event" << std::endl; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| auto feedback_event = client->downcast_feedback(std::move(event)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!feedback_event->matches_goal(*goal_id)) { // skip if the goal id does not match | ||||||||||||||||||||||
| std::cout << "Feedback not belongs to current requesting goal" << std::endl; | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if(feedback_event->matches_goal(*goal_id)) { | ||||||||||||||||||||||
| auto feedback = feedback_event->get_feedback(); | ||||||||||||||||||||||
| std::cout << "Feedback: "; | ||||||||||||||||||||||
| for(auto& num: feedback.sequence) { | ||||||||||||||||||||||
| std::cout << num << " "; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
Comment on lines
+96
to
+101
|
||||||||||||||||||||||
| if(feedback_event->matches_goal(*goal_id)) { | |
| auto feedback = feedback_event->get_feedback(); | |
| std::cout << "Feedback: "; | |
| for(auto& num: feedback.sequence) { | |
| std::cout << num << " "; | |
| } | |
| auto feedback = feedback_event->get_feedback(); | |
| std::cout << "Feedback: "; | |
| for(auto& num: feedback.sequence) { | |
| std::cout << num << " "; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| use eyre::{Context, bail}; | ||
| use std::{env::consts::EXE_SUFFIX, path::Path}; | ||
|
|
||
| use process_wrap::std::{ | ||
| ProcessGroup, StdChildWrapper as ChildWrapper, StdCommandWrap as CommandWrap, | ||
| }; | ||
|
|
||
| fn main() -> eyre::Result<()> { | ||
| if cfg!(windows) { | ||
| tracing::error!( | ||
| "The c++ example does not work on Windows currently because of a linker error" | ||
| ); | ||
| return Ok(()); | ||
| } | ||
|
|
||
| let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../"); | ||
| let target = root.join("target"); | ||
| std::env::set_current_dir(root.join(file!()).parent().unwrap()) | ||
| .wrap_err("failed to set working dir")?; | ||
|
|
||
| std::fs::create_dir_all("build")?; | ||
|
|
||
| build_package("dora-node-api-cxx", &["ros2-bridge"])?; | ||
| let node_cxxbridge = target | ||
| .join("cxxbridge") | ||
| .join("dora-node-api-cxx") | ||
| .join("install"); | ||
|
|
||
| build_cxx_node( | ||
| &root, | ||
| &[ | ||
| &dunce::canonicalize(Path::new("./").join("main.cc"))?, | ||
| &dunce::canonicalize(node_cxxbridge.join("dora-node-api.cc"))?, | ||
| &dunce::canonicalize(node_cxxbridge.join("ros2-bridge/msg/example_interfaces.cc"))?, | ||
| &dunce::canonicalize(node_cxxbridge.join("ros2-bridge/impl.cc"))?, | ||
| &dunce::canonicalize(node_cxxbridge.join("ros2-bridge/msg/action_msgs.cc"))?, | ||
| &dunce::canonicalize(node_cxxbridge.join("ros2-bridge/msg/builtin_interfaces.cc"))?, | ||
| &dunce::canonicalize(node_cxxbridge.join("ros2-bridge/msg/unique_identifier_msgs.cc"))?, | ||
| ], | ||
| "node_rust_api", | ||
| &[ | ||
| "-I", | ||
| node_cxxbridge.as_os_str().to_str().unwrap(), | ||
| "-l", | ||
| "dora_node_api_cxx", | ||
| ], | ||
| )?; | ||
|
|
||
| let dataflow_task = std::thread::spawn(|| { | ||
| dora_cli::run("dataflow.yml".to_string(), false).unwrap(); | ||
| }); | ||
|
|
||
| let mut ros_task = run_ros_node( | ||
| "examples_rclcpp_minimal_action_server", | ||
| "action_server_member_functions", | ||
| )?; | ||
|
|
||
| let dataflow_task_res = dataflow_task.join(); | ||
|
|
||
| ros_task.kill()?; | ||
|
|
||
| dataflow_task_res.map_err(|e| eyre::eyre!("the dataflow thread failed: {:?}", e))?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn run_ros_node(package: &str, node: &str) -> eyre::Result<Box<dyn ChildWrapper>> { | ||
| let mut command = CommandWrap::with_new("ros2", |cmd| { | ||
| cmd.arg("run"); | ||
| cmd.arg(package).arg(node); | ||
| }); | ||
| command.wrap(ProcessGroup::leader()); | ||
| command | ||
| .spawn() | ||
| .map_err(|e| eyre::eyre!("failed to spawn ros node: {}", e)) | ||
| } | ||
|
|
||
| fn build_package(package: &str, features: &[&str]) -> eyre::Result<()> { | ||
| let cargo = std::env::var("CARGO").unwrap(); | ||
| let mut cmd = std::process::Command::new(&cargo); | ||
| cmd.arg("build"); | ||
| cmd.arg("--package").arg(package); | ||
| if !features.is_empty() { | ||
| cmd.arg("--features").arg(features.join(",")); | ||
| } | ||
| if !cmd.status()?.success() { | ||
| bail!("failed to compile {package}"); | ||
| }; | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn build_cxx_node(root: &Path, paths: &[&Path], out_name: &str, args: &[&str]) -> eyre::Result<()> { | ||
| let mut clang = std::process::Command::new("clang++"); | ||
| clang.args(paths); | ||
| clang.arg("-std=c++17"); | ||
| #[cfg(target_os = "linux")] | ||
| { | ||
| clang.arg("-l").arg("m"); | ||
| clang.arg("-l").arg("rt"); | ||
| clang.arg("-l").arg("dl"); | ||
| clang.arg("-l").arg("z"); | ||
| clang.arg("-pthread"); | ||
| } | ||
| #[cfg(target_os = "windows")] | ||
| { | ||
| clang.arg("-ladvapi32"); | ||
| clang.arg("-luserenv"); | ||
| clang.arg("-lkernel32"); | ||
| clang.arg("-lws2_32"); | ||
| clang.arg("-lbcrypt"); | ||
| clang.arg("-lncrypt"); | ||
| clang.arg("-lschannel"); | ||
| clang.arg("-lntdll"); | ||
| clang.arg("-liphlpapi"); | ||
|
|
||
| clang.arg("-lcfgmgr32"); | ||
| clang.arg("-lcredui"); | ||
| clang.arg("-lcrypt32"); | ||
| clang.arg("-lcryptnet"); | ||
| clang.arg("-lfwpuclnt"); | ||
| clang.arg("-lgdi32"); | ||
| clang.arg("-lmsimg32"); | ||
| clang.arg("-lmswsock"); | ||
| clang.arg("-lole32"); | ||
| clang.arg("-lopengl32"); | ||
| clang.arg("-lsecur32"); | ||
| clang.arg("-lshell32"); | ||
| clang.arg("-lsynchronization"); | ||
| clang.arg("-luser32"); | ||
| clang.arg("-lwinspool"); | ||
|
|
||
| clang.arg("-Wl,-nodefaultlib:libcmt"); | ||
| clang.arg("-D_DLL"); | ||
| clang.arg("-lmsvcrt"); | ||
| } | ||
| #[cfg(target_os = "macos")] | ||
| { | ||
| clang.arg("-framework").arg("CoreServices"); | ||
| clang.arg("-framework").arg("Security"); | ||
| clang.arg("-l").arg("System"); | ||
| clang.arg("-l").arg("resolv"); | ||
| clang.arg("-l").arg("pthread"); | ||
| clang.arg("-l").arg("c"); | ||
| clang.arg("-l").arg("m"); | ||
| } | ||
| clang.args(args); | ||
| clang.arg("-L").arg(root.join("target").join("debug")); | ||
| clang | ||
| .arg("--output") | ||
| .arg(Path::new("./build").join(format!("{out_name}{EXE_SUFFIX}"))); | ||
| if let Some(parent) = paths[0].parent() { | ||
| clang.current_dir(parent); | ||
| } | ||
|
|
||
| if !clang.status()?.success() { | ||
| bail!("failed to compile c++ node"); | ||
| }; | ||
| Ok(()) | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||
| # `ros2-bridge` Rust Examples | ||||||
|
|
||||||
| These examples show how to interact between ROS2 and Dora with Rust, including topic pub/sub service client/server and action client/server. | ||||||
|
||||||
| These examples show how to interact between ROS2 and Dora with Rust, including topic pub/sub service client/server and action client/server. | |
| These examples show how to interact between ROS2 and Dora with Rust, including topic pub/sub, service client/server, and action client. Action server examples are planned for future work. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The
outputskey is defined but has no entries. If this node doesn't produce any outputs, consider removing the emptyoutputs:key entirely for clarity, or add a comment explaining why it's present but empty.