Copper v0.3.0 (alpha 3)
Copper v0.3.0 Release
We are pleased to announce the release of Copper v0.3.0 (alpha 3), which includes several new features, enhancements, and bug fixes. Below is a summary of the key changes in this release.
Note: this is an API breaking change.
Major Changes and Features
New multi-source and optional input API #44.: The Copper engine now supports multiple and optional inputs/outputs per task to better support sensor fusion use cases
In your RON file:
tasks: [
(
id: "balpos",
type: "cu_ads7883::ADS7883",
),
(
id: "railpos",
type: "cu_rp_encoder::Encoder",
),
(
id: "pidctrl",
type: "pidtask::PIDTask",
config: {
[...]
},
),
(
id: "motor",
type: "cu_rp_sn754410::SN754410",
[...]
),
],
cnx: [
// vvvvvvvvvv same dest!
(src: "balpos", dst: "pidctrl", msg: "cu_ads7883::ADSReadingPayload"),
(src: "railpos", dst: "pidctrl", msg: "cu_rp_encoder::EncoderPayload"),
(src: "pidctrl", dst: "motor", msg: "cu_rp_sn754410::MotorPayload"),
],
)
To help you manage the types that are generated, we are giving a set of macros to help you matching the correct input / output types:
impl<'cl> CuTask<'cl> for PIDTask {
// This task takes 2 inputs!
// Note: They are given in the order of task declaration.
// The input_msg! macro build a
// (&CuMsg<ADSReadingPayload>, &CuMsg<EncoderPayload>) tuple under the hood.
// it also works with 1 input and then you will get a
// straight &CuMsg<> immutable ref.
//
// For technical Rust reasons, you need to explicitely tie
// the lifetime ('cl means copperlist if you are curious:
// the internal structure of copper for messages)
type Input = input_msg!('cl, ADSReadingPayload, EncoderPayload);
// same thing but as an output this is a &mut CuMsg<MotorPayload>
type Output = output_msg!('cl, MotorPayload);
fn process(
&mut self,
clock: &RobotClock,
// here this is now straight the input type, it is a little simpler.
input: Self::Input,
output: Self::Output,
) -> CuResult<()> {
// You can unpack the tuple directly those are resp. &CuMsg<ADSReadingPayload> and &CuMsg<EncoderPayload>
let (bal_pos, rail_pos) = input;
// The messages are now optional depending
// on the context they could be expected or really optional.
let bal_tov = bal_pos.metadata.tov.expect("we should have had a message here!");
// we have a new method called set_payload for the output
// If you don't do that it will send away a message with a None payload
output.set_payload(MotorPayload { power: 0.0 });
The monitoring component is really similar to a task, but with specialized callbacks:
// This is in the RON file, just add a monitor entry like this:
tasks: [
(
id: "task0",
type: "tasks::ExampleSrc",
),
[...]
],
cnx: [
(src: "task0", dst: "task1", msg: "i32"),
[...]
],
monitor: (type: "ExampleMonitor") // here, add a config entry if necessary
)
struct ExampleMonitor {
// We give you the task ordinal to task id mapping
// (so it is stable as long as you don't change your task ids).
tasks: &'static [&'static str],
}
impl CuMonitor for ExampleMonitor {
// We pass you the config you gave in the
// RON file exactly like for the tasks.
fn new(_config: Option<&ComponentConfig>, taskids: &'static [&str]) -> CuResult<Self> {
Ok(ExampleMonitor { tasks: taskids })
}
fn start(&mut self, clock: &_RobotClock) -> CuResult<()> {
// callbacked when all the tasks, start called.
}
fn process_copperlist(&self, msgs: &[&CuMsgMetadata]) -> CuResult<()> {
// This is callbacked at the end of the processing
// of a copper list (basically near when the CL
// is getting serialized to disk after a success.
// The metadata gives you all the timings you need
// to check if your robot is still behaving nominally.
for t in msgs.iter().enumerate() {
let (taskid, metadata) = t;
debug!("Task: {} -> {}", taskid, metadata);
}
Ok(())
}
fn process_error(&self, taskid: usize, step: CuTaskState, error: &CuError) -> Decision {
// This is called back if any task reports an error
// at any step (start, process, ...)
// You can then match that taskid and compute a decision
// for your robot: Abort, Ignore, Shutdown (see the
// cu28/monitoring.rs file for semantic details.
Decision::Ignore
}
fn stop(&mut self, clock: &_RobotClock) -> CuResult<()> {
// call when the stack is stopping
Ok(())
}
}
Enhancements
Virtual Output for Sinks (#53):
before that there was no mean to monitor sinks (or hacks you might have seen on the incoming message). Now the stack behind the scene generates a () empty message for each sink you you get the perf number cleanly for them even if they don't output anything.
Balance Bot Demo (#46):
a more complete example of a real robot demo we will bring at conferences!
Notable Fixes
Serialization / Deserialization Bug on Value (#42). A code reformat shuffled the serialization IDs.
The full release notes are published here.
To follow along with our development plan, you can find the things we are working on in the readme.
As usual, if you have a question or need some help to implement Copper on your robot, come chat with us on discord