Docs Annotations

@RunPeriodically

Run a method on a fixed-rate loop.

@RunPeriodically

@RunPeriodically runs a Node method on a fixed-rate loop. Use it for continuous work: driving, PID control, publishing state.

@RunPeriodically(hz = 50)
public void update() { /* math, state machines — no hardware */ }

@RunPeriodically(hz = 50, hardware = true)
public void drive() { /* touches motors — runs on the hardware thread */ }

Attributes

AttributeTypeDefaultMeaning
hzint20target frequency in hertz. Must be > 0; an invalid value logs a warning and skips the loop.
hardwarebooleanfalsewhen true, the loop runs on the single dedicated hardware thread instead of the scheduler pool.

Method signature rules

  • No parameters. Pull inputs with getLatestValue inside the body.
  • Not static. The binder invokes it reflectively on the node instance.
  • The annotation is repeatable — one method may run at multiple rates (rare, but valid).

Timing semantics

Scheduling is fixed-delay: the next invocation starts 1000 / hz milliseconds after the previous one finishes.

  • A slow loop never overlaps itself.
  • The effective rate is hz or lower — a 20 ms body at hz = 50 actually runs ~25 Hz.
  • The first invocation happens immediately at registration.
  • All non-hardware loops share the 8-thread scheduler pool, so a blocked loop can delay others scheduled at the same moment; hardware loops are isolated on the hardware thread.
@RunPeriodically(hz = 20, hardware = true)
public void drive() {
    double y = -orchestrator.getLatestValue("g1/left_stick_y", Float.class)
                             .map(Float::doubleValue).orElse(0.0);
    left.run(m -> m.setPower(y));
}

Error behavior

An exception in the body is logged and the loop keeps running — a single throw does not kill the schedule.

Choose hardware = true for anything that reads or writes devices. A loop that forgets the flag runs on the scheduler pool, where every hardware call is a rule violation — and SafeDevice calls made from there enqueue onto the hardware thread with extra latency. The flag costs nothing and prevents both.

Next steps

  • Architecture — where the loop actually runs and why it can’t overlap itself.
  • @SubscribedTo — the reactive alternative.