Crates.io | cu29 |
lib.rs | cu29 |
version | |
source | src |
created_at | 2024-07-29 18:48:29.078119 |
updated_at | 2024-12-05 22:12:47.239746 |
description | Copper Runtime Core crate. Copper is an engine for robotics. |
homepage | https://github.com/copper-project |
repository | https://github.com/copper-project/copper-rs |
max_upload_size | |
id | 1319114 |
Cargo.toml error: | TOML parse error at line 18, column 1 | 18 | autolib = false | ^^^^^^^ unknown field `autolib`, expected one of `name`, `version`, `edition`, `authors`, `description`, `readme`, `license`, `repository`, `homepage`, `documentation`, `build`, `resolver`, `links`, `default-run`, `default_dash_run`, `rust-version`, `rust_dash_version`, `rust_version`, `license-file`, `license_dash_file`, `license_file`, `licenseFile`, `license_capital_file`, `forced-target`, `forced_dash_target`, `autobins`, `autotests`, `autoexamples`, `autobenches`, `publish`, `metadata`, `keywords`, `categories`, `exclude`, `include` |
size | 0 |
Copper is a user-friendly runtime engine for creating fast and reliable robots. Copper is to robots what a game engine is to games.
Easy: Copper offers a high-level configuration system and a natural Rust-first API.
Fast: Copper uses Rust's zero-cost abstractions and a data-oriented approach to achieve sub-microsecond latency on commodity hardware, avoiding heap allocation during execution.
Reliable: Copper leverages Rust's ownership, type system, and concurrency model to minimize bugs and ensure thread safety.
Product Oriented: Copper aims to avoid late-stage infra integration issues by generating a very predictable runtime.
Copper has been tested on: Linux (x86_64, armv7, aarch64 & riskv64) and MacOS (arm64). Testers would be welcomed on Windows and other platforms.
Copper is a data-oriented Robot SDK with these key components:
Task Graph: Described in RON, this configures the system's topology, specifying which tasks communicate and setting types for nodes and messages.
Runtime Generator: This component decides on an execution plan based on the graph's metadata. It preallocates a " Copper List" to maximize sequential memory access during execution.
Zero-Copy Data Logging: Records all messages between tasks without copying data, ensuring efficient logging.
Fast Structured Logging: Interns and indexes logging strings at compile time, avoiding runtime string construction and ensuring high-speed textual logging.
Here is robot developed with Copper in action driving its digital twin in a simulation environment with Bevy (Game Engine in Rust) and Avian3d (Physics Engine in Rust)
You have a mac or a linux machine (x86-64 or Arm) just run ...
$ cargo install cu-rp-balancebot
$ balancebot-sim
... to try it locally.
The source code for this demo is available in the examples/cu_rp_balancebot directory.
Category | Type | Description | Crate Name | |
---|---|---|---|---|
Sensors | Lidar | Velodyne/Ouster VLP16 | cu-vlp16 | |
Lidar | Hesai/XT32 | cu-hesai | ||
IMU | WitMotion WT901 | cu-wt901 | ||
ADC/Position | ADS 7883 3MPSPS SPI ADC | cu-ads7883 | ||
Encoder | Generic Directional Wheel encoder | cu-rp-encoder | ||
Actuators | GPIO | Raspberry Pi | cu-rp-gpio | |
Servo | Lewansoul Servo Bus (LX-16A, etc.) | cu-lewansoul | ||
DC Motor Driver | Half-H Driver for CD Motors | cu-rp-sn754410 | ||
Monitors | TUI Monitor | Console based monitor | cu-consolemon | |
Algorithms | PID Controller | PID Controller | cu-pid | |
Middleware | Shared Mem IPC | Iceoryx2 source Iceoryx2 sink |
cu-iceoryx2-src cu-iceoryx2-sink |
You can generate a project from a template present in the repo. It will ask you the name you want to pick interactively.
cargo install cargo-generate
git clone https://github.com/copper-project/copper-rs
cd copper-rs/templates
cargo cunew [path_where_you_want_your_project_created]
🤷 Project Name:
Check out copper-templates for more info.
Here is a simple example of a task graph in RON:
(
tasks: [
(
id: "src", // this is a friendly name
type: "FlippingSource", // This is a Rust struct name for this task see main below
),
(
id: "gpio", // another task, another name
type: "cu_rp_gpio::RPGpio", // This is the Rust struct name from another crate
config: { // You can attach config elements to your task
"pin": 4,
},
),
],
cnx: [
// Here we simply connect the tasks telling to the framework what type of messages we want to use.
(src: "src", dst: "gpio", msg: "cu_rp_gpio::RPGpioMsg"),
],
Then, on your main.rs:
// Your application will be a struct that will hold the runtime, loggers etc.
// This proc macro is where all the runtime generation happens. You can see the code generated by the macro at
// compile time.
#[copper_runtime(config = "copperconfig.ron")] // this is the ron config we just created.
struct MyApplication {}
// Here we define our own Copper Task
// It will be a source flipping a boolean
pub struct FlippingSource {
state: bool,
}
// We implement the CuSrcTask trait for our task as it is a source / driver (with no internal input from Copper itself).
impl<'cl> CuSrcTask<'cl> for FlippingSource {
type Output = output_msg!('cl, RPGpioPayload);
// You need to provide at least "new" out of the lifecycle methods.
// But you have other hooks in to the Lifecycle you can leverage to maximize your opportunity
// to not use resources outside of the critical execution path: for example start, stop,
// pre_process, post_process etc...
fn new(config: Option<&copper::config::ComponentConfig>) -> CuResult<Self>
where
Self: Sized,
{
// the config is passed from the RON config file as a Map.
Ok(Self { state: true })
}
// Process is called by the runtime at each cycle. It will give:
// 1. the reference to a monotonic clock
// 2. a mutable reference to the output message (so no need to allocate of copy anything)
// 3. a CuResult to handle errors
fn process(&mut self, clock: &RobotClock, output: Self::Output) -> CuResult<()> {
self.state = !self.state; // Flip our internal state and send the message in our output.
output.set_payload(RPGpioPayload {
on: self.state,
creation: Some(clock.now()).into(),
actuation: Some(clock.now()).into(),
});
Ok(())
}
}
fn main() {
// Copper uses a special log format called "unified logger" that is optimized for writing. It stores the messages between tasks
// but also the structured logs and telemetry.
// A log reader can be generated at the same time as the application to convert this format for post processing.
let logger_path = "/tmp/mylogfile.copper";
// This basic setup is a shortcut to get you running. If needed you can check out the content of it and customize it.
let copper_ctx =
basic_copper_setup(&PathBuf::from(logger_path), true).expect("Failed to setup logger.");
// This is the struct logging implementation tailored for Copper.
// It will store the string away from the application in an index format at compile time.
// and will store the parameter as an actual field.
// You can even name those: debug!("This string will not be constructed at runtime at all: my_parameter: {} <- but this will be logged as 1 byte.", my_parameter = 42);
debug!("Logger created at {}.", logger_path);
// A high precision monotonic clock is provided. It can be mocked for testing.
// Cloning the clock is cheap and gives you the exact same clock.
let clock = copper_ctx.clock;
debug!("Creating application... ");
let mut application =
MyApplication::new(clock.clone(), copper_ctx.unified_logger.clone())
.expect("Failed to create runtime.");
debug!("Running... starting clock: {}.", clock.now()); // The clock will be displayed with units etc.
application.run().expect("Failed to run application.");
debug!("End of program: {}.", clock.now());
}
But this is a very minimal example for a task, please see lifecycle for a more complete explanation of a task lifecycle.
Check out the deployment page for more information.
In the example directory, we have 2 equivalent applications. One written in C++ for ROS and a port in Rust with Copper.
examples/cu_caterpillar
examples/ros_caterpillar
You can try them out by either just logging on a desktop, or with GPIOs on a RPi. You should see a couple order of magnitude difference in performance.
Copper has been designed for performance first. Not unlike a game engine, we use a data oriented approach to minimize latency and maximize throughput.
As Copper is written in Rust, it is memory safe and thread safe by design. It is also designed to be easy to use and avoid common pitfalls.
As we progress on this project we plan on implementing more and more early warnings to help you avoid "the death by a thousand cuts" that can happen in a complex system.
You can find the release notes here.
[!NOTE] We are looking for contributors to help us build the best robotics framework possible. If you are interested, please join us on Discord or open an issue.
Here are some of the features we plan to implement next (in ~order of priority), if you are interested in contributing on any of those, please let us know!: