2 个版本
新 0.2.2 | 2024年8月12日 |
---|---|
0.2.1 | 2024年7月29日 |
#57 在 机器人 中
每月 287 次下载
用于 9 个crate(直接使用 2 个)
8KB
92 行

Copper
Copper 是一个用户友好的机器人框架,旨在创建快速可靠的机器人。Copper 对于机器人来说,就像游戏引擎对于游戏一样。
-
简单:Copper 提供了高级配置系统和自然的 Rust-first API。
-
快速:Copper 使用 Rust 的零成本抽象和数据导向方法,在通用硬件上实现亚微秒级延迟,避免执行期间的堆分配。
-
可靠:Copper 利用 Rust 的所有权、类型系统和并发模型来最小化错误并确保线程安全。
-
面向产品:Copper 旨在通过生成非常可预测的运行时来避免后期基础设施集成问题。
[!注意] Copper 目前仍处于 早期开发/alpha 阶段,API 可能会发生变化。我们正在寻找贡献者帮助我们构建最佳的机器人框架。如果您感兴趣,请加入我们 Gitter 或提交问题。
技术概述
Copper 是一个数据导向的运行时,具有以下关键组件
-
任务图:
在 RON 中描述,配置了系统的拓扑结构,指定了哪些任务进行通信以及设置节点和消息的类型。
-
运行时生成器:该组件根据图元数据决定执行计划。它预先分配一个 "Copper 列表",以最大化执行期间的顺序内存访问。
-
零拷贝数据记录:记录任务之间的所有消息而不复制数据,确保高效的记录。
-
快速结构化记录:在编译时对日志字符串进行内部化和索引,避免运行时字符串构建,确保高速文本记录。
已实现哪些功能?
- 基本任务生命周期接口:应该相对稳定,您可以开始贡献新的算法、传感器和执行器。
- 运行时生成:工作,但非常简单;这只是一个 BFS 类型的执行。
- 日志读取器 & 结构化日志读取器:可以导出数据,目前为Rust调试格式。
- 包含简单/最小化驱动程序:这些主要用于展示如何实现您自己的
类别 | 类型 | 描述 | 组件名 | |
---|---|---|---|---|
传感器 | 激光雷达 | ![]() |
Velodyne/Ouster VLP16 | cu_vlp16 |
惯性测量单元 | ![]() |
WitMotion WT901 | cu_wt901 | |
执行器 | 通用输入/输出 | ![]() |
树莓派 | cu_rp_gpio |
伺服电机 | ![]() |
乐+Sanso伺服总线(LX-16A等) | cu_lewansoul |
缺少哪些功能?我们计划接下来实现什么?
很多!如果你对这些项目中的任何一项感兴趣,并想贡献,请随时联系我们!
以下是我们计划接下来实现的一些功能(按优先级排序)
- 确定性日志回放:由于运行时是确定性生成的,我们需要添加钩子以将消息注入到现有运行时。
- 并行铜列表:今天铜是单线程的;这应该可以允许同时执行无争用的并发铜列表。
- 成组:由于各种传感器的输出频率差异很大,我们需要能够以丢弃/算法策略(平均值、最大值等)将消息成组。今天这可以在用户端实现,但很痛苦。
- 监控:我们需要一个并行系统,可以监听监控消息并相应地执行。
- 分布式铜:目前,我们只能创建一个进程。我们需要为每个子系统正确过滤RPC铜列表。
- ROS接口:构建一对接收器和发送器以连接到现有的ROS系统,帮助用户逐步迁移他们的基础设施。
- 模块化配置:随着使用铜构建的机器人的复杂性增加,用户将需要构建他们机器人的“变体”,而无需复制整个RON文件。
- “PGO”-样调度:将以前的日志运行传递给运行时生成器,以允许它做出更好的调度决策。
所以我们才刚刚开始,但即将到来很多有趣的东西!
启动铜项目,满足迫切需求
您可以从仓库中现有的模板生成项目。它会要求您以交互式方式选择名称。
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:
查看copper-templates以获取更多信息。
铜应用程序看起来像什么?
这是一个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"),
],
然后,在您的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,
}
// You need to provide at least "new". 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...
impl CuTaskLifecycle for FlippingSource {
fn new(_config: Option<&copper::config::NodeInstanceConfig>) -> CuResult<Self>
where
Self: Sized,
{
Ok(Self { state: true })
}
}
// We implement the CuSrcTask trait for our task as it is a source / driver (with no internal input from Copper itself).
impl CuSrcTask for FlippingSource {
type Output = RPGpioMsg;
// 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: &mut CuMsg<Self::Output>) -> CuResult<()> {
self.state = !self.state; // Flip our internal state and send the message in our output.
output.payload = RPGpioMsg {
on: self.state,
creation: 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());
}
但这是一个非常简化的任务示例,请参阅生命周期以获取对任务生命周期的更完整解释。
应用程序的部署
查看部署页面以获取更多信息。
它与ROS相比有何优势或不同之处?
性能
在示例目录中,我们有2个等效的应用程序。一个是用C++为ROS编写的,另一个是用Rust和铜移植的。
examples/cu_caterpillar
examples/ros_caterpillar
您可以在桌面或带有GPIO的RPi上进行测试,您应该看到性能上有几个数量级的差异。
铜的设计以性能为先。与游戏引擎不同,我们采用数据导向方法,以最大限度地减少延迟并最大化吞吐量。
安全性
由于铜是用Rust编写的,它设计上是内存安全和无线程安全的。它还设计得易于使用,以避免常见的陷阱。
随着我们在这个项目上的进展,我们计划实现更多早期警告,以帮助您避免在复杂系统中可能发生的“千刀万剐”。
依赖关系
~3–4.5MB
~115K SLoC