Skip to content

Commit

Permalink
update documentation to include getting started and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
N8BWert committed Sep 19, 2024
1 parent 7a5b30e commit d25fd1b
Show file tree
Hide file tree
Showing 15 changed files with 253 additions and 33 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,45 @@ The shutdown state is used to clean up any necessary work that was started durin

For our SensorFusionNode, the `Stop` state could involve saving the current state estimation to some log file so it can be used when the system begins again.

## Getting Started

To get started create a new rust binary crate via:
```sh
cargo new --bin
```

Then, add ncomm as a dependency by running:
```sh
cargo add ncomm
```

This will add ncomm with all standard library features enabled. Currently, this is the most complete version of NComm, but I will be adding more support to no_std and alloc environments in the future, in which case you can use NComm in those environments by instead running the following:
```sh
# for no_std
cargo add ncomm --default-features=false --features=nostd
# or for alloc (which automatically also includes no_std)
cargo add ncomm --default-features=false --features=alloc
```

## API Documentation

The API documentation for NComm can be found on [crates.io](https://crates.io/crates/ncomm) for release versions.

To view the API documentation for the current dev version of NComm, clone the repo and run:
```sh
cargo doc --no-deps
```

## Integrations

Currently NComm is has integration with the following packages:

* Rerun - NComm has integration with the Rerun data visualizer as both a publisher and node. To enable Rerun integration add the feature "rerun" to ncomm, ncomm-nodes, or ncomm-publishers-and-subscribers.

## Examples

NComm has a number of example projects to show just a small amount of what NComm can do. The documentation for these examples can be found [here](./examples/README.md), or by navigating to the [examples](./examples/) folder.

## Features

To allow NComm to have the possibility of being used on as many types of devices as possible I'm using a heavy dose of Rust features to conditionally compile parts of the crates. Specifically, each crate in the NComm collection has the following features:
Expand All @@ -77,6 +110,14 @@ In addition, I feel like the split of the project across Python and C++ is neces

Every Rust project would also be amiss if it didn't mention something about memory safety so here's that part. Rust is memory-safe and thread-safe by design (and by association so is NComm). This means that with NComm you are very unlikely to encounter NREs and other common pitfalls that significantly impede the progress of C++ development. NComm is also natively thread-safe so on multi-core systems NComm can often execute with significant performance improvements as more cores are added.

## Projects Using NComm

As NComm grows, I'd like to keep this README updated with links to any projects that use NComm both so new users can see how to use NComm in real projects and because I think its cool to highlight interesting projects.

The following projects use NComm:

* RoboJackets [robocup-base-station](https://github.com/RoboJackets/robocup-base-station) (NComm 0.4.1) - The RoboJackets robocup-base-station runs on a Raspberry Pi and translates commands to and from a team of autonomous soccer playing robots.

## Future

Currently, NComm is only available on Rust with the `std` toolchain. This is fine, but I would love to have `no_std` versions of the communication primitives for embedded development. However, I don't think it is likely I will make `no_std` executors and instead work on adding communication primitive support to current RTOS's like RTIC and Embassy (both of which much better than I could likely create on my own). In general, I would encourage people to add anything they think is missing to this project. I can already see a possibility for building secure network communication primitives and creating standard nodes for data visualization and logging so I encourage people to build whatever they see fit.
17 changes: 17 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# NComm Examples

### Minimal Publisher Subscriber [`examples/minimal-publisher-subscriber](./minimal-publisher-subscriber/README.md)

The minimal publisher subscriber example shows how the ROS 2 [Writing a simple publisher and subscriber](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html) tutorial would be completed in NComm. In short, the principle of the example is that there are two Nodes, one that publishes the string "Hello, World! {num}", where num is an incrementing integer that contains the number of times the publisher has published. The client receives this string and prints the string "I header: {message}", where message is the string the publisher published.

### Minimal Client Server [`examples/minimal-client-server`](./minimal-client-server/README.md)

The minimal client server example shows how the ROS 2 [Writing a simple service and client](https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Service-And-Client.html) tutorial would be complete in NComm. In short, the principle of the example is there are two Nodes, one that sends a request for the sum of two integers and one that sends the sum of the two addends back.

### Minimal Update Client Server [`examples/minimal-update-client-server](./minimal-update-client-server/README.md)

The minimal update client server example shows how the ROS 2 [Writing an action server and client](https://docs.ros.org/en/humble/Tutorials/Intermediate/Writing-an-Action-Server-Client/Cpp.html) tutorial would be completed in NComm. In short, the principle of the example is that a client asks for the nth term in the Fibonacci sequence and the update server slowly increments from the 0th term to the nth term in the Fibonacci sequence sending the current and previous term in the sequence until it reaches the nth term when it sends the nth term in the sequence back to the client.

### Rerun Publisher [`examples/rerun-publisher](./rerun-publisher/README.md")

The rerun publisher example shows a contrived example of how you can connect to the Rerun with NComm. In the example, a node publishes data sampled from a normal distribution to an instance of Rerun instantiated by creating a Rerun node.
6 changes: 3 additions & 3 deletions examples/minimal-client-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ authors.workspace = true
categories.workspace = true

[dependencies]
ncomm-core = { workspace = true }
ncomm-executors = { workspace = true }
ncomm-clients-and-servers = { workspace = true }
ncomm-core = { workspace = true, features=["std"] }
ncomm-executors = { workspace = true, features=["std"] }
ncomm-clients-and-servers = { workspace = true, features=["std"] }
crossbeam = { workspace = true }
ctrlc = { workspace = true }
rand = { workspace = true }
34 changes: 34 additions & 0 deletions examples/minimal-client-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Minimal Client Server

## Description

In this example, you spin up two nodes in a single-threaded executor. One of the nodes is a simple client who asks the server for the sum of two numbers (a and b). The server node then responds with the sum of the two numbers.

## Running

To run this example ensure you are in the example's base directory and run:
```sh
cargo run
```

## Breakdown

### [NodeIdentifier](./src/main.rs)

In NComm, all nodes need some sort of identifier so they can be added and removed from the Executor. The only requirement for this identifier is that the identifier must implement `PartialEq`. In this example, I used an enum, but you can just as easily use any other equatable type.

### [AddTwoIntsRequest and AddTwoIntsResponse](./src/main.rs)

In NComm, there is no need to use message files because everything is Rust. Instead, you can define your own types (as well as methods) as message data. In this example, The `AddTwoIntsRequest` is a struct consisting of two fields `a` and `b` which are both `u64`s. The response type, `AddTwoIntsResponse` consists of a singular field `sum` which is a `u64`.

### [Minimal Client](./src/minimal_client.rs)

The `MinimalClient` has a `LocalClient` that sends `AddTwoIntsRequest`s as requests and receives `AddTwoIntsResponses` as responses. The `MinimalClient` also has an update_delay of 500,000us which means that its update function is called every 500,000us. Every `update`, the `MinimalClient` sends a request containing two random `u64`s and checks for incoming responses. If there are responses, the `MinimalClient` prints "{a} + {b} = {sum}", where a and b are the requested addends and sum is their sum.

### [Minimal Server](./src/minimal_server.rs)

The `MinimalServer` has a `LocalServer` that receives `AddTwoIntsRequest`s and responds with `AddTwoIntsResponses`. The `MinimalServer` also has an update delay of 500,000us which means that its update function is called every 500,000 microseconds. Every `update`, the server checks for incoming requests and responds with the sum of the addends.

### [Simple Executor](./src/main.rs)

In the main function, we create a `SimpleExecutor` to execute the Nodes. The simple executor is a single threaded executor that maintains a queue of nodes to execute and runs their update function every update delay. For more information on executors, see the `ncomm-executors` documentation.
6 changes: 3 additions & 3 deletions examples/minimal-publisher-subscriber/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ authors.workspace = true
categories.workspace = true

[dependencies]
ncomm-core = { workspace = true }
ncomm-executors = { workspace = true }
ncomm-publishers-and-subscribers = { workspace = true }
ncomm-core = { workspace = true, features = ["std"] }
ncomm-executors = { workspace = true, features = ["std"] }
ncomm-publishers-and-subscribers = { workspace = true, features = ["std"] }
crossbeam = { workspace = true }
ctrlc = { workspace = true }
30 changes: 30 additions & 0 deletions examples/minimal-publisher-subscriber/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Minimal Publisher Subscriber

## Description

In this example you spin up two nodes in a single-threaded executor. One of the nodes is a simple publisher who publishes the message "Hello, World! {count}" to a subscriber who prints the message to the console.

## Running

To run this example ensure you are in the example's base directory and run:
```sh
cargo run
```

## Breakdown

### [NodeIdentifier](./src/main.rs)

In NComm, all nodes need some sort of identifier so they can be found inside of the Executor. The only requirement for this identifier is that is must implement PartialEq so Node's with a given identifier can be found inside of an executor.

### [Minimal Publisher](./src/minimal_publisher.rs)

The `MinimalPublisher` has a `LocalPublisher` that can be used to send strings. Every 500,000 microseconds, the `MinimalPublisher` sends the string "Hello, World! {count}", where count is the number of times the publisher has published the string "Hello, World!" to the subscriber.

### [Minimal Subscriber](./src/minimal_subscriber.rs)

The `MinimalSubscriber` has a `LocalSubscriber` that subscribes to receive strings. Every 500,000 microseconds, the `MinimalSubscriber` checks the currently published string message from the publisher and prints "I heard: {message}", where message is the "Hello, World! {count}" value from the publisher.

### [Simple Executor](./src/main.rs)

In the main file, a SimpleExecutor is created an populated with an owned heap pointer to the nodes. This executor will then execute the `update` methods on the nodes at their update rate (500,000 microseconds). For more information on executors, see the `ncomm-executors` documentation.
6 changes: 3 additions & 3 deletions examples/minimal-update-client-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ authors.workspace = true
categories.workspace = true

[dependencies]
ncomm-core = { workspace = true }
ncomm-executors = { workspace = true }
ncomm-update-clients-and-servers = { workspace = true }
ncomm-core = { workspace = true, features = ["std"] }
ncomm-executors = { workspace = true, features = ["std"] }
ncomm-update-clients-and-servers = { workspace = true, features = ["std"] }
crossbeam = { workspace = true }
ctrlc = { workspace = true }
34 changes: 34 additions & 0 deletions examples/minimal-update-client-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Minimal Update Client Server

## Description

In this example, you spin up two nodes, the `FibonacciUpdateClient` node requests the 10th number in the fibonacci sequence from the `FibonacciUpdateServer` node.

## Running

To run this example ensure you are in the example's base directory and run:
```sh
cargo run
```

## Breakdown

### [NodeIdentifier](./src/main.rs)

In NComm, all nodes need some sort of identifier so they can be found inside of the Executor. The only requirement for this identifier is that is must implement PartialEq so Node's with a given identifier can be found inside of an executor.

### [FibonacciRequest, FibonacciUpdate, and FibonacciResponse](./src/main.rs)

The update client sends a FibonacciRequest asking for the nth term in the Fibonacci sequence. Then, as the update server is calculating the nth term, the update server will update the update client with a `FibonaciUpdate`, which consists of the current term in the sequence as well as the previous number in the sequence. Finally, once the update server has reached the nth term in the Fibonacci sequence, the update server sends a `FibonacciResponse` containing the nth term in the sequence

### [FibonacciUpdateClient](./src/fibonacci_update_client.rs)

The `FibonacciUpdateClient` has a `LocalUpdateClient` that sends `FibonacciRequest`s and receives `FibonacciUpdate`s as updates and finally a `FibonacciResponse` as a response. Every 100,000 microseconds, the update client checks for any incoming updates from the update server and prints "Last Num: {last_num} --- Current Num: {current_num}", where last_num is the last number in the Fibonacci sequence and current_num is the current number in the Fibonacci sequence. Then, the update client checks if there are incoming responses. If there is an incoming response, the update client prints "f(10) = {nth_term}", where nth_term is the nth term in the Fibonacci sequence before sending another request for the 10th term in the Fibonacci sequence.

### [FibonacciUpdateServer](./src/fibonacci_update_server.rs)

The `FibonacciUpdateServer` has a `LocalUpdateServer` that receives `FibonacciRequest`s and sends `FibonacciUpdate`s as updates before sending a `FibonacciResponse` as the final response. Every 100,000 microseconds, the update client checks for incoming requests, setting any incoming requests as the current request to handle. Then, it increments its current term in the Fibonacci sequence before sending an update the the client with the current and last number in the Fibonacci sequence. Finally, if the update server has the 10th term in the Fibonacci sequence, the update server sends a response containing the 10th term in the Fibonacci sequence to the update client.

### [ThreadPoolExecutor](./src/main.rs)

In the main method, we create a `ThreadPoolExecutor` to execute the Nodes. In all reality there is no need to use a `ThreadPoolExecutor` in this context as there are only two nodes so one thread can probably schedule the Nodes just fine. However, to demonstrate how to use another one of the executors provided in the `ncomm-executors` crate, I figured it would make sense to use a ThreadPoolExecutor with 3 threads in the ThreadPool. For more information on executors, see the `ncomm-executors` documentation.
5 changes: 3 additions & 2 deletions examples/minimal-update-client-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#![deny(missing_docs)]

use ncomm_core::Executor;
use ncomm_executors::SimpleExecutor;
use ncomm_executors::ThreadPoolExecutor;

use crossbeam::channel::unbounded;

Expand Down Expand Up @@ -63,7 +63,8 @@ fn main() {
ctrlc::set_handler(move || tx.send(true).expect("Unable to send data"))
.expect("Error setting Ctrl-C handler");

let mut executor = SimpleExecutor::new_with(
let mut executor = ThreadPoolExecutor::new_with(
3,
rx,
vec![Box::new(update_client_node), Box::new(update_server_node)],
);
Expand Down
8 changes: 4 additions & 4 deletions examples/rerun-publisher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ categories.workspace = true
[dependencies]
crossbeam = { workspace = true }
ctrlc = { workspace = true }
ncomm-core = { workspace = true }
ncomm-executors = { workspace = true }
ncomm-nodes = { workspace = true, features = ["rerun"] }
ncomm-publishers-and-subscribers = { workspace = true }
ncomm-core = { workspace = true, features = ["std"] }
ncomm-executors = { workspace = true, features = ["std"] }
ncomm-nodes = { workspace = true, features = ["std", "rerun"] }
ncomm-publishers-and-subscribers = { workspace = true, features = ["std", "rerun"] }
rand = { workspace = true }
rand_distr = { workspace = true }
rerun = { workspace = true }
39 changes: 39 additions & 0 deletions examples/rerun-publisher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Rerun Publisher

## Description

In this example, one Node is spun up that publishes data randomly selected from a normal distribution with a mean of 1 and standard deviation of 1 to a spawned instance of the Rerun data visualizer. Although this is arguably a very contrived example of using Rerun with NComm, I would argue that it gives the user a good idea of how to use Rerun with NComm.

## Running

To run this example ensure you are in the example's base directory and run:
```sh
# If you don't already have the rerun-cli installed
cargo install rerun-cli
cargo run

# If you already have the rerun-cli installed
cargo run
```

## Breakdown

### [NodeIdentifier](./src/main.rs)

In NComm, all nodes need some sort of identifier so they can be added and removed from the Executor. The only requirement for this identifier is that the identifier must implement `PartialEq`. In this example, I used an enum, but you can just as easily use any other equatable type.

### [Data Collection Node](./src/data_collection_node.rs)

The data collection node has an OS Random number generator, a Normal distribution, and a RerunPublisher that publishes Scalars to a string path. The node's update loop is run every 10,000 microseconds and involves the node selecting a piece of data from the normal distribution and publishing that data to the running Rerun instance.

### [Rerun Node](./src/main.rs)

The Rerun node is more or less a convenience Node. All it does is during the `start` state it connects to or spawns a rerun instance and saves the data from the current Rerun session to an optional output_path. It also makes it easy to create publishers for the Rerun session it is managing by offering a convenience method to create rerun publishers.

### [Simple Executor](./src/main.rs)

In the main method, we create a `SimpleExecutor` to execute the Nodes. In reality, the `RerunNode` doesn't do anything during the `update` state so the simple executor is only calling `update` on the `DataCollectionNode` so a `SimpleExecutor` is more than enough for the situation.

## Note

This example only opens Rerun the first time it is run, so if you don't see any data logged to Rerun the first time you run the example, run it again and then you'll see the data populate the Rerun viewer.
1 change: 0 additions & 1 deletion examples/rerun-publisher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ fn main() {
println!("Creating Rerun Node");
let mut rerun_node = RerunNode::new_rerun_spawn(
"ncomm-example-project",
1_000_000,
Some("ncomm-example.rrd"),
NodeIdentifier::RerunNode,
)
Expand Down
Loading

0 comments on commit d25fd1b

Please sign in to comment.