@@ -26,6 +26,8 @@ A ROS 2 **Humble** package that provides:
2626 - [ QoS Settings] ( #qos-settings )
2727 - [ Latched Topics] ( #latched-topics )
2828- [ Triggering a Snapshot] ( #triggering-a-snapshot )
29+ - [ Immediate trigger (blocking)] ( #immediate-trigger-blocking )
30+ - [ Delayed / non-blocking trigger] ( #delayed--non-blocking-trigger )
2931- [ Snapshot Server] ( #snapshot-server )
3032 - [ Authentication] ( #authentication )
3133 - [ REST Endpoints] ( #rest-endpoints )
@@ -49,6 +51,8 @@ A ROS 2 **Humble** package that provides:
4951| ** Memory efficiency** | Messages stored as raw serialised bytes; deque-based O(1) expiry |
5052| ** Dynamic type discovery** | Topic types resolved at runtime (like ` ros2 bag record ` ) |
5153| ** On-demand snapshot** | ` std_srvs/srv/Trigger ` service writes the buffer to a rosbag |
54+ | ** Delayed snapshot** | ` ~/save_snapshot_delayed ` service: configurable pre-snapshot delay, optional blocking mode, and snapshot reason |
55+ | ** Snapshot reason** | Every snapshot includes a ` reason.md ` sidecar explaining why it was taken |
5256| ** Auto-snapshot** | Sliding-window automatic snapshots at a configurable interval |
5357| ** Configurable filename** | ` {datetime} ` , ` {date} ` , ` {time} ` , ` {timestamp} ` , ` {trigger} ` placeholders |
5458| ** Node parameter snapshotting** | Collects and stores active node parameters alongside each snapshot |
@@ -69,6 +73,8 @@ A ROS 2 **Humble** package that provides:
6973``` bash
7074cd ~ /ros2_ws
7175rosdep install --from-paths src --ignore-src -r -y
76+ # Build the interfaces package first (provides the SaveSnapshotDelayed service)
77+ colcon build --packages-select rosbag_blackbox_interfaces
7278colcon build --packages-select rosbag_blackbox
7379source install/setup.bash
7480```
@@ -98,10 +104,16 @@ ros2 launch rosbag_blackbox rosbag_blackbox.launch.py \
98104### 2 – Trigger a snapshot
99105
100106``` bash
107+ # Immediate snapshot (blocks until written)
101108ros2 service call /rosbag_blackbox/save_snapshot std_srvs/srv/Trigger
109+
110+ # Delayed snapshot – returns immediately; snapshot taken after 10 s
111+ ros2 service call /rosbag_blackbox/save_snapshot_delayed \
112+ rosbag_blackbox_interfaces/srv/SaveSnapshotDelayed \
113+ " {delay: 10.0, blocking: false, reason: 'Anomaly detected'}"
102114```
103115
104- The response message contains the full path of the new rosbag directory.
116+ The ` response. message ` field contains the full path of the new rosbag directory (or a scheduling confirmation for non-blocking calls) .
105117
106118### 3 – Query the snapshot via REST
107119
@@ -324,6 +336,8 @@ have been evicted from the time-bounded ring buffer.
324336
325337# # Triggering a Snapshot
326338
339+ # ## Immediate trigger (blocking)
340+
327341Call the service exposed by the ring-buffer node :
328342
329343` ` ` bash
@@ -350,6 +364,60 @@ The `response.message` field contains the absolute path of the newly created
350364rosbag directory. Snapshots triggered this way use the `manual` value for
351365the `{trigger}` filename placeholder.
352366
367+ # ## Delayed / non-blocking trigger
368+
369+ The `~/save_snapshot_delayed` service accepts three optional parameters :
370+
371+ | Field | Type | Description |
372+ |-------|------|-------------|
373+ | `delay` | `float64` | Seconds to wait before taking the snapshot (default `0`). |
374+ | `blocking` | `bool` | When `true`, the service call blocks until the snapshot is written. When `false` (default) it returns immediately. |
375+ | `reason` | `string` | Human-readable description of why the snapshot is being taken. Written to `reason.md` inside the snapshot directory. |
376+
377+ The service always returns immediately when `blocking` is `false`, even if a
378+ delay is configured. The actual snapshot is written in a background thread
379+ once the delay has elapsed.
380+
381+ ` ` ` bash
382+ # Take a snapshot after 10 s; returns immediately
383+ ros2 service call /rosbag_blackbox/save_snapshot_delayed \
384+ rosbag_blackbox_interfaces/srv/SaveSnapshotDelayed \
385+ "{delay: 10.0, blocking: false, reason: 'Detected anomaly in sensor data'}"
386+
387+ # Take a snapshot immediately and block until it is written
388+ ros2 service call /rosbag_blackbox/save_snapshot_delayed \
389+ rosbag_blackbox_interfaces/srv/SaveSnapshotDelayed \
390+ "{delay: 0.0, blocking: true, reason: 'Manual inspection requested'}"
391+ ` ` `
392+
393+ Or from Python :
394+
395+ ` ` ` python
396+ import rclpy
397+ from rclpy.node import Node
398+ from rosbag_blackbox_interfaces.srv import SaveSnapshotDelayed
399+
400+ rclpy.init()
401+ node = Node('snapshot_client')
402+ cli = node.create_client(SaveSnapshotDelayed, '/rosbag_blackbox/save_snapshot_delayed')
403+ cli.wait_for_service()
404+
405+ req = SaveSnapshotDelayed.Request()
406+ req.delay = 5.0 # wait 5 s before writing
407+ req.blocking = False # return immediately
408+ req.reason = 'Triggered by safety monitor'
409+
410+ future = cli.call_async(req)
411+ rclpy.spin_until_future_complete(node, future)
412+ print(future.result().message)
413+ ` ` `
414+
415+ Every snapshot written through any route (immediate, delayed, or automatic)
416+ includes a `reason.md` sidecar file. Automatic snapshots contain simply
417+ ` auto` ; immediate manual snapshots contain `manual trigger without specific
418+ reason` (unless overridden via the delayed service). The
419+ [metadata endpoint](#rest-endpoints) exposes the reason as the `reason` field.
420+
353421---
354422
355423# # Snapshot Server
@@ -395,7 +463,7 @@ containing the image as a base64-encoded data URL
395463| GET | `/health` | Liveness check; returns server status and latest snapshot path |
396464| GET | `/snapshots` | List all snapshot directory names |
397465| GET | `/snapshots/latest` | Name of the most-recently modified snapshot |
398- | GET | `/snapshots/{name}/metadata` | Topics, message counts, duration, time range |
466+ | GET | `/snapshots/{name}/metadata` | Topics, message counts, duration, time range, and snapshot reason |
399467| GET | `/snapshots/{name}/messages` | Extract and deserialise messages (filterable) |
400468| GET | `/snapshots/{name}/image/{topic}` | Nearest camera image as PNG data-URL |
401469| GET | `/snapshots/{name}/laserscan/{topic}` | LaserScan rendered as top-view PNG data-URL |
@@ -550,16 +618,20 @@ from their default values.
550618│ │ │ ┌────────────▼──────────────┐ │ │
551619│ │ │ │ save_snapshot service │ │ │
552620│ │ │ │ (std_srvs/Trigger) │ │ │
621+ │ │ │ │ save_snapshot_delayed │ │ │
622+ │ │ │ │ (SaveSnapshotDelayed) │ │ │
553623│ │ │ │ auto-snapshot timer │ │ │
554624│ │ │ └────────────┬──────────────┘ │ │
555625│ │ └───────────────┼──────────────────┘ │
556626│ │ │ writes rosbag │
557627│ │ │ + node_params.yaml │
628+ │ │ │ + reason.md │
558629│ │ ▼ │
559630│ │ ┌────────────────┐ │
560631│ │ │ snapshot dir │ │
561632│ │ │ (rosbag + │ │
562- │ │ │ node_params) │ │
633+ │ │ │ node_params + │ │
634+ │ │ │ reason.md) │ │
563635│ │ └────────┬───────┘ │
564636│ │ reads │
565637│ ┌───────────────────────────────▼───────────────────────┐ │
0 commit comments