Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/ros2' into feature/client_timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
bjsowa committed Jan 8, 2025
2 parents aa7bdbd + a000226 commit 7745904
Show file tree
Hide file tree
Showing 38 changed files with 143 additions and 96 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ jobs:
# Test supported ROS 2 distributions
# https://docs.ros.org/en/rolling/Releases.html
# NOTE: Humble does not work on the `ros2` branch, so it is tested in its own branch.
- ros: iron
os: ubuntu-22.04
- ros: jazzy
os: ubuntu-24.04
- ros: rolling
Expand Down
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ rosbridge_suite
===============

[![ROS Humble version](https://img.shields.io/ros/v/humble/rosbridge_suite)](https://index.ros.org/p/rosbridge_suite/github-RobotWebTools-rosbridge_suite/#humble)
[![ROS Iron version](https://img.shields.io/ros/v/iron/rosbridge_suite)](https://index.ros.org/p/rosbridge_suite/github-RobotWebTools-rosbridge_suite/#iron)
[![ROS Jazzy version](https://img.shields.io/ros/v/jazzy/rosbridge_suite)](https://index.ros.org/p/rosbridge_suite/github-RobotWebTools-rosbridge_suite/#jazzy)
[![ROS Rolling version](https://img.shields.io/ros/v/rolling/rosbridge_suite)](https://index.ros.org/p/rosbridge_suite/github-RobotWebTools-rosbridge_suite/#rolling)

Expand Down Expand Up @@ -56,11 +55,10 @@ Releasing requires push access to [RobotWebTools/rosbridge_suite](https://github
3. Run `catkin_prepare_release --bump [major/minor/patch]` to bump versions in package.xml and push changes to origin.
4. Run bloom-release commands to create PRs to update rosdistro:
- `bloom-release --rosdistro humble rosbridge_suite`
- `bloom-release --rosdistro iron rosbridge_suite`
- `bloom-release --rosdistro jazzy rosbridge_suite`
- `bloom-release --rosdistro rolling rosbridge_suite`

Note that right now, the Humble release is tracked in the `humble` branch, while Iron and later are tracked in the `ros2` branch.
Note that right now, the Humble release is tracked in the `humble` branch, while Jazzy and later are tracked in the `ros2` branch.

Once the PRs are merged, packages will be available for each distro after the next sync.
Build/sync status can be viewed at: [humble](http://repo.ros2.org/status_page/ros_humble_default.html), [iron](http://repo.ros2.org/status_page/ros_iron_default.html), [jazzy](http://repo.ros2.org/status_page/ros_jazzy_default.html), and [rolling](http://repo.ros2.org/status_page/ros_rolling_default.html).
Build/sync status can be viewed at: [humble](http://repo.ros2.org/status_page/ros_humble_default.html), [jazzy](http://repo.ros2.org/status_page/ros_jazzy_default.html), and [rolling](http://repo.ros2.org/status_page/ros_rolling_default.html).
3 changes: 3 additions & 0 deletions rosapi/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ if(BUILD_TESTING)
find_package(ament_cmake_pytest REQUIRED)
ament_add_pytest_test(${PROJECT_NAME}_test_stringify_field_types test/test_stringify_field_types.py)
ament_add_pytest_test(${PROJECT_NAME}_test_typedefs test/test_typedefs.py)

find_package(ament_cmake_mypy REQUIRED)
ament_mypy()
endif()
1 change: 1 addition & 0 deletions rosapi/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<exec_depend>rosgraph</exec_depend>
-->

<test_depend>ament_cmake_mypy</test_depend>
<test_depend>ament_cmake_pytest</test_depend>
<test_depend>sensor_msgs</test_depend>
<test_depend>shape_msgs</test_depend>
Expand Down
7 changes: 7 additions & 0 deletions rosapi/scripts/rosapi_node
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ from rosapi_msgs.srv import (
GetROSVersion,
GetTime,
HasParam,
Interfaces,
MessageDetails,
NodeDetails,
Nodes,
Expand Down Expand Up @@ -89,6 +90,7 @@ class Rosapi(Node):
full_name = self.get_namespace() + "/" + self.get_name()
params.init(full_name)
self.create_service(Topics, "/rosapi/topics", self.get_topics)
self.create_service(Interfaces, "/rosapi/interfaces", self.get_interfaces)
self.create_service(TopicsForType, "/rosapi/topics_for_type", self.get_topics_for_type)
self.create_service(
TopicsAndRawTypes,
Expand Down Expand Up @@ -137,6 +139,11 @@ class Rosapi(Node):
response.topics, response.types = proxy.get_topics_and_types(self.globs.topics)
return response

def get_interfaces(self, request, response):
"""Called by the rosapi/Types service. Returns a list of all the types in the system."""
response.interfaces = proxy.get_interfaces()
return response

def get_topics_for_type(self, request, response):
"""Called by the rosapi/TopicsForType service. Returns a list of all the topics that are publishing a given type"""
response.topics = proxy.get_topics_for_type(request.type, self.globs.topics)
Expand Down
6 changes: 6 additions & 0 deletions rosapi/src/rosapi/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from ros2interface.api import type_completer
from ros2node.api import (
get_node_names,
get_publisher_info,
Expand Down Expand Up @@ -60,6 +61,11 @@ def get_topics(topics_glob, include_hidden=False):
return filter_globs(topics_glob, topic_names)


def get_interfaces():
"""Returns a list of all the types in the ROS system"""
return type_completer()


def get_topics_and_types(topics_glob, include_hidden=False):
return get_publications_and_types(
topics_glob, get_topic_names_and_types, include_hidden_topics=include_hidden
Expand Down
1 change: 1 addition & 0 deletions rosapi_msgs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rosidl_generate_interfaces(${PROJECT_NAME}
srv/GetROSVersion.srv
srv/GetTime.srv
srv/HasParam.srv
srv/Interfaces.srv
srv/MessageDetails.srv
srv/Nodes.srv
srv/NodeDetails.srv
Expand Down
2 changes: 2 additions & 0 deletions rosapi_msgs/srv/Interfaces.srv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
string[] interfaces
3 changes: 3 additions & 0 deletions rosbridge_library/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,7 @@ if (BUILD_TESTING)
find_package(ament_cmake_pytest REQUIRED)
ament_add_pytest_test(test_capabilities "test/capabilities/")
ament_add_pytest_test(test_internal "test/internal/")

find_package(ament_cmake_mypy REQUIRED)
ament_mypy()
endif()
1 change: 1 addition & 0 deletions rosbridge_library/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

<test_depend>rosbridge_test_msgs</test_depend>
<test_depend>action_msgs</test_depend>
<test_depend>ament_cmake_mypy</test_depend>
<test_depend>ament_cmake_pytest</test_depend>
<test_depend>builtin_interfaces</test_depend>
<test_depend>diagnostic_msgs</test_depend>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
import fnmatch
from typing import Any

import rclpy
from action_msgs.msg import GoalStatus
from rclpy.action import ActionServer
from rclpy.action.server import CancelResponse, ServerGoalHandle
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.task import Future
from rosbridge_library.capability import Capability
from rosbridge_library.internal import message_conversion
from rosbridge_library.internal.ros_loader import get_action_class
Expand All @@ -51,9 +51,9 @@ class AdvertisedActionHandler:
def __init__(
self, action_name: str, action_type: str, protocol: Protocol, sleep_time: float = 0.001
) -> None:
self.goal_futures = {}
self.goal_handles = {}
self.goal_statuses = {}
self.goal_futures: dict[str, Future] = {}
self.goal_handles: dict[str, Any] = {}
self.goal_statuses: dict[str, GoalStatus] = {}

self.action_name = action_name
self.action_type = action_type
Expand All @@ -79,7 +79,7 @@ async def execute_callback(self, goal: Any) -> Any:
# generate a unique ID
goal_id = f"action_goal:{self.action_name}:{self.next_id()}"

def done_callback(fut: rclpy.task.Future) -> None:
def done_callback(fut: Future) -> None:
if fut.cancelled():
goal.abort()
self.protocol.log("info", f"Aborted goal {goal_id}")
Expand All @@ -94,7 +94,7 @@ def done_callback(fut: rclpy.task.Future) -> None:
else:
goal.abort()

future = rclpy.task.Future()
future: Future = Future()
future.add_done_callback(done_callback)
self.goal_handles[goal_id] = goal
self.goal_futures[goal_id] = future
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import threading
from datetime import datetime
import time

from rosbridge_library.capability import Capability

Expand Down Expand Up @@ -30,7 +30,7 @@ def spam(self):
# }
# },
# ...
lists = {}
lists: dict[str, dict] = {}

def __init__(self):
"""Create singleton instance"""
Expand Down Expand Up @@ -84,13 +84,13 @@ def __init__(self, protocol):
# 4.b) pass the reconstructed message string to protocol.incoming() # protocol.incoming is checking message fields by itself, so no need to do this before passing the reconstructed message to protocol
# 4.c) remove the fragment list to free up memory
def defragment(self, message):
now = datetime.now()
now = time.monotonic()

if self.received_fragments is not None:
for id in self.received_fragments.keys():
time_diff = now - self.received_fragments[id]["timestamp_last_append"]
if (
time_diff.total_seconds() > self.fragment_timeout
time_diff > self.fragment_timeout
and not self.received_fragments[id]["is_reconstructing"]
):
log_msg = ["fragment list ", str(id), " timed out.."]
Expand Down Expand Up @@ -188,15 +188,15 @@ def defragment(self, message):
log_msg = "".join(log_msg)
self.protocol.log("debug", log_msg)

duration = datetime.now() - now
duration = time.monotonic() - now

# Pass the reconstructed message to rosbridge
self.protocol.incoming(reconstructed_msg)
log_msg = ["reconstructed message (ID:" + str(msg_id) + ") from "]
log_msg.extend([str(msg_total), " fragments. "])
# cannot access msg.data if message is a service_response or else!
# log_msg += "[message length: " + str(len(str(json.loads(reconstructed_msg)["msg"]["data"]))) +"]"
log_msg.extend(["[duration: ", str(duration.total_seconds()), " s]"])
log_msg.extend(["[duration: ", str(duration), " s]"])
log_msg = "".join(log_msg)
self.protocol.log("info", log_msg)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import fnmatch
from functools import partial
from threading import Thread
from typing import Any

from action_msgs.msg import GoalStatus
from rosbridge_library.capability import Capability
Expand All @@ -52,7 +53,7 @@ class SendActionGoal(Capability):
cancel_action_goal_msg_fields = [(True, "action", str)]

actions_glob = None
client_handler_list = {}
client_handler_list: dict[str, ActionClientHandler] = {}

def __init__(self, protocol: Protocol) -> None:
# Call superclass constructor
Expand Down Expand Up @@ -182,7 +183,7 @@ def _failure(self, cid: str, action: str, exc: Exception) -> None:
outgoing_message["id"] = cid
self.protocol.send(outgoing_message)

def _feedback(self, cid: str, action: str, message: dict) -> None:
def _feedback(self, cid: str, action: str, message: Any) -> None:
outgoing_message = {
"op": "action_feedback",
"action": action,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@
from rosbridge_library.internal.subscription_modifiers import MessageHandler

try:
from ujson import dumps as encode_json
from ujson import dumps as encode_json # type: ignore[import-untyped]
except ImportError:
try:
from simplejson import dumps as encode_json
from simplejson import dumps as encode_json # type: ignore[import-untyped]
except ImportError:
from json import dumps as encode_json

Expand Down
16 changes: 8 additions & 8 deletions rosbridge_library/src/rosbridge_library/internal/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

import time
from threading import Thread
from typing import Any, Callable, Optional, Union
from typing import Any, Callable, Optional

from rclpy.action import ActionClient
from rclpy.expand_topic_name import expand_topic_name
Expand All @@ -59,9 +59,9 @@ def __init__(
action: str,
action_type: str,
args: dict,
success_callback: Callable[[str, str, int, bool, dict], None],
error_callback: Callable[[str, str, Exception], None],
feedback_callback: Callable[[str, str, dict], None],
success_callback: Callable[[dict], None],
error_callback: Callable[[Exception], None],
feedback_callback: Optional[Callable[[dict], None]],
node_handle: Node,
) -> None:
"""
Expand Down Expand Up @@ -90,12 +90,11 @@ def __init__(
self.error = error_callback
self.feedback = feedback_callback
self.node_handle = node_handle
self.send_goal_helper = None
self.send_goal_helper = SendGoal()

def run(self) -> None:
try:
# Call the service and pass the result to the success handler
self.send_goal_helper = SendGoal()
self.success(
self.send_goal_helper.send_goal(
self.node_handle,
Expand All @@ -110,7 +109,7 @@ def run(self) -> None:
self.error(e)


def args_to_action_goal_instance(action: str, inst: Any, args: Union[list, dict]) -> Any:
def args_to_action_goal_instance(action: str, inst: Any, args: list | dict | None) -> Any:
""" "
Populate an action goal instance with the provided args
Expand Down Expand Up @@ -142,6 +141,7 @@ def get_result_cb(self, future: Future) -> None:

def goal_response_cb(self, future: Future) -> None:
self.goal_handle = future.result()
assert self.goal_handle is not None
if not self.goal_handle.accepted:
raise Exception("Action goal was rejected")
result_future = self.goal_handle.get_result_async()
Expand All @@ -156,7 +156,7 @@ def send_goal(
action: str,
action_type: str,
args: Optional[dict] = None,
feedback_cb: Optional[Callable[[str, str, dict], None]] = None,
feedback_cb: Optional[Callable[[dict], None]] = None,
) -> dict:
# Given the action name and type, fetch a request instance
action_name = expand_topic_name(action, node_handle.get_name(), node_handle.get_namespace())
Expand Down
10 changes: 5 additions & 5 deletions rosbridge_library/src/rosbridge_library/internal/ros_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@
"""

# Variable containing the loaded classes
_loaded_msgs = {}
_loaded_srvs = {}
_loaded_actions = {}
_loaded_msgs: dict[str, Any] = {}
_loaded_srvs: dict[str, Any] = {}
_loaded_actions: dict[str, Any] = {}
_msgs_lock = Lock()
_srvs_lock = Lock()
_actions_lock = Lock()
Expand Down Expand Up @@ -185,7 +185,7 @@ def _get_class(typestring: str, subname: str, cache: Dict[str, Any], lock: Lock)
return cls


def _load_class(modname: str, subname: str, classname: str) -> None:
def _load_class(modname: str, subname: str, classname: str) -> Any:
"""Loads the manifest and imports the module that contains the specified
type.
Expand Down Expand Up @@ -220,7 +220,7 @@ def _splittype(typestring: str) -> Tuple[str, str]:
raise InvalidTypeStringException(typestring)


def _add_to_cache(cache: Dict[str, Any], lock: Lock, key: str, value: any) -> None:
def _add_to_cache(cache: Dict[str, Any], lock: Lock, key: str, value: Any) -> None:
lock.acquire()
cache[key] = value
lock.release()
Expand Down
16 changes: 8 additions & 8 deletions rosbridge_library/src/rosbridge_library/internal/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ def __init__(
service: str,
args: dict,
timeout: float,
success_callback: Callable[[str, str, int, bool, Any], None],
error_callback: Callable[[str, str, Exception], None],
success_callback: Callable[[dict], None],
error_callback: Callable[[Exception], None],
node_handle: Node,
) -> None:
"""Create a service caller for the specified service. Use start()
Expand Down Expand Up @@ -104,7 +104,7 @@ def run(self) -> None:
self.error(e)


def args_to_service_request_instance(service: str, inst: Any, args: dict) -> Any:
def args_to_service_request_instance(service: str, inst: Any, args: list | dict | None) -> Any:
"""Populate a service request instance with the provided args
args can be a dictionary of values, or a list, or None
Expand Down Expand Up @@ -133,14 +133,14 @@ def call_service(
service = expand_topic_name(service, node_handle.get_name(), node_handle.get_namespace())

service_names_and_types = dict(node_handle.get_service_names_and_types())
service_type = service_names_and_types.get(service)
if service_type is None:
service_types = service_names_and_types.get(service)
if service_types is None:
raise InvalidServiceException(service)

# service_type is a tuple of types at this point; only one type is supported.
if len(service_type) > 1:
node_handle.get_logger().warning(f"More than one service type detected: {service_type}")
service_type = service_type[0]
if len(service_types) > 1:
node_handle.get_logger().warning(f"More than one service type detected: {service_types}")
service_type = service_types[0]

service_class = get_service_class(service_type)
inst = get_service_request_instance(service_type)
Expand Down
Loading

0 comments on commit 7745904

Please sign in to comment.