Skip to content

Commit e849dac

Browse files
committed
Creation of cl_gcalcli and sm_cl_gcalcli_test_1
1 parent 34213cf commit e849dac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+9718
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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>
25+
#include <smacc2/smacc_signal.hpp>
26+
27+
namespace smacc2
28+
{
29+
namespace client_core_components
30+
{
31+
32+
/**
33+
* @brief Result of a subprocess execution
34+
*/
35+
struct SubprocessResult
36+
{
37+
int exit_code;
38+
std::string stdout_output;
39+
std::string stderr_output;
40+
bool timed_out;
41+
std::chrono::milliseconds execution_time;
42+
};
43+
44+
/**
45+
* @brief Generic subprocess execution component for running CLI tools
46+
*
47+
* This component provides a thread-safe way to execute external commands
48+
* and capture their output. It can be used by any client that needs to
49+
* interact with command-line tools.
50+
*
51+
* Example usage:
52+
* auto result = executor->executeCommand("gcalcli list", 5000);
53+
* if (result.exit_code == 0) {
54+
* // Process result.stdout_output
55+
* }
56+
*/
57+
class CpSubprocessExecutor : public smacc2::ISmaccComponent
58+
{
59+
public:
60+
CpSubprocessExecutor() : initialized_(false) {}
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+
74+
/**
75+
* @brief Execute a command synchronously
76+
*
77+
* @param command The command to execute (passed to shell)
78+
* @param timeout_ms Timeout in milliseconds (0 = no timeout)
79+
* @return SubprocessResult containing exit code, stdout, stderr
80+
*/
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);
101+
onCommandFailed_(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);
123+
onCommandFailed_(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+
{
143+
onCommandCompleted_(result.exit_code, result.stdout_output);
144+
}
145+
else
146+
{
147+
onCommandFailed_(result.stdout_output);
148+
}
149+
150+
return result;
151+
}
152+
153+
/**
154+
* @brief Execute a command asynchronously (fire and forget)
155+
*
156+
* The command runs in a separate thread. Results are delivered via signals.
157+
*
158+
* @param command The command to execute
159+
* @param timeout_ms Timeout in milliseconds
160+
*/
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>
173+
smacc2::SmaccSignalConnection onCommandCompleted(
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>
180+
smacc2::SmaccSignalConnection onCommandFailed(void (T::*callback)(const std::string &), T * object)
181+
{
182+
return this->getStateMachine()->createSignalConnection(onCommandFailed_, callback, object);
183+
}
184+
185+
private:
186+
bool initialized_;
187+
std::mutex execution_mutex_;
188+
};
189+
190+
} // namespace client_core_components
191+
} // namespace smacc2
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(cl_gcalcli)
3+
4+
# Default to C++17
5+
if(NOT CMAKE_CXX_STANDARD)
6+
set(CMAKE_CXX_STANDARD 17)
7+
endif()
8+
9+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
10+
add_compile_options(-Wall -Wextra -Wpedantic)
11+
endif()
12+
13+
# find dependencies
14+
find_package(ament_cmake REQUIRED)
15+
find_package(smacc2 REQUIRED)
16+
find_package(Boost COMPONENTS thread regex REQUIRED)
17+
18+
include_directories(
19+
${PROJECT_NAME}
20+
include
21+
${smacc2_INCLUDE_DIRS}
22+
)
23+
24+
add_library(${PROJECT_NAME}
25+
src/cl_gcalcli/cl_gcalcli.cpp
26+
src/cl_gcalcli/components/cp_gcalcli_connection.cpp
27+
src/cl_gcalcli/components/cp_calendar_poller.cpp
28+
src/cl_gcalcli/components/cp_calendar_event_listener.cpp
29+
src/cl_gcalcli/client_behaviors/cb_event_detect.cpp
30+
src/cl_gcalcli/client_behaviors/cb_status.cpp
31+
src/cl_gcalcli/client_behaviors/cb_wait_connection.cpp
32+
src/cl_gcalcli/client_behaviors/cb_monitor_connection.cpp
33+
src/cl_gcalcli/client_behaviors/cb_quick_add.cpp
34+
src/cl_gcalcli/client_behaviors/cb_refresh_agenda.cpp
35+
)
36+
37+
target_link_libraries(${PROJECT_NAME} SHARED ${Boost_LIBRARIES} smacc2)
38+
ament_target_dependencies(${PROJECT_NAME} smacc2)
39+
40+
ament_export_include_directories(include)
41+
ament_export_dependencies(smacc2 Boost)
42+
ament_export_libraries(${PROJECT_NAME} ${Boost_LIBRARIES})
43+
44+
install(
45+
DIRECTORY include/
46+
DESTINATION include
47+
)
48+
49+
install(TARGETS
50+
${PROJECT_NAME}
51+
DESTINATION lib/)
52+
53+
ament_package()

0 commit comments

Comments
 (0)