Engineering Guides · Module 15
Hardware-in-the-loop and bag replay
Replay recorded data to test software repeatably; put real hardware in the loop to test what simulation cannot.
Assumptions: Numbers in the examples are illustrative and the code is executed by the site’s tests. Facts taken from a cited source are marked; other statements are general engineering knowledge and must be confirmed on the datasheet of the part you use.
1. Engineering problem
Field data cannot be repeated, and hardware cannot always be present. A bag replay runs the software on recorded inputs as often as needed; hardware-in-the-loop runs real controllers or sensors against a simulated world. Both need care with time.
2. When is this needed?
- Reproducing a field failure.
- Regression-testing perception or localisation.
- Testing a real controller against a simulated plant.
3. Selection and design decisions
The rosbag2 README (jazzy, read 2026-11-06) describes recording and playing back topics with the ros2 bag command and storage plugins; playback has options for rate, looping and a clock topic. With use_sim_time set, nodes take time from the /clock topic published by the player or simulator; without it they use wall time, and replayed stamps look old. Hardware-in-the-loop connects real hardware (an embedded controller, a real sensor) to a simulated counterpart through the same interfaces the real system uses.
| Approach | Strength | Limit |
|---|---|---|
| Bag replay | Exactly repeatable; real sensor data | Open loop: the robot cannot react, so control and planning cannot be tested closed-loop |
| Simulation | Closed loop, any scenario | Model error (see the matching guide) |
| Hardware-in-the-loop | Real controller timing and drivers | Needs a real-time simulator and interfaces |
| Replay plus simulation | Real sensors with simulated reaction | Complex to keep consistent |
4. Advantages and disadvantages
- Slower replay helps heavy algorithms keep up and distorts real-time behaviour.
- Looping repeats the test and needs the algorithm to reset at the loop point.
- Recording everything is simple and costs disk; choose topics (see the bag strategy guide).
5. Requirements and constraints
- Time source is consistent across all nodes (sim time on or off, never mixed).
- Replayed topics do not collide with live ones.
- The bag contains what the node under test needs, including tf_static and camera_info.
6. Architecture
bag (topics, /clock) ─▶ ros2 bag play ─▶ nodes under test (use_sim_time) ─▶ outputs recorded and compared7. Implementation
- Choose and record the topics, including /tf_static.
- Replay with the clock published and use_sim_time on the nodes.
- Look for recording gaps (below) that could explain a failure.
- Run the algorithm and compare its output with the reference.
- Repeat at other rates to see timing sensitivity.
msgs = [(0.00, "scan"), (0.10, "scan"), (0.20, "scan"), (1.20, "scan"), (1.30, "scan"), (1.40, "scan")] # (bag time, topic): a one-second recording gap
def schedule(msgs, rate):
t0 = msgs[0][0]
return [(round((t - t0) / rate, 3), n) for t, n in msgs]
print("playback at 1.0x:", [t for t, _ in schedule(msgs, 1.0)])
print("playback at 0.5x:", [t for t, _ in schedule(msgs, 0.5)])
gaps = [(round(b[0] - a[0], 2), a[0]) for a, b in zip(msgs, msgs[1:]) if b[0] - a[0] > 3 * 0.1]
print("gaps longer than 3 periods (bag time, gap):", [(a, g) for g, a in gaps])
sim_clock = [round(0.1 * k, 1) for k in range(5)]
print("with use_sim_time the nodes see the clock topic:", sim_clock, "; without it they see wall time and every stamp looks 'old'")Printed (checked by the site’s tests):
playback at 1.0x: [0.0, 0.1, 0.2, 1.2, 1.3, 1.4]
playback at 0.5x: [0.0, 0.2, 0.4, 2.4, 2.6, 2.8]
gaps longer than 3 periods (bag time, gap): [(0.2, 1.0)]
with use_sim_time the nodes see the clock topic: [0.0, 0.1, 0.2, 0.3, 0.4] ; without it they see wall time and every stamp looks 'old'Reading it: a 1 s hole in a 10 Hz recording is found between 0.2 s and 1.2 s of bag time; replay at half rate stretches every timestamp by two, so the hole becomes 2 s. With simulated time the nodes see the clock topic; without it they see wall time and every stamp looks old. The messages are synthetic.
8. Real examples
- Localisation regression: a field bag with a jump is replayed after each change until the jump disappears.
9. Failure scenarios
- Replaying with use_sim_time off on some nodes.
- A bag that lacks tf_static, so nothing transforms.
- Treating open-loop replay as proof that the closed loop works.
10. Troubleshooting
- Check that /clock is published and that all nodes use sim time.
- Plot message intervals in the bag for gaps.
- Check that live and replayed topics are not both present.
11. Validation
- The schedule example prints the lines shown (executed by the site tests).
- A replayed run gives the same output twice (determinism, where the algorithm is deterministic).
12. Engineering checklist
13. Further references
- rosbag2 README (jazzy): ros2/rosbag2, read 2026-11-06.