diff --git a/.devcontainer/humble/Dockerfile b/.devcontainer/humble/Dockerfile
new file mode 100644
index 0000000..a71f968
--- /dev/null
+++ b/.devcontainer/humble/Dockerfile
@@ -0,0 +1,30 @@
+FROM ros:humble
+
+# Add vscode user with same UID and GID as your host system
+# (modified from https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user#_creating-a-nonroot-user)
+# ubuntu now has a 1000 user ubuntu
+ARG USERNAME=ubuntu
+ARG USER_UID=1000
+ARG USER_GID=$USER_UID
+
+RUN groupadd --gid $USER_GID $USERNAME \
+ && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME
+
+RUN apt-get update && apt-get upgrade -y \
+ && apt-get install -y \
+ sudo \
+ git \
+ && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
+ && chmod 0440 /etc/sudoers.d/$USERNAME
+
+# Switch from root to user
+USER $USERNAME
+
+# Rosdep update
+RUN rosdep update
+
+# Source the ROS setup file
+RUN echo "source /opt/ros/${ROS_DISTRO}/setup.bash" >> ~/.bashrc
+
+# make sure folders exist
+RUN mkdir -p ~/colcon_ws/src
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/humble/devcontainer.json
similarity index 95%
rename from .devcontainer/devcontainer.json
rename to .devcontainer/humble/devcontainer.json
index 9148ff5..02e20ce 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/humble/devcontainer.json
@@ -1,5 +1,5 @@
{
- "name": "jazzy base",
+ "name": "humble base",
"dockerFile": "Dockerfile",
"remoteUser": "ubuntu",
"containerEnv": {
diff --git a/.devcontainer/Dockerfile b/.devcontainer/jazzy/Dockerfile
similarity index 100%
rename from .devcontainer/Dockerfile
rename to .devcontainer/jazzy/Dockerfile
diff --git a/.devcontainer/jazzy/devcontainer.json b/.devcontainer/jazzy/devcontainer.json
new file mode 100644
index 0000000..e1f8dd5
--- /dev/null
+++ b/.devcontainer/jazzy/devcontainer.json
@@ -0,0 +1,17 @@
+{
+ "name": "jazzy base",
+ "dockerFile": "Dockerfile",
+ "remoteUser": "ubuntu",
+ "containerEnv": {
+ "DISPLAY": "${localEnv:DISPLAY}"
+ },
+ "runArgs": [
+ "--privileged",
+ "--network=host"
+ ],
+ "workspaceMount": "source=${localWorkspaceFolder},target=/home/ubuntu/colcon_ws/${localWorkspaceFolderBasename},type=bind",
+ "workspaceFolder": "/home/ubuntu/colcon_ws",
+ "mounts": [
+ "source=${localEnv:HOME}${localEnv:USERPROFILE}/.bash_history,target=/home/ubuntu/.bash_history,type=bind"
+ ]
+}
diff --git a/.github/workflows/amd64_jazzy.yml b/.github/workflows/amd64_jazzy.yml
new file mode 100644
index 0000000..17a2287
--- /dev/null
+++ b/.github/workflows/amd64_jazzy.yml
@@ -0,0 +1,70 @@
+name: ROS Jazzy Capabilities2
+
+on:
+ workflow_dispatch:
+
+ push:
+ branches:
+ - capabilities2-server-fabric
+ paths-ignore:
+ - README.md
+
+ pull_request:
+ branches:
+ - capabilities2-server-fabric
+ paths-ignore:
+ - README.md
+
+env:
+ REGISTRY: ghcr.io
+ OWNER: collaborativeroboticslab
+ IMAGE_NAME: capabilities2
+
+
+# https://docs.github.com/en/actions/using-jobs/using-concurrency
+concurrency:
+ # only cancel in-progress jobs or runs for the current workflow - matches against branch & tags
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+
+ build-and-push-image:
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: write
+ packages: write
+
+ steps:
+ - name: Remove unnecessary files
+ run: |
+ sudo rm -rf /usr/share/dotnet
+ sudo rm -rf "$AGENT_TOOLSDIRECTORY"
+
+ - name: Check out the repository
+ uses: actions/checkout@v4
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to the Container registry
+ run: |
+ docker login --username ${{ env.OWNER }} --password ${{ secrets.GH_PAT }} ghcr.io
+
+ - name: Build and Push
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: docker/Dockerfile
+ platforms: linux/amd64
+ push: true
+ tags: |
+ ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:latest
+ ${{ env.REGISTRY }}/${{ env.OWNER }}/${{ env.IMAGE_NAME }}:jazzy
+ build-args: |
+ GITHUB_USERNAME=${{ github.actor }}
+ GITHUB_PAT=${{ secrets.GH_PAT }}
\ No newline at end of file
diff --git a/AUTHORS b/AUTHORS
index 848e7ac..c86be4e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,4 +1,4 @@
Michael Pritchard
Thomas Muller-dardelin
-Kalana Rathnayake
+Kalana Ratnayake
Buddhi Gamage
diff --git a/TODO.md b/TODO.md
index 0a7e405..0808968 100644
--- a/TODO.md
+++ b/TODO.md
@@ -4,14 +4,16 @@
- [x] names need to be in package/name format everywhere
- [x] better docs
-- [ ] BUG: escape db function variables
+- [x] BUG: handle "'" in db queries
+- [x] BUG: escape db function variables
- [ ] close or change communication to launch proxy so that it can't be accessed from ros network
-- [ ] BUG: handle "'" in db queries
+- [ ] BUG: fix issue with connecting to services and actions started using launch proxy
+
## Features
+- [x] try using ros package to find exports automatically
+- [x] improve the event system
- [ ] implement provider definition handling in runner
-- [ ] try using ros package to find exports automatically
-- [ ] improve the event system
- [ ] move to established db handler lib
- [ ] better bt runner impl
diff --git a/capabilities2/package.xml b/capabilities2/package.xml
index 691851b..a012c4d 100644
--- a/capabilities2/package.xml
+++ b/capabilities2/package.xml
@@ -8,6 +8,7 @@
Michael Pritchard
mik-p
+ Kalana Ratnayake
Michael Pritchard
diff --git a/capabilities2_executor/CMakeLists.txt b/capabilities2_executor/CMakeLists.txt
deleted file mode 100644
index ccec815..0000000
--- a/capabilities2_executor/CMakeLists.txt
+++ /dev/null
@@ -1,69 +0,0 @@
-cmake_minimum_required(VERSION 3.8)
-project(capabilities2_executor)
-
-# Default to C++17
-if(NOT CMAKE_CXX_STANDARD)
- set(CMAKE_CXX_STANDARD 17)
-endif()
-
-if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
- add_compile_options(-Wall -Wextra -Wpedantic)
-endif()
-
-# find dependencies
-find_package(ament_cmake REQUIRED)
-find_package(rclcpp REQUIRED)
-find_package(rclcpp_action REQUIRED)
-find_package(capabilities2_msgs REQUIRED)
-
-find_package(PkgConfig)
-pkg_check_modules(TINYXML2 tinyxml2 REQUIRED)
-
-include_directories(
- include
- ${TINYXML2_INCLUDE_DIRS}
-)
-
-add_library(${PROJECT_NAME} SHARED
- src/capabilities_executor.cpp
-)
-
-target_link_libraries(${PROJECT_NAME}
- ${TINYXML2_LIBRARIES}
-)
-
-ament_target_dependencies(${PROJECT_NAME}
- rclcpp
- rclcpp_action
- capabilities2_msgs
- TINYXML2
-)
-
-add_library(${PROJECT_NAME}_file SHARED
- src/capabilities_file_parser.cpp
-)
-
-target_link_libraries(${PROJECT_NAME}_file
- ${TINYXML2_LIBRARIES}
-)
-
-ament_target_dependencies(${PROJECT_NAME}_file
- rclcpp
- rclcpp_action
- capabilities2_msgs
- TINYXML2
-)
-
-install(DIRECTORY include/
- DESTINATION include
-)
-
-install(DIRECTORY launch
- DESTINATION share/${PROJECT_NAME}
-)
-
-install(DIRECTORY config
- DESTINATION share/${PROJECT_NAME}
-)
-
-ament_package()
diff --git a/capabilities2_executor/config/.gitkeep b/capabilities2_executor/config/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/capabilities2_executor/include/capabilities2_executor/capabilities_executor.hpp b/capabilities2_executor/include/capabilities2_executor/capabilities_executor.hpp
deleted file mode 100644
index 5624987..0000000
--- a/capabilities2_executor/include/capabilities2_executor/capabilities_executor.hpp
+++ /dev/null
@@ -1,531 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-#include
-
-#include
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-/**
- * @brief Capabilities Executor
- *
- * Capabilities executor node that provides a ROS client for the capabilities server.
- * Able to receive a XML file that implements a plan via action server that it exposes
- * or via a file read.
- *
- */
-
-class CapabilitiesExecutor : public rclcpp::Node
-{
-public:
- CapabilitiesExecutor(const rclcpp::NodeOptions& options = rclcpp::NodeOptions())
- : Node("Capabilities2_Executor", options)
- {
- control_tag_list.push_back("sequential");
- control_tag_list.push_back("parallel");
- control_tag_list.push_back("recovery");
-
- this->planner_server_ = rclcpp_action::create_server(
- this, "~/capabilities",
- std::bind(&CapabilitiesExecutor::handle_goal, this, std::placeholders::_1, std::placeholders::_2),
- std::bind(&CapabilitiesExecutor::handle_cancel, this, std::placeholders::_1),
- std::bind(&CapabilitiesExecutor::handle_accepted, this, std::placeholders::_1));
-
- this->client_capabilities_ = rclcpp_action::create_client(this, "~/capabilities_fabric");
-
- get_interfaces_client_ = this->create_client("~/get_interfaces");
- get_sem_interf_client_ = this->create_client("~/get_semantic_interfaces");
-
- establish_bond_client_ = this->create_client("~/establish_bond");
- }
-
-private:
- /**
- * @brief Handle the goal request that comes in from client. returns whether goal is accepted or rejected
- *
- *
- * @param uuid uuid of the goal
- * @param goal pointer to the action goal message
- * @return rclcpp_action::GoalResponse
- */
- rclcpp_action::GoalResponse handle_goal(const rclcpp_action::GoalUUID& uuid,
- std::shared_ptr goal)
- {
- RCLCPP_INFO(this->get_logger(), "Received the goal request with the plan");
- (void)uuid;
-
- // try to parse the std::string plan from capabilities_msgs/Plan to the to a XMLDocument file
- tinyxml2::XMLError xml_status = document.Parse(goal->plan.c_str());
-
- // check if the file parsing failed
- if (xml_status != tinyxml2::XMLError::XML_SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(), "Parsing the plan from goal message failed");
- return rclcpp_action::GoalResponse::REJECT;
- }
-
- return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
- }
-
- /**
- * @brief Handle the goal cancel request that comes in from client.
- *
- * @param goal_handle pointer to the action goal handle
- * @return rclcpp_action::GoalResponse
- */
- rclcpp_action::CancelResponse
- handle_cancel(const std::shared_ptr> goal_handle)
- {
- RCLCPP_INFO(this->get_logger(), "Received the request to cancel the plan");
- (void)goal_handle;
-
- return rclcpp_action::CancelResponse::ACCEPT;
- }
-
- /**
- * @brief Handle the goal accept event originating from handle_goal.
- *
- * @param goal_handle pointer to the action goal handle
- */
- void
- handle_accepted(const std::shared_ptr> goal_handle)
- {
- execution_thread = std::make_unique(
- std::bind(&CapabilitiesExecutor::execute_plan, this, std::placeholders::_1), goal_handle);
- }
-
- /**
- * @brief request the interfaces, semantic_interfaces and providers from the capabilities2 server
- *
- * @return `true` if interface retreival is successful,`false` otherwise
- */
- bool request_information()
- {
- // create request messages
- auto request_interface = std::make_shared();
- auto request_sematic = std::make_shared();
- auto request_providers = std::make_shared();
-
- // request data from the server
- auto result_future = get_interfaces_client_->async_send_request(request_interface);
-
- RCLCPP_INFO(this->get_logger(), "Requesting Interface information from Capabilities2 Server");
-
- // wait until data is received
- if (rclcpp::spin_until_future_complete(shared_from_this(), result_future) != rclcpp::FutureReturnCode::SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(), "Failed to receive Interface information from Capabilities2 Server");
- return false;
- }
-
- RCLCPP_INFO(this->get_logger(), "Received Interface information from Capabilities2 Server");
-
- // request semantic interfaces available for each and every interface got from the server
- for (const auto& interface : result_future.get()->interfaces)
- {
- request_sematic->interface = interface;
-
- // request semantic interface from the server
- auto result_semantic_future = get_sem_interf_client_->async_send_request(request_sematic);
-
- RCLCPP_INFO(this->get_logger(), "Requesting Semantic Interface information from Capabilities2 Server for %s",
- interface.c_str());
-
- // wait until data is received
- if (rclcpp::spin_until_future_complete(shared_from_this(), result_semantic_future) !=
- rclcpp::FutureReturnCode::SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(),
- "Failed to receive Semantic Interface information from Capabilities2 Server for %s",
- interface.c_str());
- return false;
- }
-
- RCLCPP_INFO(this->get_logger(), "Received Semantic Interface information from Capabilities2 Server for %s",
- interface.c_str());
-
- // add sematic interfaces to the list if available
- if (result_semantic_future.get()->semantic_interfaces.size() > 0)
- {
- for (const auto& semantic_interface : result_semantic_future.get()->semantic_interfaces)
- {
- interface_list.push_back(semantic_interface);
-
- // request providers of the semantic interface
- request_providers->interface = semantic_interface;
- request_providers->include_semantic = true;
-
- auto result_providers_future = get_providers_client_->async_send_request(request_providers);
-
- RCLCPP_INFO(this->get_logger(),
- "Requesting Providers information from Capabilities2 Server for semantic interface %s",
- semantic_interface.c_str());
-
- // wait until data is received
- if (rclcpp::spin_until_future_complete(shared_from_this(), result_providers_future) !=
- rclcpp::FutureReturnCode::SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(), "Failed to receive Providers information from Capabilities2 Server for %s",
- semantic_interface.c_str());
- return false;
- }
-
- RCLCPP_INFO(this->get_logger(), "Received Providers information from Capabilities2 Server for %s",
- semantic_interface.c_str());
-
- // add defualt provider to the list
- providers_list.push_back(result_providers_future.get()->default_provider);
-
- // add additional providers to the list if available
- if (result_providers_future.get()->providers.size() > 0)
- {
- for (const auto& provider : result_providers_future.get()->providers)
- {
- providers_list.push_back(provider);
- }
- }
- }
- }
- // if no semantic interfaces are availble for a given interface, add the interface instead
- else
- {
- interface_list.push_back(interface);
-
- // request providers of the semantic interface
- request_providers->interface = interface;
- request_providers->include_semantic = false;
-
- auto result_providers_future = get_providers_client_->async_send_request(request_providers);
-
- RCLCPP_INFO(this->get_logger(),
- "Requesting Providers information from Capabilities2 Server for semantic interface %s",
- interface.c_str());
-
- // wait until data is received
- if (rclcpp::spin_until_future_complete(shared_from_this(), result_providers_future) !=
- rclcpp::FutureReturnCode::SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(), "Failed to receive Providers information from Capabilities2 Server for %s",
- interface.c_str());
- return false;
- }
-
- RCLCPP_INFO(this->get_logger(), "Received Providers information from Capabilities2 Server for %s",
- interface.c_str());
-
- providers_list.push_back(result_providers_future.get()->default_provider);
-
- // add sematic interfaces to the list if available
- if (result_providers_future.get()->providers.size() > 0)
- {
- for (const auto& provider : result_providers_future.get()->providers)
- {
- providers_list.push_back(provider);
- }
- }
- }
- }
-
- return true;
- }
-
- /**
- * @brief verify the plan using received interfaces
- *
- * @return `true` if interface retreival is successful,`false` otherwise
- */
- bool verify_plan()
- {
- // intialize a vector to accomodate elements from both
- std::vector tag_list(interface_list.size() + control_tag_list.size());
- std::merge(interface_list.begin(), interface_list.end(), control_tag_list.begin(), control_tag_list.end(),
- tag_list.begin());
-
- // verify whether document got 'plan' tags
- if (!capabilities2_xml_parser::check_plan_tag(document))
- {
- RCLCPP_INFO(this->get_logger(), "Execution plan is not compatible. Please recheck and update");
- return false;
- }
-
- // extract the components within the 'plan' tags
- tinyxml2::XMLElement* plan = capabilities2_xml_parser::get_plan(document);
-
- // verify whether the plan is valid
- if (!capabilities2_xml_parser::check_tags(plan, interface_list, providers_list, control_tag_list))
- {
- RCLCPP_INFO(this->get_logger(), "Execution plan is faulty. Please recheck and update");
- return false;
- }
-
- return true;
- }
-
- /**
- * @brief establish the bond with capabilities2 server
- *
- * @return `true` if bond establishing is successful,`false` otherwise
- */
- bool establish_bond()
- {
- // create bond establishing server request
- auto request_bond = std::make_shared();
-
- // send the request
- auto result_future = establish_bond_client_->async_send_request(request_bond);
-
- RCLCPP_INFO(this->get_logger(), "Requesting to establish bond with Capabilities2 Server");
-
- // wait for the result
- if (rclcpp::spin_until_future_complete(shared_from_this(), result_future) != rclcpp::FutureReturnCode::SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(), "Failed to establish bond with Capabilities2 Server");
- return false;
- }
-
- RCLCPP_INFO(this->get_logger(), "Established bond with Capabilities2 Server");
-
- bond_id = result_future.get()->bond_id;
-
- return true;
- }
-
- /**
- * @brief execute the plan
- *
- * @param server_goal_handle goal handle of the server
- */
- void execute_plan(
- const std::shared_ptr> server_goal_handle)
- {
- auto result = std::make_shared();
-
- // verify the plan
- if (!request_information())
- {
- RCLCPP_INFO(this->get_logger(), "Interface retreival failed");
-
- // TODO: improve with error codes
- result->success = false;
- server_goal_handle->canceled(result);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
- }
-
- // verify the plan
- if (!verify_plan())
- {
- RCLCPP_INFO(this->get_logger(), "Plan verification failed");
-
- // TODO: improve with error codes
- result->success = false;
- server_goal_handle->canceled(result);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
- }
-
- // extract the plan from the XMLDocument
- tinyxml2::XMLElement* plan = capabilities2_xml_parser::get_plan(document);
-
- // Extract the connections from the plan
- capabilities2_xml_parser::extract_connections(plan, connection_list);
-
- // estasblish the bond with the server
- if (!establish_bond())
- {
- RCLCPP_INFO(this->get_logger(), "Establishing bond failed");
-
- // TODO: improve with error codes
- result->success = false;
- server_goal_handle->canceled(result);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
- }
-
- auto connection_goal_msg = capabilities2_msgs::action::Connections::Goal();
- connection_goal_msg.bond_id = bond_id;
- connection_goal_msg.header.stamp = this->get_clock()->now();
-
- capabilities2_msgs::msg::CapabilityConnection connection_msg;
-
- for (const auto& connection : connection_list)
- {
- connection_msg.source.capability = connection.source_event;
- connection_msg.source.provider = connection.source_provider;
- connection_msg.target.capability = connection.target_event;
- connection_msg.target.provider = connection.target_provider;
-
- if (connection.connection == capabilities2_executor::connection_type_t::ON_SUCCESS_START)
- connection_msg.connection_type = capabilities2_msgs::msg::CapabilityConnection::ON_SUCCESS_START;
-
- if (connection.connection == capabilities2_executor::connection_type_t::ON_START_START)
- connection_msg.connection_type = capabilities2_msgs::msg::CapabilityConnection::ON_START_START;
-
- if (connection.connection == capabilities2_executor::connection_type_t::ON_FAILURE_START)
- connection_msg.connection_type = capabilities2_msgs::msg::CapabilityConnection::ON_FAILURE_START;
-
- connection_msg.target_parameters = capabilities2_xml_parser::convert_to_string(connection.event_element);
-
- connection_goal_msg.connections.push_back(connection_msg);
- }
-
- auto send_goal_options = rclcpp_action::Client::SendGoalOptions();
-
- // send goal options
- // goal response callback
- send_goal_options.goal_response_callback =
- [this, server_goal_handle](
- const rclcpp_action::ClientGoalHandle::SharedPtr& goal_handle) {
- if (!goal_handle)
- {
- RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
-
- auto result = std::make_shared();
-
- // TODO: improve with error codes
- result->success = false;
- server_goal_handle->canceled(result);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
- }
- else
- {
- RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result");
- }
- };
-
- // result callback
- send_goal_options.result_callback =
- [this, server_goal_handle](
- const rclcpp_action::ClientGoalHandle::WrappedResult& result) {
- auto result_out = std::make_shared();
-
- switch (result.code)
- {
- case rclcpp_action::ResultCode::SUCCEEDED:
- break;
- case rclcpp_action::ResultCode::ABORTED:
- RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
-
- // TODO: improve with error codes
- result_out->success = false;
- server_goal_handle->canceled(result_out);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
-
- return;
- case rclcpp_action::ResultCode::CANCELED:
- RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
-
- // TODO: improve with error codes
- result_out->success = false;
- server_goal_handle->canceled(result_out);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
-
- return;
- default:
- RCLCPP_ERROR(this->get_logger(), "Unknown result code");
-
- // TODO: improve with error codes
- result_out->success = false;
- server_goal_handle->canceled(result_out);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Cancelled");
- return;
- }
-
- if (result.result->failed_connections.size() > 0)
- {
- // TODO: improve with error codes
- result_out->success = false;
-
- for (const auto& failed_connection : result.result->failed_connections)
- {
- RCLCPP_ERROR(this->get_logger(), "Failed Events : %s", failed_connection.target_parameters.c_str());
-
- result_out->failed_elements.push_back(failed_connection.target_parameters);
- }
-
- server_goal_handle->canceled(result_out);
-
- RCLCPP_ERROR(this->get_logger(), "Server Execution Cancelled");
- }
- else
- {
- // TODO: improve with error codes
- result_out->success = true;
- server_goal_handle->succeed(result_out);
-
- RCLCPP_INFO(this->get_logger(), "Server Execution Succeeded");
- }
-
- rclcpp::shutdown();
- };
-
- this->client_capabilities_->async_send_goal(connection_goal_msg, send_goal_options);
- }
-
-private:
- /** File Path link */
- std::string plan_file_path;
-
- /** flag to select loading from file or accepting via action server */
- bool read_file;
-
- /** Bond ID between capabilities server and this client */
- std::string bond_id;
-
- /** XML Document */
- tinyxml2::XMLDocument document;
-
- /** vector of connections */
- std::vector connection_list;
-
- /** Execution Thread */
- std::shared_ptr execution_thread;
-
- /** Interface List */
- std::vector interface_list;
-
- /** Providers List */
- std::vector providers_list;
-
- /** Control flow List */
- std::vector control_tag_list;
-
- /** action client for connecting with capabilities server*/
- rclcpp_action::Client::SharedPtr client_capabilities_;
-
- /** action server that exposes executor*/
- std::shared_ptr> planner_server_;
-
- /** action server goal handle*/
- std::shared_ptr> goal_handle;
-
- /** Get interfaces from capabilities server */
- rclcpp::Client::SharedPtr get_interfaces_client_;
-
- /** Get semantic interfaces from capabilities server */
- rclcpp::Client::SharedPtr get_sem_interf_client_;
-
- /** Get providers from capabilities server */
- rclcpp::Client::SharedPtr get_providers_client_;
-
- /** establish bond */
- rclcpp::Client::SharedPtr establish_bond_client_;
-};
\ No newline at end of file
diff --git a/capabilities2_executor/include/capabilities2_executor/capabilities_file_parser.hpp b/capabilities2_executor/include/capabilities2_executor/capabilities_file_parser.hpp
deleted file mode 100644
index 5164897..0000000
--- a/capabilities2_executor/include/capabilities2_executor/capabilities_file_parser.hpp
+++ /dev/null
@@ -1,133 +0,0 @@
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include
-
-#include
-#include
-
-/**
- * @brief Capabilities Executor File Parser
- *
- * Capabilities Executor File Parser node that provides a ROS client for the capabilities executor.
- * Will read an XML file that implements a plan and send it to the server
- */
-
-class CapabilitiesFileParser : public rclcpp::Node
-{
-public:
- CapabilitiesFileParser(const rclcpp::NodeOptions &options = rclcpp::NodeOptions())
- : Node("Capabilities2_File_Parser", options)
- {
- declare_parameter("plan_file_path", "plan.xml");
- plan_file_path = get_parameter("plan_file_path").as_string();
-
- this->client_ptr_ = rclcpp_action::create_client(this, "~/capabilities");
-
- this->timer_ = this->create_wall_timer(std::chrono::milliseconds(500), std::bind(&CapabilitiesFileParser::send_goal, this));
- }
-
- void send_goal()
- {
- this->timer_->cancel();
-
- // try to load the file
- tinyxml2::XMLError xml_status = document.LoadFile(plan_file_path.c_str());
-
- // check if the file loading failed
- if (xml_status != tinyxml2::XMLError::XML_SUCCESS)
- {
- RCLCPP_INFO(this->get_logger(), "Loading the file from path : %s failed", plan_file_path.c_str());
- rclcpp::shutdown();
- }
-
- if (!this->client_ptr_->wait_for_action_server())
- {
- RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting");
- rclcpp::shutdown();
- }
-
- auto goal_msg = capabilities2_msgs::action::Plan::Goal();
- goal_msg.plan = capabilities2_xml_parser::convert_to_string(document);
-
- RCLCPP_INFO(this->get_logger(), "Sending goal");
-
- auto send_goal_options = rclcpp_action::Client::SendGoalOptions();
-
- // send goal options
- // goal response callback
- send_goal_options.goal_response_callback =
- [this](const rclcpp_action::ClientGoalHandle::SharedPtr &goal_handle)
- {
- if (!goal_handle)
- {
- RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
- }
- else
- {
- RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result");
- }
- };
-
- // result callback
- send_goal_options.result_callback =
- [this](const rclcpp_action::ClientGoalHandle::WrappedResult &result)
- {
- switch (result.code)
- {
- case rclcpp_action::ResultCode::SUCCEEDED:
- break;
- case rclcpp_action::ResultCode::ABORTED:
- RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
- return;
- case rclcpp_action::ResultCode::CANCELED:
- RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
- return;
- default:
- RCLCPP_ERROR(this->get_logger(), "Unknown result code");
- return;
- }
-
- if (result.result->success)
- {
- RCLCPP_ERROR(this->get_logger(), "Plan executed successfully");
- }
- else
- {
- RCLCPP_ERROR(this->get_logger(), "Plan failed to complete");
-
- if (result.result->failed_elements.size() > 0)
- {
- RCLCPP_ERROR(this->get_logger(), "Plan failed due to incompatible XMLElements in the plan");
-
- for (const auto &failed_element : result.result->failed_elements)
- RCLCPP_ERROR(this->get_logger(), "Failed Elements : %s", failed_element.c_str());
- }
- }
-
- rclcpp::shutdown();
- };
-
- this->client_ptr_->async_send_goal(goal_msg, send_goal_options);
- }
-
-private:
- /** File Path link */
- std::string plan_file_path;
-
- /** XML Document */
- tinyxml2::XMLDocument document;
-
- /** action client */
- rclcpp_action::Client::SharedPtr client_ptr_;
-
- /** action server */
- rclcpp::TimerBase::SharedPtr timer_;
-};
\ No newline at end of file
diff --git a/capabilities2_executor/include/capabilities2_executor/capabilities_xml_parser.hpp b/capabilities2_executor/include/capabilities2_executor/capabilities_xml_parser.hpp
deleted file mode 100644
index 102d345..0000000
--- a/capabilities2_executor/include/capabilities2_executor/capabilities_xml_parser.hpp
+++ /dev/null
@@ -1,265 +0,0 @@
-#include
-#include
-#include
-#include
-
-namespace capabilities2_xml_parser
-{
- /**
- * @brief extract elements related plan
- *
- * @param document XML document to extract plan from
- *
- * @return plan in the form of tinyxml2::XMLElement*
- */
- tinyxml2::XMLElement *get_plan(tinyxml2::XMLDocument &document)
- {
- return document.FirstChildElement("Plan");
- }
-
- /**
- * @brief search a string in a vector of strings
- *
- * @param list vector of strings to be searched
- * @param value string to be searched in the vector
- *
- * @return `true` if value is found in list and `false` otherwise
- */
- bool search(std::vector list, std::string value)
- {
- return (std::find(list.begin(), list.end(), value) != list.end());
- }
-
- /**
- * @brief check if the element in question is the last in its level
- *
- * @param element element to be checked
- *
- * @return `true` if last and `false` otherwise
- */
- bool isLastElement(tinyxml2::XMLElement *element)
- {
- return (element == element->Parent()->LastChildElement());
- }
-
- /**
- * @brief check if the xml document has valid plan tags
- *
- * @param document XML document in question
- *
- * @return `true` if its valid and `false` otherwise
- */
- bool check_plan_tag(tinyxml2::XMLDocument &document)
- {
- std::string plan_tag(document.FirstChildElement()->Name());
-
- if (plan_tag == "Plan")
- return true;
- else
- return false;
- }
-
- /**
- * @brief convert XMLElement to std::string
- *
- * @param element element to be converted
- *
- * @return std::string converted element
- */
- std::string convert_to_string(tinyxml2::XMLElement *element)
- {
- tinyxml2::XMLPrinter printer;
-
- element->Accept(&printer);
-
- std::string value = printer.CStr();
-
- return value;
- }
-
- /**
- * @brief convert XMLDocument to std::string
- *
- * @param document element to be converted
- *
- * @return std::string converted document
- */
- std::string convert_to_string(tinyxml2::XMLDocument &document)
- {
- tinyxml2::XMLPrinter printer;
-
- document.Accept(&printer);
-
- std::string value = printer.CStr();
-
- return value;
- }
-
- /**
- * @brief check the plan for invalid/unsupported control and event tags
- * uses recursive approach to go through the plan
- *
- * @param element XML Element to be evaluated
- * @param events_list list containing valid event tags
- * @param providers_list list containing providers
- * @param control_list list containing valid control tags
- *
- * @return `true` if element valid and supported and `false` otherwise
- */
- bool check_tags(tinyxml2::XMLElement *element,
- std::vector &events_list,
- std::vector &providers_list,
- std::vector &control_list)
- {
- const char **name;
- const char **provider;
-
- element->QueryStringAttribute("name", name);
- element->QueryStringAttribute("provider", provider);
-
- std::string typetag(element->Name());
- std::string nametag(*name);
- std::string providertag(*provider);
-
- bool hasChildren = !element->NoChildren();
- bool hasSiblings = !capabilities2_xml_parser::isLastElement(element);
- bool foundInControl = capabilities2_xml_parser::search(control_list, nametag);
- bool foundInEvents = capabilities2_xml_parser::search(events_list, nametag);
- bool foundInProviders = capabilities2_xml_parser::search(providers_list, providertag);
- bool returnValue = true;
-
- if (typetag == "Control")
- {
- if (foundInControl and hasChildren)
- returnValue = returnValue and capabilities2_xml_parser::check_tags(element->FirstChildElement(), events_list, providers_list, control_list);
-
- if (foundInControl and hasSiblings)
- returnValue = returnValue and capabilities2_xml_parser::check_tags(element->NextSiblingElement(), events_list, providers_list, control_list);
-
- if (foundInControl and !hasSiblings)
- returnValue = returnValue;
-
- if (!foundInControl)
- return false;
- }
- else if (typetag == "Event")
- {
- if (foundInEvents and foundInProviders and hasSiblings)
- returnValue = returnValue and capabilities2_xml_parser::check_tags(element->NextSiblingElement(), events_list, providers_list, control_list);
-
- if (foundInEvents and foundInProviders and !hasSiblings)
- returnValue = returnValue;
-
- if (!foundInEvents)
- return false;
-
- if (!foundInProviders)
- return false;
- }
- else
- {
- return false;
- }
-
- return returnValue;
- }
-
- /**
- * @brief Adds an event connection to the capabilities2 fabric
- *
- * @param source_event the source event from which the connection would start
- * @param source_provider provider of the source event
- * @param target_event the target event which will be connected via the connection
- * @param target_provider provider of the target event
- * @param connection the type of connection
- * @param event_element the tinyxml2::XMLElement which contains runtime parameters
- *
- * @return The connection object
- */
- capabilities2_executor::connection_t create_connection(std::string source_event, std::string source_provider,
- std::string target_event, std::string target_provider,
- capabilities2_executor::connection_type_t connection, tinyxml2::XMLElement *event_element)
- {
- capabilities2_executor::connection_t connect;
-
- connect.source_event = source_event;
- connect.source_provider = source_provider;
- connect.target_event = target_event;
- connect.target_provider = target_provider;
- connect.connection = connection;
- connect.event_element = event_element;
-
- return connect;
- }
-
- /**
- * @brief parse through the plan and extract the connections
- *
- * @param element XML Element to be evaluated
- * @param connection_list std::vector containing extracted connections
- * @param connection the type of connection
- * @param source_event source event name. if not provided, "start" is taken as default
- * @param source_provider provider of the source event, if not provided "" is taken as default
- * @param target_event target event name. if not provided, "stop" is taken as default
- * @param target_provider provider of the target event. if not provided, "stop" is taken as default
- */
- void extract_connections(tinyxml2::XMLElement *element, std::vector &connection_list,
- capabilities2_executor::connection_type_t connection = capabilities2_executor::connection_type_t::ON_SUCCESS_START,
- std::string source_event = "start", std::string source_provider = "",
- std::string target_event = "stop", std::string target_provider = "")
- {
- const char **name;
- const char **provider;
-
- element->QueryStringAttribute("name", name);
- element->QueryStringAttribute("provider", provider);
-
- std::string typetag(element->Name());
- std::string nametag(*name);
- std::string providertag(*provider);
-
- bool hasChildren = !element->NoChildren();
- bool hasSiblings = !capabilities2_xml_parser::isLastElement(element);
-
- if ((typetag == "Control") and (nametag == "sequential"))
- {
- if (hasChildren)
- capabilities2_xml_parser::extract_connections(element->FirstChildElement(), connection_list, capabilities2_executor::connection_type_t::ON_SUCCESS_START,
- source_event, source_provider, target_event, target_provider);
-
- if (hasSiblings)
- capabilities2_xml_parser::extract_connections(element->NextSiblingElement(), connection_list, connection,
- source_event, source_provider, target_event, target_provider);
- }
- else if ((typetag == "Control") and (nametag == "parallel"))
- {
- if (hasChildren)
- capabilities2_xml_parser::extract_connections(element->FirstChildElement(), connection_list, capabilities2_executor::connection_type_t::ON_START_START,
- source_event, source_provider, target_event, target_provider);
-
- if (hasSiblings)
- capabilities2_xml_parser::extract_connections(element->NextSiblingElement(), connection_list, connection,
- source_event, source_provider, target_event, target_provider);
- }
- else if ((typetag == "Control") and (nametag == "recovery"))
- {
- if (hasChildren)
- capabilities2_xml_parser::extract_connections(element->FirstChildElement(), connection_list, capabilities2_executor::connection_type_t::ON_FAILURE_START,
- source_event, source_provider, target_event, target_provider);
-
- if (hasSiblings)
- capabilities2_xml_parser::extract_connections(element->NextSiblingElement(), connection_list, connection,
- source_event, source_provider, target_event, target_provider);
- }
- else if (typetag == "Event")
- {
- capabilities2_executor::connection_t connection_obj = create_connection(source_event, source_provider, nametag, providertag, connection, element);
- connection_list.push_back(connection_obj);
-
- if (hasSiblings)
- capabilities2_xml_parser::extract_connections(element->NextSiblingElement(), connection_list, connection,
- nametag, providertag, target_event, target_provider);
- }
- }
-
-} // namespace capabilities2_xml_parser
diff --git a/capabilities2_executor/include/capabilities2_executor/structs/connection.hpp b/capabilities2_executor/include/capabilities2_executor/structs/connection.hpp
deleted file mode 100644
index 30b8a21..0000000
--- a/capabilities2_executor/include/capabilities2_executor/structs/connection.hpp
+++ /dev/null
@@ -1,27 +0,0 @@
-#include
-#include
-
-namespace capabilities2_executor
-{
- enum connection_type_t
- {
- ON_START_START,
- ON_START_STOP,
- ON_SUCCESS_START,
- ON_SUCCESS_STOP,
- ON_FAILURE_START,
- ON_FAILURE_STOP,
- ON_TERMINATE_START,
- ON_TERMINATE_STOP
- };
-
- struct connection_t {
- std::string source_event;
- std::string source_provider;
- std::string target_event;
- std::string target_provider;
- connection_type_t connection;
- tinyxml2::XMLElement* event_element;
- };
-
-} // namespace capabilities2_executor
diff --git a/capabilities2_executor/launch/.gitkeep b/capabilities2_executor/launch/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/capabilities2_executor/readme.md b/capabilities2_executor/readme.md
deleted file mode 100644
index 999186c..0000000
--- a/capabilities2_executor/readme.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Capabilities2 Executor
-
-This is a simple executor that demonstrates how to use the capabilities2 library to execute multi-skill tasks.
-
-## Usage
diff --git a/capabilities2_executor/src/capabilities_executor.cpp b/capabilities2_executor/src/capabilities_executor.cpp
deleted file mode 100644
index 98c5a43..0000000
--- a/capabilities2_executor/src/capabilities_executor.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#include
-
-int main(int argc, char * argv[])
-{
- rclcpp::init(argc, argv);
- rclcpp::spin(std::make_shared());
- rclcpp::shutdown();
- return 0;
-}
diff --git a/capabilities2_executor/src/capabilities_file_parser.cpp b/capabilities2_executor/src/capabilities_file_parser.cpp
deleted file mode 100644
index 00c2410..0000000
--- a/capabilities2_executor/src/capabilities_file_parser.cpp
+++ /dev/null
@@ -1,9 +0,0 @@
-#include
-
-int main(int argc, char * argv[])
-{
- rclcpp::init(argc, argv);
- rclcpp::spin(std::make_shared());
- rclcpp::shutdown();
- return 0;
-}
diff --git a/capabilities2_launch_proxy/capabilities2_launch_proxy/__init__.py b/capabilities2_launch_proxy/capabilities2_launch_proxy/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/capabilities2_launch_proxy/capabilities2_launch_proxy/capabilities_launch_proxy.py b/capabilities2_launch_proxy/capabilities2_launch_proxy/capabilities_launch_proxy.py
deleted file mode 100755
index 26a5845..0000000
--- a/capabilities2_launch_proxy/capabilities2_launch_proxy/capabilities_launch_proxy.py
+++ /dev/null
@@ -1,287 +0,0 @@
-'''
-capabilities launch proxy server
-
-implements a launch server responding to messages to launch and shutdown launch files
-use in conjunction with capabilities2_server to launch capabilities
-
-acts like ros2 launch command but from an action interface
-'''
-
-import os
-import threading
-from typing import Set
-from typing import Text
-from typing import Optional
-import rclpy
-from rclpy.executors import MultiThreadedExecutor
-from rclpy.executors import ExternalShutdownException
-import rclpy.logging
-from rclpy.node import Node
-from rclpy.action import ActionServer
-from rclpy.action import CancelResponse
-from rclpy.action.server import ServerGoalHandle
-from launch import LaunchService
-from launch import LaunchContext
-from launch.event import Event
-from launch.events.process import ShutdownProcess
-from launch.event_handler import EventHandler
-from launch.event_handlers import OnProcessStart
-from launch.actions import ExecuteProcess
-from launch.actions import EmitEvent
-from launch.actions import RegisterEventHandler
-from launch.launch_description_sources import AnyLaunchDescriptionSource
-# from launch.some_entities_type import SomeEntitiesType
-from capabilities2_msgs.action import Launch
-from capabilities2_msgs.msg import CapabilityEvent
-
-
-class CancelLaunchGoalEvent(Event):
- """
- Launch goal cancel event
-
- used when a launch goal is canceled
- the result should be that the launch description associated with a goal
- is shutdown in the running LaunchService
- other goals should not be affected
- """
-
- name = 'launch.event.CancelLaunchGoal'
-
- def __init__(self, *, goal_handle: ServerGoalHandle) -> None:
- """
- Create a LaunchGoalCancelEvent
- """
- self.goal_handle = goal_handle
-
-
-class CancelEventHandler(EventHandler):
- """
- Cancel event handler
-
- handle cancel launch goal events
- cancels a launch goal if it matches the goal handle passed in
- """
-
- def __init__(self, goal_handle: ServerGoalHandle, pids: Set[int]):
- super().__init__(matcher=lambda event: isinstance(event, CancelLaunchGoalEvent))
- self.goal_handle = goal_handle
- self.pids = pids
-
- def handle(self, event: Event, context: LaunchContext):
- """
- handle event
- """
-
- super().handle(event, context)
-
- # check if event is a cancel launch goal event
- if not isinstance(event, CancelLaunchGoalEvent):
- return None
-
- # if the goals match then cancel
- if event.goal_handle == self.goal_handle:
- return EmitEvent(event=ShutdownProcess(
- process_matcher=lambda action: action.process_details['pid'] in self.pids
- ))
-
- return None
-
-
-class CapabilitiesLaunchProxy(Node):
- """
- Capabilities Launch proxy server
- """
-
- def __init__(self, node_name='capabilities_launch_proxy'):
- # super class init
- super().__init__(node_name)
-
- # active files set
- self.active_launch_files: Set[Text] = set()
- self.launch_set_lock = threading.Lock()
-
- # create launch action server
- self.launch_sub = ActionServer(
- self,
- Launch,
- '~/launch',
- handle_accepted_callback=self.handle_accepted_cb,
- execute_callback=self.execute_cb,
- cancel_callback=self.cancel_cb
- )
-
- # cap event pub
- self.event_pub = self.create_publisher(
- CapabilityEvent,
- '~/events',
- 10
- )
-
- # create launch service
- self.launch_service = LaunchService()
-
- # log start
- self.get_logger().info('capabilities launch proxy started')
-
- # service interface
- def get_active_launch_files_cb(self):
- """
- get active launch files
- """
- return self.active_launch_files
-
- # action interface
- def handle_accepted_cb(self, goal_handle: ServerGoalHandle) -> None:
- """
- handle accepted callback
- """
- # start execution and detach
- threading.Thread(target=goal_handle.execute).start()
-
- # execute callback is main launch feature
- def execute_cb(self, goal_handle: ServerGoalHandle) -> Launch.Result:
- """
- launch execute callback
- """
-
- # check if file exists
- if not os.path.isfile(goal_handle.request.launch_file_path):
- # file does not exist
- self.get_logger().error("file does not exist: '{}'".format(
- goal_handle.request.launch_file_path))
-
- # abort goal
- goal_handle.abort()
- return Launch.Result()
-
- with self.launch_set_lock:
- # launch context already exists guard
- if goal_handle.request.launch_file_path in self.active_launch_files:
- self.get_logger().error("launch already exists for source '{}'".format(
- goal_handle.request.launch_file_path))
-
- goal_handle.abort()
- return Launch.Result()
-
- # add context to active files
- self.active_launch_files.add(goal_handle.request.launch_file_path)
-
- # get launch description from file
- description = AnyLaunchDescriptionSource(
- goal_handle.request.launch_file_path).get_launch_description(self.launch_service.context)
-
- # set up process id get event handler
- description_process_ids: Set[int] = set()
-
- # go through all actions and collect all processes (mostly nodes) in the description
- for e in description.entities:
- # check the type of entity is an execute process action
- if isinstance(e, ExecuteProcess):
- # register event handler for process start event
- # on process start store the pid
- description.add_action(
- RegisterEventHandler(OnProcessStart(
- target_action=e,
- on_start=lambda event, context: description_process_ids.add(
- event.pid)
- ))
- )
-
- # add shutdown event to launch description
- # add shutdown event handler to launch description
- # this uses the cancel goal event handler and cancel goal event
- description.add_action(RegisterEventHandler(CancelEventHandler(
- goal_handle,
- description_process_ids
- )))
-
- # add to launch service
- self.launch_service.include_launch_description(description)
-
- # bind request to this thread
- self.get_logger().info('launching: {}'.format(
- goal_handle.request.launch_file_path
- ))
-
- # sleep rate for holding this action open
- rate = self.create_rate(1.0)
-
- # loop while not canceled
- while rclpy.ok():
- # if cancelled
- if goal_handle.is_cancel_requested:
- self.get_logger().warn("launch canceled: '{}'".format(
- goal_handle.request.launch_file_path
- ))
-
- # shutdown context
- self.launch_service.emit_event(
- CancelLaunchGoalEvent(goal_handle=goal_handle))
-
- # delete context from active files
- with self.launch_set_lock:
- self.active_launch_files.remove(
- goal_handle.request.launch_file_path)
-
- goal_handle.canceled()
- return Launch.Result()
-
- # send event feedback
- feedback = Launch.Feedback()
- feedback.event.type = 'launched'
- goal_handle.publish_feedback(feedback)
-
- # spin indefinitely (sleep loop this thread)
- rate.sleep()
-
- goal_handle.succeed()
- return Launch.Result()
-
- def cancel_cb(self, goal_handle: ServerGoalHandle) -> CancelResponse:
- """
- accept all cancellations
- """
- return CancelResponse.ACCEPT
-
-
-# main function
-def main():
- """
- main function
- """
-
- try:
- # init node
- rclpy.init()
-
- # instantiate the capabilities launch server
- capabilities_launch_proxy = CapabilitiesLaunchProxy()
-
- # spin node in new thread
- executor = MultiThreadedExecutor()
- executor.add_node(capabilities_launch_proxy)
- executor_thread = threading.Thread(target=executor.spin)
- executor_thread.start()
-
- # run the launch service
- capabilities_launch_proxy.get_logger().info('running LaunchService')
- capabilities_launch_proxy.launch_service.run(
- shutdown_when_idle=False)
-
- # cancel the launch services
- capabilities_launch_proxy.launch_service.shutdown()
-
- executor.shutdown()
- executor_thread.join()
- capabilities_launch_proxy.destroy_node()
-
- # rclpy.shutdown()
-
- except ExternalShutdownException:
- pass
-
-
-# main
-if __name__ == '__main__':
- # run the proxy
- main()
diff --git a/capabilities2_launch_proxy/package.xml b/capabilities2_launch_proxy/package.xml
deleted file mode 100644
index ab70338..0000000
--- a/capabilities2_launch_proxy/package.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
- capabilities2_launch_proxy
- 0.0.1
- ros2 launch proxy to support launching capabilities similar to ros1 capabilities
- mik-p
-
- MIT
-
- rclpy
- launch
- std_msgs
- capabilities2_msgs
-
-
- ament_python
-
-
diff --git a/capabilities2_launch_proxy/readme.md b/capabilities2_launch_proxy/readme.md
deleted file mode 100644
index e749a41..0000000
--- a/capabilities2_launch_proxy/readme.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# capabilities2_launch_proxy
-
-Provides an Action API proxy to the ros2 launch framework. Implements features that are only available in python since the `launch` API is not available in C++.
-
-Contains the essential features to implement the ros1 [`capabilities`](https://wiki.ros.org/capabilities) package launch functionality in ros2.
-
-Use in conjunction with `capabilities2_server` package, to provide a full capabilities server.
-
-## how it works
-
-Implements three main functions:
-
-| Function | Description |
-| --- | --- |
-| `launch` | Run a launch file requested a runtime via a network request |
-| `shutdown` | shutdown a launch file that has been started |
-| `events` | send capability events during operation |
-
-The lifecylce of these functions are typically handled by the capabilities server but could be used for other things.
-
-## Use without capabilities2 server
-
-The proxy provides an action called `~/launch`. The `goal` is a file path to launch from. The feedback provides events about the launch status.
-
-### Add to a launch file
-
-```python
-from launch import LaunchDescription
-from launch_ros.actions import Node
-
-def generate_launch_description():
- return LaunchDescription([
- # create launch proxy node
- Node(
- package='capabilities2_launch_proxy',
- executable='capabilities_launch_proxy',
- name='capabilities_launch_proxy'
- )
- ])
-```
-
-### Run standalone
-
-```bash
-ros2 run capabilities2_launch_proxy capabilities_launch_proxy
-```
diff --git a/capabilities2_launch_proxy/resource/capabilities2_launch_proxy b/capabilities2_launch_proxy/resource/capabilities2_launch_proxy
deleted file mode 100644
index e69de29..0000000
diff --git a/capabilities2_launch_proxy/setup.cfg b/capabilities2_launch_proxy/setup.cfg
deleted file mode 100644
index d3f4af5..0000000
--- a/capabilities2_launch_proxy/setup.cfg
+++ /dev/null
@@ -1,4 +0,0 @@
-[develop]
-script_dir=$base/lib/capabilities2_launch_proxy
-[install]
-install_scripts=$base/lib/capabilities2_launch_proxy
diff --git a/capabilities2_launch_proxy/setup.py b/capabilities2_launch_proxy/setup.py
deleted file mode 100644
index 1707673..0000000
--- a/capabilities2_launch_proxy/setup.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from setuptools import find_packages, setup
-
-package_name = 'capabilities2_launch_proxy'
-
-setup(
- name=package_name,
- version='0.0.1',
- packages=find_packages(exclude=['test']),
- data_files=[
- ('share/ament_index/resource_index/packages',
- ['resource/' + package_name]),
- ('share/' + package_name, ['package.xml']),
- ],
- install_requires=['setuptools'],
- zip_safe=True,
- maintainer='mik-p',
- maintainer_email='mppritchard3@hotmail.com',
- description='ros2 launch proxy to support launching capabilities similar to ros1 capabilities',
- license='MIT',
- tests_require=['pytest'],
- entry_points={
- 'console_scripts': [
- 'capabilities_launch_proxy = capabilities2_launch_proxy.capabilities_launch_proxy:main',
- ],
- },
-)
diff --git a/capabilities2_launch_proxy/test/call_use_launch_runner.py b/capabilities2_launch_proxy/test/call_use_launch_runner.py
deleted file mode 100644
index f8d3f9c..0000000
--- a/capabilities2_launch_proxy/test/call_use_launch_runner.py
+++ /dev/null
@@ -1,94 +0,0 @@
-''' test call cap services '''
-
-from bondpy import bondpy
-import rclpy
-from capabilities2_msgs.srv import EstablishBond
-from capabilities2_msgs.srv import GetRunningCapabilities
-from capabilities2_msgs.srv import UseCapability
-
-
-def test_get_running_capabilities(n):
- # get running capabilities
- client = n.create_client(
- GetRunningCapabilities,
- '/capabilities/get_running_capabilities'
- )
-
- # wait for service
- client.wait_for_service()
-
- # send request
- request = GetRunningCapabilities.Request()
- future = client.call_async(request)
- rclpy.spin_until_future_complete(n, future)
-
- # print result
- for s in future.result().running_capabilities:
- print(s)
-
-
-# main
-if __name__ == '__main__':
-
- rclpy.init()
-
- node = rclpy.create_node('test_call_cap_srvs')
-
- # do tests
- # test_get_interfaces_srv(node)
- # test_get_semantic_interfaces_srv(node)
- # test_get_providers_srv(node)
- # test_get_capability_specs_srv(node)
-
- print("test use capability")
- # get a bond
- bond_cli = node.create_client(
- EstablishBond,
- '/capabilities/establish_bond'
- )
-
- # wait for service
- bond_cli.wait_for_service()
-
- # send request
- request = EstablishBond.Request()
- future = bond_cli.call_async(request)
- rclpy.spin_until_future_complete(node, future)
-
- # print result
- print(future.result())
- # keep bond id
- bond_id = future.result().bond_id
-
- # create a use capability request
- use_client = node.create_client(
- UseCapability,
- '/capabilities/use_capability'
- )
-
- # wait for service
- use_client.wait_for_service()
-
- # send request
- request = UseCapability.Request()
- request.capability = 'std_capabilities/empty'
- request.preferred_provider = 'std_capabilities/empty'
- request.bond_id = future.result().bond_id
- future = use_client.call_async(request)
- rclpy.spin_until_future_complete(node, future)
-
- # print result
- print(future.result())
-
- # get running capabilities
- test_get_running_capabilities(node)
-
- # keep bond alive
- bond = bondpy.Bond(node, "/capabilities/bonds", bond_id)
- bond.start()
- rclpy.spin(node)
-
- node.destroy_node()
- rclpy.shutdown()
-
- exit(0)
diff --git a/capabilities2_msgs/CMakeLists.txt b/capabilities2_msgs/CMakeLists.txt
index 669eb9c..529c0ce 100644
--- a/capabilities2_msgs/CMakeLists.txt
+++ b/capabilities2_msgs/CMakeLists.txt
@@ -17,14 +17,12 @@ find_package(std_msgs REQUIRED)
set(msg_files
"msg/Capability.msg"
- "msg/CapabilityEvent.msg"
"msg/CapabilitySpec.msg"
"msg/Remapping.msg"
"msg/RunningCapability.msg"
"msg/NaturalCapability.msg"
"msg/CapabilityCommand.msg"
"msg/CapabilityResponse.msg"
- "msg/CapabilityConnection.msg"
)
set(srv_files
@@ -41,13 +39,14 @@ set(srv_files
"srv/StartCapability.srv"
"srv/StopCapability.srv"
"srv/UseCapability.srv"
+ "srv/ConfigureCapability.srv"
+ "srv/TriggerCapability.srv"
+ "srv/Launch.srv"
)
set(action_files
"action/Capability.action"
"action/Launch.action"
- "action/Plan.action"
- "action/Connections.action"
)
rosidl_generate_interfaces(${PROJECT_NAME}
diff --git a/capabilities2_msgs/action/Connections.action b/capabilities2_msgs/action/Connections.action
deleted file mode 100644
index 80c885d..0000000
--- a/capabilities2_msgs/action/Connections.action
+++ /dev/null
@@ -1,14 +0,0 @@
-# action to send a list of interface connections to capabilities server
-# goal
-std_msgs/Header header
-string bond_id
-CapabilityConnection[] connections
----
-# result
-std_msgs/Header header
-CapabilityConnection[] failed_connections
----
-# feedback
-std_msgs/Header header
-int32 success_count
-int32 total_count
diff --git a/capabilities2_msgs/action/Launch.action b/capabilities2_msgs/action/Launch.action
index d4c26d4..c6e8c95 100644
--- a/capabilities2_msgs/action/Launch.action
+++ b/capabilities2_msgs/action/Launch.action
@@ -8,4 +8,3 @@ std_msgs/Header header
---
# feedback
std_msgs/Header header
-capabilities2_msgs/CapabilityEvent event
diff --git a/capabilities2_msgs/action/Plan.action b/capabilities2_msgs/action/Plan.action
deleted file mode 100644
index ad84fa9..0000000
--- a/capabilities2_msgs/action/Plan.action
+++ /dev/null
@@ -1,12 +0,0 @@
-# action to transfer a plan for execution
-# goal
-std_msgs/Header header
-string plan
----
-# result
-std_msgs/Header header
-bool success
-string[] failed_elements
----
-# feedback
-std_msgs/Header header
diff --git a/capabilities2_msgs/msg/Capability.msg b/capabilities2_msgs/msg/Capability.msg
index bab67a8..5bf97a1 100644
--- a/capabilities2_msgs/msg/Capability.msg
+++ b/capabilities2_msgs/msg/Capability.msg
@@ -1,4 +1,8 @@
# Capability
string capability
+
# Used provider
string provider
+
+# trigger parameters
+string parameters
diff --git a/capabilities2_msgs/msg/CapabilityConnection.msg b/capabilities2_msgs/msg/CapabilityConnection.msg
deleted file mode 100644
index 04b538b..0000000
--- a/capabilities2_msgs/msg/CapabilityConnection.msg
+++ /dev/null
@@ -1,14 +0,0 @@
-# Declare types of connections
-uint8 ON_START_START = 0
-uint8 ON_START_STOP = 1
-uint8 ON_SUCCESS_START = 2
-uint8 ON_SUCCESS_STOP = 3
-uint8 ON_FAILURE_START = 4
-uint8 ON_FAILURE_STOP = 5
-uint8 ON_TERMINATE_START = 6
-uint8 ON_TERMINATE_STOP = 7
-
-Capability source
-Capability target
-uint8 connection_type
-string target_parameters
\ No newline at end of file
diff --git a/capabilities2_msgs/msg/CapabilityEvent.msg b/capabilities2_msgs/msg/CapabilityEvent.msg
deleted file mode 100644
index 1a52c94..0000000
--- a/capabilities2_msgs/msg/CapabilityEvent.msg
+++ /dev/null
@@ -1,16 +0,0 @@
-std_msgs/Header header
-# Capability which this event pertains to
-string capability
-# Capability provider which this event pertains to
-string provider
-
-# Event types
-string LAUNCHED="launched"
-string STOPPED="stopped"
-string TERMINATED="terminated"
-string SERVER_READY="server_ready"
-# Event type
-string type
-
-# PID of the related process
-int32 pid
diff --git a/capabilities2_msgs/package.xml b/capabilities2_msgs/package.xml
index 3ac0ada..9130417 100644
--- a/capabilities2_msgs/package.xml
+++ b/capabilities2_msgs/package.xml
@@ -8,6 +8,7 @@
Michael Pritchard
mik-p
+ Kalana Ratnayake
Michael Pritchard
diff --git a/capabilities2_msgs/srv/ConfigureCapability.srv b/capabilities2_msgs/srv/ConfigureCapability.srv
new file mode 100644
index 0000000..b9e6905
--- /dev/null
+++ b/capabilities2_msgs/srv/ConfigureCapability.srv
@@ -0,0 +1,8 @@
+Capability source
+Capability target_on_start
+Capability target_on_stop
+Capability target_on_success
+Capability target_on_failure
+string connection_description
+int32 trigger_id
+---
\ No newline at end of file
diff --git a/capabilities2_msgs/srv/Launch.srv b/capabilities2_msgs/srv/Launch.srv
new file mode 100644
index 0000000..a1938ed
--- /dev/null
+++ b/capabilities2_msgs/srv/Launch.srv
@@ -0,0 +1,6 @@
+std_msgs/Header header
+string package_name
+string launch_file_name
+---
+std_msgs/Header header
+string status
\ No newline at end of file
diff --git a/capabilities2_msgs/srv/TriggerCapability.srv b/capabilities2_msgs/srv/TriggerCapability.srv
new file mode 100644
index 0000000..b9160a7
--- /dev/null
+++ b/capabilities2_msgs/srv/TriggerCapability.srv
@@ -0,0 +1,3 @@
+string capability
+string parameters
+---
diff --git a/capabilities2_runner/CMakeLists.txt b/capabilities2_runner/CMakeLists.txt
index 02d553d..6021b30 100644
--- a/capabilities2_runner/CMakeLists.txt
+++ b/capabilities2_runner/CMakeLists.txt
@@ -15,21 +15,18 @@ find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(pluginlib REQUIRED)
find_package(capabilities2_msgs REQUIRED)
-find_package(PkgConfig)
-pkg_check_modules(TINYXML2 tinyxml2 REQUIRED)
-
-include_directories(include
-${TINYXML2_INCLUDE_DIRS}
+find_package(capabilities2_utils REQUIRED)
+find_package(event_logger REQUIRED)
+find_package(event_logger_msgs REQUIRED)
+find_package(tinyxml2_vendor REQUIRED)
+find_package(TinyXML2 REQUIRED) # provided by tinyxml2 upstream, or tinyxml2_vendor
+
+include_directories(
+ include
)
add_library(${PROJECT_NAME} SHARED
src/standard_runners.cpp
- # src/launch_runner.cpp
- # src/action_runner.cpp
-)
-
-target_link_libraries(${PROJECT_NAME}
- ${TINYXML2_LIBRARIES}
)
ament_target_dependencies(${PROJECT_NAME}
@@ -37,7 +34,10 @@ ament_target_dependencies(${PROJECT_NAME}
rclcpp_action
pluginlib
capabilities2_msgs
- TINYXML2
+ capabilities2_utils
+ event_logger
+ event_logger_msgs
+ TinyXML2
)
pluginlib_export_plugin_description_file(${PROJECT_NAME} plugins.xml)
diff --git a/capabilities2_runner/include/capabilities2_runner/action_runner.hpp b/capabilities2_runner/include/capabilities2_runner/action_runner.hpp
index 4b63d53..7c768df 100644
--- a/capabilities2_runner/include/capabilities2_runner/action_runner.hpp
+++ b/capabilities2_runner/include/capabilities2_runner/action_runner.hpp
@@ -1,10 +1,12 @@
#pragma once
#include
+#include
#include
#include
#include
+#include
#include
#include
#include
@@ -36,60 +38,25 @@ class ActionRunner : public RunnerBase
* @param node shared pointer to the capabilities node. Allows to use ros node related functionalities
* @param run_config runner configuration loaded from the yaml file
* @param action_name action name used in the yaml file, used to load specific configuration from the run_config
- * @param on_started function pointer to trigger at the start of the action client in the runner
- * @param on_terminated function pointer to trigger at the termination of the action client in the runner
- * @param on_stopped function pointer to trigger at the termination of the action client by the server
*/
- virtual void init_action(rclcpp::Node::SharedPtr node, const runner_opts& run_config, const std::string& action_name,
- std::function on_started = nullptr,
- std::function on_terminated = nullptr,
- std::function on_stopped = nullptr)
+ virtual void init_action(rclcpp::Node::SharedPtr node, const runner_opts& run_config, const std::string& action_name)
{
// initialize the runner base by storing node pointer and run config
- init_base(node, run_config, on_started, on_terminated, on_stopped);
+ init_base(node, run_config);
// create an action client
action_client_ = rclcpp_action::create_client(node_, action_name);
// wait for action server
- RCLCPP_INFO(node_->get_logger(), "%s waiting for action: %s", run_config_.interface.c_str(), action_name.c_str());
+ info_("waiting for action: " + action_name);
- if (!action_client_->wait_for_action_server(std::chrono::seconds(3)))
+ if (!action_client_->wait_for_action_server(std::chrono::seconds(1000)))
{
- RCLCPP_ERROR(node_->get_logger(), "%s failed to connect to action: %s", run_config_.interface.c_str(),
- action_name.c_str());
+ error_("failed to connect to action: " + action_name);
throw runner_exception("failed to connect to action server");
}
- // send goal options
- // goal response callback
- send_goal_options_.goal_response_callback =
- [this](const typename rclcpp_action::ClientGoalHandle::SharedPtr& goal_handle) {
- // publish event
- if (goal_handle)
- if (on_started_)
- on_started_(run_config_.interface);
-
- // store goal handle to be used with stop funtion
- goal_handle_ = goal_handle;
- };
-
- // result callback
- send_goal_options_.result_callback =
- [this](const typename rclcpp_action::ClientGoalHandle::WrappedResult& wrapped_result) {
- if (wrapped_result.code == rclcpp_action::ResultCode::SUCCEEDED)
- {
- // Do something
- }
- else
- {
- // send terminated event
- if (on_terminated_)
- {
- on_terminated_(run_config_.interface);
- }
- }
- };
+ info_("connected with action: " + action_name);
}
/**
@@ -115,106 +82,143 @@ class ActionRunner : public RunnerBase
{
try
{
- std::shared_future cancel_future =
- action_client_->async_cancel_goal(
- goal_handle_, [this](action_msgs::srv::CancelGoal_Response::SharedPtr response) {
- if (response->return_code != action_msgs::srv::CancelGoal_Response::ERROR_NONE)
- {
- // throw runner_exception("failed to cancel runner");
- }
-
- // publish event
- if (on_stopped_)
- {
- on_stopped_(run_config_.interface);
- }
- });
+ auto cancel_future = action_client_->async_cancel_goal(
+ goal_handle_, [this](action_msgs::srv::CancelGoal_Response::SharedPtr response) {
+ if (response->return_code != action_msgs::srv::CancelGoal_Response::ERROR_NONE)
+ {
+ // throw runner_exception("failed to cancel runner");
+ error_("Runner cancellation failed.");
+ }
+
+ // Trigger on_stopped event if defined
+ if (events[runner_id].on_stopped.interface != "")
+ {
+ event_(EventType::STOPPED, -1, events[runner_id].on_stopped.interface,
+ events[runner_id].on_stopped.provider);
+ triggerFunction_(events[runner_id].on_stopped.interface,
+ update_on_stopped(events[runner_id].on_stopped.parameters));
+ }
+ });
// wait for action to be stopped. hold the thread for 2 seconds to help keep callbacks in scope
// BUG: the line below does not work in jazzy build, so a workaround is used
- // rclcpp::spin_until_future_complete(node_->get_node_base_interface(), cancel_future, std::chrono::seconds(2));
auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (std::chrono::steady_clock::now() < timeout)
{
+ // Check if the cancel operation is complete
if (cancel_future.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready)
break;
}
}
catch (const rclcpp_action::exceptions::UnknownGoalHandleError& e)
{
- RCLCPP_ERROR(node_->get_logger(), "failed to cancel goal: %s", e.what());
+ error_("failed to cancel goal: " + std::string(e.what()));
throw runner_exception(e.what());
}
}
+
+ info_("removing event options");
+
+ // remove all event options for this runner instance
+ const auto n = events.size();
+ events.clear();
+ info_("removed event options for " + std::to_string(n) + " runner ids");
+
+ info_("runner cleaned. stopping..");
}
/**
- * @brief the trigger function on the action runner is used to trigger an action.
- * this method provides a mechanism for injecting parameters or a goal into the action
- * and then trigger the action
+ * @brief Trigger process to be executed.
*
- * @param parameters
- * @return std::optional)>>
+ * This method utilizes paramters set via the trigger() function
+ *
+ * @param parameters pointer to tinyxml2::XMLElement that contains parameters
*/
- virtual std::optional)>>
- trigger(std::shared_ptr parameters = nullptr) override
+ virtual void execution(int id) override
{
- // the action is being triggered out of order
- if (!goal_handle_)
- throw runner_exception("cannot trigger action that was not started");
-
// if parameters are not provided then cannot proceed
- if (!parameters)
+ if (!parameters_[id])
throw runner_exception("cannot trigger action without parameters");
// generate a goal from parameters if provided
- typename ActionT::Goal goal_msg = generate_goal(parameters);
+ goal_msg_ = generate_goal(parameters_[id], id);
- // trigger the action client with goal
- auto goal_handle_future = action_client_->async_send_goal(goal_msg, send_goal_options_);
+ info_("goal generated for event ", id);
- // spin until send future completes
- if (rclcpp::spin_until_future_complete(node_, goal_handle_future) != rclcpp::FutureReturnCode::SUCCESS)
- {
- RCLCPP_ERROR(node_->get_logger(), "send goal call failed");
- return std::nullopt;
- }
+ std::unique_lock lock(mutex_);
+ completed_ = false;
+
+ // trigger the action client with goal
+ send_goal_options_.goal_response_callback =
+ [this, id](const typename rclcpp_action::ClientGoalHandle::SharedPtr& goal_handle) {
+ if (goal_handle)
+ {
+ info_("goal accepted. Waiting for result", id);
- // get result future
- typename rclcpp_action::ClientGoalHandle::SharedPtr goal_handle;
- auto result_future = action_client_->async_get_result(goal_handle);
-
- // create a function to call for the result
- // the future will be returned to the caller
- // and the caller can provide a conversion function
- // to handle the result
- std::function)> result_callback =
- [this, result_future](std::shared_ptr result) {
- // wait for result
- if (rclcpp::spin_until_future_complete(node_, result_future) != rclcpp::FutureReturnCode::SUCCESS)
+ // trigger the events related to on_started state
+ if (events[id].on_started.interface != "")
+ {
+ event_(EventType::STARTED, id, events[id].on_started.interface, events[id].on_started.provider);
+ triggerFunction_(events[id].on_started.interface, update_on_started(events[id].on_started.parameters));
+ }
+ }
+ else
{
- RCLCPP_ERROR(node_->get_logger(), "get result call failed");
- return;
+ error_("goal rejected", id);
}
- // get wrapped result
- typename rclcpp_action::ClientGoalHandle::WrappedResult wrapped_result = result_future.get();
+ // store goal handle to be used with stop funtion
+ goal_handle_ = goal_handle;
+ };
+
+ send_goal_options_.feedback_callback =
+ [this, id](typename rclcpp_action::ClientGoalHandle::SharedPtr goal_handle,
+ const typename ActionT::Feedback::ConstSharedPtr feedback_msg) {
+ std::string feedback = generate_feedback(feedback_msg);
- // convert the result
+ if (feedback != "")
+ {
+ info_("received feedback: " + feedback, id);
+ }
+ };
+
+ send_goal_options_.result_callback =
+ [this, id](const typename rclcpp_action::ClientGoalHandle::WrappedResult& wrapped_result) {
+ info_("received result", id);
if (wrapped_result.code == rclcpp_action::ResultCode::SUCCEEDED)
{
- // publish event
- if (on_result_)
+ info_("action succeeded.", id);
+
+ // trigger the events related to on_success state
+ if (events[id].on_success.interface != "")
{
- on_result_(run_config_.interface);
+ event_(EventType::SUCCEEDED, id, events[id].on_success.interface, events[id].on_success.provider);
+ triggerFunction_(events[id].on_success.interface, update_on_success(events[id].on_success.parameters));
}
+ }
+ else
+ {
+ error_("action failed", id);
- result = generate_result(wrapped_result.result);
- return;
+ // trigger the events related to on_failure state
+ if (events[id].on_failure.interface != "")
+ {
+ event_(EventType::FAILED, id, events[id].on_failure.interface, events[id].on_failure.provider);
+ triggerFunction_(events[id].on_failure.interface, update_on_failure(events[id].on_failure.parameters));
+ }
}
+
+ result_ = wrapped_result.result;
+ completed_ = true;
+ cv_.notify_all();
};
- return result_callback;
+ goal_handle_future_ = action_client_->async_send_goal(goal_msg_, send_goal_options_);
+ info_("goal sent. Waiting for acceptance.", id);
+
+ // Conditional wait
+ cv_.wait(lock, [this] { return completed_; });
+ info_("action complete. Thread closing.", id);
}
protected:
@@ -229,20 +233,20 @@ class ActionRunner : public RunnerBase
* @param parameters
* @return ActionT::Goal the generated goal
*/
- virtual typename ActionT::Goal generate_goal(std::shared_ptr parameters) = 0;
+ virtual typename ActionT::Goal generate_goal(tinyxml2::XMLElement* parameters, int id) = 0;
/**
- * @brief generate a typed erased goal result
+ * @brief Generate a std::string from feedback message
*
- * this method is used in a callback passed to the trigger caller to get type erased result
- * from the action the result can be passed by the caller or ignored
+ * This function is used to convert feedback messages into generic strings
*
- * The pattern needs to be implemented in the derived class
+ * A pattern needs to be implemented in the derived class. If the feedback string
+ * is empty, nothing will be printed on the screen
*
- * @param wrapped_result
- * @return std::shared_ptr
+ * @param parameters
+ * @return ActionT::Feedback the received feedback
*/
- virtual std::shared_ptr generate_result(const typename ActionT::Result::SharedPtr& result) = 0;
+ virtual std::string generate_feedback(const typename ActionT::Feedback::ConstSharedPtr msg) = 0;
/**< action client */
typename rclcpp_action::Client::SharedPtr action_client_;
@@ -253,6 +257,21 @@ class ActionRunner : public RunnerBase
/** goal handle parameter to capture goal response from goal_response_callback */
typename rclcpp_action::ClientGoalHandle::SharedPtr goal_handle_;
+
+ /** Wrapped Result */
+ typename rclcpp_action::ClientGoalHandle::WrappedResult wrapped_result_;
+
+ /** Result */
+ typename ActionT::Result::SharedPtr result_;
+
+ /** Goal message */
+ typename ActionT::Goal goal_msg_;
+
+ /** Goal Handle Future message */
+ std::shared_future::SharedPtr> goal_handle_future_;
+
+ /** Result Future*/
+ std::shared_future::WrappedResult> result_future_;
};
} // namespace capabilities2_runner
diff --git a/capabilities2_runner/include/capabilities2_runner/encap_runner.hpp b/capabilities2_runner/include/capabilities2_runner/encap_runner.hpp
index 0e45708..931454e 100644
--- a/capabilities2_runner/include/capabilities2_runner/encap_runner.hpp
+++ b/capabilities2_runner/include/capabilities2_runner/encap_runner.hpp
@@ -33,21 +33,15 @@ class EnCapRunner : public NoTriggerActionRunner
* this is deferred since the action client topic name is not known at this level
* of abstraction
*
- * @param node
- * @param run_config
- * @param action_name
- * @param on_started
- * @param on_terminated
- * @param on_stopped
+ * @param node shared pointer to the capabilities node. Allows to use ros node related functionalities
+ * @param run_config runner configuration loaded from the yaml file
+ * @param action_name action name used in the yaml file, used to load specific configuration from the run_config
*/
virtual void init_encapsulated_action(rclcpp::Node::SharedPtr node, const runner_opts& run_config,
- const std::string& action_name,
- std::function on_started = nullptr,
- std::function on_terminated = nullptr,
- std::function on_stopped = nullptr)
+ const std::string& action_name, std::function print)
{
// init the base action runner
- init_action(node, run_config, action_name, on_started, on_terminated, on_stopped);
+ init_action(node, run_config, action_name, print);
// create an encapsulating action server
encap_action_ = rclcpp_action::create_server(
diff --git a/capabilities2_runner/include/capabilities2_runner/launch_runner.hpp b/capabilities2_runner/include/capabilities2_runner/launch_runner.hpp
index 9323d9a..b4c101d 100644
--- a/capabilities2_runner/include/capabilities2_runner/launch_runner.hpp
+++ b/capabilities2_runner/include/capabilities2_runner/launch_runner.hpp
@@ -1,10 +1,7 @@
#pragma once
-#include
-#include
-#include
-#include
-#include
+#include
+#include
namespace capabilities2_runner
{
@@ -14,54 +11,140 @@ namespace capabilities2_runner
*
* Create a launch file runner to run a launch file based capability
*/
-class LaunchRunner : public NoTriggerActionRunner
+class LaunchRunner : public RunnerBase
{
public:
- LaunchRunner() : NoTriggerActionRunner()
+ using Launch = capabilities2_msgs::srv::Launch;
+
+ /**
+ * @brief Constructor which needs to be empty due to plugin semantics
+ */
+ LaunchRunner() : RunnerBase()
{
}
- virtual void start(rclcpp::Node::SharedPtr node, const runner_opts& run_config,
- std::function on_started = nullptr,
- std::function on_terminated = nullptr,
- std::function on_stopped = nullptr) override
+ /**
+ * @brief Starter function for starting the launch runner
+ *
+ * @param node shared pointer to the capabilities node. Allows to use ros node related functionalities
+ * @param run_config runner configuration loaded from the yaml file
+ */
+ virtual void start(rclcpp::Node::SharedPtr node, const runner_opts& run_config) override
{
- // store node pointer and run_config
- init_action(node, run_config, "capabilities_launch_proxy/launch", on_started, on_terminated, on_stopped);
+ init_base(node, run_config);
+
+ package_name = run_config_.runner.substr(0, run_config_.runner.find("/"));
+ launch_name = run_config_.runner.substr(run_config_.runner.find("/") + 1);
+
+ // create an service client
+ start_service_client_ = node_->create_client("/capabilities/launch/start");
+
+ RCLCPP_INFO(node_->get_logger(), "%s waiting for service: /capabilities/launch/start",
+ run_config_.interface.c_str());
- // get the package path from environment variable
- std::string package_path;
- try
+ if (!start_service_client_->wait_for_service(std::chrono::seconds(3)))
{
- package_path = ament_index_cpp::get_package_share_directory(get_package_name());
+ RCLCPP_ERROR(node_->get_logger(), "%s failed to connect to service: /capabilities/launch/start",
+ run_config_.interface.c_str());
+ throw runner_exception("Failed to connect to server: /capabilities/launch/start");
}
- catch (const std::exception& e)
+
+ RCLCPP_INFO(node_->get_logger(), "%s connected to service: /capabilities/launch/start",
+ run_config_.interface.c_str());
+
+ // create an service client
+ stop_service_client_ = node_->create_client("/capabilities/launch/stop");
+
+ // wait for action server
+ RCLCPP_INFO(node_->get_logger(), "%s waiting for service: /capabilities/launch/stop",
+ run_config_.interface.c_str());
+
+ if (!stop_service_client_->wait_for_service(std::chrono::seconds(3)))
{
- RCLCPP_ERROR(node_->get_logger(), "Failed to get package share directory: %s", e.what());
- throw runner_exception("failed to get package share directory");
+ RCLCPP_ERROR(node_->get_logger(), "%s failed to connect to service: /capabilities/launch/stop",
+ run_config_.interface.c_str());
+ throw runner_exception("Failed to connect to server: /capabilities/launch/stop");
}
- // resolve launch path
- // get full path to launch file
- // join package path with package name using path functions
- std::string launch_file_path = std::filesystem::path(package_path).append(run_config_.runner).string();
+ RCLCPP_INFO(node_->get_logger(), "%s connected to service: /capabilities/launch/stop",
+ run_config_.interface.c_str());
+
+ // generate a reequest from launch_name and package_name
+ auto request_msg = std::make_shared();
+
+ request_msg->package_name = package_name;
+ request_msg->launch_file_name = launch_name;
+
+ RCLCPP_INFO(node_->get_logger(), "Requesting to launch %s from %s", launch_name.c_str(), package_name.c_str());
+
+ auto result_future = start_service_client_->async_send_request(
+ request_msg, [this](typename rclcpp::Client::SharedFuture future) {
+ if (!future.valid())
+ {
+ RCLCPP_ERROR(node_->get_logger(), "Request to launch %s from %s failed", launch_name.c_str(),
+ package_name.c_str());
+ return;
+ }
+
+ RCLCPP_INFO(node_->get_logger(), "Request to launch %s from %s succeeded", launch_name.c_str(),
+ package_name.c_str());
+ });
+ }
+
+ /**
+ * @brief stop function to cease functionality and shutdown
+ *
+ */
+ virtual void stop() override
+ {
+ // if the node pointer is empty then throw an error
+ // this means that the runner was not started and is being used out of order
+
+ if (!node_)
+ throw runner_exception("cannot stop runner that was not started");
- // the launch file path
- RCLCPP_DEBUG(node_->get_logger(), "launch file path: %s", launch_file_path.c_str());
+ // generate a reequest from launch_name and package_name
+ auto request_msg = std::make_shared();
- // create a launch goal
- capabilities2_msgs::action::Launch::Goal goal;
- goal.launch_file_path = launch_file_path;
+ request_msg->package_name = package_name;
+ request_msg->launch_file_name = launch_name;
- send_goal_options_.result_callback = nullptr;
+ RCLCPP_INFO(node_->get_logger(), "Requesting to stop %s from %s", launch_name.c_str(), package_name.c_str());
- // launch runner using action client
- action_client_->async_send_goal(goal, send_goal_options_);
+ auto result_future = stop_service_client_->async_send_request(
+ request_msg, [this](typename rclcpp::Client::SharedFuture future) {
+ if (!future.valid())
+ {
+ RCLCPP_ERROR(node_->get_logger(), "Request to stop %s from %s failed ", launch_name.c_str(),
+ package_name.c_str());
+ return;
+ }
+
+ RCLCPP_INFO(node_->get_logger(), "Request to launch %s from %s succeeded ", launch_name.c_str(),
+ package_name.c_str());
+ });
+
+ info_("stopping runner");
}
-private:
- /** launch file path */
- std::string launch_file_path;
+ // throw on trigger function
+ void trigger(const std::string& parameters) override
+ {
+ throw runner_exception("No Trigger as this is launch runner");
+ }
+
+protected:
+ // throw on triggerExecution function
+ void execution(int id) override
+ {
+ throw runner_exception("no triggerExecution() this is a no-trigger action runner");
+ }
+
+ std::string launch_name;
+ std::string package_name;
+
+ rclcpp::Client::SharedPtr start_service_client_;
+ rclcpp::Client::SharedPtr stop_service_client_;
};
-} // namespace capabilities2_runner
+} // namespace capabilities2_runner
\ No newline at end of file
diff --git a/capabilities2_runner/include/capabilities2_runner/multi_action_runner.hpp b/capabilities2_runner/include/capabilities2_runner/multi_action_runner.hpp
deleted file mode 100644
index 1cbb1c1..0000000
--- a/capabilities2_runner/include/capabilities2_runner/multi_action_runner.hpp
+++ /dev/null
@@ -1,266 +0,0 @@
-#pragma once
-
-#include
-#include
-#include
-#include