Skip to content

Commit

Permalink
Re-write readme for re-org and rename examples
Browse files Browse the repository at this point in the history
  • Loading branch information
carter committed Jan 6, 2025
1 parent 20dc166 commit d17c2a6
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 48 deletions.
119 changes: 71 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,97 @@
# RosLibRust
[![Noetic](https://github.com/Carter12s/roslibrust/actions/workflows/noetic.yml/badge.svg)](https://github.com/Carter12s/roslibrust/actions/workflows/noetic.yml)
[![Galactic](https://github.com/Carter12s/roslibrust/actions/workflows/galactic.yml/badge.svg)](https://github.com/Carter12s/roslibrust/actions/workflows/galactic.yml)
[![Humble](https://github.com/Carter12s/roslibrust/actions/workflows/humble.yml/badge.svg)](https://github.com/Carter12s/roslibrust/actions/workflows/humble.yml)
[![Iron](https://github.com/Carter12s/roslibrust/actions/workflows/iron.yml/badge.svg)](https://github.com/Carter12s/roslibrust/actions/workflows/iron.yml)
[![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

This package aims to provide a convenient "async first" library for interacting with ROS.
[![Noetic](https://github.com/roslibrust/roslibrust/actions/workflows/noetic.yml/badge.svg)](https://github.com/roslibrust/roslibrust/actions/workflows/noetic.yml)
[![Galactic](https://github.com/roslibrust/roslibrust/actions/workflows/galactic.yml/badge.svg)](https://github.com/roslibrust/roslibrust/actions/workflows/galactic.yml)
[![Humble](https://github.com/roslibrust/roslibrust/actions/workflows/humble.yml/badge.svg)](https://github.com/roslibrust/roslibrust/actions/workflows/humble.yml)
[![Iron](https://github.com/roslibrust/roslibrust/actions/workflows/iron.yml/badge.svg)](https://github.com/roslibrust/roslibrust/actions/workflows/iron.yml)
[![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Currently this packaged provides support for both ROS1 native communication (TCPROS) and rosbridge's protocol which provides support for both ROS1 and ROS2 albeit with some overhead.
This crate provides a convenient "async first" library for interacting with ROS.
This crate defines generic traits for interacting with ROS-like systems, and implementations of those traits for various backends.

Information about the rosbridge protocol can be found [here](https://github.com/RobotWebTools/rosbridge_suite).
This crate is **pure rust and requires no ROS1 or ROS2 dependencies or installation**.

Note on documentation:
All information about the crate itself (examples, documentation, tutorials, etc.) lives in the source code and can be viewed on [docs.rs](https://docs.rs/roslibrust).
This readme is for "Meta" information about developing for the crate.
This allows writing generic behaviors like:

Fully Supported via rosbridge: Noetic, Galactic, Humble, Iron.
```rust
async fn relay<T: TopicProvider>(ros: T) -> RosResult<()> {
let mut subscriber = ros.subscribe::<std_msgs::String>("in").await?;
let mut publisher = ros.advertise::<std_msgs::String>("out").await?;
while let Some(msg) = subscriber.next().await {
println!("Got message: {}", msg.data);
publisher.publish(&msg).await?;
}
Ok(())
}
```

Fully Supported via ROS1 native: Noetic
That can then be used with any backend:

## Code Generation of ROS Messages
```rust
#[tokio::main]
async fn main() -> RosResult<()> {
// Relay messages over a rosbridge connection with either ROS1 or ROS2!
let ros = roslibrust::rosbridge::ClientHandle::new("ws://localhost:9090").await?;
relay(ros).await?;

// Relay messages over a native ROS1 connection
let ros = roslibrust::ros1::NodeHandle::new("http://localhost:11311", "relay").await?;
relay(ros).await?;

// Relay messages over a mock ROS connection for testing
let ros = roslibrust::mock::MockRos::new();
relay(ros).await?;

// Relay messages over a zenoh connection compatible with zenoh-ros1-plugin / zenoh-ros1-bridge
let ros = roslibrust::zenoh::ZenohClient::new(zenoh::open(zenoh::Config::default()).await?);
relay(ros).await?;

// TODO - not supported yet!
// Relay messages over a native ROS2 connection
// let ros = roslibrust::ros2::NodeHandle::new("http://localhost:11311", "relay").await?;
// relay(ros).await?;

Ok(())
}
```

The crates `roslibrust_codegen` and `roslibrust_codegen_macro` support code generation in Rust for ROS1 and ROS2 message, service, and action files. Many of the examples use the macro for convenience. `find_and_generate_ros_messages` is a macro which accepts an optional list of paths relative to the project workspace directory and will additionally check the `ROS_PACKAGE_PATH` environment variable for paths to ROS packages.
All of this is backed by common traits for ROS messages, topics, and services. `roslibrust_codegen` provides generation of Rust types from both ROS1 and ROS2 .msg/.srv files and
`roslibrust_codegen_macro` provides a convenient macro for generating these types:

It's used like this:
```rust
roslibrust_codegen_macro::find_and_generate_ros_messages!("assets/ros1_common_interfaces/std_msgs");
// Will generate types from all packages in ROS_PACKAGE_PATH
roslibrust_codegen_macro::find_and_generate_ros_messages!();
```

Code generation can also be done with a build.rs script using the same code generation backend called by the macro. See the contents of `example_package` for a detailed example of how this can be done. While the proc_macros are extremely convenient for getting started
there is currently no (good) way for a proc_macro to inform the compiler that it needs to be re-generated when an external file
changes. Using a build script requires more setup, but can correctly handling re-building when message files are edited.
If you want to see what the generated code looks like check [here](https://github.com/RosLibRust/roslibrust/blob/master/roslibrust_test/src/ros1.rs).
While the macro is useful for getting started, we recommend using `roslibrust_codegen` with a `build.rs` as shown in [example_package](https://github.com/RosLibRust/roslibrust/tree/master/example_package).
This allows cargo to know when message files are edited and automatically re-generate the code.

Generated message types are compatible with both the ROS1 native and RosBridge backends.
## Getting Started / Examples

## Roadmap
Examples can be found in [examples](https://github.com/RosLibRust/roslibrust/tree/master/roslibrust/examples).
We recommend looking at the examples prefixed with `generic_` first, these examples show the recommended style of using `roslibrust` through the generic traits.
Code written this way can be used with any backend, and critically can be tested with the mock backend.

| Feature | rosbridge | ROS1 | ROS2 |
|------------------------------|-------------------------------------------------------------|------|------|
| examples ||| x |
| message_gen ||||
| advertise / publish ||| x |
| unadvertise ||| x |
| subscribe ||| x |
| unsubscribe ||| x |
| services ||| x |
| actions | (codgen of message types only) |
| rosapi || x | x |
| TLS / wss:// | Should be working, untested | N/A | N/A |
Examples prefixed with `ros1_`, `rosbridge_`, and `zenoh_` show direct use of specific backends if you are only interested in a single backend.
Some backends may provide additional functionality not available through the generic traits.

Upcoming features in rough order:
To get started with writing a node with `roslibrust` we recommend looking at [example_package](https://github.com/RosLibRust/roslibrust/tree/master/example_package) and setting up your
`Cargo.toml` and `build.rs` in a similar way.
Some important tips to keep in mind with using the crate:

- Ability to write generic clients via ROS trait
- In memory backend that can be used for testing
- Support for parameter server
* This crate is built around the [tokio runtime](https://docs.rs/tokio/latest/tokio/) and requires tokio to work. All backends expect to be created inside a tokio runtime.
* The generic traits `TopicProvider` and `ServiceProvider` are not [object safe](https://doc.rust-lang.org/reference/items/traits.html#object-safety) due to their generic parameters. This means you cannot use them as trait objects with `Box<dyn TopicProvider>` or `Box<dyn ServiceProvider>`. Instead, they should be used as compile time generics like `fn foo(ros: impl TopicProvider)` or `struct MyNode<T: TopicProvider> { ros: T }`.
* By default the roslibrust crate does not include any backends. You must enable the specific backends you want to use with features in `Cargo.toml` like `roslibrust = { version = "0.12", features = ["ros1"] }`.

## Contributing

Contribution through reporting of issues encountered and implementation in PRs is welcome! Before landing a large PR with lots of code implemented, please open an issue if there isn't a relevant one already available and chat with a maintainer to make sure the design fits well with all supported platforms and any in-progress implementation efforts.

### Minimum Supported Rust Version / MSRV

We don't have an official MSRV yet.
We uphold the rust lang [Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct).

Due to cargo 1.72 enabling "doctest-in-workspace" by default it is recommended to use Rust 1.72+ for development.
Previous rust versions are support but will require some incantations when executing doctests.

The experimental topic_provider feature currently relies on `async fn` in traits from Rust 1.75.
When that feature standardizes that will likely become our MSRV.
### Minimum Supported Rust Version / MSRV

### Running Tests
MSRV is currently set to 1.75 to enable `async fn` in traits.

There are various unit tests and integration tests behind feature flags. For tests with ROS1, both through rosbridge and native clients, you'll need a locally running `rosbridge_websocket` node and `rosmaster`. Then run with `cargo test --features "ros1_test ros1"`. For tests with ROS2, you'll need a running rosbridge server, then run with `cargo test --features "ros2_test"`. You can find relevant `Dockerfile`s and docker compose configurations udner the `docker` directory.
We are likely to increase the MSRV to 1.83 when support for `async closures` lands.
3 changes: 3 additions & 0 deletions roslibrust/examples/generic_client_services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ async fn main() {
}

// Basic example of a node that publishes and subscribes to itself
// This node will work with any backend
impl<T: ServiceProvider> MyNode<T> {
fn handle_service(
_request: std_srvs::SetBoolRequest,
Expand Down Expand Up @@ -63,13 +64,15 @@ async fn main() {
}
}

// Use our generic node with the rosbridge backend
// create a rosbridge handle and start node
let ros = roslibrust::rosbridge::ClientHandle::new("ws://localhost:9090")
.await
.unwrap();
let node = MyNode { ros };
tokio::spawn(async move { node.run().await });

// Use our generic node with the ros1 backend
// create a ros1 handle and start node
let ros = roslibrust::ros1::NodeHandle::new("http://localhost:11311", "/my_node")
.await
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit d17c2a6

Please sign in to comment.