Skip to content

Commit 3b76cf6

Browse files
committed
various fixes
1 parent c69a26b commit 3b76cf6

4 files changed

Lines changed: 86 additions & 1 deletion

File tree

open-codegen/opengen/config/build_config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,15 @@ def with_build_mode(self, build_mode):
192192
:return: current instance of BuildConfiguration
193193
194194
"""
195+
if build_mode not in (
196+
BuildConfiguration.DEBUG_MODE,
197+
BuildConfiguration.RELEASE_MODE,
198+
):
199+
raise ValueError(
200+
"build mode must be either "
201+
f"'{BuildConfiguration.DEBUG_MODE}' or "
202+
f"'{BuildConfiguration.RELEASE_MODE}'"
203+
)
195204
self.__build_mode = build_mode
196205
return self
197206

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

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
#include "open_optimizer.hpp"
2020

2121
namespace {{ros.package_name}} {
22+
/**
23+
* ROS2 node that wraps the generated OpEn solver.
24+
*
25+
* The node subscribes to `OptimizationParameters`, validates and copies the
26+
* incoming request into the native solver buffers, invokes the generated C
27+
* bindings, and publishes one `OptimizationResult` message for each request.
28+
*/
2229
class OptimizationEngineNode : public rclcpp::Node {
2330
private:
2431
using OptimizationParametersMsg = {{ros.package_name}}::msg::OptimizationParameters;
@@ -41,6 +48,11 @@ class OptimizationEngineNode : public rclcpp::Node {
4148
static constexpr int32_t kInvalidInitialYErrorCode = 1700;
4249
static constexpr int32_t kInvalidParameterErrorCode = 3003;
4350

51+
/**
52+
* Convert the configured solver loop rate in Hz to a ROS2 timer period.
53+
*
54+
* A non-positive rate falls back to a conservative default of 100 ms.
55+
*/
4456
static std::chrono::milliseconds rateToPeriod(double rate)
4557
{
4658
if (rate <= 0.0) {
@@ -53,6 +65,9 @@ class OptimizationEngineNode : public rclcpp::Node {
5365
return std::chrono::milliseconds(period_ms);
5466
}
5567

68+
/**
69+
* Build a human-readable dimension-mismatch message for invalid requests.
70+
*/
5671
std::string makeDimensionErrorMessage(
5772
const char* label,
5873
size_t provided,
@@ -63,6 +78,12 @@ class OptimizationEngineNode : public rclcpp::Node {
6378
return oss.str();
6479
}
6580

81+
/**
82+
* Populate `results_` with a structured error response.
83+
*
84+
* This is used both for request-validation failures in the ROS2 wrapper
85+
* and for solver-side failures propagated through the generated bindings.
86+
*/
6687
void setErrorResult(
6788
uint8_t status,
6889
int32_t error_code,
@@ -83,8 +104,16 @@ class OptimizationEngineNode : public rclcpp::Node {
83104
results_.solve_time_ms = 0.0;
84105
}
85106

107+
/**
108+
* Validate the most recent request and copy it into the solver buffers.
109+
*
110+
* On success, this method updates `p_`, `u_`, `y_`, and `init_penalty_`
111+
* and returns `true`. On failure, it prepares an error result in `results_`
112+
* and returns `false` without invoking the solver.
113+
*/
86114
bool validateAndUpdateInputData()
87115
{
116+
// A non-positive or missing penalty falls back to the generated default.
88117
init_penalty_ = (params_.initial_penalty > 1.0)
89118
? params_.initial_penalty
90119
: ROS2_NODE_{{meta.optimizer_name|upper}}_DEFAULT_INITIAL_PENALTY;
@@ -103,6 +132,7 @@ class OptimizationEngineNode : public rclcpp::Node {
103132
p_[i] = params_.parameter[i];
104133
}
105134

135+
// If no initial guess is provided, keep the previous `u_` as a warm start.
106136
if (!params_.initial_guess.empty()
107137
&& params_.initial_guess.size() != {{meta.optimizer_name|upper}}_NUM_DECISION_VARIABLES) {
108138
setErrorResult(
@@ -120,6 +150,7 @@ class OptimizationEngineNode : public rclcpp::Node {
120150
}
121151
}
122152

153+
// Likewise, an empty `initial_y` means "reuse the previous multipliers".
123154
if (!params_.initial_y.empty() && params_.initial_y.size() != {{meta.optimizer_name|upper}}_N1) {
124155
setErrorResult(
125156
OptimizationResultMsg::STATUS_INVALID_REQUEST,
@@ -139,11 +170,17 @@ class OptimizationEngineNode : public rclcpp::Node {
139170
return true;
140171
}
141172

173+
/**
174+
* Invoke the generated C solver interface on the current buffers.
175+
*/
142176
{{meta.optimizer_name}}SolverStatus solve()
143177
{
144178
return {{meta.optimizer_name}}_solve(cache_, u_, p_, y_, &init_penalty_);
145179
}
146180

181+
/**
182+
* Lazily allocate the solver workspace and multiplier buffer.
183+
*/
147184
void initializeSolverIfNeeded()
148185
{
149186
if (y_ == nullptr) {
@@ -154,6 +191,9 @@ class OptimizationEngineNode : public rclcpp::Node {
154191
}
155192
}
156193

194+
/**
195+
* Convert the solver status structure into the ROS2 result message.
196+
*/
157197
void updateResults({{meta.optimizer_name}}SolverStatus& status)
158198
{
159199
results_.solution.clear();
@@ -173,18 +213,29 @@ class OptimizationEngineNode : public rclcpp::Node {
173213
results_.penalty = status.penalty;
174214
results_.status = static_cast<uint8_t>(status.exit_status);
175215
results_.error_code = status.error_code;
216+
// The bindings expose a null-terminated C buffer; convert it once here.
176217
results_.error_message = std::string(status.error_message);
177218
results_.solve_time_ms = static_cast<double>(status.solve_time_ns) / 1000000.0;
178219
results_.infeasibility_f2 = status.f2_norm;
179220
results_.infeasibility_f1 = status.delta_y_norm_over_c;
180221
}
181222

223+
/**
224+
* Store the latest optimization request received on the parameters topic.
225+
*/
182226
void receiveRequestCallback(const OptimizationParametersMsg::ConstSharedPtr msg)
183227
{
184228
params_ = *msg;
185229
has_received_request_ = true;
186230
}
187231

232+
/**
233+
* Process at most one pending request and publish exactly one response.
234+
*
235+
* Repeated timer ticks do not republish stale results: once a request has
236+
* been handled, `has_received_request_` is cleared until the next message
237+
* arrives on the input topic.
238+
*/
188239
void solveAndPublish()
189240
{
190241
if (!has_received_request_) {
@@ -193,16 +244,22 @@ class OptimizationEngineNode : public rclcpp::Node {
193244
initializeSolverIfNeeded();
194245
if (!validateAndUpdateInputData()) {
195246
publisher_->publish(results_);
247+
// Mark the request as consumed so the timer does not republish stale errors.
196248
has_received_request_ = false;
197249
return;
198250
}
199251
{{meta.optimizer_name}}SolverStatus status = solve();
200252
updateResults(status);
201253
publisher_->publish(results_);
254+
// Each request should produce exactly one response message.
202255
has_received_request_ = false;
203256
}
204257

205258
public:
259+
/**
260+
* Construct the ROS2 node, declare runtime parameters, and create the
261+
* publisher, subscriber, and wall timer used by the generated wrapper.
262+
*/
206263
OptimizationEngineNode()
207264
: Node(ROS2_NODE_{{meta.optimizer_name|upper}}_NODE_NAME)
208265
{
@@ -232,6 +289,9 @@ class OptimizationEngineNode : public rclcpp::Node {
232289
std::bind(&OptimizationEngineNode::solveAndPublish, this));
233290
}
234291

292+
/**
293+
* Release any lazily allocated solver resources.
294+
*/
235295
~OptimizationEngineNode() override
236296
{
237297
if (y_ != nullptr) {
@@ -244,6 +304,10 @@ class OptimizationEngineNode : public rclcpp::Node {
244304
};
245305
} /* end of namespace {{ros.package_name}} */
246306

307+
/**
308+
* Start the generated ROS2 optimizer node and hand control to the ROS2
309+
* executor until shutdown is requested.
310+
*/
247311
int main(int argc, char** argv)
248312
{
249313
rclcpp::init(argc, argv);

open-codegen/test/test.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
import numpy as np
1313

1414

15+
16+
17+
1518
class RustBuildTestCase(unittest.TestCase):
1619

1720
TEST_DIR = ".python_test_build"
@@ -916,6 +919,16 @@ def test_optimizer_meta_invalid_version2(self):
916919

917920
self.assertIn("Cargo package version", str(context.exception))
918921

922+
def test_with_build_mode_rejects_invalid_values(self):
923+
"""`with_build_mode` should reject unsupported build modes."""
924+
build_config = og.config.BuildConfiguration()
925+
926+
with self.assertRaisesRegex(
927+
ValueError,
928+
"build mode must be either 'debug' or 'release'",
929+
):
930+
build_config.with_build_mode("profile")
931+
919932

920933
if __name__ == '__main__':
921934
logging.getLogger('retry').setLevel(logging.ERROR)

open-codegen/test/test_ros2.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ def test_ros_and_ros2_configs_clear_each_other(self):
4848
self.assertIs(build_config.ros2_config, ros2_config)
4949
self.assertIsNone(build_config.ros_config)
5050

51-
5251
class Ros2TemplateCustomizationTestCase(unittest.TestCase):
5352
"""Generation tests for custom ROS2 configuration values."""
5453

0 commit comments

Comments
 (0)