SMACC2
Loading...
Searching...
No Matches
cp_subprocess_executor.hpp
Go to the documentation of this file.
1// Copyright 2024 RobosoftAI 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#pragma once
16
17#include <chrono>
18#include <functional>
19#include <mutex>
20#include <string>
21
22#include <rclcpp/rclcpp.hpp>
23#include <smacc2/common.hpp>
24#include <smacc2/component.hpp>
26
27namespace smacc2
28{
29namespace client_core_components
30{
31
36{
38 std::string stdout_output;
39 std::string stderr_output;
41 std::chrono::milliseconds execution_time;
42};
43
58{
59public:
61
62 virtual ~CpSubprocessExecutor() = default;
63
64 void onInitialize() override
65 {
66 if (!initialized_)
67 {
68 RCLCPP_INFO_STREAM(
69 getLogger(), "[" << this->getName() << "] CpSubprocessExecutor initialized");
70 initialized_ = true;
71 }
72 }
73
81 SubprocessResult executeCommand(const std::string & command, int timeout_ms = 30000)
82 {
83 std::lock_guard<std::mutex> lock(execution_mutex_);
84
85 SubprocessResult result;
86 result.exit_code = -1;
87 result.timed_out = false;
88
89 auto start_time = std::chrono::steady_clock::now();
90
91 RCLCPP_DEBUG_STREAM(getLogger(), "[" << this->getName() << "] Executing: " << command);
92
93 // Execute command and capture output
94 std::string full_command = command + " 2>&1";
95 FILE * pipe = popen(full_command.c_str(), "r");
96
97 if (!pipe)
98 {
99 result.stderr_output = "Failed to execute command: popen() failed";
100 RCLCPP_ERROR_STREAM(getLogger(), "[" << this->getName() << "] " << result.stderr_output);
102 return result;
103 }
104
105 // Read output
106 std::array<char, 4096> buffer;
107 std::string output;
108
109 while (fgets(buffer.data(), buffer.size(), pipe) != nullptr)
110 {
111 output += buffer.data();
112
113 // Check timeout
114 if (timeout_ms > 0)
115 {
116 auto elapsed = std::chrono::steady_clock::now() - start_time;
117 if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() > timeout_ms)
118 {
119 pclose(pipe);
120 result.timed_out = true;
121 result.stderr_output = "Command timed out after " + std::to_string(timeout_ms) + "ms";
122 RCLCPP_WARN_STREAM(getLogger(), "[" << this->getName() << "] " << result.stderr_output);
124 return result;
125 }
126 }
127 }
128
129 int status = pclose(pipe);
130 result.exit_code = WEXITSTATUS(status);
131 result.stdout_output = output;
132
133 auto end_time = std::chrono::steady_clock::now();
134 result.execution_time =
135 std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
136
137 RCLCPP_DEBUG_STREAM(
138 getLogger(), "[" << this->getName() << "] Command completed with exit code "
139 << result.exit_code << " in " << result.execution_time.count() << "ms");
140
141 if (result.exit_code == 0)
142 {
144 }
145 else
146 {
148 }
149
150 return result;
151 }
152
161 void executeCommandAsync(const std::string & command, int timeout_ms = 30000)
162 {
163 std::thread([this, command, timeout_ms]() { this->executeCommand(command, timeout_ms); })
164 .detach();
165 }
166
167 // Signals
168 smacc2::SmaccSignal<void(int exit_code, const std::string & output)> onCommandCompleted_;
169 smacc2::SmaccSignal<void(const std::string & error)> onCommandFailed_;
170
171 // Signal connection helpers
172 template <typename T>
174 void (T::*callback)(int, const std::string &), T * object)
175 {
176 return this->getStateMachine()->createSignalConnection(onCommandCompleted_, callback, object);
177 }
178
179 template <typename T>
181 void (T::*callback)(const std::string &), T * object)
182 {
183 return this->getStateMachine()->createSignalConnection(onCommandFailed_, callback, object);
184 }
185
186private:
189};
190
191} // namespace client_core_components
192} // namespace smacc2
ISmaccStateMachine * getStateMachine()
virtual std::string getName() const
rclcpp::Logger getLogger() const
smacc2::SmaccSignalConnection createSignalConnection(TSmaccSignal &signal, TMemberFunctionPrototype callback, TSmaccObjectType *object)
Generic subprocess execution component for running CLI tools.
smacc2::SmaccSignalConnection onCommandFailed(void(T::*callback)(const std::string &), T *object)
SubprocessResult executeCommand(const std::string &command, int timeout_ms=30000)
Execute a command synchronously.
void executeCommandAsync(const std::string &command, int timeout_ms=30000)
Execute a command asynchronously (fire and forget)
smacc2::SmaccSignal< void(const std::string &error)> onCommandFailed_
smacc2::SmaccSignal< void(int exit_code, const std::string &output)> onCommandCompleted_
smacc2::SmaccSignalConnection onCommandCompleted(void(T::*callback)(int, const std::string &), T *object)
boost::signals2::connection SmaccSignalConnection