Skip to content

Commit 14f3835

Browse files
committed
feat: add beacon mode switch to sensor diagnostics demo
Add BEACON_MODE environment variable (none/topic/param) that controls entity enrichment via gateway beacon plugins. Sensor nodes conditionally publish MedkitDiscoveryHint messages (topic mode) or declare ros2_medkit.discovery.* parameters (param mode) with sensor-specific metadata (entity_id, display_name, component, function membership, sensor type, data topic, frame ID). Shared BeaconHelper class avoids code duplication across 4 sensor nodes. Launch file resolves the appropriate beacon plugin based on mode. Docker-compose passes BEACON_MODE through to the container. Closes #41
1 parent b899b58 commit 14f3835

9 files changed

Lines changed: 301 additions & 14 deletions

File tree

demos/sensor_diagnostics/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ ament_target_dependencies(lidar_sim_node
2222
sensor_msgs
2323
diagnostic_msgs
2424
rcl_interfaces
25+
ros2_medkit_msgs
2526
)
2627

2728
# IMU simulator node
@@ -30,6 +31,7 @@ ament_target_dependencies(imu_sim_node
3031
rclcpp
3132
sensor_msgs
3233
diagnostic_msgs
34+
ros2_medkit_msgs
3335
)
3436

3537
# GPS simulator node
@@ -38,6 +40,7 @@ ament_target_dependencies(gps_sim_node
3840
rclcpp
3941
sensor_msgs
4042
diagnostic_msgs
43+
ros2_medkit_msgs
4144
)
4245

4346
# Camera simulator node
@@ -47,6 +50,7 @@ ament_target_dependencies(camera_sim_node
4750
sensor_msgs
4851
diagnostic_msgs
4952
rcl_interfaces
53+
ros2_medkit_msgs
5054
)
5155

5256
# Anomaly detector node

demos/sensor_diagnostics/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ This demo showcases ros2_medkit's data monitoring, configuration management, and
1111
- **Focus on diagnostics** - Pure ros2_medkit features without robot complexity
1212
- **Configurable faults** - Runtime fault injection via REST API
1313
- **Dual fault reporting** - Demonstrates both legacy (diagnostics) and modern (direct) paths
14+
- **Beacon discovery** - Optional push (topic) or pull (parameter) entity enrichment
1415

1516
## Quick Start
1617

@@ -255,6 +256,58 @@ curl http://localhost:8080/api/v1/faults | jq
255256
| `brightness` | int | 128 | Base brightness (0-255) |
256257
| `inject_black_frames` | bool | false | Randomly inject black frames |
257258

259+
## Beacon Mode (Entity Enrichment)
260+
261+
The gateway's beacon plugins let sensor nodes publish extra metadata (display names, process info, topology hints) that enriches the SOVD entity model at runtime - without modifying the manifest.
262+
263+
Three modes are available, controlled by the `BEACON_MODE` environment variable:
264+
265+
| Mode | Plugin | Mechanism | Description |
266+
|------|--------|-----------|-------------|
267+
| `none` | - | - | Default. No beacon plugins. Entities come from manifest + runtime discovery only. |
268+
| `topic` | topic_beacon | Push (ROS 2 topic) | Sensor nodes publish `MedkitDiscoveryHint` messages on `/ros2_medkit/discovery` every 5s. Gateway subscribes and enriches entities. |
269+
| `param` | parameter_beacon | Pull (ROS 2 parameters) | Sensor nodes declare `ros2_medkit.discovery.*` parameters. Gateway polls them every 5s. |
270+
271+
### Usage
272+
273+
```bash
274+
# Docker - set BEACON_MODE before starting
275+
BEACON_MODE=topic docker compose up -d
276+
BEACON_MODE=param docker compose up -d
277+
docker compose up -d # default: none
278+
279+
# Local (non-Docker)
280+
BEACON_MODE=topic ros2 launch sensor_diagnostics_demo demo.launch.py
281+
```
282+
283+
### Viewing Beacon Data
284+
285+
When a beacon mode is active, each sensor entity gets enriched with extra metadata visible through the API:
286+
287+
```bash
288+
# Topic beacon metadata
289+
curl http://localhost:8080/api/v1/apps/lidar-sim/x-medkit-topic-beacon | jq
290+
291+
# Parameter beacon metadata
292+
curl http://localhost:8080/api/v1/apps/lidar-sim/x-medkit-param-beacon | jq
293+
```
294+
295+
The beacon data includes:
296+
- **entity_id** - Manifest app ID (e.g., `lidar-sim`)
297+
- **display_name** - Human-friendly name (e.g., `LiDAR Simulator`)
298+
- **component_id** - Parent component (e.g., `lidar-unit`)
299+
- **function_ids** - Function membership (e.g., `sensor-monitoring`)
300+
- **process_id** / **hostname** - Process-level diagnostics
301+
- **metadata** - Sensor-specific key-value pairs (sensor_type, data_topic, frame_id)
302+
303+
### How It Works
304+
305+
**Topic beacon** (push): Each sensor node creates a publisher on `/ros2_medkit/discovery` and publishes a `MedkitDiscoveryHint` message every 5 seconds. The gateway's `topic_beacon` plugin subscribes to this topic and merges the hints into the entity model. Hints have a TTL (default 10s) - if a node stops publishing, the data goes stale.
306+
307+
**Parameter beacon** (pull): Each sensor node declares ROS 2 parameters under the `ros2_medkit.discovery.*` prefix. The gateway's `parameter_beacon` plugin polls all nodes for these parameters every 5 seconds. No explicit publishing is needed - the gateway reads the parameters via the ROS 2 parameter service.
308+
309+
Both mechanisms enrich the same entities defined in the manifest. They do not create new entities (the `allow_new_entities` option is disabled). Only one beacon mode should be active at a time - they serve the same purpose via different transport mechanisms.
310+
258311
## Use Cases
259312

260313
1. **CI/CD Testing** - Validate ros2_medkit without heavy simulation

demos/sensor_diagnostics/docker-compose.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ services:
77
container_name: sensor_diagnostics_demo
88
environment:
99
- ROS_DOMAIN_ID=40
10+
- BEACON_MODE=${BEACON_MODE:-none}
1011
ports:
1112
- "8080:8080"
1213
stdin_open: true
@@ -37,6 +38,7 @@ services:
3738
container_name: sensor_diagnostics_demo_ci
3839
environment:
3940
- ROS_DOMAIN_ID=40
41+
- BEACON_MODE=${BEACON_MODE:-none}
4042
ports:
4143
- "8080:8080"
4244
command: >

demos/sensor_diagnostics/launch/demo.launch.py

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,19 @@
33
Lightweight demo without Gazebo - pure sensor simulation with fault injection.
44
55
Demonstrates two fault reporting paths:
6-
1. Legacy path: Sensors /diagnostics topic diagnostic_bridge fault_manager
6+
1. Legacy path: Sensors -> /diagnostics topic -> diagnostic_bridge -> fault_manager
77
- Used by: LiDAR, Camera
88
- Standard ROS 2 diagnostics pattern
99
10-
2. Modern path: Sensors anomaly_detector ReportFault service fault_manager
10+
2. Modern path: Sensors -> anomaly_detector -> ReportFault service -> fault_manager
1111
- Used by: IMU, GPS
1212
- Direct ros2_medkit fault reporting
1313
14+
Beacon modes (set via BEACON_MODE env var):
15+
none - No beacon plugins (default)
16+
topic - Topic beacon: sensor nodes push MedkitDiscoveryHint messages
17+
param - Parameter beacon: gateway polls sensor node parameters
18+
1419
Namespace structure:
1520
/sensors - Simulated sensor nodes (lidar, imu, gps, camera)
1621
/processing - Anomaly detector
@@ -50,6 +55,9 @@ def generate_launch_description():
5055
sensor_params_file = os.path.join(pkg_dir, "config", "sensor_params.yaml")
5156
manifest_file = os.path.join(pkg_dir, "config", "sensor_manifest.yaml")
5257

58+
# Beacon mode from environment (controls both plugin loading and node behavior)
59+
beacon_mode = os.environ.get('BEACON_MODE', 'none')
60+
5361
# Resolve plugin paths
5462
graph_provider_path = _resolve_plugin_path(
5563
'ros2_medkit_graph_provider', 'ros2_medkit_graph_provider_plugin')
@@ -65,8 +73,32 @@ def generate_launch_description():
6573
if procfs_plugin_path:
6674
active_plugins.append('procfs_introspection')
6775
plugin_overrides['plugins.procfs_introspection.path'] = procfs_plugin_path
76+
77+
# Beacon plugin (mutually exclusive - only one beacon type at a time)
78+
if beacon_mode == 'topic':
79+
topic_beacon_path = _resolve_plugin_path(
80+
'ros2_medkit_topic_beacon', 'topic_beacon_plugin')
81+
if topic_beacon_path:
82+
active_plugins.append('topic_beacon')
83+
plugin_overrides['plugins.topic_beacon.path'] = topic_beacon_path
84+
plugin_overrides['plugins.topic_beacon.topic'] = \
85+
'/ros2_medkit/discovery'
86+
plugin_overrides['plugins.topic_beacon.beacon_ttl_sec'] = 10.0
87+
elif beacon_mode == 'param':
88+
param_beacon_path = _resolve_plugin_path(
89+
'ros2_medkit_param_beacon', 'param_beacon_plugin')
90+
if param_beacon_path:
91+
active_plugins.append('parameter_beacon')
92+
plugin_overrides['plugins.parameter_beacon.path'] = \
93+
param_beacon_path
94+
plugin_overrides['plugins.parameter_beacon.poll_interval_sec'] = \
95+
5.0
96+
6897
plugin_overrides['plugins'] = active_plugins
6998

99+
# Sensor node beacon parameter (passed to all sensor nodes)
100+
beacon_params = {"beacon_mode": beacon_mode}
101+
70102
# Launch arguments
71103
use_sim_time = LaunchConfiguration("use_sim_time", default="false")
72104

@@ -76,7 +108,8 @@ def generate_launch_description():
76108
DeclareLaunchArgument(
77109
"use_sim_time",
78110
default_value="false",
79-
description="Use simulation time (set to true if using with Gazebo)",
111+
description="Use simulation time (set to true if using "
112+
"with Gazebo)",
80113
),
81114
# ===== Sensor Nodes (under /sensors namespace) =====
82115
# Legacy path sensors: publish DiagnosticArray to /diagnostics
@@ -86,45 +119,64 @@ def generate_launch_description():
86119
name="lidar_sim",
87120
namespace="sensors",
88121
output="screen",
89-
parameters=[sensor_params_file, {"use_sim_time": use_sim_time}],
122+
parameters=[
123+
sensor_params_file,
124+
{"use_sim_time": use_sim_time},
125+
beacon_params,
126+
],
90127
),
91128
Node(
92129
package="sensor_diagnostics_demo",
93130
executable="camera_sim_node",
94131
name="camera_sim",
95132
namespace="sensors",
96133
output="screen",
97-
parameters=[sensor_params_file, {"use_sim_time": use_sim_time}],
134+
parameters=[
135+
sensor_params_file,
136+
{"use_sim_time": use_sim_time},
137+
beacon_params,
138+
],
98139
),
99-
# Modern path sensors: monitored by anomaly_detector ReportFault
140+
# Modern path sensors: monitored by anomaly_detector -> ReportFault
100141
Node(
101142
package="sensor_diagnostics_demo",
102143
executable="imu_sim_node",
103144
name="imu_sim",
104145
namespace="sensors",
105146
output="screen",
106-
parameters=[sensor_params_file, {"use_sim_time": use_sim_time}],
147+
parameters=[
148+
sensor_params_file,
149+
{"use_sim_time": use_sim_time},
150+
beacon_params,
151+
],
107152
),
108153
Node(
109154
package="sensor_diagnostics_demo",
110155
executable="gps_sim_node",
111156
name="gps_sim",
112157
namespace="sensors",
113158
output="screen",
114-
parameters=[sensor_params_file, {"use_sim_time": use_sim_time}],
159+
parameters=[
160+
sensor_params_file,
161+
{"use_sim_time": use_sim_time},
162+
beacon_params,
163+
],
115164
),
116165
# ===== Processing Nodes (under /processing namespace) =====
117-
# Modern path: anomaly_detector monitors IMU/GPS and calls ReportFault
166+
# Modern path: anomaly_detector monitors IMU/GPS, calls ReportFault
118167
Node(
119168
package="sensor_diagnostics_demo",
120169
executable="anomaly_detector_node",
121170
name="anomaly_detector",
122171
namespace="processing",
123172
output="screen",
124-
parameters=[sensor_params_file, {"use_sim_time": use_sim_time}],
173+
parameters=[
174+
sensor_params_file,
175+
{"use_sim_time": use_sim_time},
176+
],
125177
),
126178
# ===== Diagnostic Bridge (Legacy path) =====
127-
# Bridges /diagnostics topic (DiagnosticArray) fault_manager
179+
# Bridges /diagnostics topic (DiagnosticArray) -> fault_manager
128180
# Handles faults from: LiDAR, Camera
129181
Node(
130182
package="ros2_medkit_diagnostic_bridge",
@@ -156,13 +208,14 @@ def generate_launch_description():
156208
),
157209
# ===== Fault Manager (at root namespace) =====
158210
# Services at /fault_manager/* (e.g., /fault_manager/report_fault)
159-
# Both paths report here: diagnostic_bridge (legacy) and anomaly_detector (modern)
160-
# Also handles snapshot and rosbag capture when faults are confirmed
211+
# Both paths report here: diagnostic_bridge (legacy) and
212+
# anomaly_detector (modern)
213+
# Also handles snapshot and rosbag capture on fault confirmation
161214
Node(
162215
package="ros2_medkit_fault_manager",
163216
executable="fault_manager_node",
164217
name="fault_manager",
165-
namespace="", # Root namespace so services are at /fault_manager/*
218+
namespace="", # Root namespace: services at /fault_manager/*
166219
output="screen",
167220
parameters=[
168221
medkit_params_file,

0 commit comments

Comments
 (0)