Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ bevy_time = "0.16"
bevy_utils = "0.16"
clap = { version = "4.5.23", features = ["derive"] }
mime_guess = "2.0.5"
rclrs = { git = "https://github.com/mxgrey/ros2_rust", branch = "crossflow", features = ["serde", "schemars"] }
schemars = "0.9.0"
serde = "1.0.219"
serde_json = "1.0.140"
Expand Down Expand Up @@ -95,6 +96,10 @@ zenoh = { workspace = true, features = ["unstable"], optional = true }
zenoh-ext = { workspace = true, features = ["unstable"], optional = true }
# --- end zenoh

# --- Dependencies for ros2 feature
rclrs = { workspace = true, optional = true }
# --- end ros2

tracing = { workspace = true }
strum = { version = "0.26.3", optional = true, features = ["derive"] }
semver = { version = "1.0.24", optional = true }
Expand Down Expand Up @@ -137,6 +142,13 @@ zenoh = [
"dep:tonic-prost-build",
]

ros2 = [
"dep:rclrs",
"dep:futures-lite",
"dep:serde",
"dep:schemars",
]

# Turn on all capability related features. This differs from --all-features
# because it would not include single_threaded_async which has a diminishing
# effect on capabilities.
Expand All @@ -145,6 +157,7 @@ maximal = [
"trace",
"grpc",
"zenoh",
"ros2",
]

[dev-dependencies]
Expand All @@ -157,6 +170,7 @@ test-log = { version = "0.2.16", features = [
members = [
"examples/diagram/calculator",
"examples/zenoh-examples",
"examples/ros2",
"diagram-editor",
"diagram-editor/wasm",
"examples/diagram/calculator_wasm",
Expand All @@ -171,3 +185,6 @@ doc = false

[build-dependencies]
tonic-prost-build = { workspace = true, optional = true }

[patch.crates-io]
rclrs = { git = "https://github.com/mxgrey/ros2_rust", branch = "crossflow" }
61 changes: 58 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,64 @@
[![Crates.io Version](https://img.shields.io/crates/v/crossflow)](https://crates.io/crates/crossflow)

> [!IMPORTANT]
> For the ROS 2 integration feature, check out the [`ros2` branch](https://github.com/open-rmf/crossflow/tree/ros2).
>
> That feature is kept separate for now because it requires additional non-Rust setup. It will be merged into `main` after dynamic message introspection is finished.
> You are on a branch with experimental support for ROS 2 via [`rclrs`](https://github.com/ros2-rust/ros2_rust).
> This will require more steps to set up than usual, so installation instructions for the necessary packages are below:

1. Install ROS Jazzy according to the [normal installation instructions](https://docs.ros.org/en/jazzy/Installation.html).

2. Install Rust according to the [normal installation instructions](https://www.rust-lang.org/tools/install).

3. Run these commands to set up the workspace with the message bindings:

```bash
sudo apt install -y git libclang-dev python3-pip python3-vcstool # libclang-dev is required by bindgen
```

```bash
pip install git+https://github.com/colcon/colcon-cargo.git --break-system-packages
```

```bash
pip install git+https://github.com/colcon/colcon-ros-cargo.git --break-system-packages
```

For now we need a fork of `cargo-ament-build` until [this PR](https://github.com/ros2-rust/cargo-ament-build/pull/26) is merged and released:

```bash
cargo install --git https://github.com/mxgrey/cargo-ament-build
```

Create a workspace with the necessary repos:

```bash
mkdir -p workspace/src && cd workspace
```

```bash
git clone https://github.com/open-rmf/crossflow src/crossflow -b ros2
```

```bash
vcs import src < src/crossflow/ros2-feature.repos
```

Source the ROS distro and build the workspace:

```bash
source /opt/ros/jazzy/setup.bash
```

```bash
colcon build --allow-overriding action_msgs builtin_interfaces common_interfaces composition_interfaces example_interfaces geometry_msgs lifecycle_msgs nav_msgs rcl_interfaces rosgraph_msgs rosidl_default_generators rosidl_default_runtime sensor_msgs sensor_msgs_py service_msgs statistics_msgs std_msgs std_srvs trajectory_msgs type_description_interfaces unique_identifier_msgs visualization_msgs
```

4. After `colcon build` has finished, you should see a `.cargo/config.toml` file inside your workspace, with `[patch.crates-io.___]` sections pointing to the generated message bindings. Now you should source the workspace using

```bash
source install/setup.bash
```

Now you can run the [ROS 2 example](examples/ros2/README.md). You can also create your own crate in this colcon workspace and link as shown in the example.

# Reactive Programming for Bevy

Expand Down
6 changes: 3 additions & 3 deletions diagram-editor/server/basic_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ pub struct RunArgs {
#[arg(help = "path to the diagram to run")]
diagram: String,

#[arg(help = "json containing the request to the diagram")]
request: String,
#[arg(help = "json containing the request to the diagram. Defaults to null if not set.")]
request: Option<String>,
}

#[derive(Parser, Debug)]
Expand All @@ -72,7 +72,7 @@ pub fn headless(args: RunArgs, registry: DiagramElementRegistry) -> Result<(), B
let file = File::open(args.diagram).unwrap();
let diagram = Diagram::from_reader(file)?;

let request = serde_json::Value::from_str(&args.request)?;
let request = serde_json::Value::from_str(args.request.as_ref().map(|s| s.as_str()).unwrap_or("null"))?;
let mut promise =
app.world_mut()
.command(|cmds| -> Result<Promise<serde_json::Value>, DiagramError> {
Expand Down
38 changes: 38 additions & 0 deletions examples/ros2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[package]
name = "ros2-workflow-examples"
edition = "2021"
version = "0.0.1"

[[bin]]
name = "nav-executor"
path = "src/nav_executor.rs"

[[bin]]
name = "fake-plan-generator"
path = "src/fake_plan_generator.rs"

[[bin]]
name = "goal-requester"
path = "src/goal_requester.rs"

[dependencies]
# Dependency to help us run the diagram editor and executor
crossflow_diagram_editor = { version = "0.0.1", path = "../../diagram-editor", features = ["basic_executor"] }

# Dependencies for the nav operation catalog
crossflow = { version = "0.0.1", path = "../..", features = ["diagram", "ros2"] }
serde = { workspace = true }
serde_json = { workspace = true }
schemars = { workspace = true }

# Dependencies for our custom ROS operations
rclrs = { workspace = true }
nav_msgs = { version = "*", features = ["serde", "schemars"] }
geometry_msgs = { version = "*", features = ["serde", "schemars"] }
std_msgs = { version = "*", features = ["serde", "schemars"] }
builtin_interfaces = { version = "*", features = ["serde", "schemars"] }
example_interfaces = { version = "*", features = ["serde", "schemars"] }

# Dependencies for goal-requester and fake-plan-generator
clap = { workspace = true }
rand = "0.9.2"
70 changes: 70 additions & 0 deletions examples/ros2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
## Building workflows with ROS nodes

The examples in this directory showcase how to incorporate ROS primitives into a workflow. We model a small portion of a navigation workflow that involves receiving goals, fetching paths to connect those goals from a planning service, and then queuing up the paths for execution.

This diagram represents the workflow implemented by [`diagrams/default-nav-system.json`](diagrams/default-nav-system.json):

![nav-example-workflow](assets/figures/nav-example.png)

After following the build instructions in the [root README](../../README.md), run this example with the following steps:

1. Source the workspace with

```bash
source install/setup.bash # run in the root of your colcon workspace
```

2. Then you can go into this `examples/ros2` directory and run the example with

```bash
cargo run --bin nav-executor -- run diagrams/default-nav-system.json # run in this directory
```

3. The `nav-example` workflow will wait until it receives some goals to process. You can send it some randomized goals by **opening a new terminal** in the same directory and running

```bash
source install/setup.bash # run in the root of your colcon workspace
```

```bash
cargo run --bin goal-requester # run in this directory
```

Now the workflow will print out all the plans that it has received from the `fake-plan-generator` and stored in its buffer. If the workflow were connected to a real navigation system, it could pull these plans from the buffer one at a time and execute them.

4. Our workflow also allows the paths to be cleared out of the buffer, i.e. "cancelled". To cancel the current set of goals, run:

```bash
cargo run --bin goal-requester -- --cancel
```

After that you should see a printout of

```
Paths currently waiting to run:
[]
```

which indicates that the buffer has been successfully cleared out.

5. You can view and edit the navigation workflow by running the web-based editor:

```bash
cargo run --bin nav-executor -- serve # run in this directory
```

Open up a web browser (chrome works best) and go to [http://localhost:3000](http://localhost:3000). Use the upload button to load the default nav workflow from [`diagrams/default-nav-system.json`](diagrams/default-nav-system.json). After you've edited it, you can run it immediately using the play button (enter `null` into the input box that pops up). Or you can use the export button to download and save your changes.

6. The diagram editor does not currently have a button for cancelling a workflow. To stop a workflow that is currently running, use

```bash
ros2 topic pub end_workflow std_msgs/Empty
```

The default workflow will listen to the `/end_workflow` topic for any message, and then exit as soon as one is received. After you have triggerd the workflow to end, you should this message in the response:

```
"workflow ended by /end_workflow request"
```

7. To see how to register custom diagram operations and any ROS messages, services, or actions that you need, take a look at [`src/nav_ops_catalog.rs`](src/nav_ops_catalog.rs).
Binary file added examples/ros2/assets/figures/nav-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
136 changes: 136 additions & 0 deletions examples/ros2/diagrams/default-nav-system.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
{
"$schema": "https://raw.githubusercontent.com/open-rmf/crossflow/refs/heads/main/diagram.schema.json",
"version": "0.1.0",
"templates": {},
"start": "setup",
"ops": {
"quit_request": {
"type": "transform",
"cel": "\"workflow ended by /end_workflow request\"",
"next": {
"builtin": "terminate"
}
},
"end_workflow": {
"type": "node",
"builder": "nav_manager__std_msgs_msg_Empty__subscription",
"next": {
"builtin": "dispose"
},
"display_text": "End Workflow",
"config": {
"topic": "end_workflow"
},
"stream_out": {
"out": "quit_request"
}
},
"setup": {
"type": "fork_clone",
"next": [
"print_welcome",
"receive_cancel",
"end_workflow"
]
},
"print_welcome": {
"type": "node",
"builder": "print",
"config": {
"message": "Waiting to receive goals from 'request_goal' topic",
"include_value": false
},
"next": "receive_goals"
},
"receive_goals": {
"type": "node",
"builder": "nav_manager__nav_msgs_msg_Goals__subscription",
"config": {
"topic": "request_goal"
},
"next": "quit_error",
"display_text": "Receive Goals",
"stream_out": {
"out": "fetch_plans"
}
},
"quit_error": {
"type": "transform",
"cel": "\"error: unable to receive goals\"",
"next": {
"builtin": "terminate"
}
},
"fetch_plans": {
"type": "node",
"builder": "fetch_plans",
"config": {
"planner_service": "get_plan",
"tolerance": 0.1
},
"next": {
"builtin": "dispose"
},
"display_text": "Fetch Plans",
"stream_out": {
"paths": "path_buffer"
}
},
"path_buffer": {
"type": "buffer",
"settings": {
"retention": "keep_all"
}
},
"listen_to_path_buffer": {
"type": "listen",
"buffers": [
"path_buffer"
],
"next": "print_paths"
},
"print_paths": {
"type": "node",
"builder": "print_from_buffer",
"config": "Paths currently waiting to run:\n",
"next": {
"builtin": "dispose"
},
"display_text": "Print Paths"
},
"receive_cancel": {
"type": "node",
"builder": "nav_manager__std_msgs_msg_Empty__subscription",
"config": {
"topic": "cancel_goals"
},
"next": {
"builtin": "dispose"
},
"display_text": "Cancel Goals",
"stream_out": {
"out": "trigger_clear_paths"
}
},
"trigger_clear_paths": {
"type": "transform",
"cel": "null",
"next": "access_paths"
},
"access_paths": {
"type": "buffer_access",
"buffers": [
"path_buffer"
],
"next": "clear_paths"
},
"clear_paths": {
"type": "node",
"builder": "clear_paths",
"next": {
"builtin": "dispose"
},
"display_text": "Clear Paths"
}
}
}
Loading
Loading