Skip to content

Commit adcc3c2

Browse files
authored
Merge branch 'RobotWebTools:ros2' into ros2
2 parents 53b87aa + 2ed864b commit adcc3c2

31 files changed

+134
-34
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ jobs:
2424
# NOTE: Humble does not work on the `ros2` branch, so it is tested in its own branch.
2525
- ros: jazzy
2626
os: ubuntu-24.04
27+
- ros: kilted
28+
os: ubuntu-24.04
2729
- ros: rolling
2830
os: ubuntu-24.04
2931

rosapi/CHANGELOG.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
Changelog for package rosapi
33
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44

5+
2.3.0 (2025-05-19)
6+
------------------
7+
* Handle extra IDL slots when doing array introspection (`#1031 <https://github.com/RobotWebTools/rosbridge_suite/issues/1031>`_)
8+
* Add services to return Action interface details (`#1021 <https://github.com/RobotWebTools/rosbridge_suite/issues/1021>`_)
9+
* Fix array-like parameter serialization in rosbridge get_param (`#1018 <https://github.com/RobotWebTools/rosbridge_suite/issues/1018>`_)
10+
* Contributors: David Fernàndez López, Noah Wardlow, Scott Bell, Błażej Sowa
11+
512
2.2.0 (2025-02-26)
613
------------------
714
* New async rosapi params module implementation (`#1001 <https://github.com/RobotWebTools/rosbridge_suite/issues/1001>`_)

rosapi/package.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0"?>
22
<package format="3">
33
<name>rosapi</name>
4-
<version>2.2.0</version>
4+
<version>2.3.0</version>
55
<description>
66
Provides service calls for getting ros meta-information, like list of
77
topics, services, params, etc.
@@ -25,6 +25,7 @@
2525
<exec_depend>rosbridge_library</exec_depend>
2626
<exec_depend>ros2node</exec_depend>
2727
<exec_depend>ros2service</exec_depend>
28+
<exec_depend>ros2action</exec_depend>
2829
<exec_depend>ros2topic</exec_depend>
2930

3031
<test_depend>ament_cmake_mypy</test_depend>

rosapi/scripts/rosapi_node

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ from rosapi_msgs.srv import (
4747
ActionFeedbackDetails,
4848
ActionGoalDetails,
4949
ActionResultDetails,
50+
ActionType,
5051
DeleteParam,
5152
GetActionServers,
5253
GetParam,
@@ -112,6 +113,7 @@ class Rosapi(Node):
112113
self.create_service(Nodes, "~/nodes", self.get_nodes)
113114
self.create_service(NodeDetails, "~/node_details", self.get_node_details)
114115
self.create_service(GetActionServers, "~/action_servers", self.get_action_servers)
116+
self.create_service(ActionType, "~/action_type", self.get_action_type)
115117
self.create_service(TopicType, "~/topic_type", self.get_topic_type)
116118
self.create_service(ServiceType, "~/service_type", self.get_service_type)
117119
self.create_service(Publishers, "~/publishers", self.get_publishers)
@@ -335,6 +337,12 @@ class Rosapi(Node):
335337
]
336338
return response
337339

340+
def get_action_type(self, request, response):
341+
"""Called by the rosapi/ActionType service. Given the name of an action, returns its type.
342+
If the action does not exist, an empty string is returned."""
343+
response.type = proxy.get_action_type(request.action)
344+
return response
345+
338346
async def set_param(self, request, response):
339347
try:
340348
node_name, param_name = self._get_node_and_param_name(request.name)

rosapi/src/rosapi/objectutils.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -201,18 +201,18 @@ def _handle_array_information(instance):
201201
fieldtypes = []
202202
fieldarraylen = []
203203
examples = []
204-
for i in range(len(instance.__slots__)):
205-
name = instance.__slots__[i]
206-
fieldnames.append(name)
204+
for slot in instance.__slots__:
205+
key = slot[1:] if slot.startswith("_") else slot
206+
if key not in instance._fields_and_field_types:
207+
continue
207208

208-
field_type, arraylen = _handle_type_and_array_len(instance, name)
209+
fieldnames.append(key)
210+
field_type, arraylen = _handle_type_and_array_len(instance, slot)
209211
fieldarraylen.append(arraylen)
210212

211-
field_instance = getattr(instance, name)
212-
fieldtypes.append(_type_name(field_type, field_instance))
213-
214-
example = _handle_example(arraylen, field_type, field_instance)
215-
examples.append(str(example))
213+
value = getattr(instance, slot)
214+
fieldtypes.append(_type_name(field_type, value))
215+
examples.append(str(_handle_example(arraylen, field_type, value)))
216216

217217
return fieldnames, fieldtypes, fieldarraylen, examples
218218

rosapi/src/rosapi/params.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import fnmatch
3535
from json import dumps, loads
36+
from typing import TYPE_CHECKING
3637

3738
from rcl_interfaces.msg import Parameter, ParameterType, ParameterValue
3839
from rcl_interfaces.srv import GetParameters, ListParameters, SetParameters
@@ -44,6 +45,9 @@
4445
from rosapi.async_helper import futures_wait_for
4546
from rosapi.proxy import get_nodes
4647

48+
if TYPE_CHECKING:
49+
from rclpy.client import Client
50+
4751
""" Methods to interact with the param server. Values have to be passed
4852
as JSON in order to facilitate dynamically typed SRV messages """
4953

@@ -120,7 +124,7 @@ async def _set_param(node_name: str, name: str, value: str, parameter_type=None)
120124
setattr(parameter.value, _parameter_type_mapping[parameter_type], loads(value))
121125

122126
assert _node is not None
123-
client = _node.create_client(
127+
client: Client = _node.create_client(
124128
SetParameters,
125129
f"{node_name}/set_parameters",
126130
callback_group=MutuallyExclusiveCallbackGroup(),
@@ -175,7 +179,7 @@ async def _get_param(node_name: str, name: str) -> ParameterValue:
175179
"""Internal helper function for get_param"""
176180

177181
assert _node is not None
178-
client = _node.create_client(
182+
client: Client = _node.create_client(
179183
GetParameters,
180184
f"{node_name}/get_parameters",
181185
callback_group=MutuallyExclusiveCallbackGroup(),
@@ -250,7 +254,7 @@ async def get_param_names(params_glob: str | None) -> list[str]:
250254
if node_name == _node.get_fully_qualified_name():
251255
continue
252256

253-
client = _node.create_client(
257+
client: Client = _node.create_client(
254258
ListParameters,
255259
f"{node_name}/list_parameters",
256260
callback_group=MutuallyExclusiveCallbackGroup(),

rosapi/src/rosapi/proxy.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3232
# POSSIBILITY OF SUCH DAMAGE.
3333

34+
from ros2action.api import get_action_names_and_types
3435
from ros2interface.api import type_completer
3536
from ros2node.api import (
3637
get_node_names,
@@ -264,3 +265,16 @@ def get_service_node(queried_type, services_glob, include_hidden=False):
264265
return node_name[0]
265266
else:
266267
return ""
268+
269+
270+
def get_action_type(action_name, include_hidden=False):
271+
"""Returns the type of the specified ROS action.
272+
If the action does not exist, an empty string is returned."""
273+
274+
names_and_types = get_action_names_and_types(node=_node, include_hidden_actions=include_hidden)
275+
276+
for name, types in names_and_types:
277+
if name == action_name and types:
278+
return types[0]
279+
280+
return ""

rosapi/test/test_typedefs.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ def test_handle_sequences(self):
4141
# should be None for an atomic
4242
self.assertEqual(actual_typedef, None)
4343

44+
def test_skip_private_slots_in_array_info(self):
45+
# create a fake msg with one real field ('data') and one internal slot
46+
class MockMsg:
47+
__slots__ = ["_check_fields", "_important_data"]
48+
_fields_and_field_types = {"important_data": "int32"}
49+
50+
def __init__(self):
51+
self._important_data = 123
52+
self._check_fields = None
53+
54+
inst = MockMsg()
55+
# call the private helper directly
56+
names, types, lens, examples = objectutils._handle_array_information(inst)
57+
58+
# should only see our single '_important_data' field
59+
self.assertEqual(names, ["important_data"])
60+
# raw type should be 'int32' (no array)
61+
self.assertEqual(types, ["int32"])
62+
self.assertEqual(lens, [-1])
63+
# example should be the stringified value of 123
64+
self.assertEqual(examples, ["123"])
65+
4466

4567
if __name__ == "__main__":
4668
unittest.main()

rosapi_msgs/CHANGELOG.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
Changelog for package rosapi_msgs
33
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44

5+
2.3.0 (2025-05-19)
6+
------------------
7+
* Add services to return Action interface details (`#1021 <https://github.com/RobotWebTools/rosbridge_suite/issues/1021>`_)
8+
* Contributors: David Fernàndez López
9+
510
2.2.0 (2025-02-26)
611
------------------
712
* New async rosapi params module implementation (`#1001 <https://github.com/RobotWebTools/rosbridge_suite/issues/1001>`_)

rosapi_msgs/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ rosidl_generate_interfaces(${PROJECT_NAME}
2626
srv/ActionGoalDetails.srv
2727
srv/ActionResultDetails.srv
2828
srv/ActionFeedbackDetails.srv
29+
srv/ActionType.srv
2930
srv/Services.srv
3031
srv/ServicesForType.srv
3132
srv/ServiceType.srv

0 commit comments

Comments
 (0)