diff --git a/clover/CMakeLists.txt b/clover/CMakeLists.txt
index a97efc8d..23c48451 100644
--- a/clover/CMakeLists.txt
+++ b/clover/CMakeLists.txt
@@ -81,6 +81,7 @@ add_service_files(
SetAttitude.srv
SetRates.srv
SetLEDEffect.srv
+ Execute.srv
)
## Generate actions in the 'action' folder
@@ -167,6 +168,8 @@ add_executable(vpe_publisher src/vpe_publisher.cpp)
add_executable(led src/led.cpp)
+add_executable(shell src/shell.cpp)
+
target_link_libraries(simple_offboard
${catkin_LIBRARIES}
${GeographicLib_LIBRARIES}
@@ -180,10 +183,14 @@ target_link_libraries(vpe_publisher ${catkin_LIBRARIES})
target_link_libraries(led ${catkin_LIBRARIES})
+target_link_libraries(shell ${catkin_LIBRARIES})
+
add_dependencies(simple_offboard ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(led ${PROJECT_NAME}_generate_messages_cpp)
+add_dependencies(shell ${PROJECT_NAME}_generate_messages_cpp)
+
## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
diff --git a/clover/launch/clover.launch b/clover/launch/clover.launch
index 50646e66..61a03084 100644
--- a/clover/launch/clover.launch
+++ b/clover/launch/clover.launch
@@ -10,6 +10,7 @@
+
@@ -76,4 +77,7 @@
+
+
+
diff --git a/clover/src/shell.cpp b/clover/src/shell.cpp
new file mode 100644
index 00000000..06a9bd54
--- /dev/null
+++ b/clover/src/shell.cpp
@@ -0,0 +1,50 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+ros::Duration timeout;
+
+// TODO: handle timeout
+bool handle(clover::Execute::Request& req, clover::Execute::Response& res)
+{
+ ROS_INFO("Execute: %s", req.cmd.c_str());
+
+ std::array buffer;
+ std::string result;
+
+ FILE *fp = popen(req.cmd.c_str(), "r");
+
+ if (fp == NULL) {
+ res.code = clover::Execute::Request::CODE_FAIL;
+ res.output = "popen() failed";
+ return true;
+ }
+
+ while (fgets(buffer.data(), buffer.size(), fp) != nullptr) {
+ res.output += buffer.data();
+ }
+
+ res.code = pclose(fp);
+ return true;
+}
+
+
+int main(int argc, char **argv)
+{
+ ros::init(argc, argv, "shell");
+ ros::NodeHandle nh, nh_priv("~");
+
+ timeout = ros::Duration(nh_priv.param("timeout", 3.0));
+
+ auto gt_serv = nh.advertiseService("exec", &handle);
+
+ ROS_INFO("shell: ready");
+ ros::spin();
+}
diff --git a/clover/srv/Execute.srv b/clover/srv/Execute.srv
new file mode 100644
index 00000000..b61a9509
--- /dev/null
+++ b/clover/srv/Execute.srv
@@ -0,0 +1,7 @@
+int32 CODE_FAIL = -1
+int32 CODE_TIMEOUT = -2
+
+string cmd
+---
+string output
+int32 code
diff --git a/clover/test/basic.py b/clover/test/basic.py
index 39efc0bb..fe546f7d 100755
--- a/clover/test/basic.py
+++ b/clover/test/basic.py
@@ -2,6 +2,7 @@
import rospy
import pytest
from mavros_msgs.msg import State
+from clover import srv
@pytest.fixture()
def node():
@@ -27,3 +28,19 @@ def test_simple_offboard_services_available():
def test_web_video_server(node):
import urllib2
urllib2.urlopen("http://localhost:8080").read()
+
+def test_shell(node):
+ execute = rospy.ServiceProxy('exec', srv.Execute)
+ execute.wait_for_service(5)
+
+ res = execute(cmd='echo foo')
+ assert res.code == 0
+ assert res.output == 'foo\n'
+
+ res = execute(cmd='foo')
+ assert res.code == 32512
+ assert res.output == ''
+
+ res = execute(cmd='ls foo')
+ assert res.code == 512
+ assert res.output == ''
diff --git a/clover/test/basic.test b/clover/test/basic.test
index aa297005..46a923e6 100755
--- a/clover/test/basic.test
+++ b/clover/test/basic.test
@@ -32,6 +32,8 @@
+
+
startup: { r: 255, g: 255, b: 255 }
diff --git a/clover/www/index.html b/clover/www/index.html
index d25ea74d..3da27946 100644
--- a/clover/www/index.html
+++ b/clover/www/index.html
@@ -8,7 +8,17 @@
3D visualization for markers map (ros3djs)
+
+
+