diff --git a/jsk_robot_common/complex_recovery/CMakeLists.txt b/jsk_robot_common/complex_recovery/CMakeLists.txt
new file mode 100644
index 0000000000..7dd42684f9
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/CMakeLists.txt
@@ -0,0 +1,89 @@
+cmake_minimum_required(VERSION 2.8.3)
+project(complex_recovery)
+
+add_compile_options(-std=c++11)
+
+find_package(catkin REQUIRED
+ COMPONENTS
+ costmap_2d
+ nav_core
+ pluginlib
+ roscpp
+ tf2
+ tf2_ros
+)
+
+catkin_package(
+ INCLUDE_DIRS include
+ LIBRARIES complex_recovery
+ CATKIN_DEPENDS
+ costmap_2d
+ nav_core
+ pluginlib
+ roscpp
+ tf2
+ tf2_ros
+)
+
+# Abort if indigo or kinetic
+if ( $ENV{ROS_DISTRO} STREQUAL "indigo" OR $ENV{ROS_DISTRO} STREQUAL "kinetic" )
+ return()
+endif()
+
+include_directories(
+ include
+ ${catkin_INCLUDE_DIRS}
+)
+
+add_library(complex_recovery
+ src/sequential_complex_recovery.cpp
+ src/parallel_complex_recovery.cpp
+ src/utils.cpp
+)
+add_dependencies(complex_recovery
+ ${${PROJECT_NAME}_EXPORTED_TARGETS}
+ ${catkin_EXPORTED_TARGETS}
+)
+target_link_libraries(complex_recovery
+ ${catkin_LIBRARIES}
+)
+
+install(TARGETS complex_recovery
+ ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
+ LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
+ RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
+ )
+
+install(DIRECTORY include/${PROJECT_NAME}/
+ DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
+ FILES_MATCHING PATTERN "*.h"
+)
+
+install(FILES complex_recovery_plugins.xml
+ DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
+)
+
+#
+# Testing
+#
+if (CATKIN_ENABLE_TESTING)
+ find_package(rostest REQUIRED)
+
+ catkin_add_executable_with_gtest(sequential_complex_recovery_test_node
+ tests/sequential_complex_recovery_test_node.cpp
+ )
+ target_link_libraries(sequential_complex_recovery_test_node
+ ${catkin_LIBRARIES}
+ ${PROJECT_NAME}
+ )
+ add_rostest(tests/sequential_complex_recovery.test)
+
+ catkin_add_executable_with_gtest(parallel_complex_recovery_test_node
+ tests/parallel_complex_recovery_test_node.cpp
+ )
+ target_link_libraries(parallel_complex_recovery_test_node
+ ${catkin_LIBRARIES}
+ ${PROJECT_NAME}
+ )
+ add_rostest(tests/parallel_complex_recovery.test)
+endif()
diff --git a/jsk_robot_common/complex_recovery/README.md b/jsk_robot_common/complex_recovery/README.md
new file mode 100644
index 0000000000..fc65f10920
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/README.md
@@ -0,0 +1,67 @@
+# complex_recovery
+
+This package provides recovery behavior plugins which combines multi recoveries to one recovery behavior.
+This is useful for assuring a set of recovery behavior to run at one time.
+
+There are two types of recoveries. One is to run multi recoveries sequentially and another is to run them in parallel.
+
+![complex_recovery_diagrams](https://user-images.githubusercontent.com/9410362/189815175-a3265d23-01d4-4ae9-831c-b01aacad2872.png)
+
+## complex_recovery/SequentialComplexRecovery
+
+* `~recovery_behaviors` (list of dictionaries which has `name` and `type` entries, default: None)
+
+Example configuration of `move_base` is like
+
+```yaml
+recovery_behavior_enabled: true
+recovery_behaviors:
+ - name: "speak_then_clear_costmap0"
+ type: "complex_recovery/SequentialComplexRecovery"
+speak_then_clear_costmap0:
+ recovery_behaviors:
+ - name: "speak_and_wait0"
+ type: "speak_and_wait_recovery/SpeakAndWaitRecovery"
+ - name: "clear_costmap0"
+ type: "clear_costmap_recovery/ClearCostmapRecovery"
+ speak_and_wait0:
+ speak_text: "I'm clearing costmap."
+ duration_wait: 5.0
+ duration_timeout: 1.0
+ sound_action: /sound_play
+ clear_costmap0:
+ reset_distance: 1.0
+```
+
+In this case, `speak_and_clear_costmap0` recovery runs `speak_and_wait0` recovery first, then runs `clear_costmap0`.
+So a robot speaks first and then clear its costmap.
+
+## complex_recovery/ParallelComplexRecovery
+
+* `~recovery_behaviors` (list of dictionaries which has `name` and `type` entries, default: None)
+
+Example configuration of `move_base` is like
+
+```yaml
+recovery_behavior_enabled: true
+recovery_behaviors:
+ - name: "speak_and_rotate_costmap0"
+ type: "complex_recovery/SequentialComplexRecovery"
+speak_and_rotate_costmap0:
+ recovery_behaviors:
+ - name: "speak_and_wait0"
+ type: "speak_and_wait_recovery/SpeakAndWaitRecovery"
+ - name: "rotate0"
+ type: "rotate_recovery/RotateRecovery"
+ speak_and_wait0:
+ speak_text: "I'm rotating."
+ duration_wait: 5.0
+ duration_timeout: 1.0
+ sound_action: /sound_play
+ rotate0:
+ sim_granularity: 0.017
+ frequency: 20.0
+```
+
+In this case, `speak_and_rotate_costmap0` recovery runs `speak_and_wait0` and `rotate0` simultaneously.
+So a robot speaks during its rotation.
diff --git a/jsk_robot_common/complex_recovery/complex_recovery_plugins.xml b/jsk_robot_common/complex_recovery/complex_recovery_plugins.xml
new file mode 100644
index 0000000000..52d7a4b4d5
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/complex_recovery_plugins.xml
@@ -0,0 +1,18 @@
+
+
+
+ A recovery behavior that runs other recovery behaviors sequentially.
+
+
+
+
+ A recovery behavior that runs other recovery behaviors in parallel.
+
+
+
diff --git a/jsk_robot_common/complex_recovery/include/complex_recovery/parallel_complex_recovery.h b/jsk_robot_common/complex_recovery/include/complex_recovery/parallel_complex_recovery.h
new file mode 100644
index 0000000000..fc56d9c217
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/include/complex_recovery/parallel_complex_recovery.h
@@ -0,0 +1,32 @@
+#ifndef COMPLEX_RECOVERY_H
+#define COMPLEX_RECOVERY_H
+
+#include
+#include
+#include
+#include
+
+namespace complex_recovery
+{
+
+class ParallelComplexRecovery : public nav_core::RecoveryBehavior
+{
+public:
+ ParallelComplexRecovery();
+ void initialize(
+ std::string name,
+ tf2_ros::Buffer* tf_buffer,
+ costmap_2d::Costmap2DROS* global_costmap,
+ costmap_2d::Costmap2DROS* local_costmap);
+ void runBehavior();
+ ~ParallelComplexRecovery();
+
+private:
+ bool initialized_;
+ std::vector > recovery_behaviors_;
+ std::vector recovery_behavior_names_;
+ pluginlib::ClassLoader recovery_loader_;
+};
+};
+
+#endif
diff --git a/jsk_robot_common/complex_recovery/include/complex_recovery/sequential_complex_recovery.h b/jsk_robot_common/complex_recovery/include/complex_recovery/sequential_complex_recovery.h
new file mode 100644
index 0000000000..507e0ea0ab
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/include/complex_recovery/sequential_complex_recovery.h
@@ -0,0 +1,32 @@
+#ifndef COMPLEX_RECOVERY_H
+#define COMPLEX_RECOVERY_H
+
+#include
+#include
+#include
+#include
+
+namespace complex_recovery
+{
+
+class SequentialComplexRecovery : public nav_core::RecoveryBehavior
+{
+public:
+ SequentialComplexRecovery();
+ void initialize(
+ std::string name,
+ tf2_ros::Buffer*,
+ costmap_2d::Costmap2DROS* global_costmap,
+ costmap_2d::Costmap2DROS* local_costmap);
+ void runBehavior();
+ ~SequentialComplexRecovery();
+
+private:
+ bool initialized_;
+ std::vector > recovery_behaviors_;
+ std::vector recovery_behavior_names_;
+ pluginlib::ClassLoader recovery_loader_;
+};
+};
+
+#endif
diff --git a/jsk_robot_common/complex_recovery/include/complex_recovery/utils.h b/jsk_robot_common/complex_recovery/include/complex_recovery/utils.h
new file mode 100644
index 0000000000..a02ee51eec
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/include/complex_recovery/utils.h
@@ -0,0 +1,21 @@
+#include
+#include
+#include
+#include
+#include
+
+namespace complex_recovery
+{
+
+
+bool loadRecoveryBehaviors(
+ std::string parent_name,
+ ros::NodeHandle& node,
+ pluginlib::ClassLoader& recovery_loader,
+ std::vector >& recovery_behaviors,
+ std::vector& recovery_behavior_names,
+ tf2_ros::Buffer* ptr_tf_buffer,
+ costmap_2d::Costmap2DROS* ptr_global_costmap,
+ costmap_2d::Costmap2DROS* ptr_local_costmap
+ );
+};
diff --git a/jsk_robot_common/complex_recovery/package.xml b/jsk_robot_common/complex_recovery/package.xml
new file mode 100644
index 0000000000..1892a9fc61
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/package.xml
@@ -0,0 +1,30 @@
+
+
+ complex_recovery
+ 1.1.0
+ The complex_recovery package
+
+ Koki Shinjo
+ Kei Okada
+ Koki Shinjo
+
+ BSD
+
+
+ catkin
+
+ costmap_2d
+ nav_core
+ pluginlib
+ roscpp
+ tf2
+ tf2_ros
+
+ rostest
+ speak_and_wait_recovery
+ sound_play
+
+
+
+
+
diff --git a/jsk_robot_common/complex_recovery/src/parallel_complex_recovery.cpp b/jsk_robot_common/complex_recovery/src/parallel_complex_recovery.cpp
new file mode 100644
index 0000000000..80425e0f1b
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/src/parallel_complex_recovery.cpp
@@ -0,0 +1,84 @@
+#include
+#include
+#include
+#include
+
+PLUGINLIB_EXPORT_CLASS(complex_recovery::ParallelComplexRecovery, nav_core::RecoveryBehavior)
+
+namespace complex_recovery
+{
+
+ParallelComplexRecovery::ParallelComplexRecovery():
+ initialized_(false),
+ recovery_loader_("nav_core", "nav_core::RecoveryBehavior")
+{
+}
+
+void ParallelComplexRecovery::initialize(
+ std::string name,
+ tf2_ros::Buffer* tf_buffer,
+ costmap_2d::Costmap2DROS* global_costmap,
+ costmap_2d::Costmap2DROS* local_costmap)
+{
+ if (not initialized_) {
+ ros::NodeHandle private_nh("~/" + name);
+ bool success = loadRecoveryBehaviors(
+ name,
+ private_nh,
+ recovery_loader_,
+ recovery_behaviors_,
+ recovery_behavior_names_,
+ tf_buffer,
+ global_costmap,
+ local_costmap
+ );
+ if ( not success ) {
+ ROS_ERROR("Failed to load behaviors.");
+ } else {
+ ROS_INFO("Behaviors are loaded.");
+ for (auto behavior_name = recovery_behavior_names_.begin(); behavior_name != recovery_behavior_names_.end(); behavior_name++) {
+ ROS_INFO("behavior: %s", behavior_name->c_str());
+ }
+ }
+
+ initialized_ = true;
+ } else {
+ ROS_ERROR("You should not call initialize twice on this object, doing nothing");
+ }
+}
+
+ParallelComplexRecovery::~ParallelComplexRecovery()
+{
+ recovery_behaviors_.clear();
+}
+
+void ParallelComplexRecovery::runBehavior()
+{
+ if (not initialized_)
+ {
+ ROS_ERROR("This object must be initialized before runBehavior is called");
+ return;
+ }
+
+ std::vector> threads;
+
+ ROS_INFO("Start executing behaviors in parallel.");
+ for (auto index = 0; index < recovery_behaviors_.size(); index++) {
+ ROS_INFO("start executing behavior %s", recovery_behavior_names_[index].c_str());
+ threads.push_back(
+ std::shared_ptr(
+ new std::thread(
+ &nav_core::RecoveryBehavior::runBehavior,
+ recovery_behaviors_[index]
+ )));
+ }
+
+ ROS_INFO("Waiting for behaviors to finish.");
+ for (auto thread = threads.begin(); thread != threads.end(); thread++) {
+ (*thread)->join();
+ }
+
+ ROS_INFO("All behaviors have finished.");
+}
+
+};
diff --git a/jsk_robot_common/complex_recovery/src/sequential_complex_recovery.cpp b/jsk_robot_common/complex_recovery/src/sequential_complex_recovery.cpp
new file mode 100644
index 0000000000..4a71196ea1
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/src/sequential_complex_recovery.cpp
@@ -0,0 +1,71 @@
+#include
+#include
+#include
+
+PLUGINLIB_EXPORT_CLASS(complex_recovery::SequentialComplexRecovery, nav_core::RecoveryBehavior)
+
+namespace complex_recovery
+{
+
+SequentialComplexRecovery::SequentialComplexRecovery():
+ initialized_(false),
+ recovery_loader_("nav_core", "nav_core::RecoveryBehavior")
+{
+}
+
+void SequentialComplexRecovery::initialize(
+ std::string name,
+ tf2_ros::Buffer* tf_buffer,
+ costmap_2d::Costmap2DROS* global_costmap,
+ costmap_2d::Costmap2DROS* local_costmap)
+{
+ if (not initialized_) {
+ ros::NodeHandle private_nh("~/" + name);
+ bool success = loadRecoveryBehaviors(
+ name,
+ private_nh,
+ recovery_loader_,
+ recovery_behaviors_,
+ recovery_behavior_names_,
+ tf_buffer,
+ global_costmap,
+ local_costmap
+ );
+ if ( not success ) {
+ ROS_ERROR("Failed to load behaviors.");
+ } else {
+ ROS_INFO("Behaviors are loaded.");
+ for (auto behavior_name = recovery_behavior_names_.begin(); behavior_name != recovery_behavior_names_.end(); behavior_name++) {
+ ROS_INFO("behavior: %s", behavior_name->c_str());
+ }
+ }
+
+ initialized_ = true;
+ } else {
+ ROS_ERROR("You should not call initialize twice on this object, doing nothing");
+ }
+}
+
+SequentialComplexRecovery::~SequentialComplexRecovery()
+{
+ recovery_behaviors_.clear();
+}
+
+void SequentialComplexRecovery::runBehavior()
+{
+ if (not initialized_)
+ {
+ ROS_ERROR("This object must be initialized before runBehavior is called");
+ return;
+ }
+
+ ROS_INFO("Start executing behaviors sequentially.");
+ for (auto index = 0; index < recovery_behaviors_.size(); index++) {
+ ROS_INFO("executing behavior %s", recovery_behavior_names_[index].c_str());
+ recovery_behaviors_[index]->runBehavior();
+ }
+
+ ROS_INFO("All behaviors have finished.");
+}
+
+};
diff --git a/jsk_robot_common/complex_recovery/src/utils.cpp b/jsk_robot_common/complex_recovery/src/utils.cpp
new file mode 100644
index 0000000000..660d91007f
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/src/utils.cpp
@@ -0,0 +1,108 @@
+#include
+
+namespace complex_recovery
+{
+
+/**
+ *
+ * Almost copied from https://github.com/ros-planning/navigation/blob/47c9c629fc93e6c6bbcd3109eb2d5b55efce400d/move_base/src/move_base.cpp#L1019-L1102
+ */
+bool loadRecoveryBehaviors(
+ std::string parent_name,
+ ros::NodeHandle& node,
+ pluginlib::ClassLoader& recovery_loader,
+ std::vector >& recovery_behaviors,
+ std::vector& recovery_behavior_names,
+ tf2_ros::Buffer* ptr_tf_buffer,
+ costmap_2d::Costmap2DROS* ptr_global_costmap,
+ costmap_2d::Costmap2DROS* ptr_local_costmap
+ )
+{
+ XmlRpc::XmlRpcValue behavior_list;
+ if(node.getParam("recovery_behaviors", behavior_list)){
+ if(behavior_list.getType() == XmlRpc::XmlRpcValue::TypeArray){
+ for(int i = 0; i < behavior_list.size(); ++i){
+ if(behavior_list[i].getType() == XmlRpc::XmlRpcValue::TypeStruct){
+ if(behavior_list[i].hasMember("name") && behavior_list[i].hasMember("type")){
+ //check for recovery behaviors with the same name
+ for(int j = i + 1; j < behavior_list.size(); j++){
+ if(behavior_list[j].getType() == XmlRpc::XmlRpcValue::TypeStruct){
+ if(behavior_list[j].hasMember("name") && behavior_list[j].hasMember("type")){
+ std::string name_i = behavior_list[i]["name"];
+ std::string name_j = behavior_list[j]["name"];
+ if(name_i == name_j){
+ ROS_ERROR("A recovery behavior with the name %s already exists, this is not allowed. Using the default recovery behaviors instead.",
+ name_i.c_str());
+ return false;
+ }
+ }
+ }
+ }
+ }
+ else{
+ ROS_ERROR("Recovery behaviors must have a name and a type and this does not. Using the default recovery behaviors instead.");
+ return false;
+ }
+ }
+ else{
+ ROS_ERROR("Recovery behaviors must be specified as maps, but they are XmlRpcType %d. We'll use the default recovery behaviors instead.",
+ behavior_list[i].getType());
+ return false;
+ }
+ }
+
+ //if we've made it to this point, we know that the list is legal so we'll create all the recovery behaviors
+ for(int i = 0; i < behavior_list.size(); ++i){
+ try{
+ //check if a non fully qualified name has potentially been passed in
+ if(!recovery_loader.isClassAvailable(behavior_list[i]["type"])){
+ std::vector classes = recovery_loader.getDeclaredClasses();
+ for(unsigned int i = 0; i < classes.size(); ++i){
+ if(behavior_list[i]["type"] == recovery_loader.getName(classes[i])){
+ //if we've found a match... we'll get the fully qualified name and break out of the loop
+ ROS_WARN("Recovery behavior specifications should now include the package name. You are using a deprecated API. Please switch from %s to %s in your yaml file.",
+ std::string(behavior_list[i]["type"]).c_str(), classes[i].c_str());
+ behavior_list[i]["type"] = classes[i];
+ break;
+ }
+ }
+ }
+
+ boost::shared_ptr behavior(recovery_loader.createInstance(behavior_list[i]["type"]));
+
+ //shouldn't be possible, but it won't hurt to check
+ if(behavior.get() == NULL){
+ ROS_ERROR("The ClassLoader returned a null pointer without throwing an exception. This should not happen");
+ return false;
+ }
+
+ //initialize the recovery behavior with its name
+ std::string base_name = behavior_list[i]["name"];
+ behavior->initialize(
+ parent_name + "/" + base_name,
+ ptr_tf_buffer, ptr_global_costmap, ptr_local_costmap);
+ recovery_behavior_names.push_back(behavior_list[i]["name"]);
+ recovery_behaviors.push_back(behavior);
+ }
+ catch(pluginlib::PluginlibException& ex){
+ ROS_ERROR("Failed to load a plugin. Using default recovery behaviors. Error: %s", ex.what());
+ return false;
+ }
+ }
+ }
+ else{
+ ROS_ERROR("The recovery behavior specification must be a list, but is of XmlRpcType %d. We'll use the default recovery behaviors instead.",
+ behavior_list.getType());
+ return false;
+ }
+ }
+ else{
+ //if no recovery_behaviors are specified, we'll just load the defaults
+ return false;
+ }
+
+ //if we've made it here... we've constructed a recovery behavior list successfully
+ return true;
+}
+
+};
diff --git a/jsk_robot_common/complex_recovery/tests/parallel_complex_recovery.test b/jsk_robot_common/complex_recovery/tests/parallel_complex_recovery.test
new file mode 100644
index 0000000000..17dc02c20c
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/tests/parallel_complex_recovery.test
@@ -0,0 +1,56 @@
+
+
+
+ recovery:
+ recovery_behaviors:
+ - name: 'wait_and_speak_complex0'
+ type: 'complex_recovery/SequentialComplexRecovery'
+ - name: 'wait_and_speak_complex1'
+ type: 'complex_recovery/SequentialComplexRecovery'
+ wait_and_speak_complex0:
+ recovery_behaviors:
+ - name: 'wait_3_secs'
+ type: 'speak_and_wait_recovery/SpeakAndWaitRecovery'
+ - name: 'speak_and_wait0'
+ type: 'speak_and_wait_recovery/SpeakAndWaitRecovery'
+ wait_3_secs:
+ speak_text: ''
+ duration_wait: 3.0
+ duration_timeout: 2.0
+ result_timeout: 1.0
+ speak_and_wait0:
+ speak_text: 'test0'
+ duration_wait: 2.0
+ duration_timeout: 2.0
+ result_timeout: 1.0
+ wait_and_speak_complex1:
+ recovery_behaviors:
+ - name: 'wait_5_secs'
+ type: 'speak_and_wait_recovery/SpeakAndWaitRecovery'
+ - name: 'speak_and_wait1'
+ type: 'speak_and_wait_recovery/SpeakAndWaitRecovery'
+ wait_5_secs:
+ speak_text: ''
+ duration_wait: 5.0
+ duration_timeout: 2.0
+ result_timeout: 1.0
+ speak_and_wait1:
+ speak_text: 'test1'
+ duration_wait: 2.0
+ duration_timeout: 2.0
+ result_timeout: 1.0
+
+
+
+
+
diff --git a/jsk_robot_common/complex_recovery/tests/parallel_complex_recovery_test_node.cpp b/jsk_robot_common/complex_recovery/tests/parallel_complex_recovery_test_node.cpp
new file mode 100644
index 0000000000..3d54f9cad7
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/tests/parallel_complex_recovery_test_node.cpp
@@ -0,0 +1,21 @@
+#include
+#include "complex_recovery/parallel_complex_recovery.h"
+
+int main(int argc, char **argv)
+{
+ ros::init(argc, argv, "complex_recovery_simple_node");
+ ros::NodeHandle nh;
+
+ complex_recovery::ParallelComplexRecovery recovery;
+ recovery.initialize(std::string("recovery"),NULL,NULL,NULL);
+
+ ros::Rate rate(10);
+ while (ros::ok()) {
+ ROS_WARN("Spoken a test");
+ recovery.runBehavior();
+ ros::spinOnce();
+ rate.sleep();
+ }
+
+ return 0;
+}
diff --git a/jsk_robot_common/complex_recovery/tests/sequential_complex_recovery.test b/jsk_robot_common/complex_recovery/tests/sequential_complex_recovery.test
new file mode 100644
index 0000000000..78bfd748f2
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/tests/sequential_complex_recovery.test
@@ -0,0 +1,34 @@
+
+
+
+ recovery:
+ recovery_behaviors:
+ - name: 'speak_and_wait0'
+ type: 'speak_and_wait_recovery/SpeakAndWaitRecovery'
+ - name: 'speak_and_wait1'
+ type: 'speak_and_wait_recovery/SpeakAndWaitRecovery'
+ speak_and_wait0:
+ speak_text: 'test0'
+ duration_wait: 2.0
+ duration_timeout: 2.0
+ result_timeout: 1.0
+ speak_and_wait1:
+ speak_text: 'test1'
+ duration_wait: 2.0
+ duration_timeout: 2.0
+ result_timeout: 1.0
+
+
+
+
+
diff --git a/jsk_robot_common/complex_recovery/tests/sequential_complex_recovery_test_node.cpp b/jsk_robot_common/complex_recovery/tests/sequential_complex_recovery_test_node.cpp
new file mode 100644
index 0000000000..618ff5656c
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/tests/sequential_complex_recovery_test_node.cpp
@@ -0,0 +1,21 @@
+#include
+#include "complex_recovery/sequential_complex_recovery.h"
+
+int main(int argc, char **argv)
+{
+ ros::init(argc, argv, "complex_recovery_simple_node");
+ ros::NodeHandle nh;
+
+ complex_recovery::SequentialComplexRecovery recovery;
+ recovery.initialize(std::string("recovery"),NULL,NULL,NULL);
+
+ ros::Rate rate(10);
+ while (ros::ok()) {
+ ROS_WARN("Spoken a test");
+ recovery.runBehavior();
+ ros::spinOnce();
+ rate.sleep();
+ }
+
+ return 0;
+}
diff --git a/jsk_robot_common/complex_recovery/tests/test_parallel_complex_recovery.py b/jsk_robot_common/complex_recovery/tests/test_parallel_complex_recovery.py
new file mode 100755
index 0000000000..8d7fea0de8
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/tests/test_parallel_complex_recovery.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+from sound_play.msg import SoundRequestResult
+from sound_play.msg import SoundRequestAction
+import rospy
+import actionlib
+import threading
+import sys
+import unittest
+import rostest
+
+PKG = 'complex_recovery'
+NAME = 'parallel_complex_recovery_test'
+
+
+class TestParallelComplexRecovery(unittest.TestCase):
+
+ def __init__(self, *args):
+ super(self.__class__, self).__init__(*args)
+ rospy.init_node(NAME)
+
+ self.lock_for_msg = threading.Lock()
+ self.speech_text_list = []
+
+ self.action_server = \
+ actionlib.SimpleActionServer(
+ '/sound_play', SoundRequestAction, execute_cb=self.handler, auto_start=False)
+ self.action_server.start()
+
+ def handler(self, goal):
+
+ with self.lock_for_msg:
+ self.speech_text_list.append(goal.sound_request.arg)
+ rospy.logwarn('Receive a message')
+ self.action_server.set_succeeded(
+ SoundRequestResult(playing=True, stamp=rospy.Time.now()))
+
+ def test_parallel_complex_recovery(self):
+
+ rate = rospy.Rate(1)
+ while not rospy.is_shutdown():
+ with self.lock_for_msg:
+ if 'test0' in self.speech_text_list and 'test1' in self.speech_text_list:
+ break
+ else:
+ rospy.logwarn('waiting')
+ rate.sleep()
+
+ with self.lock_for_msg:
+ rospy.logwarn('speech_text_list: {}'.format(self.speech_text_list))
+ self.assertTrue('test0' in self.speech_text_list and 'test1' in self.speech_text_list)
+
+
+if __name__ == '__main__':
+ rostest.rosrun(PKG, NAME, TestParallelComplexRecovery, sys.argv)
diff --git a/jsk_robot_common/complex_recovery/tests/test_sequential_complex_recovery.py b/jsk_robot_common/complex_recovery/tests/test_sequential_complex_recovery.py
new file mode 100755
index 0000000000..4dce3752b1
--- /dev/null
+++ b/jsk_robot_common/complex_recovery/tests/test_sequential_complex_recovery.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+from sound_play.msg import SoundRequestResult
+from sound_play.msg import SoundRequestAction
+import rospy
+import actionlib
+import threading
+import sys
+import unittest
+import rostest
+
+PKG = 'complex_recovery'
+NAME = 'sequential_complex_recovery_test'
+
+
+class TestSequentialComplexRecovery(unittest.TestCase):
+
+ def __init__(self, *args):
+ super(self.__class__, self).__init__(*args)
+ rospy.init_node(NAME)
+
+ self.lock_for_msg = threading.Lock()
+ self.speech_text_list = []
+
+ self.action_server = \
+ actionlib.SimpleActionServer(
+ '/sound_play', SoundRequestAction, execute_cb=self.handler, auto_start=False)
+ self.action_server.start()
+
+ def handler(self, goal):
+
+ with self.lock_for_msg:
+ self.speech_text_list.append(goal.sound_request.arg)
+ rospy.logwarn('Receive a message')
+ self.action_server.set_succeeded(
+ SoundRequestResult(playing=True, stamp=rospy.Time.now()))
+
+ def test_sequential_complex_recovery(self):
+
+ rate = rospy.Rate(1)
+ while not rospy.is_shutdown():
+ with self.lock_for_msg:
+ if 'test0' in self.speech_text_list and 'test1' in self.speech_text_list:
+ break
+ else:
+ rospy.logwarn('waiting')
+ rate.sleep()
+
+ with self.lock_for_msg:
+ rospy.logwarn('speech_text_list: {}'.format(self.speech_text_list))
+ self.assertTrue('test0' in self.speech_text_list and 'test1' in self.speech_text_list)
+
+
+if __name__ == '__main__':
+ rostest.rosrun(PKG, NAME, TestSequentialComplexRecovery, sys.argv)