1919#include " open_optimizer.hpp"
2020
2121namespace {{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+ */
2229class OptimizationEngineNode : public rclcpp ::Node {
2330private:
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
205258public:
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+ */
247311int main (int argc, char ** argv)
248312{
249313 rclcpp::init (argc, argv);
0 commit comments