Skip to content

Commit a2c7682

Browse files
committed
Use python launch files for ROS2 and add a Fibonacci test action server to the image
1 parent 1d549b7 commit a2c7682

File tree

5 files changed

+230
-8
lines changed

5 files changed

+230
-8
lines changed

.github/workflows/test-ros2.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- name: Set up docker containers
5151
run: |
5252
docker build -t gramaziokohler/rosbridge:integration_tests_ros2 ./docker/ros2
53-
docker run -d -p 9090:9090 --name rosbridge gramaziokohler/rosbridge:integration_tests_ros2 /bin/bash -c "ros2 launch /integration-tests.launch"
53+
docker run -d -p 9090:9090 --name rosbridge gramaziokohler/rosbridge:integration_tests_ros2 /bin/bash -c "ros2 launch /integration-tests-launch.py"
5454
docker ps -a
5555
- name: Run linter
5656
run: |

docker/ros2/Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@ RUN apt-get update && apt-get install -y \
88
ros-${ROS_DISTRO}-rosbridge-suite \
99
# ros-${ROS_DISTRO}-tf2-web-republisher \
1010
# ros-${ROS_DISTRO}-ros-tutorials \
11-
# ros-${ROS_DISTRO}-actionlib-tutorials \
11+
ros-${ROS_DISTRO}-demo-nodes-py \
12+
ros-${ROS_DISTRO}-example-interfaces \
1213
--no-install-recommends \
1314
# Clear apt-cache to reduce image size
1415
&& rm -rf /var/lib/apt/lists/*
1516

16-
# Copy launch
17-
COPY ./integration-tests.launch /
17+
# Copy launch and example action server
18+
COPY ./integration-tests-launch.py /
19+
COPY ./fibonacci_server.py /
1820

1921
EXPOSE 9090
2022

docker/ros2/fibonacci_server.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
# Copyright 2019 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import threading
16+
import time
17+
18+
from example_interfaces.action import Fibonacci
19+
20+
import rclpy
21+
from rclpy.action import ActionServer, CancelResponse, GoalResponse
22+
from rclpy.callback_groups import ReentrantCallbackGroup
23+
from rclpy.executors import ExternalShutdownException
24+
from rclpy.executors import MultiThreadedExecutor
25+
from rclpy.node import Node
26+
27+
28+
class MinimalActionServer(Node):
29+
"""Minimal action server that processes one goal at a time."""
30+
31+
def __init__(self):
32+
super().__init__('minimal_action_server')
33+
self._goal_handle = None
34+
self._goal_lock = threading.Lock()
35+
self._action_server = ActionServer(
36+
self,
37+
Fibonacci,
38+
'fibonacci',
39+
execute_callback=self.execute_callback,
40+
goal_callback=self.goal_callback,
41+
handle_accepted_callback=self.handle_accepted_callback,
42+
cancel_callback=self.cancel_callback,
43+
callback_group=ReentrantCallbackGroup())
44+
self.get_logger().info('Starting fibonacci action server..')
45+
46+
def destroy(self):
47+
self._action_server.destroy()
48+
super().destroy_node()
49+
50+
def goal_callback(self, goal_request):
51+
"""Accept or reject a client request to begin an action."""
52+
self.get_logger().info('Received goal request')
53+
return GoalResponse.ACCEPT
54+
55+
def handle_accepted_callback(self, goal_handle):
56+
with self._goal_lock:
57+
# This server only allows one goal at a time
58+
if self._goal_handle is not None and self._goal_handle.is_active:
59+
self.get_logger().info('Aborting previous goal')
60+
# Abort the existing goal
61+
self._goal_handle.abort()
62+
self._goal_handle = goal_handle
63+
64+
goal_handle.execute()
65+
66+
def cancel_callback(self, goal):
67+
"""Accept or reject a client request to cancel an action."""
68+
self.get_logger().info('Received cancel request')
69+
return CancelResponse.ACCEPT
70+
71+
def execute_callback(self, goal_handle):
72+
"""Execute the goal."""
73+
self.get_logger().info('Executing goal...')
74+
75+
# Append the seeds for the Fibonacci sequence
76+
feedback_msg = Fibonacci.Feedback()
77+
feedback_msg.sequence = [0, 1]
78+
79+
# Start executing the action
80+
for i in range(1, goal_handle.request.order):
81+
# If goal is flagged as no longer active (ie. another goal was accepted),
82+
# then stop executing
83+
if not goal_handle.is_active:
84+
self.get_logger().info('Goal aborted')
85+
return Fibonacci.Result()
86+
87+
if goal_handle.is_cancel_requested:
88+
goal_handle.canceled()
89+
self.get_logger().info('Goal canceled')
90+
return Fibonacci.Result()
91+
92+
# Update Fibonacci sequence
93+
feedback_msg.sequence.append(feedback_msg.sequence[i] + feedback_msg.sequence[i-1])
94+
95+
self.get_logger().info('Publishing feedback: {0}'.format(feedback_msg.sequence))
96+
97+
# Publish the feedback
98+
goal_handle.publish_feedback(feedback_msg)
99+
100+
# Sleep for demonstration purposes
101+
time.sleep(1)
102+
103+
with self._goal_lock:
104+
if not goal_handle.is_active:
105+
self.get_logger().info('Goal aborted')
106+
return Fibonacci.Result()
107+
108+
goal_handle.succeed()
109+
110+
# Populate result message
111+
result = Fibonacci.Result()
112+
result.sequence = feedback_msg.sequence
113+
114+
self.get_logger().info('Returning result: {0}'.format(result.sequence))
115+
116+
return result
117+
118+
119+
def main(args=None):
120+
try:
121+
rclpy.init(args=args)
122+
action_server = MinimalActionServer()
123+
124+
# We use a MultiThreadedExecutor to handle incoming goal requests concurrently
125+
executor = MultiThreadedExecutor()
126+
rclpy.spin(action_server, executor=executor)
127+
except (KeyboardInterrupt, ExternalShutdownException):
128+
pass
129+
130+
131+
if __name__ == '__main__':
132+
main()
133+
134+
135+
# import time
136+
137+
# from example_interfaces.action import Fibonacci
138+
# from rcl_interfaces.msg import SetParametersResult
139+
140+
# import rclpy
141+
# from rclpy.action import ActionServer, CancelResponse
142+
# from rclpy.executors import ExternalShutdownException
143+
# from rclpy.executors import MultiThreadedExecutor
144+
# from rclpy.node import Node
145+
# from rclpy.parameter import Parameter
146+
# from rclpy.qos import qos_profile_system_default
147+
# from rclpy.service_introspection import ServiceIntrospectionState
148+
149+
150+
# class FibonacciActionServer(Node):
151+
152+
# def __init__(self):
153+
# super().__init__('fibonacci_action_server')
154+
# self._action_server = ActionServer(
155+
# self,
156+
# Fibonacci,
157+
# 'fibonacci',
158+
# self.execute_callback,
159+
# cancel_callback=self.cancel_callback)
160+
161+
# def execute_callback(self, goal_handle):
162+
# self.get_logger().info('Executing goal...')
163+
164+
# feedback_msg = Fibonacci.Feedback()
165+
# feedback_msg.sequence = [0, 1]
166+
167+
# for i in range(1, goal_handle.request.order):
168+
# if goal_handle.is_cancel_requested:
169+
# goal_handle.canceled()
170+
# self.get_logger().info('Goal canceled')
171+
# return Fibonacci.Result()
172+
# feedback_msg.sequence.append(
173+
# feedback_msg.sequence[i] + feedback_msg.sequence[i-1])
174+
# self.get_logger().info('Feedback: {0}'.format(feedback_msg.sequence))
175+
# goal_handle.publish_feedback(feedback_msg)
176+
# time.sleep(1)
177+
178+
# goal_handle.succeed()
179+
180+
# result = Fibonacci.Result()
181+
# result.sequence = feedback_msg.sequence
182+
# return result
183+
184+
# def cancel_callback(self, goal_handle):
185+
# self.get_logger().info('Canceling goal...')
186+
# return CancelResponse.ACCEPT
187+
188+
189+
# def main(args=None):
190+
# try:
191+
# rclpy.init(args=args)
192+
# fibonacci_action_server = FibonacciActionServer()
193+
# fibonacci_action_server.get_logger().info('Init...')
194+
# executor = MultiThreadedExecutor()
195+
# fibonacci_action_server.get_logger().info('Starting..')
196+
# rclpy.spin(fibonacci_action_server, executor=executor)
197+
# except (KeyboardInterrupt, ExternalShutdownException):
198+
# pass
199+
200+
201+
# if __name__ == '__main__':
202+
# main()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from launch import LaunchDescription
2+
from launch.substitutions import PathJoinSubstitution
3+
from launch_ros.substitutions import FindPackageShare
4+
from launch.actions import ExecuteProcess, IncludeLaunchDescription
5+
6+
def generate_launch_description():
7+
return LaunchDescription([
8+
# Start rosbridge_websocket
9+
IncludeLaunchDescription(
10+
PathJoinSubstitution([
11+
FindPackageShare('rosbridge_server'),
12+
'launch',
13+
'rosbridge_websocket_launch.xml'
14+
])
15+
),
16+
17+
# Start fibonacci_server.py with python3
18+
ExecuteProcess(
19+
cmd=['python3', "/fibonacci_server.py"],
20+
output='screen'
21+
)
22+
])

docker/ros2/integration-tests.launch

Lines changed: 0 additions & 4 deletions
This file was deleted.

0 commit comments

Comments
 (0)