Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.

Commit 152eaa9

Browse files
author
fred-labs
authored
add action: wait_for_nodes (#164)
1 parent af67a52 commit 152eaa9

File tree

5 files changed

+171
-1
lines changed

5 files changed

+171
-1
lines changed

docs/libraries.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,26 @@ Wait for any message on a ROS topic.
12271227
- QoS Preset Profile for the subscriber (default: ``qos_preset_profiles!system_default``)
12281228

12291229

1230+
``wait_for_nodes()``
1231+
^^^^^^^^^^^^^^^^^^^^^
1232+
1233+
Wait for nodes to get available.
1234+
1235+
.. list-table::
1236+
:widths: 15 15 5 65
1237+
:header-rows: 1
1238+
:class: tight-table
1239+
1240+
* - Parameter
1241+
- Type
1242+
- Default
1243+
- Description
1244+
* - ``nodes``
1245+
- ``list of string``
1246+
-
1247+
- List of nodes to wait for
1248+
1249+
12301250
``wait_for_topics()``
12311251
^^^^^^^^^^^^^^^^^^^^^
12321252

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright (C) 2024 Intel 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,
10+
# software 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
13+
# and limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
import py_trees
18+
from rclpy.node import Node
19+
from scenario_execution.actions.base_action import BaseAction
20+
from ros2node.api import get_node_names
21+
22+
23+
class RosWaitForNodes(BaseAction):
24+
"""
25+
Wait for nodes to get available
26+
"""
27+
28+
def __init__(self, nodes: list):
29+
super().__init__()
30+
if not isinstance(nodes, list):
31+
raise TypeError(f'Nodes needs to be list of string, got {type(nodes)}.')
32+
else:
33+
self.nodes = nodes
34+
self.node = None
35+
36+
def setup(self, **kwargs):
37+
try:
38+
self.node: Node = kwargs['node']
39+
except KeyError as e:
40+
error_message = "didn't find 'node' in setup's kwargs [{}][{}]".format(
41+
self.name, self.__class__.__name__)
42+
raise KeyError(error_message) from e
43+
44+
def update(self) -> py_trees.common.Status:
45+
available_nodes = get_node_names(node=self.node, include_hidden_nodes=False)
46+
available_nodes = [seq[2] for seq in available_nodes]
47+
result = all(elem in available_nodes for elem in self.nodes)
48+
if result:
49+
return py_trees.common.Status.SUCCESS
50+
else:
51+
return py_trees.common.Status.RUNNING

scenario_execution_ros/scenario_execution_ros/lib_osc/ros.osc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ action wait_for_data:
145145
topic_type: string # class of the message type (e.g. std_msgs.msg.String)
146146
qos_profile: qos_preset_profiles = qos_preset_profiles!system_default # qos profile for the subscriber
147147

148+
action wait_for_nodes:
149+
# Wait for nodes to get available.
150+
nodes: list of string # list of nodes to wait for
151+
148152
action wait_for_topics:
149153
# Wait for topics to get available (i.e. publisher gets available).
150-
topics: list of topics # list of topics to wait for
154+
topics: list of string # list of topics to wait for

scenario_execution_ros/setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
'topic_monitor = scenario_execution_ros.actions.ros_topic_monitor:RosTopicMonitor',
6565
'topic_publish = scenario_execution_ros.actions.ros_topic_publish:RosTopicPublish',
6666
'wait_for_data = scenario_execution_ros.actions.ros_topic_wait_for_data:RosTopicWaitForData',
67+
'wait_for_nodes = scenario_execution_ros.actions.ros_wait_for_nodes:RosWaitForNodes',
6768
'wait_for_topics = scenario_execution_ros.actions.ros_topic_wait_for_topics:RosTopicWaitForTopics',
6869
],
6970
'scenario_execution.osc_libraries': [
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# Copyright (C) 2024 Intel 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,
10+
# software 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
13+
# and limitations under the License.
14+
#
15+
# SPDX-License-Identifier: Apache-2.0
16+
17+
18+
import os
19+
import unittest
20+
import rclpy
21+
import py_trees
22+
import threading
23+
from scenario_execution_ros import ROSScenarioExecution
24+
from scenario_execution.model.osc2_parser import OpenScenario2Parser
25+
from scenario_execution.model.model_to_py_tree import create_py_tree
26+
from scenario_execution.utils.logging import Logger
27+
from antlr4.InputStream import InputStream
28+
29+
os.environ["PYTHONUNBUFFERED"] = '1'
30+
31+
32+
class TestRosLogCheck(unittest.TestCase):
33+
# pylint: disable=missing-function-docstring
34+
35+
def setUp(self):
36+
rclpy.init()
37+
38+
self.received_msgs = []
39+
self.node = rclpy.create_node('test_wait_for_nodes')
40+
41+
self.executor = rclpy.executors.MultiThreadedExecutor()
42+
self.executor.add_node(self.node)
43+
self.executor_thread = threading.Thread(target=self.executor.spin)
44+
self.executor_thread.start()
45+
46+
self.callback_group = rclpy.callback_groups.ReentrantCallbackGroup()
47+
48+
self.parser = OpenScenario2Parser(Logger('test', False))
49+
self.scenario_execution_ros = ROSScenarioExecution()
50+
self.tree = py_trees.composites.Sequence(name="", memory=True)
51+
52+
def execute(self, scenario_content):
53+
parsed_tree = self.parser.parse_input_stream(InputStream(scenario_content))
54+
model = self.parser.create_internal_model(parsed_tree, self.tree, "test.osc", False)
55+
self.tree = create_py_tree(model, self.tree, self.parser.logger, False)
56+
self.scenario_execution_ros.tree = self.tree
57+
self.scenario_execution_ros.run()
58+
59+
def tearDown(self):
60+
self.node.destroy_node()
61+
rclpy.try_shutdown()
62+
self.executor_thread.join()
63+
64+
def test_success(self):
65+
scenario_content = """
66+
import osc.ros
67+
68+
scenario test_success:
69+
do parallel:
70+
serial:
71+
wait_for_nodes(['/test_wait_for_nodes'])
72+
emit end
73+
time_out: serial:
74+
wait elapsed(3s)
75+
emit fail
76+
"""
77+
self.execute(scenario_content)
78+
self.assertTrue(self.scenario_execution_ros.process_results())
79+
80+
def test_timeout(self):
81+
scenario_content = """
82+
import osc.ros
83+
84+
scenario test_success:
85+
do parallel:
86+
serial:
87+
wait_for_nodes(['UNKNOWN'])
88+
emit end
89+
time_out: serial:
90+
wait elapsed(3s)
91+
emit fail
92+
"""
93+
self.execute(scenario_content)
94+
self.assertFalse(self.scenario_execution_ros.process_results())

0 commit comments

Comments
 (0)