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