xil: FIXS ships the simulated dyno; fixs.config.get; openpilot's hold; fixs.driver startInGear - #416
Merged
Merged
Conversation
…actly Transport: udp read the cell without waiting, so each tick got the answer to the PREVIOUS tick's reference: one CARLA step behind inprocess. The udp exchange now drains stale packets, sends, and blocks for this tick's reply (UDP_REPLY_TIMEOUT_S 0.1 s). A miss passes the reference through and is counted; UDP_MAX_MISSES (20) in a row stops the run with the cell's address. LocalLink now carries the same 8-byte float32 packet as the wire. With full precision the ~1e-7 rounding alone, fed back through the closed loop, grew to 2.8 m/s of ego speed within 200 s against an otherwise identical udp run. Inprocess results therefore shift slightly from before. Measured on MLK rung 5 (to 29300 s, 3999 ticks): inprocess and udp logs byte-identical, agent and bridge.
The block quoted a 0.73 s first-tick cost that the same change had already removed, and read as current. Three lines now.
fixs.config.get(section) reads the run's scenario yaml, read-only, by
lowercase names (get('xil')['transport']), with ip/port taken from the
section's first VehicleSubscription.
fixs.xil.dyno() builds the inprocess dyno and returns None for udp and
tcp: an application that speaks to a rig opens its own socket, to
fixs.config.get('xil')['ip']. tcp stays accepted as a reserved value.
The udp lockstep this branch added to _Dyno and UdpLink (drain,
recv_wait, UDP_REPLY_TIMEOUT_S, UDP_MAX_MISSES) goes with it. LocalLink
still rounds through the float32 packet.
It builds the simulated dyno the scenario describes whenever EnableXil
is on. Which dyno answers a run is the application's choice, made from
fixs.config.get('xil')['transport']; a udp application's stand-in rig
can now build the same dynosim from the same scenario.
It builds the simulated dyno from the vehicle, dyno and driver parameters
it is given. Whether a dyno is in the loop is the caller's question
(fixs.xil.enabled, fixs.config.get('xil')), so a udp application's
stand-in rig can build the same dynosim with no yaml at all.
XilSetup.Vehicle/Dyno/Driver go with it; no scenario set them.
…aref, dt) RobotDriver brakes while slower than stop_speed_mps and asked for less than stop_accel_mps2 -- openpilot's should_stop (0.3 m/s, 0.1 m/s^2). Both default to 0.0, which is off, so a default bench is unchanged. The acceleration command is explicit: dynosim().exchange(vref, aref, dt) takes it from the caller and hands it to the driver's hold. Nothing in the bench derives it.
CARLA's autobox starts a vehicle in neutral and does not leave it below about 15% throttle (measured on vehicle.tesla.model3: 10% never moves, 15% moves after 3.6 s). A pedal-driven ego asked to creep never moves. One control with manual_gear_shift=True, gear=1 right after spawn engages first; the autobox keeps it through later stops, and 2-5% throttle then moves the car. No creep at zero throttle. The C++ bridge (VirCarlaEnv) does not have this yet.
fixs.driver(exchange, firstGear=True) calls carla.ego.apply_control with manual_gear_shift=True, gear=1 on its first controlled step and commands nothing else that step, so the host does not overwrite it; the autobox keeps first from then on. Off by default. Replaces the bridge doing this for every ego (47a00f2).
…ine stays plain fixs.driver.set(**options) records options for every driver built after the call; fixs.driver(exchange, **options) still wins over them. The first-gear option is renamed startInGear (was firstGear), off by default.
yunlishao
approved these changes
Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FIXS ships the simulated dyno and nothing else; a rig on the wire is the application's. This PR gives an application what it needs to do that cleanly, and makes the simulated dyno able to start the way a real openpilot-driven rig does.
fixs.config.get(section): the scenario yaml, read-only$FIXS_CONFIG_YAML, or the onefixs.connect()resolved).Setup, every key goes to snake_case (XilSetup.EnableXil→get('xil')['enable_xil']).MappingProxyType. Namedgetso asetcan come later.fixs.xil.dynosim(): the simulated dyno, a plain constructorfixs.xil.dynosim(vehicle=..., dyno=..., driver=...)replacesfixs.xil.dyno(). It builds the simulated dyno from the parameters it is given and reads no scenario and no transport. Whether a dyno is in the loop is the caller's question:fixs.xil.enabled()andfixs.config.get('xil')['transport'].Transport: udpmeans the application talks to its own rig atip/port; FIXS opens no socket.tcpstays a valid value, reserved.exchange(vref, aref, dt): the acceleration command is explicit and comes from the caller. The dyno never derives it.XilSetup.Vehicle/Dyno/Driverare removed fromConfigHelper.py; no scenario set them._Dynoudp/tcp,UdpLink.drain()/recv_wait(),UDP_REPLY_TIMEOUT_S,UDP_MAX_MISSES) is removed.The dyno's robot driver can hold like openpilot
RobotDriverbrakes while slower thanstop_speed_mpsand asked for less thanstop_accel_mps2: openpilot'sshould_stop(drive_helpers.py:v_ego < 0.3 and a_target < 0.1). Both default to0.0, which is off, so a default bench is unchanged. Set throughdynosim(driver={'stop_speed_mps': 0.3, 'stop_accel_mps2': 0.1}); the rule reads thearefpassed toexchange.fixs.driver:startInGear, andfixs.driver.set()CARLA's automatic gearbox leaves a spawned car in neutral below about 15% throttle. Measured on
vehicle.tesla.model3: 10% never moves (gear stays 0), 15% moves after 3.6 s, 20% at once; with gear 1 forced, 2% throttle moves it. A pedal-driven ego asked to creep therefore never moves.startInGear=True: on its first controlled stepfixs.drivercallscarla.ego.apply_control(carla.VehicleControl(manual_gear_shift=True, gear=1))and writes no pedal command, so the host does not overwrite it. The autobox keeps first gear from then on, through stops. No creep at zero throttle. Off by default.fixs.driver.set(**options): options for every driver built after the call, so a controller can set one conditionally and keepDriver = fixs.driver(exchange)plain. An option passed tofixs.driver(...)still wins; unknown names are refused.Kept from the first commit
LocalLinkrounds values through the same float32 packet the wire carries, so an inprocess run and a run over the wire see the same numbers. Behaviour change: inprocess results shift slightly against runs made before this branch.Related Issues
No tracker. Found while preparing the ORNL rig test (comma.ai openpilot drives the vehicle on their hub dyno). The application side is ORNL-Real-Sim/FIXS_Applications#84 (MLK eco-driving rung 6), which depends on this PR.
How to test
New:
test_fixs_config.py; the hold (test_xil_dyno_hookup.py: off by default, a flat 0.15 m/s witharef0 stays at rest,aref1.5 lets go, no hold above 0.3 m/s);startInGearandfixs.driver.set(test_fixs_driver.py). End-to-end runs are on #84.Checklist
tests/Python/unit: 492 passed, 8 skipped)doc/ConfigSetup.md:Transportrow,fixs.config.get,fixs.xil.dynosim())Not changed: the C++ bridge (
VirCarlaEnv);startInGearlives infixs.driver, so it needs nothing there.