Skip to content

Commit 17912c2

Browse files
authored
Merge branch 'ros2' into dependabot/github_actions/actions/upload-artifact-7
2 parents 99acae3 + 6dc7144 commit 17912c2

File tree

7 files changed

+57
-71
lines changed

7 files changed

+57
-71
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ jobs:
2828
include:
2929
# Test supported ROS 2 distributions
3030
# https://docs.ros.org/en/rolling/Releases.html
31-
# NOTE: Humble and Jazzy do not work on the `ros2` branch, so they are tested in their own branches.
32-
- ros: kilted
33-
os: ubuntu-24.04
31+
# NOTE: Humble, Jazzy and Kilted do not work on the `ros2` branch, so they are tested in their own branches.
3432
- ros: rolling
3533
os: ubuntu-24.04
3634

rosbridge_library/package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ROS action, like subscribe, publish, call service, and interact with params.
2626
<exec_depend>python3-ujson</exec_depend>
2727
<exec_depend>rcl_interfaces</exec_depend>
2828
<exec_depend>rclpy</exec_depend>
29+
<exec_depend>rosidl_pycommon</exec_depend>
2930

3031
<test_depend>action_msgs</test_depend>
3132
<test_depend>ament_cmake_mypy</test_depend>

rosbridge_library/src/rosbridge_library/capabilities/advertise_action.py

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@
3333
from __future__ import annotations
3434

3535
import fnmatch
36-
from typing import TYPE_CHECKING, Generic, cast
36+
from typing import TYPE_CHECKING, Any, Generic, cast
3737

3838
from action_msgs.msg import GoalStatus
3939
from rclpy.action import ActionServer
40-
from rclpy.action.server import CancelResponse, ServerGoalHandle
40+
from rclpy.action.server import CancelResponse
4141
from rclpy.callback_groups import ReentrantCallbackGroup
4242
from rclpy.task import Future
4343

@@ -47,23 +47,29 @@
4747
from rosbridge_library.internal.type_support import (
4848
ROSActionFeedbackT,
4949
ROSActionGoalT,
50+
ROSActionImplT,
5051
ROSActionResultT,
5152
ROSMessage,
5253
)
5354

5455
if TYPE_CHECKING:
56+
from rclpy.action.server import ServerGoalHandle
57+
5558
from rosbridge_library.protocol import Protocol
5659

5760

58-
class AdvertisedActionHandler(Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]):
61+
class AdvertisedActionHandler(
62+
Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT]
63+
):
5964
id_counter = 1
6065

6166
def __init__(
6267
self, action_name: str, action_type: str, protocol: Protocol, sleep_time: float = 0.001
6368
) -> None:
6469
self.goal_futures: dict[str, Future[ROSActionResultT]] = {}
6570
self.goal_handles: dict[
66-
str, ServerGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]
71+
str,
72+
ServerGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT],
6773
] = {}
6874
self.goal_statuses: dict[str, int] = {}
6975

@@ -72,15 +78,15 @@ def __init__(
7278
self.protocol = protocol
7379
self.sleep_time = sleep_time
7480
# setup the action
75-
self.action_server: ActionServer[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT] = (
76-
ActionServer(
77-
protocol.node_handle,
78-
get_action_class(action_type),
79-
action_name,
80-
self.execute_callback, # type: ignore[arg-type] # rclpy type hint does not support coroutines
81-
cancel_callback=self.cancel_callback, # type: ignore[arg-type] # rclpy type hint is incorrect
82-
callback_group=ReentrantCallbackGroup(), # https://github.com/ros2/rclpy/issues/834#issuecomment-961331870
83-
)
81+
self.action_server = ActionServer[
82+
ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT
83+
](
84+
protocol.node_handle,
85+
get_action_class(action_type),
86+
action_name,
87+
self.execute_callback, # type: ignore[arg-type] # rclpy type hint does not support coroutines
88+
cancel_callback=self.cancel_callback, # type: ignore[arg-type] # rclpy type hint is incorrect
89+
callback_group=ReentrantCallbackGroup(), # https://github.com/ros2/rclpy/issues/834#issuecomment-961331870
8490
)
8591

8692
def next_id(self) -> int:
@@ -89,7 +95,10 @@ def next_id(self) -> int:
8995
return next_id_value
9096

9197
async def execute_callback(
92-
self, goal: ServerGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]
98+
self,
99+
goal: ServerGoalHandle[
100+
ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT
101+
],
93102
) -> ROSActionResultT:
94103
"""
95104
Execute action goal.
@@ -143,7 +152,10 @@ def done_callback(fut: Future[ROSActionResultT]) -> None:
143152
del self.goal_handles[goal_id]
144153

145154
def cancel_callback(
146-
self, goal: ServerGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]
155+
self,
156+
goal: ServerGoalHandle[
157+
ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT
158+
],
147159
) -> CancelResponse:
148160
"""
149161
Cancel action goal.
@@ -270,8 +282,8 @@ def advertise_action(self, message: dict) -> None:
270282

271283
# setup and store the action information
272284
action_type: str = message["type"]
273-
action_handler: AdvertisedActionHandler[ROSMessage, ROSMessage, ROSMessage] = (
274-
AdvertisedActionHandler(action_name, action_type, self.protocol)
285+
action_handler = AdvertisedActionHandler[ROSMessage, ROSMessage, ROSMessage, Any](
286+
action_name, action_type, self.protocol
275287
)
276288
self.protocol.external_action_list[action_name] = action_handler
277289
self.protocol.log("info", f"Advertised action {action_name}")

rosbridge_library/src/rosbridge_library/capabilities/send_action_goal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def send_action_goal(self, message: dict) -> None:
134134
f_cb = partial(self._feedback, cid, action) if message.get("feedback", False) else None
135135

136136
# Run action client handler in the same thread.
137-
client_handler: ActionClientHandler[ROSMessage, ROSMessage, ROSMessage] = (
137+
client_handler: ActionClientHandler[ROSMessage, ROSMessage, ROSMessage, Any] = (
138138
ActionClientHandler(
139139
trim_action_name(action),
140140
action_type,

rosbridge_library/src/rosbridge_library/internal/actions.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
from rosbridge_library.internal.type_support import (
5050
ROSActionFeedbackT,
5151
ROSActionGoalT,
52+
ROSActionImplT,
5253
ROSActionResultT,
5354
)
5455

@@ -69,7 +70,9 @@ def __init__(self, action_name: str) -> None:
6970
Exception.__init__(self, f"Action {action_name} does not exist")
7071

7172

72-
class ActionClientHandler(Thread, Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]):
73+
class ActionClientHandler(
74+
Thread, Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT]
75+
):
7376
def __init__(
7477
self,
7578
action: str,
@@ -104,9 +107,9 @@ def __init__(
104107
self.error = error_callback
105108
self.feedback = feedback_callback
106109
self.node_handle = node_handle
107-
self.send_goal_helper: SendGoal[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT] = (
108-
SendGoal()
109-
)
110+
self.send_goal_helper = SendGoal[
111+
ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT
112+
]()
110113

111114
def run(self) -> None:
112115
try:
@@ -143,7 +146,7 @@ def args_to_action_goal_instance(inst: ROSMessage, args: list | dict[str, Any] |
143146
populate_instance(msg, inst)
144147

145148

146-
class SendGoal(Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]):
149+
class SendGoal(Generic[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT]):
147150
"""Helper class to send action goals."""
148151

149152
result: GetResultServiceResponse[ROSActionResultT] | Exception | None = None
@@ -152,15 +155,19 @@ def __init__(self, server_timeout_time: float = 1.0, sleep_time: float = 0.001)
152155
self.server_timeout_time = server_timeout_time
153156
self.sleep_time = sleep_time
154157
self.goal_handle: (
155-
ClientGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT] | None
158+
ClientGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT]
159+
| None
156160
) = None
157161
self.goal_canceled = False
158162

159163
def get_result_cb(self, future: Future[GetResultServiceResponse[ROSActionResultT]]) -> None:
160164
self.result = future.result()
161165

162166
def goal_response_cb(
163-
self, future: Future[ClientGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT]]
167+
self,
168+
future: Future[
169+
ClientGoalHandle[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT]
170+
],
164171
) -> None:
165172
self.goal_handle = future.result()
166173
assert self.goal_handle is not None
@@ -193,7 +200,7 @@ def send_goal(
193200
args_to_action_goal_instance(inst, args)
194201

195202
self.result = None
196-
client: ActionClient[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT] = ActionClient(
203+
client = ActionClient[ROSActionGoalT, ROSActionResultT, ROSActionFeedbackT, ROSActionImplT](
197204
node_handle, action_class, action_name
198205
)
199206
if not client.wait_for_server(timeout_sec=self.server_timeout_time):

rosbridge_library/src/rosbridge_library/internal/type_support.py

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,37 +32,13 @@
3232

3333
from __future__ import annotations
3434

35-
from typing import Protocol, TypeVar, runtime_checkable
35+
from typing import Any, TypeAlias, TypeVar
3636

37+
from rosidl_pycommon.interface_base_classes import BaseAction, BaseImpl, BaseMessage, BaseService
3738

38-
@runtime_checkable
39-
class ROSMessage(Protocol):
40-
"""Protocol for ROS message types."""
41-
42-
__slots__: list[str]
43-
_fields_and_field_types: dict[str, str]
44-
45-
def get_fields_and_field_types(self) -> dict[str, str]:
46-
"""Return a dictionary of field names to field types."""
47-
48-
49-
@runtime_checkable
50-
class ROSService(Protocol):
51-
"""Protocol for ROS service types."""
52-
53-
Request: type[ROSMessage]
54-
Response: type[ROSMessage]
55-
Event: type[ROSMessage]
56-
57-
58-
@runtime_checkable
59-
class ROSAction(Protocol):
60-
"""Protocol for ROS action types."""
61-
62-
Goal: type[ROSMessage]
63-
Result: type[ROSMessage]
64-
Feedback: type[ROSMessage]
65-
39+
ROSMessage: TypeAlias = BaseMessage
40+
ROSService: TypeAlias = BaseService
41+
ROSAction: TypeAlias = BaseAction
6642

6743
# Type variables for ROS types
6844
ROSMessageT = TypeVar("ROSMessageT", bound=ROSMessage)
@@ -73,3 +49,4 @@ class ROSAction(Protocol):
7349
ROSActionGoalT = TypeVar("ROSActionGoalT", bound=ROSMessage)
7450
ROSActionResultT = TypeVar("ROSActionResultT", bound=ROSMessage)
7551
ROSActionFeedbackT = TypeVar("ROSActionFeedbackT", bound=ROSMessage)
52+
ROSActionImplT = TypeVar("ROSActionImplT", bound=BaseImpl[Any, Any, Any])

rosbridge_library/test/internal/actions/test_actions.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,15 @@ def __init__(self, executor: Executor) -> None:
3333
self.executor = executor
3434
self.node = Node("action_tester")
3535
self.executor.add_node(self.node)
36-
self.action_server: ActionServer[Fibonacci_Goal, Fibonacci_Result, Fibonacci_Feedback] = (
37-
ActionServer(
38-
self.node,
39-
Fibonacci,
40-
"get_fibonacci_sequence",
41-
self.execute_callback,
42-
)
36+
self.action_server = ActionServer(
37+
self.node, Fibonacci, "get_fibonacci_sequence", self.execute_callback
4338
)
4439

4540
def __del__(self) -> None:
4641
self.executor.remove_node(self.node)
4742

4843
def execute_callback(
49-
self, goal: ServerGoalHandle[Fibonacci_Goal, Fibonacci_Result, Fibonacci_Feedback]
44+
self, goal: ServerGoalHandle[Fibonacci_Goal, Fibonacci_Result, Fibonacci_Feedback, Any]
5045
) -> Fibonacci_Result:
5146
self.goal = goal
5247
feedback_msg = Fibonacci.Feedback()
@@ -164,11 +159,7 @@ def get_result_callback(future: Future[GetResultServiceResponse[Fibonacci_Result
164159
received["msg"] = response.result
165160

166161
# First, call the action the 'proper' way
167-
client: ActionClient[Fibonacci_Goal, Fibonacci_Result, Fibonacci_Feedback] = ActionClient(
168-
self.node,
169-
Fibonacci,
170-
"get_fibonacci_sequence",
171-
)
162+
client = ActionClient(self.node, Fibonacci, "get_fibonacci_sequence")
172163
client.wait_for_server()
173164
goal = Fibonacci.Goal()
174165
goal.order = 5

0 commit comments

Comments
 (0)