Skip to content

Commit 9520ed5

Browse files
Tomoya FujitaTomoya Fujita
authored andcommitted
check for invalid ROS discovery configuration and print warning if needed.
Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
1 parent 6e3c7ac commit 9520ed5

4 files changed

Lines changed: 105 additions & 0 deletions

File tree

ros2cli/ros2cli/helpers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,27 @@ def collect_stdin():
122122
return lines
123123

124124

125+
def check_discovery_configuration():
126+
"""
127+
Check for invalid ROS discovery configuration and print warning if needed.
128+
129+
Warns when ROS_AUTOMATIC_DISCOVERY_RANGE=OFF is set without ROS_STATIC_PEERS,
130+
which results in no discovery mechanism being available.
131+
"""
132+
discovery_range = os.environ.get('ROS_AUTOMATIC_DISCOVERY_RANGE', '')
133+
static_peers = os.environ.get('ROS_STATIC_PEERS', '')
134+
135+
if discovery_range == 'OFF' and not static_peers:
136+
print(
137+
'Warning: ROS_AUTOMATIC_DISCOVERY_RANGE=OFF with no ROS_STATIC_PEERS configured.\n'
138+
'No discovery mechanism is available. Results will be empty.\n'
139+
'Either:\n'
140+
' - Set ROS_STATIC_PEERS to specify peers explicitly, or\n'
141+
' - Change ROS_AUTOMATIC_DISCOVERY_RANGE to LOCALHOST or SUBNET',
142+
file=sys.stderr
143+
)
144+
145+
125146
def get_rmw_additional_env(rmw_implementation: str) -> Dict[str, str]:
126147
"""Get a dictionary of additional environment variables based on rmw."""
127148
if rmw_implementation == 'rmw_zenoh_cpp':

ros2cli/ros2cli/node/direct.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import rclpy.action
2020

2121
from rclpy.parameter import Parameter
22+
from ros2cli.helpers import check_discovery_configuration
2223
from ros2cli.node import NODE_NAME_PREFIX
2324
DEFAULT_TIMEOUT = 0.5
2425

2526

2627
class DirectNode:
2728

2829
def __init__(self, args, *, node_name: Optional[str] = None):
30+
# Check for invalid discovery configuration
31+
check_discovery_configuration()
32+
2933
timeout_reached = False
3034

3135
def timer_callback():

ros2cli/ros2cli/node/strategy.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from typing import Optional
1616

17+
from ros2cli.helpers import check_discovery_configuration
1718
from ros2cli.node.daemon import add_arguments as add_daemon_node_arguments
1819
from ros2cli.node.daemon import DaemonNode
1920
from ros2cli.node.daemon import is_daemon_running
@@ -25,6 +26,9 @@
2526
class NodeStrategy:
2627

2728
def __init__(self, args, *, node_name: Optional[str] = None):
29+
# Check for invalid discovery configuration
30+
check_discovery_configuration()
31+
2832
use_daemon = not getattr(args, 'no_daemon', False)
2933
if use_daemon and is_daemon_running(args):
3034
self._daemon_node = DaemonNode(args)

ros2cli/test/test_helpers.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright 2026 Sony Corporation
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import io
16+
import os
17+
from unittest.mock import patch
18+
19+
from ros2cli.helpers import check_discovery_configuration
20+
21+
22+
def test_check_discovery_configuration_off_without_peers():
23+
"""Test warning is shown when ROS_AUTOMATIC_DISCOVERY_RANGE=OFF without ROS_STATIC_PEERS."""
24+
env = {
25+
'ROS_AUTOMATIC_DISCOVERY_RANGE': 'OFF',
26+
}
27+
# Make sure ROS_STATIC_PEERS is not set
28+
env_without_peers = {k: v for k, v in os.environ.items() if k != 'ROS_STATIC_PEERS'}
29+
env_without_peers.update(env)
30+
31+
# Capture stderr
32+
stderr_capture = io.StringIO()
33+
with patch.dict(os.environ, env_without_peers, clear=True):
34+
with patch('sys.stderr', stderr_capture):
35+
check_discovery_configuration()
36+
37+
output = stderr_capture.getvalue()
38+
assert 'Warning: ROS_AUTOMATIC_DISCOVERY_RANGE=OFF' in output
39+
assert 'No discovery mechanism is available' in output
40+
assert 'ROS_STATIC_PEERS' in output
41+
assert 'LOCALHOST or SUBNET' in output
42+
43+
44+
def test_check_discovery_configuration_off_with_peers():
45+
"""Test no warning when ROS_AUTOMATIC_DISCOVERY_RANGE=OFF with ROS_STATIC_PEERS set."""
46+
env = {
47+
'ROS_AUTOMATIC_DISCOVERY_RANGE': 'OFF',
48+
'ROS_STATIC_PEERS': '192.168.1.10;192.168.1.11',
49+
}
50+
51+
# Capture stderr
52+
stderr_capture = io.StringIO()
53+
with patch.dict(os.environ, env, clear=True):
54+
with patch('sys.stderr', stderr_capture):
55+
check_discovery_configuration()
56+
57+
output = stderr_capture.getvalue()
58+
assert output == '', 'Expected no warning output'
59+
60+
61+
def test_check_discovery_configuration_not_set():
62+
"""Test no warning when ROS_AUTOMATIC_DISCOVERY_RANGE is not set (default behavior)."""
63+
# Make sure neither env var is set
64+
env_clean = {
65+
k: v for k, v in os.environ.items()
66+
if k not in ('ROS_AUTOMATIC_DISCOVERY_RANGE', 'ROS_STATIC_PEERS')
67+
}
68+
69+
# Capture stderr
70+
stderr_capture = io.StringIO()
71+
with patch.dict(os.environ, env_clean, clear=True):
72+
with patch('sys.stderr', stderr_capture):
73+
check_discovery_configuration()
74+
75+
output = stderr_capture.getvalue()
76+
assert output == '', 'Expected no warning output'

0 commit comments

Comments
 (0)