Skip to content

Commit e87c3cd

Browse files
committed
ROS2 support
- first implementation of ROS2 pkg generation - basic unit tests (it works)
1 parent 25ea76c commit e87c3cd

15 files changed

Lines changed: 727 additions & 157 deletions

open-codegen/opengen/builder/optimizer_builder.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import sys
1313

1414
from importlib.metadata import version
15-
from .ros_builder import RosBuilder
15+
from .ros_builder import ROS2Builder, RosBuilder
1616

1717
_AUTOGEN_COST_FNAME = 'auto_casadi_cost.c'
1818
_AUTOGEN_GRAD_FNAME = 'auto_casadi_grad.c'
@@ -920,4 +920,11 @@ def build(self):
920920
self.__solver_config)
921921
ros_builder.build()
922922

923+
if self.__build_config.ros2_config is not None:
924+
ros2_builder = ROS2Builder(
925+
self.__meta,
926+
self.__build_config,
927+
self.__solver_config)
928+
ros2_builder.build()
929+
923930
return self.__info()

open-codegen/opengen/builder/ros_builder.py

Lines changed: 171 additions & 150 deletions
Large diffs are not rendered by default.

open-codegen/opengen/config/build_config.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ def __init__(self, build_dir="."):
5757
self.__build_c_bindings = False
5858
self.__build_python_bindings = False
5959
self.__ros_config = None
60+
self.__ros2_config = None
6061
self.__tcp_interface_config = None
6162
self.__local_path = None
6263
self.__allocator = RustAllocator.DefaultAllocator
@@ -135,6 +136,14 @@ def ros_config(self) -> RosConfiguration:
135136
"""
136137
return self.__ros_config
137138

139+
@property
140+
def ros2_config(self) -> RosConfiguration:
141+
"""ROS2 package configuration
142+
143+
:return: instance of RosConfiguration
144+
"""
145+
return self.__ros2_config
146+
138147
@property
139148
def allocator(self) -> RustAllocator:
140149
"""
@@ -257,6 +266,21 @@ def with_ros(self, ros_config: RosConfiguration):
257266
"""
258267
self.__build_c_bindings = True # no C++ bindings, no ROS package mate
259268
self.__ros_config = ros_config
269+
self.__ros2_config = None
270+
return self
271+
272+
def with_ros2(self, ros_config: RosConfiguration):
273+
"""
274+
Activates the generation of a ROS2 package. The caller must provide an
275+
instance of RosConfiguration
276+
277+
:param ros_config: Configuration of ROS2 package
278+
279+
:return: current instance of BuildConfiguration
280+
"""
281+
self.__build_c_bindings = True # no C++ bindings, no ROS package
282+
self.__ros2_config = ros_config
283+
self.__ros_config = None
260284
return self
261285

262286
def with_tcp_interface_config(self, tcp_interface_config=TcpServerConfiguration()):
@@ -300,4 +324,6 @@ def to_dict(self):
300324
build_dict["tcp_interface_config"] = self.__tcp_interface_config.to_dict()
301325
if self.__ros_config is not None:
302326
build_dict["ros_config"] = self.__ros_config.to_dict()
327+
if self.__ros2_config is not None:
328+
build_dict["ros2_config"] = self.__ros2_config.to_dict()
303329
return build_dict

open-codegen/opengen/config/ros_config.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
class RosConfiguration:
55
"""
6-
Configuration of auto-generated ROS package
6+
Configuration of an auto-generated ROS or ROS2 package
77
"""
88

99
def __init__(self):
@@ -61,7 +61,7 @@ def description(self):
6161

6262
@property
6363
def rate(self):
64-
"""ROS node rate in Hz
64+
"""ROS/ROS2 node rate in Hz
6565
6666
:return: rate, defaults to `10.0`
6767
"""
@@ -87,7 +87,7 @@ def params_topic_queue_size(self):
8787
def with_package_name(self, pkg_name):
8888
"""
8989
Set the package name, which is the same as the name
90-
of the folder that will store the auto-generated ROS node.
90+
of the folder that will store the auto-generated ROS/ROS2 node.
9191
The node name can contain lowercase and uppercase
9292
characters and underscores, but not spaces or other symbols
9393
@@ -124,6 +124,7 @@ def with_node_name(self, node_name):
124124
def with_rate(self, rate):
125125
"""
126126
Set the rate of the ROS node
127+
or ROS2 node
127128
128129
:param rate: rate in Hz
129130
:type rate: float
@@ -135,7 +136,7 @@ def with_rate(self, rate):
135136

136137
def with_description(self, description):
137138
"""
138-
Set the description of the ROS package
139+
Set the description of the ROS or ROS2 package
139140
140141
:param description: description, defaults to "parametric optimization with OpEn"
141142
:type description: string
@@ -149,7 +150,7 @@ def with_queue_sizes(self,
149150
result_topic_queue_size=100,
150151
parameter_topic_queue_size=100):
151152
"""
152-
Set queue sizes for ROS node
153+
Set queue sizes for ROS or ROS2 node
153154
154155
:param result_topic_queue_size: queue size of results, defaults to 100
155156
:type result_topic_queue_size: int, optional

open-codegen/opengen/templates/ros/open_optimizer.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/**
22
* This is an auto-generated file by Optimization Engine (OpEn)
3-
* OpEn is a free open-source software - see doc.optimization-engine.xyz
3+
* OpEn is a free open-source software -
4+
* see https://alphaville.github.io/optimization-engine
45
* dually licensed under the MIT and Apache v2 licences.
56
*
67
*/
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
project({{ros.package_name}})
3+
4+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
5+
add_compile_options(-Wall -Wextra -Wpedantic)
6+
endif()
7+
8+
find_package(ament_cmake REQUIRED)
9+
find_package(rclcpp REQUIRED)
10+
find_package(Python3 REQUIRED COMPONENTS Interpreter Development NumPy)
11+
set(Python_EXECUTABLE ${Python3_EXECUTABLE})
12+
set(Python_INCLUDE_DIRS ${Python3_INCLUDE_DIRS})
13+
set(Python_LIBRARIES ${Python3_LIBRARIES})
14+
set(Python_NumPy_INCLUDE_DIRS ${Python3_NumPy_INCLUDE_DIRS})
15+
find_package(rosidl_default_generators REQUIRED)
16+
17+
set(msg_files
18+
"msg/OptimizationResult.msg"
19+
"msg/OptimizationParameters.msg"
20+
)
21+
22+
rosidl_generate_interfaces(${PROJECT_NAME}
23+
${msg_files}
24+
)
25+
26+
ament_export_dependencies(rosidl_default_runtime)
27+
28+
include_directories(
29+
${PROJECT_SOURCE_DIR}/include
30+
)
31+
32+
set(NODE_NAME {{ros.node_name}})
33+
add_executable(${NODE_NAME} src/open_optimizer.cpp)
34+
ament_target_dependencies(${NODE_NAME} rclcpp)
35+
target_link_libraries(
36+
${NODE_NAME}
37+
${PROJECT_SOURCE_DIR}/extern_lib/lib{{meta.optimizer_name}}.a
38+
m
39+
dl
40+
)
41+
rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} "rosidl_typesupport_cpp")
42+
target_link_libraries(${NODE_NAME} "${cpp_typesupport_target}")
43+
44+
install(TARGETS
45+
${NODE_NAME}
46+
DESTINATION lib/${PROJECT_NAME}
47+
)
48+
49+
install(DIRECTORY
50+
config
51+
launch
52+
DESTINATION share/${PROJECT_NAME}
53+
)
54+
55+
ament_package()
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
float64[] parameter # parameter p (mandatory)
2+
float64[] initial_guess # u0 (optional/recommended)
3+
float64[] initial_y # y0 (optional)
4+
float64 initial_penalty # initial penalty (optional)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Constants match the enumeration of status codes
2+
uint8 STATUS_CONVERGED=0
3+
uint8 STATUS_NOT_CONVERGED_ITERATIONS=1
4+
uint8 STATUS_NOT_CONVERGED_OUT_OF_TIME=2
5+
uint8 STATUS_NOT_CONVERGED_COST=3
6+
uint8 STATUS_NOT_CONVERGED_FINITE_COMPUTATION=4
7+
8+
float64[] solution # optimizer (solution)
9+
uint8 inner_iterations # number of inner iterations
10+
uint16 outer_iterations # number of outer iterations
11+
uint8 status # status code
12+
float64 cost # cost at solution
13+
float64 norm_fpr # norm of FPR of last inner problem
14+
float64 penalty # penalty value
15+
float64[] lagrange_multipliers # vector of Lagrange multipliers
16+
float64 infeasibility_f1 # infeasibility wrt F1
17+
float64 infeasibility_f2 # infeasibility wrt F2
18+
float64 solve_time_ms # solution time in ms
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# ROS2 Package {{ros.package_name}}
2+
3+
4+
## Installation and Setup
5+
6+
Move or link the auto-generated ROS2 package (folder `{{ros.package_name}}`) to your workspace source tree (typically `~/ros2_ws/src/`).
7+
8+
Compile with:
9+
10+
```console
11+
cd ~/ros2_ws/
12+
colcon build --packages-select {{ros.package_name}}
13+
source install/setup.bash
14+
```
15+
16+
If you build the package in-place from its own directory instead of a larger
17+
workspace, source the generated setup script from `install/`:
18+
19+
```console
20+
# bash
21+
source install/setup.bash
22+
23+
# zsh
24+
source install/setup.zsh
25+
```
26+
27+
On macOS, ROS2 logging may need an explicit writable directory:
28+
29+
```console
30+
mkdir -p .ros_log
31+
export ROS_LOG_DIR="$PWD/.ros_log"
32+
```
33+
34+
35+
## Launch and Use
36+
37+
Start the optimizer in one terminal. The process stays in the foreground while
38+
the node is running.
39+
40+
```console
41+
# terminal 1
42+
source install/setup.bash # or: source install/setup.zsh
43+
ros2 run {{ros.package_name}} {{ros.node_name}}
44+
```
45+
46+
In a second terminal, source the same environment and verify discovery:
47+
48+
```console
49+
# terminal 2
50+
source install/setup.bash # or: source install/setup.zsh
51+
ros2 node list --no-daemon --spin-time 5
52+
ros2 topic list --no-daemon --spin-time 5
53+
```
54+
55+
You should see the node `/{{ros.node_name}}`, the input topic
56+
`/{{ros.subscriber_subtopic}}`, and the output topic
57+
`/{{ros.publisher_subtopic}}`.
58+
59+
Then publish a request to the configured parameters topic
60+
(default: `/{{ros.subscriber_subtopic}}`):
61+
62+
```console
63+
ros2 topic pub --once /{{ros.subscriber_subtopic}} {{ros.package_name}}/msg/OptimizationParameters "{parameter: [YOUR_PARAMETER_VECTOR], initial_guess: [INITIAL_GUESS_OPTIONAL], initial_y: [], initial_penalty: 15.0}"
64+
```
65+
66+
The result will be announced on the configured result topic
67+
(default: `/{{ros.publisher_subtopic}}`):
68+
69+
```console
70+
ros2 topic echo /{{ros.publisher_subtopic}}
71+
```
72+
73+
To get the optimal solution you can do:
74+
75+
```console
76+
ros2 topic echo /{{ros.publisher_subtopic}} --field solution
77+
```
78+
79+
80+
## Messages
81+
82+
This package involves two messages: `OptimizationParameters`
83+
and `OptimizationResult`, which are used to define the input
84+
and output values to the node. `OptimizationParameters` specifies
85+
the parameter vector, the initial guess (optional), the initial
86+
guess for the vector of Lagrange multipliers and the initial value
87+
of the penalty value. `OptimizationResult` is a message containing
88+
all information related to the solution of the optimization
89+
problem, including the optimal solution, the solver status,
90+
solution time, Lagrange multiplier vector and more.
91+
92+
The message structures are defined in the following msg files:
93+
94+
- [`OptimizationParameters.msg`](msg/OptimizationParameters.msg)
95+
- [`OptimizationResult.msg`](msg/OptimizationResult.msg)
96+
97+
98+
## Configure
99+
100+
You can configure the rate and topic names by editing
101+
[`config/open_params.yaml`](config/open_params.yaml).
102+
103+
104+
## Directory structure and contents
105+
106+
The following auto-generated files are included in your ROS2 package:
107+
108+
```txt
109+
├── CMakeLists.txt
110+
├── config
111+
│   └── open_params.yaml
112+
├── extern_lib
113+
│   └── librosenbrock.a
114+
├── include
115+
│   ├── open_optimizer.hpp
116+
│   └── rosenbrock_bindings.hpp
117+
├── launch
118+
│   └── open_optimizer.launch.py
119+
├── msg
120+
│   ├── OptimizationParameters.msg
121+
│   └── OptimizationResult.msg
122+
├── package.xml
123+
├── README.md
124+
└── src
125+
└── open_optimizer.cpp
126+
```

0 commit comments

Comments
 (0)