Skip to content

xil: the dyno's robot driver takes a transport delay, an actuator function and an accel mode - #421

Merged
yunlishao merged 3 commits into
dev_v0.9.0from
feature/xil_driver_delay
Oct 7, 2026
Merged

yunlishao merged 3 commits into
dev_v0.9.0from
feature/xil_driver_delay

Conversation

@yunlishao-vibe

@yunlishao-vibe yunlishao-vibe commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The simulated dyno's robot driver gets what the ORNL rig work needs to model a real driver (comma.ai openpilot on the hub dyno): a transport delay, a hook for pedal dynamics the application defines itself, and a second control mode that tracks an acceleration command instead of a speed.

RobotDriver(delay_s=...): transport delay on the pedal

  • The pedal the car gets is the robot's command from delay_s ago; (0, 0) until there is one.
  • Held per step, so it resolves to whole steps of the caller's dt.
  • driver.pedal stays the command just made; only the returned (throttle, brake) is delayed.
  • 0.0 is off and the default. A default bench is bit-identical to before.

RobotDriver(actuator=fn): the application's own pedal dynamics

  • fn(throttle, brake, dt) -> (throttle, brake), called after the delay, with the last word on what reaches the car. FIXS does not clamp what it returns.
  • If fn has a reset(), RobotDriver.reset() calls it.
  • Both options pass straight through fixs.xil.dynosim(driver={'delay_s': ..., 'actuator': fn}), so fixs/xil.py is unchanged.

RobotDriver(mode='accel'): track the acceleration command

  • mode='speed' (default) is the existing PI on speed error, bit-identical to before.
  • mode='accel': PI on a_ref - a_meas, no feedforward; v_ref is ignored. a_meas is dv/dt through a first-order filter (accel_filter_s, 0.1 s).
  • Default gains kp_accel 0.05, ki_accel 0.5. With an aref inferred from speed these are about 3x the speed-mode loop (see below); kp_accel 0.0425, ki_accel 0.025 match speed mode.
  • max_accel_mps2 / max_decel_mps2 clamp a_ref directly.
  • The openpilot hold is unchanged. Standstill in accel mode: a_ref <= 0 at v <= standstill_ref_mps holds the brake.
  • a_ref is required in accel mode.

What a delay does to the default PI

Dyno alone, dt 0.05, step 0 -> 15 -> 5 m/s, default gains, envelope 1.8 m/s^2:

delay peak toward 15 min after drop to 5 30 s after each step
0.0 s 15.98 4.08 15.000 / 5.000
0.3 s 16.53 3.56 15.000 / 5.000
0.5 s 16.91 3.48 15.310 / 5.269
0.8 s 18.59 1.54 14.255 / 4.649

End to end, MLK eco-driving rung 6 (Transport: udp, startInGear, openpilot hold 0.3 / 0.1), first 200 s of ego time:

0 s 0.5 s 0.7 s
launches (stays above 0.3 m/s) 6.3 s 14.7 s never
full stops 8 117 112
time cruising above 2 m/s 130 s 101 s 0 s
distance 810 m 570 m 22 m

Same runs with the robot in accel mode (the server still infers aref = (vref - vdyno) / 0.1):

speed 0 s accel 0 s speed 0.5 s accel 0.5 s speed 0.7 s accel 0.7 s
what the ego does drives drives; stop-go at one red light late launch, then drives limit cycle 0-11 m/s limit cycle 0-0.6 m/s limit cycle 0-9 m/s
launches (above 0.3 m/s for 2 s) 6.5 s 3.9 s 15.8 s 51.4 s never 15.4 s
full stops 8 130 117 32 112 43
time stopped 7.9 s 36.8 s 54.7 s 42.5 s 114 s 32.5 s
time cruising above 2 m/s 130 s 123 s 101 s 116 s 0 s 138 s
median dyno vs advisory while cruising 0.029 0.005 0.313 0.712 - 0.692 m/s
distance 810 m 584 m 570 m 668 m 22 m 890 m

Accel mode at 0.5 and 0.7 s does not drive: the speed saws between 0 and 9-11 m/s, about 4 m/s either side of the advisory. Its launch time, cruise time and distance count that oscillation. One run per cell; after the first light the runs meet different signal phases, so later numbers compare trajectories that have diverged. Of accel mode's 130 stops at 0 s, 121 are in 160-200 s, creeping at a red light 1.6 m from the line: a 0.15 m/s creep sits inside the hold's band, whose aref < 0.1 is v > 0.14 under the inferred aref, and accel mode reaches that in a step.

At 0.7 s in speed mode the dyno limit-cycles at the 0.15 m/s creep request (0 -> 0.55 -> 0 m/s, about 1.8 s period), with or without the hold. Detuning the PI steadies the creep but lets a 15 m/s step overshoot to 20-22 m/s, so no single gain set covers both.

Why accel mode differs: the same error, with gains times 1/T

Eco-driving sends vref = v_carla + a_eco*T (T = 0.1 s) and the server infers aref = (vref - v_dyno)/T. Written in e = vref - v_dyno, accel mode is a PID on the same speed error as speed mode:

speed:  u = kp*e + ki*int(e)
accel:  u = (kp_a/T)*e + (ki_a/T)*int(e) - kp_a*a_dyno - ki_a*(v_dyno - v_dyno0)
gain on v_dyno speed mode accel, default accel, kp_a 0.0425 / ki_a 0.025
P 0.45 kp_a/T + ki_a = 1.0 0.45
I 0.25 ki_a/T = 5.0 0.25
D 0 0.05 0.04

The delay sits in L(s) = C(s) * b/s * exp(-tau*s) (b ~ 7.25 m/s^2 per unit pedal below base speed). Linearised: speed mode crosses over near 3.3 rad/s with ~67 deg of phase margin, a delay margin of ~0.35 s, which matches the dyno-alone table above; default accel mode crosses near 9 rad/s with ~37 deg, a delay margin of ~0.07 s.

Dyno alone, creep 0.15 m/s for 20 s then 10 m/s (creep range / step peak / last 20 s):

delay speed mode accel, gain-matched accel, default
0 s 0.15 / 10.80 / 10.00 0.15 / 10.56 / 10.00 0.15 / 10.08 / 10.00
0.3 s 0.15 / 11.33 / 10.00 0.15 / 10.83 / 10.00 0-0.93 / 12.20 / 8.35-12.20
0.5 s 0-0.33 / 11.47 / 9.56-10.48 0-0.33 / 11.05 / 9.77-10.25 0-1.84 / 13.20 / 7.97-13.20
0.7 s 0-0.37 / 12.48 / 9.22-10.81 0-0.35 / 11.25 / 9.64-10.39 0-5.82 / 14.20 / 7.45-14.20

So with aref inferred from speed, the mode choice is a gain choice. The modes differ in kind only if the rig receives eco-driving's own a_eco (the packet's second float is unused).

Related Issues

No tracker. Follows #416. The application side (the knob in udp_dyno_server.py) goes to ORNL-Real-Sim/FIXS_Applications on test/xil_udp_lockstep.

How to test

pytest tests/Python/unit -q

New in test_xil_dyno.py: accel mode (tracks a constant a_ref, ignores v_ref, clamps to the envelope, holds at rest, needs a_ref, unknown mode refused); the delayed output equals an undelayed driver's output from delay_s earlier; reset() empties the delay line and resets the actuator; the actuator sees the delayed command and has the last word; a 0.2 s delay still settles on 15 m/s.

Checklist

  • Code compiles/runs as expected
  • Tests pass locally (tests/Python/unit: 530 passed, 8 skipped)
  • Documentation is updated (no doc lists the robot driver's parameters)
  • Issue linked above

@yunlishao-vibe yunlishao-vibe changed the title xil: the dyno's robot driver takes a transport delay and an actuator function xil: the dyno's robot driver takes a transport delay, an actuator function and an accel mode Sep 30, 2026
@yunlishao-vibe
yunlishao-vibe marked this pull request as ready for review October 7, 2026 16:16
@yunlishao
yunlishao merged commit 17acd6d into dev_v0.9.0 Oct 7, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants