This guide walks you through implementing a minimal component in under 5 minutes.
The Component Protocol
Every component implements three lifecycle methods:
template <typename Scalar>
class MyComponent : public Component<Scalar> {
public:
void Provision(Backplane<Scalar>& bp, const ComponentConfig& cfg) override;
void Stage(Backplane<Scalar>& bp, const RunConfig& rc) override;
void Step(Scalar t, Scalar dt) override;
};
| Method | When | What to Do |
| Provision | Once at startup | Register outputs, load config, allocate memory |
| Stage | Each run start | Resolve input pointers, apply ICs |
| Step | Every Δt | Read inputs → compute → write outputs |
Example: A Gain Component
#include <icarus/Component.hpp>
template <typename Scalar>
class Gain : public Component<Scalar> {
Scalar gain_;
const Scalar* input_;
Scalar* output_;
public:
void Provision(Backplane<Scalar>& bp, const ComponentConfig& cfg) override {
gain_ = cfg.get<double>("gain", 1.0);
bp.register_output(name() + ".output", &output_value_);
}
void Stage(Backplane<Scalar>& bp, const RunConfig& rc) override {
input_ = bp.resolve<Scalar>(cfg_.get<std::string>("input_signal")).ptr();
}
void Step(Scalar t, Scalar dt)
override {
*output_ = gain_ * (*input_);
}
private:
Scalar output_value_;
};
@ Step
Definition Error.hpp:231
Configuration
# config/components/gain.yaml
components:
- type: Gain
name: MyGain
params:
gain: 2.5
input_signal: "Sensor.raw_value"
Key Rules
- No allocation in Step() — all memory allocated in Provision()
- No string lookups in Step() — resolve pointers in Stage()
- Use janus:: math — janus::sin(), not std::sin()
- Use janus::where() for branching — no if/else on Scalar
- Template on Scalar — enables both double and casadi::MX
Symbolic Compatibility Checklist
Scalar y = janus::where(x > 0, a, b);
Scalar z = janus::sin(angle);
if (x > 0) { y = a; } else { y = b; }
double z = std::sin(angle);
Next Steps