Duration & Frequency API

HORUS provides user-defined literals that mirror the Rust DurationExt trait: 100_hz, 10_ms, 200_us. These make scheduling configuration readable and type-safe.

#include <horus/duration.hpp>
using namespace horus::literals;

Quick Reference

LiteralTypeExampleValue
_hzhorus::Frequency100_hz100 Hz frequency
_sstd::chrono::microseconds5_s5,000,000 us
_msstd::chrono::microseconds10_ms10,000 us
_usstd::chrono::microseconds200_us200 us
_nsstd::chrono::nanoseconds500_ns500 ns

All duration literals produce std::chrono types. The _hz literal produces horus::Frequency.


Duration Type

using horus::Duration = std::chrono::microseconds;

HORUS uses microseconds as the standard duration unit internally. The _s and _ms literals convert to microseconds automatically.


Literals

Enable the literals with:

using namespace horus::literals;

Signatures

constexpr horus::Frequency           operator""_hz(unsigned long long hz);
constexpr std::chrono::microseconds  operator""_s(unsigned long long s);
constexpr std::chrono::microseconds  operator""_ms(unsigned long long ms);
constexpr std::chrono::microseconds  operator""_us(unsigned long long us);
constexpr std::chrono::nanoseconds   operator""_ns(unsigned long long ns);

Note: _ns returns std::chrono::nanoseconds; all other duration literals return std::chrono::microseconds.

Examples

auto rate = 100_hz;     // Frequency(100.0)
auto timeout = 5_s;     // 5,000,000 microseconds
auto budget = 10_ms;    // 10,000 microseconds
auto jitter = 800_us;   // 800 microseconds
auto precise = 500_ns;  // 500 nanoseconds

Frequency Class

horus::Frequency wraps a frequency value in Hz and provides methods to derive scheduling parameters.

Constructor

constexpr explicit Frequency(double hz);

Creates a frequency from a double. Prefer the _hz literal for integer frequencies.

value()

constexpr double value() const;

Returns the frequency in Hz.

auto rate = 100_hz;
double hz = rate.value();  // 100.0

period()

constexpr std::chrono::microseconds period() const;

Returns the period (time between ticks) as microseconds. Computed as 1,000,000 / hz.

auto rate = 100_hz;
auto p = rate.period();    // 10,000 us = 10 ms
FrequencyPeriod
10_hz100,000 us (100 ms)
50_hz20,000 us (20 ms)
100_hz10,000 us (10 ms)
500_hz2,000 us (2 ms)
1000_hz1,000 us (1 ms)

budget_default()

constexpr std::chrono::microseconds budget_default() const;

Returns the default compute budget: 80% of the period. This is the maximum time a node's tick() should take under normal conditions.

auto rate = 100_hz;
auto budget = rate.budget_default();  // 8,000 us = 8 ms (80% of 10 ms)

deadline_default()

constexpr std::chrono::microseconds deadline_default() const;

Returns the default deadline: 95% of the period. If tick() exceeds this, the Miss policy is triggered.

auto rate = 100_hz;
auto deadline = rate.deadline_default();  // 9,500 us = 9.5 ms (95% of 10 ms)

Derived Timing Table

FrequencyPeriodBudget (80%)Deadline (95%)
10_hz100 ms80 ms95 ms
50_hz20 ms16 ms19 ms
100_hz10 ms8 ms9.5 ms
500_hz2 ms1.6 ms1.9 ms
1000_hz1 ms0.8 ms0.95 ms

Miss Enum

Defines what happens when a node's tick() exceeds its deadline.

enum class Miss {
    Warn,      // Log a warning, continue running
    Skip,      // Skip the next tick to catch up
    SafeMode,  // Call enter_safe_state() on the node
    Stop,      // Stop the scheduler entirely
};
VariantBehaviorUse Case
Miss::WarnLogs a deadline-miss warning. Node keeps running at normal rate.Non-critical nodes: logging, visualization, telemetry
Miss::SkipSkips the next scheduled tick to recover timing. Prevents cascading overruns.Compute-heavy nodes: planning, perception
Miss::SafeModeCalls enter_safe_state() on the node, then continues.Safety-critical nodes: motor control, force limiting
Miss::StopTriggers scheduler shutdown. All nodes receive shutdown().Hard real-time systems where any overrun is catastrophic

Using with NodeBuilder

Durations and frequencies are designed for the scheduler's builder API.

rate()

Sets the node's tick frequency. The scheduler calls tick() at this rate.

using namespace horus::literals;
horus::Scheduler sched;

sched.add("motor_ctrl")
    .rate(1000_hz)
    .tick([&] { /* runs every 1 ms */ })
    .build();

budget()

Sets the maximum expected execution time for tick(). If exceeded, the watchdog records a budget overrun.

sched.add("motor_ctrl")
    .rate(1000_hz)
    .budget(800_us)   // tick() should finish within 800 us
    .tick([&] { /* motor control */ })
    .build();

deadline()

Sets the hard deadline. If exceeded, the Miss policy is triggered.

sched.add("motor_ctrl")
    .rate(1000_hz)
    .budget(800_us)
    .deadline(950_us)
    .on_miss(horus::Miss::SafeMode)
    .tick([&] { /* motor control */ })
    .build();

Full Example

A complete scheduler setup with three nodes at different rates and miss policies.

#include <horus/node.hpp>
#include <horus/duration.hpp>
using namespace horus::literals;

int main() {
    horus::Scheduler sched;
    sched.tick_rate(1000_hz);

    // Hard RT: motor control at 1 kHz, enters safe state on overrun
    sched.add("motor_ctrl")
        .rate(1000_hz)
        .budget(800_us)
        .deadline(950_us)
        .on_miss(horus::Miss::SafeMode)
        .pin_core(2)
        .priority(90)
        .order(0)
        .tick([&] { /* PID loop */ })
        .build();

    // Soft RT: state estimation at 200 Hz, skips on overrun
    sched.add("estimator")
        .rate(200_hz)
        .budget(4_ms)
        .deadline(4500_us)
        .on_miss(horus::Miss::Skip)
        .order(10)
        .tick([&] { /* EKF update */ })
        .build();

    // Best effort: logging at 10 Hz, warns on overrun
    sched.add("logger")
        .rate(10_hz)
        .budget(50_ms)
        .on_miss(horus::Miss::Warn)
        .order(100)
        .tick([&] { /* write logs */ })
        .build();

    sched.run();
}

Conversion Examples

Frequency to Duration

auto rate = 100_hz;
auto period = rate.period();              // 10,000 us
auto budget = rate.budget_default();      // 8,000 us
auto deadline = rate.deadline_default();  // 9,500 us

Duration Arithmetic

Since durations are std::chrono types, standard arithmetic works:

auto a = 500_us;
auto b = 300_us;
auto sum = a + b;        // 800 us
auto diff = a - b;       // 200 us
auto scaled = a * 2;     // 1000 us

// Compare
bool fits = (800_us < 1_ms);  // true