diff --git a/ROSBRIDGE_PROTOCOL.md b/ROSBRIDGE_PROTOCOL.md index e58850d61..4a7e82468 100644 --- a/ROSBRIDGE_PROTOCOL.md +++ b/ROSBRIDGE_PROTOCOL.md @@ -93,12 +93,12 @@ ROS operations: * **call_service** - a service call * **service_response** - a service response * Actions: - * **advertise_action** - advertise an external action server - * **unadvertise_action** - unadvertise an external action server - * **send_action_goal** - a goal sent to an action server - * **cancel_action_goal** - cancel an in-progress action goal - * **action_feedback** - feedback messages from an action server - * **action_result** - an action result + * **advertise_action** - advertise an external action server + * **unadvertise_action** - unadvertise an external action server + * **send_action_goal** - a goal sent to an action server + * **cancel_action_goal** - cancel an in-progress action goal + * **action_feedback** - feedback messages from an action server + * **action_result** - an action result In general, actions or operations that the client takes (such as publishing and subscribing) have opcodes which are verbs (subscribe, call_service, unadvertise diff --git a/rosapi/package.xml b/rosapi/package.xml index af550dc72..c81c9b06f 100644 --- a/rosapi/package.xml +++ b/rosapi/package.xml @@ -17,7 +17,7 @@ Jihoon Lee Foxglove - ament_cmake_ros + ament_python rosapi_msgs builtin_interfaces @@ -41,7 +41,7 @@ rmw_dds_common - ament_cmake + ament_python diff --git a/rosapi/src/rosapi/__init__.py b/rosapi/resource/rosapi similarity index 100% rename from rosapi/src/rosapi/__init__.py rename to rosapi/resource/rosapi diff --git a/rosapi/rosapi/__init__.py b/rosapi/rosapi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rosapi/src/rosapi/glob_helper.py b/rosapi/rosapi/glob_helper.py similarity index 100% rename from rosapi/src/rosapi/glob_helper.py rename to rosapi/rosapi/glob_helper.py diff --git a/rosapi/src/rosapi/objectutils.py b/rosapi/rosapi/objectutils.py similarity index 99% rename from rosapi/src/rosapi/objectutils.py rename to rosapi/rosapi/objectutils.py index 0c981c051..7a31a0ad5 100644 --- a/rosapi/src/rosapi/objectutils.py +++ b/rosapi/rosapi/objectutils.py @@ -35,9 +35,10 @@ import logging import re -from rosapi.stringify_field_types import stringify_field_types from rosbridge_library.internal import ros_loader +from rosapi.stringify_field_types import stringify_field_types + # Keep track of atomic types and special types atomics = [ "bool", diff --git a/rosapi/src/rosapi/params.py b/rosapi/rosapi/params.py similarity index 98% rename from rosapi/src/rosapi/params.py rename to rosapi/rosapi/params.py index 988324895..209ab4f90 100644 --- a/rosapi/src/rosapi/params.py +++ b/rosapi/rosapi/params.py @@ -40,6 +40,7 @@ from rclpy.parameter import get_parameter_value from ros2node.api import get_absolute_node_name from ros2param.api import call_get_parameters, call_set_parameters + from rosapi.proxy import get_nodes """ Methods to interact with the param server. Values have to be passed @@ -75,7 +76,9 @@ def init(parent_node_name): parent_node_basename = parent_node_name.split("/")[-1] param_node_name = f"{parent_node_basename}_params" _node = rclpy.create_node( - param_node_name, cli_args=["--ros-args", "-r", f"__node:={param_node_name}"] + param_node_name, + cli_args=["--ros-args", "-r", f"__node:={param_node_name}"], + start_parameter_services=False, ) _parent_node_name = get_absolute_node_name(parent_node_name) diff --git a/rosapi/src/rosapi/proxy.py b/rosapi/rosapi/proxy.py similarity index 100% rename from rosapi/src/rosapi/proxy.py rename to rosapi/rosapi/proxy.py diff --git a/rosapi/scripts/rosapi_node b/rosapi/rosapi/rosapi_node.py similarity index 100% rename from rosapi/scripts/rosapi_node rename to rosapi/rosapi/rosapi_node.py diff --git a/rosapi/src/rosapi/stringify_field_types.py b/rosapi/rosapi/stringify_field_types.py similarity index 100% rename from rosapi/src/rosapi/stringify_field_types.py rename to rosapi/rosapi/stringify_field_types.py diff --git a/rosapi/setup.cfg b/rosapi/setup.cfg new file mode 100644 index 000000000..7b315f355 --- /dev/null +++ b/rosapi/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rosapi +[install] +install_scripts=$base/lib/rosapi diff --git a/rosapi/setup.py b/rosapi/setup.py new file mode 100644 index 000000000..d86d5febe --- /dev/null +++ b/rosapi/setup.py @@ -0,0 +1,29 @@ +import os + +from setuptools import find_packages, setup + +package_name = "rosapi" + +setup( + name=package_name, + version="1.3.2", + packages=find_packages(exclude=["test"]), + data_files=[ + # Install marker file in the package index + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + # Include our package.xml file + (os.path.join("share", package_name), ["package.xml"]), + ], + install_requires=["setuptools"], + zip_safe=True, + author="Jonathan Mace", + author_email="jonathan.c.mace@gmail.com", + maintainer="Jihoon Lee, Foxglove", + maintainer_email="jihoonlee.in@gmail.com, ros-tooling@foxglove.dev", + description="Provides service calls for getting ros meta-information, like list of topics, services, params, etc.", + license="BSD", + tests_require=["pytest"], + entry_points={ + "console_scripts": ["rosapi_node = rosapi.rosapi_node:main"], + }, +) diff --git a/rosapi/test/test_stringify_field_types.py b/rosapi/test/test_stringify_field_types.py index ca551bbd0..b6e36bc63 100644 --- a/rosapi/test/test_stringify_field_types.py +++ b/rosapi/test/test_stringify_field_types.py @@ -1,9 +1,10 @@ #!/usr/bin/env python import unittest -from rosapi.stringify_field_types import stringify_field_types from rosbridge_library.internal.ros_loader import InvalidModuleException +from rosapi.stringify_field_types import stringify_field_types + class TestObjectUtils(unittest.TestCase): def test_stringify_field_types(self): diff --git a/rosbridge_library/src/rosbridge_library/internal/message_conversion.py b/rosbridge_library/src/rosbridge_library/internal/message_conversion.py index d3361cfa3..a0d833ce2 100644 --- a/rosbridge_library/src/rosbridge_library/internal/message_conversion.py +++ b/rosbridge_library/src/rosbridge_library/internal/message_conversion.py @@ -90,7 +90,8 @@ ] ros_header_types = ["Header", "std_msgs/Header", "roslib/Header"] ros_binary_types = ["uint8[]", "char[]", "sequence", "sequence"] -list_tokens = re.compile("<(.+?)>") +# Remove the list type wrapper, and length specifier, from rostypes i.e. sequence +list_tokens = re.compile(r"<(.+?)(, \d+)?>") bounded_array_tokens = re.compile(r"(.+)\[.*\]") ros_binary_types_list_braces = [ ("uint8[]", re.compile(r"uint8\[[^\]]*\]")), @@ -392,7 +393,6 @@ def _to_object_inst(msg, rostype, roottype, clock, inst, stack): inst.stamp = clock.now().to_msg() inst_fields = inst.get_fields_and_field_types() - for field_name in msg: # Add this field to the field stack field_stack = stack + [field_name] diff --git a/rosbridge_library/src/rosbridge_library/internal/subscribers.py b/rosbridge_library/src/rosbridge_library/internal/subscribers.py index 0bb94a809..a64c90b7f 100644 --- a/rosbridge_library/src/rosbridge_library/internal/subscribers.py +++ b/rosbridge_library/src/rosbridge_library/internal/subscribers.py @@ -176,6 +176,11 @@ def subscribe(self, client_id, callback): # In any case, the first message is handled using new_sub_callback, # which adds the new callback to the subscriptions dictionary. self.new_subscriptions.update({client_id: callback}) + infos = self.node_handle.get_publishers_info_by_topic(self.topic) + if any(pub.qos_profile.durability == DurabilityPolicy.TRANSIENT_LOCAL for pub in infos): + self.qos.durability = DurabilityPolicy.TRANSIENT_LOCAL + if any(pub.qos_profile.reliability == ReliabilityPolicy.BEST_EFFORT for pub in infos): + self.qos.reliability = ReliabilityPolicy.BEST_EFFORT if self.new_subscriber is None: self.new_subscriber = self.node_handle.create_subscription( self.msg_class, @@ -196,7 +201,7 @@ def unsubscribe(self, client_id): with self.rlock: if client_id in self.new_subscriptions: del self.new_subscriptions[client_id] - else: + if client_id in self.subscriptions: del self.subscriptions[client_id] def has_subscribers(self): diff --git a/rosbridge_library/test/internal/test_message_conversion.py b/rosbridge_library/test/internal/test_message_conversion.py index 9ccba4f05..f837e6275 100755 --- a/rosbridge_library/test/internal/test_message_conversion.py +++ b/rosbridge_library/test/internal/test_message_conversion.py @@ -316,3 +316,21 @@ def test_float32_msg(rostype, data): ints = list(map(int, range(0, 16))) ret = test_float32_msg(rostype, ints) np.testing.assert_array_equal(ret, np.array(ints)) + + # Test a float32 array with a length with non-numeric characters in it + def test_float32_complexboundedarray(self): + def test_nestedboundedarray_msg(rostype, data): + msg = {"data": {"data": data}} + inst = ros_loader.get_message_instance(rostype) + c.populate_instance(msg, inst) + self.validate_instance(inst) + return inst.data + + for msgtype in ["TestNestedBoundedArray"]: + rostype = "rosbridge_test_msgs/" + msgtype + + # From List[float] + floats = list(map(float, range(0, 16))) + ret = test_nestedboundedarray_msg(rostype, floats) + + self.assertEqual(c._from_inst(ret, rostype), {"data": floats}) diff --git a/rosbridge_server/package.xml b/rosbridge_server/package.xml index 07596660c..2278e926e 100644 --- a/rosbridge_server/package.xml +++ b/rosbridge_server/package.xml @@ -14,8 +14,7 @@ Jihoon Lee Foxglove - ament_cmake - ament_cmake_ros + ament_python python3-tornado python3-twisted @@ -34,6 +33,6 @@ std_srvs - ament_cmake + ament_python diff --git a/rosbridge_server/resource/rosbridge_server b/rosbridge_server/resource/rosbridge_server new file mode 100644 index 000000000..e69de29bb diff --git a/rosbridge_server/src/rosbridge_server/__init__.py b/rosbridge_server/rosbridge_server/__init__.py similarity index 100% rename from rosbridge_server/src/rosbridge_server/__init__.py rename to rosbridge_server/rosbridge_server/__init__.py diff --git a/rosbridge_server/src/rosbridge_server/client_manager.py b/rosbridge_server/rosbridge_server/client_manager.py similarity index 100% rename from rosbridge_server/src/rosbridge_server/client_manager.py rename to rosbridge_server/rosbridge_server/client_manager.py diff --git a/rosbridge_server/scripts/rosbridge_websocket.py b/rosbridge_server/rosbridge_server/rosbridge_websocket.py similarity index 98% rename from rosbridge_server/scripts/rosbridge_websocket.py rename to rosbridge_server/rosbridge_server/rosbridge_websocket.py index bfa968d15..49d2a52b6 100755 --- a/rosbridge_server/scripts/rosbridge_websocket.py +++ b/rosbridge_server/rosbridge_server/rosbridge_websocket.py @@ -337,7 +337,13 @@ def main(args=None): executor = rclpy.executors.SingleThreadedExecutor() executor.add_node(node) - spin_callback = PeriodicCallback(lambda: executor.spin_once(timeout_sec=0.01), 1) + + def spin_ros(): + executor.spin_once(timeout_sec=0.01) + if not rclpy.ok(): + shutdown_hook() + + spin_callback = PeriodicCallback(spin_ros, 1) spin_callback.start() try: start_hook() diff --git a/rosbridge_server/src/rosbridge_server/websocket_handler.py b/rosbridge_server/rosbridge_server/websocket_handler.py similarity index 100% rename from rosbridge_server/src/rosbridge_server/websocket_handler.py rename to rosbridge_server/rosbridge_server/websocket_handler.py diff --git a/rosbridge_server/scripts/rosbridge_websocket b/rosbridge_server/scripts/rosbridge_websocket deleted file mode 120000 index 647069442..000000000 --- a/rosbridge_server/scripts/rosbridge_websocket +++ /dev/null @@ -1 +0,0 @@ -rosbridge_websocket.py \ No newline at end of file diff --git a/rosbridge_server/setup.cfg b/rosbridge_server/setup.cfg new file mode 100644 index 000000000..ffe2ab328 --- /dev/null +++ b/rosbridge_server/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/rosbridge_server +[install] +install_scripts=$base/lib/rosbridge_server diff --git a/rosbridge_server/setup.py b/rosbridge_server/setup.py new file mode 100644 index 000000000..3f98a8e28 --- /dev/null +++ b/rosbridge_server/setup.py @@ -0,0 +1,35 @@ +import os +from glob import glob + +from setuptools import find_packages, setup + +package_name = "rosbridge_server" + +setup( + name=package_name, + version="1.3.2", + packages=find_packages(exclude=["test"]), + data_files=[ + # Install marker file in the package index + ("share/ament_index/resource_index/packages", ["resource/" + package_name]), + # Include our package.xml file + (os.path.join("share", package_name), ["package.xml"]), + # Include all launch files. + ( + os.path.join("share", package_name, "launch"), + glob(os.path.join("launch", "*launch.[pxy][yma]*")), + ), + ], + install_requires=["setuptools"], + zip_safe=True, + author="Jonathan Mace", + author_email="jonathan.c.mace@gmail.com", + maintainer="Jihoon Lee, Foxglove", + maintainer_email="jihoonlee.in@gmail.com, ros-tooling@foxglove.dev", + description="A WebSocket interface to rosbridge.", + license="BSD", + tests_require=["pytest"], + entry_points={ + "console_scripts": ["rosbridge_websocket = rosbridge_server.rosbridge_websocket:main"], + }, +) diff --git a/rosbridge_test_msgs/CMakeLists.txt b/rosbridge_test_msgs/CMakeLists.txt index e327c419d..4ef1e87df 100644 --- a/rosbridge_test_msgs/CMakeLists.txt +++ b/rosbridge_test_msgs/CMakeLists.txt @@ -20,6 +20,7 @@ rosidl_generate_interfaces(${PROJECT_NAME} msg/TestUInt8FixedSizeArray16.msg msg/TestFloat32Array.msg msg/TestFloat32BoundedArray.msg + msg/TestNestedBoundedArray.msg srv/AddTwoInts.srv srv/SendBytes.srv srv/TestArrayRequest.srv diff --git a/rosbridge_test_msgs/msg/TestNestedBoundedArray.msg b/rosbridge_test_msgs/msg/TestNestedBoundedArray.msg new file mode 100644 index 000000000..a35d82372 --- /dev/null +++ b/rosbridge_test_msgs/msg/TestNestedBoundedArray.msg @@ -0,0 +1 @@ +TestFloat32BoundedArray data