diff --git a/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.cpp b/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.cpp index 02cbe58cef253fc0d741c182caa32b7c3d2a529c..d77d737156239241ed8a62952e788da11c99bb55 100644 --- a/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.cpp +++ b/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.cpp @@ -156,6 +156,16 @@ namespace armarx return SkillManagerComponentPluginUser::executeSkill(info, current); } + skills::manager::dto::SkillExecutionID + SkillsMemory::executeSkillAsync(const skills::manager::dto::SkillExecutionRequest& info, + const Ice::Current& current) + { + auto e = skills::SkillExecutionRequest::FromIce(info); + skillExecutionRequestCoreSegment.addSkillExecutionRequest(e); + + return SkillManagerComponentPluginUser::executeSkillAsync(info, current); + } + void SkillsMemory::updateStatusForSkill(const skills::provider::dto::SkillStatusUpdate& update, const skills::callback::dto::ProviderID& providerId, @@ -166,14 +176,27 @@ namespace armarx skillEventCoreSegment.addSkillUpdateEvent(u); } + IceUtil::Optional<skills::manager::dto::SkillStatusUpdate> + SkillsMemory::getSkillExecutionStatus(const skills::manager::dto::SkillExecutionID& executionId, + const Ice::Current& current) + { + auto eid = skills::SkillExecutionID::FromIce(executionId); + auto op = this->skillEventCoreSegment.getSkillStatusUpdate(eid); + if (op.has_value()) + { + return op->toManagerIce(); + } + return {}; + } + skills::manager::dto::SkillStatusUpdateMap - SkillsMemory::getLatestSkillExecutionStatuses(int n, const Ice::Current& current) + SkillsMemory::getSkillExecutionStatuses(const Ice::Current& current) { - auto m = skillEventCoreSegment.getLatestSkillEvents(n); skills::manager::dto::SkillStatusUpdateMap ret; - for (const auto& [k, v] : m) + auto updates = this->skillEventCoreSegment.getSkillStatusUpdates(); + for (const auto& [k, v] : updates) { - ret[k.toManagerIce()] = v.toManagerIce(); + ret.insert({k.toManagerIce(), v.toManagerIce()}); } return ret; } diff --git a/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.h b/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.h index 81109b0bdf8787d6446e02fc11dcfe56ba484c4a..ead9f19122bc80266196aff40f138b4bd87c77f1 100644 --- a/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.h +++ b/source/RobotAPI/components/armem/server/SkillsMemory/SkillsMemory.h @@ -85,12 +85,21 @@ namespace armarx executeSkill(const skills::manager::dto::SkillExecutionRequest& info, const Ice::Current& current) override; + + skills::manager::dto::SkillExecutionID + executeSkillAsync(const skills::manager::dto::SkillExecutionRequest& info, + const Ice::Current& current) override; + void updateStatusForSkill(const skills::provider::dto::SkillStatusUpdate& update, const skills::callback::dto::ProviderID& id, const Ice::Current& current) override; + IceUtil::Optional<skills::manager::dto::SkillStatusUpdate> + getSkillExecutionStatus(const skills::manager::dto::SkillExecutionID& executionId, + const Ice::Current& current) override; + skills::manager::dto::SkillStatusUpdateMap - getLatestSkillExecutionStatuses(int n, const Ice::Current& current) override; + getSkillExecutionStatuses(const Ice::Current& current) override; // WritingInterface interface armem::data::CommitResult commit(const armem::data::Commit& commit, diff --git a/source/RobotAPI/components/skills/SkillProviderExample/CMakeLists.txt b/source/RobotAPI/components/skills/SkillProviderExample/CMakeLists.txt index afbb7ef2abd07d6e718c7480ba7d84455574041d..b13f663e25b6c31fbb4ab3b919ce81431d617b70 100644 --- a/source/RobotAPI/components/skills/SkillProviderExample/CMakeLists.txt +++ b/source/RobotAPI/components/skills/SkillProviderExample/CMakeLists.txt @@ -13,10 +13,20 @@ set(COMPONENT_LIBS set(SOURCES SkillProviderExample.cpp + HelloWorld.cpp + Incomplete.cpp + Chaining.cpp + Callback.cpp + Timeout.cpp ) set(HEADERS SkillProviderExample.h + HelloWorld.h + Incomplete.h + Chaining.h + Callback.h + Timeout.h ) armarx_add_component("${SOURCES}" "${HEADERS}") diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Callback.cpp b/source/RobotAPI/components/skills/SkillProviderExample/Callback.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d13f3bb8df87414c8facac89adde3f587c34382c --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Callback.cpp @@ -0,0 +1,39 @@ + +#include "Callback.h" + +namespace armarx::skills::provider +{ + CallbackSkill::CallbackSkill() : SimpleSkill(GetSkillDescription()) + { + } + + SkillDescription + CallbackSkill::GetSkillDescription() + { + return SkillDescription{{"ShowMeCallbacks"}, + "This skill does shows callbacks", + {}, + armarx::core::time::Duration::MilliSeconds(1000), + nullptr}; + } + + Skill::MainResult + CallbackSkill::main(const MainInput& in) + { + ARMARX_IMPORTANT << "Logging three updates via the callback"; + auto up1 = std::make_shared<aron::data::Dict>(); + up1->addElement("updateInfo", std::make_shared<aron::data::String>("Update 1")); + + in.callback(skills::SkillStatus::Running, up1); + + auto up2 = std::make_shared<aron::data::Dict>(); + up2->addElement("updateInfo", std::make_shared<aron::data::String>("Update 2")); + in.callback(skills::SkillStatus::Running, up2); + + auto up3 = std::make_shared<aron::data::Dict>(); + up3->addElement("updateInfo", std::make_shared<aron::data::String>("Update 3")); + in.callback(skills::SkillStatus::Running, up3); + + return {TerminatedSkillStatus::Succeeded, nullptr}; + } +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Callback.h b/source/RobotAPI/components/skills/SkillProviderExample/Callback.h new file mode 100644 index 0000000000000000000000000000000000000000..c29c2b6f3d6007b1fda30b0c781b91be03a15232 --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Callback.h @@ -0,0 +1,41 @@ + +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Fabian Reister ( fabian dot reister at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + + +// RobotAPI +#include <RobotAPI/libraries/skills/provider/SimpleSkill.h> + +namespace armarx::skills::provider +{ + class CallbackSkill : public SimpleSkill + { + public: + CallbackSkill(); + + static SkillDescription GetSkillDescription(); + + private: + Skill::MainResult main(const MainInput&) final; + }; +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Chaining.cpp b/source/RobotAPI/components/skills/SkillProviderExample/Chaining.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c40b511a667ab4ea94647723c4532c64b115559a --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Chaining.cpp @@ -0,0 +1,39 @@ + + +#include "Chaining.h" + +namespace armarx::skills::provider +{ + + ChainingSkill::ChainingSkill() : SimpleSkill(GetSkillDescription()) + { + } + + SkillDescription + ChainingSkill::GetSkillDescription() + { + return SkillDescription{{"ChainingSkill"}, + "This skill calls the Timeout skill three times. The last " + "execution is aborted due to a timeout of this skill.", + {}, + armarx::core::time::Duration::MilliSeconds(5000), + nullptr}; + } + + Skill::MainResult + ChainingSkill::main(const MainInput& in) + { + SkillProxy prx(manager, skills::SkillID(*getSkillId().providerId, "Timeout")); + + ARMARX_INFO << "CALL PROXY FIRST TIME"; + callSubskill(prx); + ARMARX_INFO << "CALL PROXY SECOND TIME"; + callSubskill(prx); + ARMARX_INFO << "CALL PROXY THIRD TIME"; + callSubskill(prx); + + this->throwIfSkillShouldTerminate(); + + return {TerminatedSkillStatus::Succeeded, nullptr}; + } +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Chaining.h b/source/RobotAPI/components/skills/SkillProviderExample/Chaining.h new file mode 100644 index 0000000000000000000000000000000000000000..991c4a5aaab5d1be83f9684ca5bdbca1638b946d --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Chaining.h @@ -0,0 +1,40 @@ + +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Fabian Reister ( fabian dot reister at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + +// RobotAPI +#include <RobotAPI/libraries/skills/provider/SimpleSkill.h> + +namespace armarx::skills::provider +{ + class ChainingSkill : public SimpleSkill + { + public: + ChainingSkill(); + + static SkillDescription GetSkillDescription(); + + private: + Skill::MainResult main(const MainInput&) final; + }; +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/HelloWorld.cpp b/source/RobotAPI/components/skills/SkillProviderExample/HelloWorld.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ccfa7f31a5e4dd7a235f1ef1d60a441441a8a595 --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/HelloWorld.cpp @@ -0,0 +1,49 @@ + + +#include "HelloWorld.h" + +#include <RobotAPI/libraries/aron/converter/json/NLohmannJSONConverter.h> +#include <RobotAPI/libraries/aron/core/type/variant/container/Object.h> +#include <RobotAPI/libraries/aron/core/type/variant/primitive/String.h> + +namespace armarx::skills::provider +{ + HelloWorldSkill::HelloWorldSkill() : + SimpleSpecializedSkill<skills::Example::HelloWorldAcceptedType>(GetSkillDescription()) + { + } + + SkillDescription + HelloWorldSkill::GetSkillDescription() + { + armarx::skills::Example::HelloWorldAcceptedType root_profile_params; + root_profile_params.some_float = 5; + root_profile_params.some_int = 42; + root_profile_params.some_text = "YOLO"; + root_profile_params.some_list_of_matrices.push_back(Eigen::Matrix3f::Zero()); + root_profile_params.some_matrix = Eigen::Matrix3f::Zero(); + + return SkillDescription{{"HelloWorld"}, + "This skill logs a message on ARMARX_IMPORTANT", + root_profile_params.toAron(), + armarx::core::time::Duration::MilliSeconds(1000), + armarx::skills::Example::HelloWorldAcceptedType::ToAronType()}; + } + + Skill::MainResult + HelloWorldSkill::main(const SpecializedMainInput& in) + { + ARMARX_IMPORTANT << "Hi, from the Hello World Skill.\n" + << "I received the following data: \n" + << aron::data::converter::AronNlohmannJSONConverter::ConvertToNlohmannJSON( + in.parameters.toAron()) + .dump(2) + << "\n" + << "Type fulfilled? " + << parameters->fullfillsType( + armarx::skills::Example::HelloWorldAcceptedType::ToAronType()) + << "\n" + << "(executed at: " << IceUtil::Time::now() << ")"; + return {TerminatedSkillStatus::Succeeded, nullptr}; + } +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/HelloWorld.h b/source/RobotAPI/components/skills/SkillProviderExample/HelloWorld.h new file mode 100644 index 0000000000000000000000000000000000000000..840b058fb171685db777126157fc21028db89c99 --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/HelloWorld.h @@ -0,0 +1,42 @@ + +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Fabian Reister ( fabian dot reister at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + +// RobotAPI +#include <RobotAPI/components/skills/SkillProviderExample/aron/HelloWorldAcceptedType.aron.generated.h> +#include <RobotAPI/libraries/skills/provider/SimpleSpecializedSkill.h> + +namespace armarx::skills::provider +{ + // Skills: + class HelloWorldSkill : public SimpleSpecializedSkill<skills::Example::HelloWorldAcceptedType> + { + public: + HelloWorldSkill(); + + static SkillDescription GetSkillDescription(); + + private: + Skill::MainResult main(const SpecializedMainInput& in) final; + }; +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Incomplete.cpp b/source/RobotAPI/components/skills/SkillProviderExample/Incomplete.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ac9bf9af3d980d76695ae64176c60d063a440263 --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Incomplete.cpp @@ -0,0 +1,55 @@ + + +#include "Incomplete.h" + +#include <RobotAPI/components/skills/SkillProviderExample/aron/HelloWorldAcceptedType.aron.generated.h> + +#include "HelloWorld.h" + +namespace armarx::skills::provider +{ + + IncompleteSkill::IncompleteSkill() : SimpleSkill(GetSkillDescription()) + { + } + + SkillDescription + IncompleteSkill::GetSkillDescription() + { + auto d = HelloWorldSkill::GetSkillDescription(); + return SkillDescription{{"IncompleteSkill"}, + d.description, + {}, + d.timeout + armarx::core::time::Duration::Seconds(2), + d.parametersType}; + } + + Skill::PrepareResult + IncompleteSkill::prepare() + { + if (!first_prepared) + { + first_prepared = true; + + // set parameters after two seconds... + std::thread foo( + [&]() + { + auto d = HelloWorldSkill::GetSkillDescription(); + std::this_thread::sleep_for(std::chrono::milliseconds(2000)); + this->setParameters(d.rootProfileDefaults); + }); + foo.detach(); + } + + return {.status = ActiveOrTerminatedSkillStatus::Succeeded}; + } + + Skill::MainResult + IncompleteSkill::main(const MainInput& in) + { + auto s = HelloWorldSkill(); + s.setParameters(in.parameters); + return s.mainOfSkill(); + } +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Incomplete.h b/source/RobotAPI/components/skills/SkillProviderExample/Incomplete.h new file mode 100644 index 0000000000000000000000000000000000000000..6cf7b6e73b597b201d9d5213db160151d3a971c8 --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Incomplete.h @@ -0,0 +1,44 @@ + +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Fabian Reister ( fabian dot reister at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + +// RobotAPI +#include <RobotAPI/libraries/skills/provider/SimpleSkill.h> + +namespace armarx::skills::provider +{ + + class IncompleteSkill : public SimpleSkill + { + public: + IncompleteSkill(); + + static SkillDescription GetSkillDescription(); + + private: + Skill::PrepareResult prepare() final; + Skill::MainResult main(const MainInput&) final; + + std::atomic_bool first_prepared = false; + }; +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.cpp b/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.cpp index 4e82f6d7e1408170e7d992996cebcba7d56e7fbb..4230249f7d9eb30ca641da0bbd5f2934c2307d48 100644 --- a/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.cpp +++ b/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.cpp @@ -1,192 +1,8 @@ - #include "SkillProviderExample.h" -#include <RobotAPI/components/skills/SkillProviderExample/aron/HelloWorldAcceptedType.aron.generated.h> -#include <RobotAPI/libraries/aron/converter/json/NLohmannJSONConverter.h> -#include <RobotAPI/libraries/aron/core/type/variant/container/Object.h> -#include <RobotAPI/libraries/aron/core/type/variant/primitive/String.h> - namespace armarx::skills::provider { - HelloWorldSkill::HelloWorldSkill() : - SimpleSpecializedSkill<skills::Example::HelloWorldAcceptedType>(GetSkillDescription()) - { - } - - SkillDescription - HelloWorldSkill::GetSkillDescription() - { - armarx::skills::Example::HelloWorldAcceptedType root_profile_params; - root_profile_params.some_float = 5; - root_profile_params.some_int = 42; - root_profile_params.some_text = "YOLO"; - root_profile_params.some_list_of_matrices.push_back(Eigen::Matrix3f::Zero()); - root_profile_params.some_matrix = Eigen::Matrix3f::Zero(); - - return SkillDescription{{"HelloWorld"}, - "This skill logs a message on ARMARX_IMPORTANT", - root_profile_params.toAron(), - armarx::core::time::Duration::MilliSeconds(1000), - armarx::skills::Example::HelloWorldAcceptedType::ToAronType()}; - } - - Skill::MainResult - HelloWorldSkill::main(const SpecializedMainInput& in) - { - ARMARX_IMPORTANT << "Hi, from the Hello World Skill.\n" - << "I received the following data: \n" - << aron::data::converter::AronNlohmannJSONConverter::ConvertToNlohmannJSON( - in.parameters.toAron()) - .dump(2) - << "\n" - << "Type fulfilled? " - << parameters->fullfillsType( - armarx::skills::Example::HelloWorldAcceptedType::ToAronType()) - << "\n" - << "(executed at: " << IceUtil::Time::now() << ")"; - return {TerminatedSkillStatus::Succeeded, nullptr}; - } - - IncompleteSkill::IncompleteSkill() : SimpleSkill(GetSkillDescription()) - { - } - - SkillDescription - IncompleteSkill::GetSkillDescription() - { - auto d = HelloWorldSkill::GetSkillDescription(); - return SkillDescription{{"IncompleteSkill"}, - d.description, - {}, - d.timeout + armarx::core::time::Duration::Seconds(2), - d.parametersType}; - } - - Skill::PrepareResult - IncompleteSkill::prepare() - { - if (!first_prepared) - { - first_prepared = true; - std::thread foo( - [&]() - { - auto d = HelloWorldSkill::GetSkillDescription(); - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - this->setParameters(d.rootProfileDefaults); - }); - foo.detach(); - } - - auto s = HelloWorldSkill(); - s.setParameters(this->getParameters()); - s.mainOfSkill(); - - return {.status = ActiveOrTerminatedSkillStatus::Succeeded}; - } - - Skill::MainResult - IncompleteSkill::main(const MainInput& in) - { - auto s = HelloWorldSkill(); - s.setParameters(in.parameters); - return s.mainOfSkill(); - } - - ChainingSkill::ChainingSkill() : SimpleSkill(GetSkillDescription()) - { - } - - SkillDescription - ChainingSkill::GetSkillDescription() - { - return SkillDescription{{"ChainingSkill"}, - "This skill calls the HelloWorld skill three times.", - {}, - armarx::core::time::Duration::MilliSeconds(3000), - nullptr}; - } - - Skill::MainResult - ChainingSkill::main(const MainInput& in) - { - armarx::skills::Example::HelloWorldAcceptedType exec1; - armarx::skills::Example::HelloWorldAcceptedType exec2; - armarx::skills::Example::HelloWorldAcceptedType exec3; - - exec1.some_text = "Hello from the ChainingSkill 1"; - exec2.some_text = "Hello from the ChainingSkill 2"; - exec3.some_text = "Hello from the ChainingSkill 3"; - - SkillProxy skillExecPrx( - manager, skills::SkillID(skills::ProviderID("SkillProviderExample"), "HelloWorld")); - - skillExecPrx.executeSkill(getSkillId().toString(), exec1.toAron()); - skillExecPrx.executeSkill(getSkillId().toString(), exec2.toAron()); - skillExecPrx.executeSkill(getSkillId().toString(), exec3.toAron()); - - return {TerminatedSkillStatus::Succeeded, nullptr}; - } - - TimeoutSkill::TimeoutSkill() : - PeriodicSkill(GetSkillDescription(), armarx::core::time::Frequency::Hertz(5)) - { - } - - SkillDescription - TimeoutSkill::GetSkillDescription() - { - return SkillDescription{{"Timeout"}, - "This fails with timeout reached", - {}, - armarx::core::time::Duration::MilliSeconds(5000), - nullptr}; - } - - PeriodicSkill::StepResult - TimeoutSkill::step() - { - // do heavy work - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - - return {ActiveOrTerminatedSkillStatus::Running, nullptr}; - } - - CallbackSkill::CallbackSkill() : SimpleSkill(GetSkillDescription()) - { - } - - SkillDescription - CallbackSkill::GetSkillDescription() - { - return SkillDescription{{"ShowMeCallbacks"}, - "This skill does shows callbacks", - {}, - armarx::core::time::Duration::MilliSeconds(1000), - nullptr}; - } - - Skill::MainResult - CallbackSkill::main(const MainInput& in) - { - ARMARX_IMPORTANT << "Logging three updates via the callback"; - auto up1 = std::make_shared<aron::data::Dict>(); - up1->addElement("updateInfo", std::make_shared<aron::data::String>("Update 1")); - - in.callback(skills::SkillStatus::Running, up1); - - auto up2 = std::make_shared<aron::data::Dict>(); - up2->addElement("updateInfo", std::make_shared<aron::data::String>("Update 2")); - in.callback(skills::SkillStatus::Running, up2); - - auto up3 = std::make_shared<aron::data::Dict>(); - up3->addElement("updateInfo", std::make_shared<aron::data::String>("Update 3")); - in.callback(skills::SkillStatus::Running, up3); - - return {TerminatedSkillStatus::Succeeded, nullptr}; - } - SkillProviderExample::SkillProviderExample() : SkillProviderComponentPluginUser() { } diff --git a/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.h b/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.h index 24d607450191b6ab98f3ba45112a417337358308..b8391b0881f625af03b00b601983251c4d90558b 100644 --- a/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.h +++ b/source/RobotAPI/components/skills/SkillProviderExample/SkillProviderExample.h @@ -27,73 +27,16 @@ #include <ArmarXCore/core/Component.h> // RobotAPI -#include <RobotAPI/components/skills/SkillProviderExample/aron/HelloWorldAcceptedType.aron.generated.h> -#include <RobotAPI/libraries/skills/provider/SimpleSkill.h> -#include <RobotAPI/libraries/skills/provider/SimpleSpecializedSkill.h> #include <RobotAPI/libraries/skills/provider/SkillProviderComponentPlugin.h> -#include <RobotAPI/libraries/skills/provider/SkillProxy.h> + +#include "Callback.h" +#include "Chaining.h" +#include "HelloWorld.h" +#include "Incomplete.h" +#include "Timeout.h" namespace armarx::skills::provider { - // Skills: - class HelloWorldSkill : public SimpleSpecializedSkill<skills::Example::HelloWorldAcceptedType> - { - public: - HelloWorldSkill(); - - static SkillDescription GetSkillDescription(); - - private: - Skill::MainResult main(const SpecializedMainInput& in) final; - }; - - class IncompleteSkill : public SimpleSkill - { - public: - IncompleteSkill(); - - static SkillDescription GetSkillDescription(); - - private: - Skill::PrepareResult prepare() final; - Skill::MainResult main(const MainInput&) final; - - std::atomic_bool first_prepared = false; - }; - - class ChainingSkill : public SimpleSkill - { - public: - ChainingSkill(); - - static SkillDescription GetSkillDescription(); - - private: - Skill::MainResult main(const MainInput&) final; - }; - - class TimeoutSkill : public PeriodicSkill - { - public: - TimeoutSkill(); - - static SkillDescription GetSkillDescription(); - - private: - PeriodicSkill::StepResult step() final; - }; - - class CallbackSkill : public SimpleSkill - { - public: - CallbackSkill(); - - static SkillDescription GetSkillDescription(); - - private: - Skill::MainResult main(const MainInput&) final; - }; - /** * @defgroup Component-ExampleClient ExampleClient * @ingroup RobotAPI-Components diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Timeout.cpp b/source/RobotAPI/components/skills/SkillProviderExample/Timeout.cpp new file mode 100644 index 0000000000000000000000000000000000000000..270dae01909e9bf0261b222257d22c4aec38a1ea --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Timeout.cpp @@ -0,0 +1,30 @@ + +#include "Timeout.h" + +namespace armarx::skills::provider +{ + + TimeoutSkill::TimeoutSkill() : + PeriodicSkill(GetSkillDescription(), armarx::core::time::Frequency::Hertz(5)) + { + } + + SkillDescription + TimeoutSkill::GetSkillDescription() + { + return SkillDescription{{"Timeout"}, + "This fails with timeout reached", + {}, + armarx::core::time::Duration::MilliSeconds(2000), + nullptr}; + } + + PeriodicSkill::StepResult + TimeoutSkill::step() + { + // do heavy work + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + return {ActiveOrTerminatedSkillStatus::Running, nullptr}; + } +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/components/skills/SkillProviderExample/Timeout.h b/source/RobotAPI/components/skills/SkillProviderExample/Timeout.h new file mode 100644 index 0000000000000000000000000000000000000000..347e216a956c22d8d38b3b0b434ec0b5f4cf0225 --- /dev/null +++ b/source/RobotAPI/components/skills/SkillProviderExample/Timeout.h @@ -0,0 +1,42 @@ + +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Fabian Reister ( fabian dot reister at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + + +// RobotAPI +#include <RobotAPI/libraries/skills/provider/PeriodicSkill.h> + +namespace armarx::skills::provider +{ + + class TimeoutSkill : public PeriodicSkill + { + public: + TimeoutSkill(); + + static SkillDescription GetSkillDescription(); + + private: + PeriodicSkill::StepResult step() final; + }; +} // namespace armarx::skills::provider diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp index b7880b87f624a20617e9d95738baf41c42620385..87450efc502a8430bffab8d205e0e3eec2dbbcb0 100644 --- a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp +++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp @@ -356,8 +356,9 @@ namespace armarx { std::scoped_lock l(updateMutex); - auto currentManagerStatuses = memory->getLatestSkillExecutionStatuses( - 100); // we assume that there are no more than 100 new skills.. + auto currentManagerStatuses = + memory + ->getSkillExecutionStatuses(); // we assume that there are no more than 100 new skills.. for (const auto& [k, v] : currentManagerStatuses) { @@ -378,19 +379,19 @@ namespace armarx // update values found->setText(3, QString::fromStdString( - statusUpdate.hasBeenConstructed() ? " (\xfb) " : "")); + statusUpdate.hasBeenConstructed() ? " yes " : " no ")); found->setText(4, QString::fromStdString( - statusUpdate.hasBeenInitialized() ? " (\xfb) " : "")); + statusUpdate.hasBeenInitialized() ? " yes " : " no ")); found->setText(5, QString::fromStdString( - statusUpdate.hasBeenPrepared() ? " (\xfb) " : "")); + statusUpdate.hasBeenPrepared() ? " yes " : " no ")); found->setText(6, QString::fromStdString( - statusUpdate.hasBeenRunning() ? " (\xfb) " : "")); + statusUpdate.hasBeenRunning() ? " yes " : " no ")); found->setText(7, QString::fromStdString( - statusUpdate.hasBeenTerminated() ? " (\xfb) " : "")); + statusUpdate.hasBeenTerminated() ? " yes " : " no ")); break; } } @@ -408,18 +409,19 @@ namespace armarx item->setText(2, QString::fromStdString(executionId.skillId.toString())); item->setText(3, QString::fromStdString( - statusUpdate.hasBeenConstructed() ? " (\xfb) " : "")); + statusUpdate.hasBeenConstructed() ? " yes " : " no ")); item->setText(4, QString::fromStdString( - statusUpdate.hasBeenInitialized() ? " (\xfb) " : "")); + statusUpdate.hasBeenInitialized() ? " yes " : " no ")); item->setText( 5, - QString::fromStdString(statusUpdate.hasBeenPrepared() ? " (\xfb) " : "")); + QString::fromStdString(statusUpdate.hasBeenPrepared() ? " yes " : " no ")); item->setText( - 6, QString::fromStdString(statusUpdate.hasBeenRunning() ? " (\xfb) " : "")); - item->setText( - 7, - QString::fromStdString(statusUpdate.hasBeenTerminated() ? " (\xfb) " : "")); + 6, + QString::fromStdString(statusUpdate.hasBeenRunning() ? " yes " : " no ")); + item->setText(7, + QString::fromStdString( + statusUpdate.hasBeenTerminated() ? " yes " : " no ")); } } } diff --git a/source/RobotAPI/interface/skills/SkillMemoryInterface.ice b/source/RobotAPI/interface/skills/SkillMemoryInterface.ice index 934d2fabaf539697a8416f4567c734e36de25858..3d0d8ee61b23a5e184514b87792323d379c81173 100644 --- a/source/RobotAPI/interface/skills/SkillMemoryInterface.ice +++ b/source/RobotAPI/interface/skills/SkillMemoryInterface.ice @@ -36,11 +36,7 @@ module armarx module dti { interface SkillMemoryInterface extends armem::server::MemoryInterface, - dti::StatechartListenerInterface, manager::dti::SkillManagerInterface - { - manager::dto::SkillStatusUpdateMap getLatestSkillExecutionStatuses( - int n); // returns latest n statusupdates of all providers - }; + dti::StatechartListenerInterface, manager::dti::SkillManagerInterface{}; } } } diff --git a/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.cpp b/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.cpp index d50fcba804fd4d498fb1a0f0974c9057b1ae3ae9..11dc8d0dfb40953de6acc8f81921884808339376 100644 --- a/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.cpp +++ b/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.cpp @@ -53,39 +53,58 @@ namespace armarx::skills::segment } std::map<skills::SkillExecutionID, skills::SkillStatusUpdate> - SkillEventCoreSegment::getLatestSkillEvents(int n) + SkillEventCoreSegment::getSkillStatusUpdates() { std::map<skills::SkillExecutionID, skills::SkillStatusUpdate> ret; auto coreSegment = this->segmentPtr; ARMARX_CHECK(coreSegment); - // 1. get all events and sort them by timestamp. - std::vector<skills::SkillStatusUpdate> sorted; coreSegment->forEachInstance( - [&sorted](const armem::wm::EntityInstance& i) + [&](const armem::wm::EntityInstance& i) { auto event = i.dataAs<armarx::skills::arondto::SkillStatusUpdate>(); skills::SkillStatusUpdate up; armem::fromAron(event, up); - sorted.emplace_back(std::move(up)); + + if (auto it = ret.find(up.executionId); it != ret.end() && up < it->second) + { + return; + } + + // set or replace + ret[up.executionId] = up; }); - std::sort(sorted.begin(), - sorted.end(), - [](const skills::SkillStatusUpdate& a, const skills::SkillStatusUpdate& b) - { return b < a; }); + // for (const auto& [k, v] : ret) + // { + // ARMARX_IMPORTANT << "Skill " << k.skillId << " has stati: " << int(v.status); + // } - for (const auto& el : sorted) - { - if (ret.size() >= (size_t)n) - { - break; - } + return ret; + } - ret.emplace( - std::piecewise_construct, std::make_tuple(el.executionId), std::make_tuple(el)); - } + std::optional<skills::SkillStatusUpdate> + SkillEventCoreSegment::getSkillStatusUpdate(const skills::SkillExecutionID& id) + { + std::optional<skills::SkillStatusUpdate> ret = std::nullopt; + auto coreSegment = this->segmentPtr; + ARMARX_CHECK(coreSegment); + coreSegment->forEachInstance( + [&](const armem::wm::EntityInstance& i) + { + auto event = i.dataAs<armarx::skills::arondto::SkillStatusUpdate>(); + skills::SkillStatusUpdate up; + armem::fromAron(event, up); + + if (up.executionId == id) + { + if (!ret || (ret.has_value() && *ret < up)) + { + ret = up; + } + } + }); return ret; } } // namespace armarx::skills::segment diff --git a/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.h b/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.h index 492c26090479a2be9c80442aeddc814378975486..0537cbdbdf97d80649e10baa30786d2d1fb5870c 100644 --- a/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.h +++ b/source/RobotAPI/libraries/armem_skills/server/segment/SkillEventSegment.h @@ -27,7 +27,10 @@ namespace armarx::skills::segment void addSkillUpdateEvent(const skills::SkillStatusUpdate& update); - std::map<skills::SkillExecutionID, skills::SkillStatusUpdate> getLatestSkillEvents(int n); + std::map<skills::SkillExecutionID, skills::SkillStatusUpdate> getSkillStatusUpdates(); + + std::optional<skills::SkillStatusUpdate> + getSkillStatusUpdate(const skills::SkillExecutionID& id); private: }; diff --git a/source/RobotAPI/libraries/skills/core/CMakeLists.txt b/source/RobotAPI/libraries/skills/core/CMakeLists.txt index 66795b7ecdf0b0aa0897a07f5e47e5f5d489d4a1..29145e60e305fa10decf06200685dcb99ec6a3d5 100644 --- a/source/RobotAPI/libraries/skills/core/CMakeLists.txt +++ b/source/RobotAPI/libraries/skills/core/CMakeLists.txt @@ -24,6 +24,7 @@ armarx_add_library( SkillPreparationInput.cpp SkillParameterization.cpp Skill.cpp + SkillProxy.cpp SkillDescription.cpp HEADERS error/Exception.h @@ -36,6 +37,7 @@ armarx_add_library( SkillPreparationInput.h SkillParameterization.h Skill.h + SkillProxy.h SkillDescription.h ) diff --git a/source/RobotAPI/libraries/skills/core/Skill.cpp b/source/RobotAPI/libraries/skills/core/Skill.cpp index 7d8d9b9c8c59c4476e7f5b2872cd5967972a65cb..34895c9066ca7202bb6e96c2885869f22ec26875 100644 --- a/source/RobotAPI/libraries/skills/core/Skill.cpp +++ b/source/RobotAPI/libraries/skills/core/Skill.cpp @@ -14,16 +14,76 @@ namespace armarx void Skill::installConditionWithCallback(std::function<bool()>&& f, std::function<void()>&& cb) { - std::lock_guard l(conditionCallbacksMutex); + std::scoped_lock l(conditionCallbacksMutex); conditionCallbacks.push_back({f, cb}); } + std::optional<TerminatedSkillStatusUpdate> + Skill::callSubskill(const SkillProxy& prx, const aron::data::DictPtr& params) + { + auto eid = callSubskillAsync(prx, params); + auto ret = prx.join(eid); + ARMARX_IMPORTANT << "FINISHED SUBSKILL. RET IS " << ret.has_value(); + return ret; + } + + skills::SkillExecutionID + Skill::callSubskillAsync(const skills::SkillProxy& prx, const aron::data::DictPtr& params) + { + std::unique_lock l(subskillsMutex); + + std::string executorHistory = this->executorName + "->" + getSkillId().toString(); + auto eid = prx.executeSkillAsync(executorHistory, params); + this->subskills.push_back(eid); + return eid; + } + + void + Skill::updateParameters(const aron::data::DictPtr& d) + { + std::scoped_lock l(this->parametersMutex); + if (this->parameters == nullptr) + { + // set params as there has been no update before. + this->parameters = d; + } + else + { + // merge params into existing. Note that this may update already set params. + this->parameters->mergeAndReplaceCopy(d); + } + } + + void + Skill::setParameters(const aron::data::DictPtr& d) + { + // we only set the params if the skill is not already running + if (running or exiting or finished) + { + return; + } + + std::scoped_lock l(this->parametersMutex); + this->parameters = d; + } + + aron::data::DictPtr + Skill::getParameters() const + { + std::scoped_lock l(this->parametersMutex); + return this->parameters; + } + Skill::InitResult Skill::_init() { //ARMARX_IMPORTANT << "Initializing skill '" << description.skillName << "'"; this->initializing = true; this->constructing = false; + this->preparing = false; + this->running = false; + this->exiting = false; + this->finished = false; // install timeout condition installConditionWithCallback( @@ -36,7 +96,7 @@ namespace armarx [&]() { armarx::core::time::Metronome metronome(conditionCheckingThreadFrequency); - while (constructing or initializing or preparing or + while (initializing or preparing or running) // when the skill ends/aborts this variable will be set to false { { @@ -51,13 +111,15 @@ namespace armarx } } } + const auto sleepDuration = metronome.waitForNextTick(); if (not sleepDuration.isPositive()) { - ARMARX_WARNING - << deactivateSpam() << "PeriodicSkill: execution took too long (" - << -sleepDuration << " vs " - << conditionCheckingThreadFrequency.toCycleDuration() << ")"; + ARMARX_WARNING << deactivateSpam() + << "ConditionCheckingThread: execution took too long (" + << -sleepDuration << " vs " + << conditionCheckingThreadFrequency.toCycleDuration() + << ")"; } } }); @@ -69,6 +131,10 @@ namespace armarx { this->preparing = true; this->initializing = false; + this->constructing = false; + this->running = false; + this->exiting = false; + this->finished = false; if (shouldSkillTerminate()) { @@ -94,7 +160,11 @@ namespace armarx Skill::_main() { this->running = true; + this->initializing = false; + this->constructing = false; this->preparing = false; + this->exiting = false; + this->finished = false; return {.status = TerminatedSkillStatus::Succeeded}; } @@ -102,16 +172,21 @@ namespace armarx Skill::_exit() { // ARMARX_IMPORTANT << "Exiting Skill '" << description.skillName << "'"; + this->exiting = true; this->running = false; this->initializing = false; this->constructing = false; this->preparing = false; + this->finished = false; if (conditionCheckingThread.joinable()) { conditionCheckingThread.join(); } exited = armarx::core::time::DateTime::Now(); + + this->finished = true; + this->exiting = false; return {.status = TerminatedSkillStatus::Succeeded}; } @@ -152,13 +227,6 @@ namespace armarx return {.status = skills::mergeSkillStatuseses(_res.status, res.status)}; } - void - Skill::notifyTimeoutReached() - { - timeoutReached = true; - onTimeoutReached(); - } - void Skill::throwIfSkillShouldTerminate(const std::function<void()>& do_before, const std::string& abortedMessage) @@ -224,10 +292,21 @@ namespace armarx void Skill::notifySkillToStop() { + std::scoped_lock l(subskillsMutex); stopped = true; + _onStopRequested(); onStopRequested(); } + void + Skill::notifyTimeoutReached() + { + std::scoped_lock l(subskillsMutex); + timeoutReached = true; + _onTimeoutReached(); + onTimeoutReached(); + } + bool Skill::shouldSkillTerminate() const { @@ -235,6 +314,32 @@ namespace armarx } // condition effects + void + Skill::_onTimeoutReached() + { + if (!manager) + { + return; + } + for (const auto& execId : subskills) + { + manager->abortSkillAsync(execId.toManagerIce()); + } + } + + void + Skill::_onStopRequested() + { + if (!manager) + { + return; + } + for (const auto& execId : subskills) + { + manager->abortSkillAsync(execId.toManagerIce()); + } + } + void Skill::onTimeoutReached() { diff --git a/source/RobotAPI/libraries/skills/core/Skill.h b/source/RobotAPI/libraries/skills/core/Skill.h index ff3cf1e823a9bdcf2e8b4db8b68232a1b4a34cbe..5ec80e77d9956e8516ae84ffec16313bab0c9206 100644 --- a/source/RobotAPI/libraries/skills/core/Skill.h +++ b/source/RobotAPI/libraries/skills/core/Skill.h @@ -19,6 +19,7 @@ #include "SkillDescription.h" #include "SkillID.h" #include "SkillPreparationInput.h" +#include "SkillProxy.h" #include "SkillStatusUpdate.h" #include "error/Exception.h" @@ -32,95 +33,112 @@ namespace armarx using CallbackT = std::function<void(const SkillStatus s, const armarx::aron::data::DictPtr&)>; + /// A result struct for skill initialization struct InitResult { TerminatedSkillStatus status; }; + /// A result struct for skill preparing struct PrepareResult { ActiveOrTerminatedSkillStatus status; }; + /// A result struct for th main method of a skill struct MainResult { TerminatedSkillStatus status; aron::data::DictPtr data = nullptr; }; + /// A result struct for skill exit function struct ExitResult { TerminatedSkillStatus status; }; + /// We completely remove the default constructor! A skill without a desciption cannot exist Skill() = delete; + + /// Constructor of a skill for inheritance. Every skill must have a skill description Skill(const SkillDescription&); - virtual ~Skill() = default; - /// The id of the skill (combination of provider and name must be unique). + /// Virtual destructor of a skill + virtual ~Skill() + { + //ARMARX_IMPORTANT << "DESTROY SKILL " << getSkillId(); + } + + /// Get the id of the skill SkillID getSkillId() const { return description.skillId; } + /// Get the description of a skill SkillDescription getSkillDescription() const { return description; } + /// Set the provider id of the description of the skill. + /// This method is called when creating a skill in a skill provider void setProviderId(const skills::ProviderID& pid) { description.skillId.providerId = pid; } + void + setCallback(const CallbackT& callback) + { + this->callback = callback; + } + + void + setManager(const manager::dti::SkillManagerInterfacePrx& manager) + { + this->manager = manager; + } + + void + setExecutorName(const std::string& executorName) + { + this->executorName = executorName; + } + + /// Prepare a skill once. This method is called in a loop as long as it returns RUNNING + /// If the loop does not terminate with SUCCEDED the skill execution is failed. PrepareResult prepareSkill(); + /// Initialization of a skill. Called directly after construction. + /// If this method does not return SUCCEEDED the skill execution is failed. InitResult initSkill(); + /// Main method of a skill. MainResult mainOfSkill(); + /// Exit method of a skill. It is guaranteed that exit is always called + /// (unless there is a segfault or similar) ExitResult exitSkill(); - // Condition listeners - // used to notify the skill from extern to stop + /// Notify the skill from extern to stop void notifySkillToStop(); - // returns whether the skill should terminate as soon as possible + /// Returns whether the skill should terminate as soon as possible bool shouldSkillTerminate() const; - // merge parameters to the local parameters of the skill - void - updateParameters(const aron::data::DictPtr& d) - { - std::scoped_lock l(this->parametersMutex); - if (this->parameters == nullptr) - { - this->parameters = d; - } - else - { - this->parameters->mergeAndReplaceCopy(d); - } - } + /// Merge parameters to the local parameters of the skill + void updateParameters(const aron::data::DictPtr& d); - // hard set the parameters, ignoring everything that has been set or merged before - void - setParameters(const aron::data::DictPtr& d) - { - std::scoped_lock l(this->parametersMutex); - this->parameters = d; - } + /// Hard set the parameters, ignoring everything that has been set or merged before + void setParameters(const aron::data::DictPtr& d); - // get the parameters - aron::data::DictPtr - getParameters() const - { - std::scoped_lock l(this->parametersMutex); - return this->parameters; - } + /// Get the parameters of a skill that have been set so far + aron::data::DictPtr getParameters() const; protected: void throwIfSkillShouldTerminate(const std::string& abortedMessage = ""); @@ -134,12 +152,17 @@ namespace armarx // fires if the skill reaches timeout void notifyTimeoutReached(); + private: // helper methods to do all the static initialization stuff InitResult _init(); PrepareResult _prepare(); MainResult _main(); ExitResult _exit(); + void _onTimeoutReached(); + void _onStopRequested(); + + protected: /// Override this method with the actual implementation. virtual InitResult init(); @@ -152,7 +175,7 @@ namespace armarx /// Override this method with the actual implementation. virtual ExitResult exit(); - private: + protected: /// Override these methods if you want to do something special when notification comes virtual void onTimeoutReached(); virtual void onStopRequested(); @@ -162,35 +185,42 @@ namespace armarx void installConditionWithCallback(std::function<bool()>&& f, std::function<void()>&& cb); + /// call a subskill and block until the subskill terminates. + /// If you call a subskill this way it will be stopped if the current skill stops. + std::optional<TerminatedSkillStatusUpdate> + callSubskill(const skills::SkillProxy& prx, const aron::data::DictPtr& = nullptr); + + /// Similar to callSubskill but non-blocking + skills::SkillExecutionID callSubskillAsync(const skills::SkillProxy& prx, + const aron::data::DictPtr& = nullptr); + public: // running params armarx::core::time::DateTime started = armarx::core::time::DateTime::Now(); armarx::core::time::DateTime exited = armarx::core::time::DateTime::Invalid(); - // parameterization. Will be set from implementation wrapper. - // const params after initialization!! - CallbackT callback; + protected: + // parameterization. Will be set from implementation wrapper. No getters available + // const after construction!! + CallbackT callback = CallbackT(); manager::dti::SkillManagerInterfacePrx manager = nullptr; - std::string executorName = "INVALID EXECUTOR NAME"; + std::string executorName = ""; protected: - // non-const params + // non-const params. Const after preparation. Getters are available mutable std::mutex parametersMutex; armarx::aron::data::DictPtr parameters = nullptr; // The descripion of the skill SkillDescription description; - // active conditions. First is condition (bool return func) - mutable std::mutex conditionCallbacksMutex; - std::vector<std::pair<std::function<bool()>, std::function<void()>>> - conditionCallbacks = {}; - - // Status variables. + // Status variables std::atomic_bool constructing = true; std::atomic_bool initializing = false; std::atomic_bool preparing = false; std::atomic_bool running = false; + std::atomic_bool exiting = false; + std::atomic_bool finished = false; // Conditionals to indicate that an event has occured. Use conditions this way std::atomic_bool stopped = false; @@ -198,8 +228,16 @@ namespace armarx private: - std::thread conditionCheckingThread; // A therad that checks the conditions frequently + // active conditions. First is condition (bool return func) + mutable std::mutex conditionCallbacksMutex; + std::vector<std::pair<std::function<bool()>, std::function<void()>>> + conditionCallbacks = {}; + + std::thread conditionCheckingThread; // A thread that checks the conditions frequently armarx::Frequency conditionCheckingThreadFrequency = armarx::Frequency::Hertz(20); + + mutable std::mutex subskillsMutex; + std::vector<skills::SkillExecutionID> subskills; }; template <class T> diff --git a/source/RobotAPI/libraries/skills/core/SkillExecutionID.h b/source/RobotAPI/libraries/skills/core/SkillExecutionID.h index f639b4b4e1f619fa7347f462395714e8b864e005..6d9975bb469eead6760e260ead049dec5e1ab4ea 100644 --- a/source/RobotAPI/libraries/skills/core/SkillExecutionID.h +++ b/source/RobotAPI/libraries/skills/core/SkillExecutionID.h @@ -18,10 +18,6 @@ namespace armarx { struct SkillExecutionID { - SkillID skillId; - std::string executorName; - armarx::core::time::DateTime executionStartedTime; - SkillExecutionID() = default; SkillExecutionID(const SkillID&, const std::string& executorName, @@ -30,26 +26,26 @@ namespace armarx bool operator==(const SkillExecutionID& other) const { - if (skillId != other.skillId) - { - return false; - } - if (executionStartedTime != other.executionStartedTime) - { - return false; - } - if (executorName != other.executorName) - { - return false; - } - return true; + return this->toString() == other.toString(); } bool operator<(const SkillExecutionID& other) const { - // We explicitly do not compare skillIds as we ONLY want to bring the executionids in some (temporal) order - return executionStartedTime < other.executionStartedTime; + return this->toString() < other.toString(); + } + + bool + operator<=(const SkillExecutionID& other) const + { + return this->toString() <= other.toString(); + } + + std::string + toString() const + { + return skillId.toString() + " requested by " + executorName + " at " + + executionStartedTime.toDateTimeString(); } skills::manager::dto::SkillExecutionID toManagerIce() const; @@ -60,6 +56,11 @@ namespace armarx static SkillExecutionID FromIce(const skills::provider::dto::SkillExecutionID&, const std::optional<skills::ProviderID>& providerName); + + + SkillID skillId; + std::string executorName; + armarx::core::time::DateTime executionStartedTime; }; } // namespace skills diff --git a/source/RobotAPI/libraries/skills/core/SkillProxy.cpp b/source/RobotAPI/libraries/skills/core/SkillProxy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f73c43dfd152c756f80e5ac3e83e901fe59e8ee1 --- /dev/null +++ b/source/RobotAPI/libraries/skills/core/SkillProxy.cpp @@ -0,0 +1,118 @@ +#include "SkillProxy.h" + +#include <chrono> +#include <thread> + +namespace armarx +{ + namespace skills + { + SkillProxy::SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, + const SkillID& skillId) : + manager(manager) + { + ARMARX_CHECK_NOT_NULL(manager); + skillDescription = SkillDescription::FromIce( + manager->getSkillDescription(skillId.toManagerIce()).value()); + ARMARX_CHECK(skillDescription.skillId.isFullySpecified()); + } + + SkillProxy::SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, + const SkillDescription& skillDesc) : + manager(manager), skillDescription(skillDesc) + { + ARMARX_CHECK_NOT_NULL(manager); + ARMARX_CHECK(skillDesc.skillId.isFullySpecified()); + } + + SkillDescription + SkillProxy::getSkillDescription() const + { + return skillDescription; + } + + SkillID + SkillProxy::getSkillId() const + { + return skillDescription.skillId; + } + + TerminatedSkillStatusUpdate + SkillProxy::executeSkill(const std::string& executorName, + const aron::data::DictPtr& params) const + { + ARMARX_CHECK_NOT_NULL(manager); + skills::manager::dto::SkillExecutionRequest req; + req.executorName = executorName; + req.parameters = aron::data::Dict::ToAronDictDTO(params); + req.skillId = skillDescription.skillId.toManagerIce(); + + auto terminatingUpdateIce = manager->executeSkill(req); + return TerminatedSkillStatusUpdate::FromIce(terminatingUpdateIce); + } + + SkillExecutionID + SkillProxy::executeSkillAsync(const std::string& executorName, + const aron::data::DictPtr& params) const + { + ARMARX_CHECK_NOT_NULL(manager); + skills::manager::dto::SkillExecutionRequest req; + req.executorName = executorName; + req.parameters = aron::data::Dict::ToAronDictDTO(params); + req.skillId = skillDescription.skillId.toManagerIce(); + + auto execIdIce = manager->executeSkillAsync(req); + return SkillExecutionID::FromIce(execIdIce); + } + + std::optional<TerminatedSkillStatusUpdate> + SkillProxy::join(const SkillExecutionID& executionId) const + { + ARMARX_CHECK_NOT_NULL(manager); + auto s = this->manager->getSkillExecutionStatus(executionId.toManagerIce()); + + while (s) + { + auto statusUpdate = skills::SkillStatusUpdate::FromIce(*s); + + if (statusUpdate.hasBeenTerminated()) + { + break; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + s = this->manager->getSkillExecutionStatus(executionId.toManagerIce()); + } + + if (!s) + { + // Either the manager already removed the result (then it is unknown) or the execution id does not exist. + return std::nullopt; + } + + return TerminatedSkillStatusUpdate::FromIce(*s); + } + + bool + SkillProxy::abortSkill(const SkillExecutionID& id) const + { + ARMARX_CHECK_NOT_NULL(manager); + auto r = manager->abortSkill(id.toManagerIce()); + return r.success; + } + + bool + SkillProxy::abortSkillAsync(const SkillExecutionID& id) const + { + ARMARX_CHECK_NOT_NULL(manager); + auto r = manager->abortSkillAsync(id.toManagerIce()); + return r.success; + } + + aron::data::DictPtr + SkillProxy::getRootProfileParameters() const + { + return skillDescription.rootProfileDefaults; + } + } // namespace skills +} // namespace armarx diff --git a/source/RobotAPI/libraries/skills/core/SkillProxy.h b/source/RobotAPI/libraries/skills/core/SkillProxy.h new file mode 100644 index 0000000000000000000000000000000000000000..e20ca01a0bc4b6de877e34a3ad6bc0edcbaa4689 --- /dev/null +++ b/source/RobotAPI/libraries/skills/core/SkillProxy.h @@ -0,0 +1,63 @@ +#pragma once + +#include <RobotAPI/libraries/skills/core/SkillDescription.h> +#include <RobotAPI/libraries/skills/core/SkillStatusUpdate.h> + +namespace armarx +{ + namespace skills + { + /* Manages the remote execution of a skill and converts the ice types */ + class SkillProxy : public armarx::Logging + { + public: + /// We remove the default constructor as every skill proxy requires a manager + SkillProxy() = delete; + + /// set the skill proxy using a skillId. Queries the manager to get the description. + SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, + const SkillID& skillId); + + /// set the proxy using a skill description + SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, + const SkillDescription& skillDesc); + + /// copy ctor + SkillProxy(const SkillProxy& o) = default; + + /// get the skill description + SkillDescription getSkillDescription() const; + + /// get the skill id from the skill description + SkillID getSkillId() const; + + // Provide a similar API as the skillprovider + /// execute a skill and block until skill terminates + TerminatedSkillStatusUpdate + executeSkill(const std::string& executorName, + const aron::data::DictPtr& params = nullptr) const; + + /// execute a skill. Do not block during execution + SkillExecutionID executeSkillAsync(const std::string& executorName, + const aron::data::DictPtr& params = nullptr) const; + + /// poll execution status and block until its null or terminated + std::optional<TerminatedSkillStatusUpdate> + join(const SkillExecutionID& executionId) const; + + /// ask skill to abort ASAP. Blocks until skill stopped + bool abortSkill(const SkillExecutionID& executionId) const; + + /// ask skill to abort ASAP + bool abortSkillAsync(const SkillExecutionID& executionId) const; + + // Utiliy methods + /// get the default parameters of the skill. TODO: Skill profiles in memory! + aron::data::DictPtr getRootProfileParameters() const; + + protected: + manager::dti::SkillManagerInterfacePrx manager; + SkillDescription skillDescription; + }; + } // namespace skills +} // namespace armarx diff --git a/source/RobotAPI/libraries/skills/manager/SkillManagerComponentPlugin.cpp b/source/RobotAPI/libraries/skills/manager/SkillManagerComponentPlugin.cpp index 4b7cdec1b588ee65f80d188ccd786623b5e9665c..233aa841b730c6f096393634d417bd41eccd7e93 100644 --- a/source/RobotAPI/libraries/skills/manager/SkillManagerComponentPlugin.cpp +++ b/source/RobotAPI/libraries/skills/manager/SkillManagerComponentPlugin.cpp @@ -580,9 +580,6 @@ namespace armarx this->plugin->removeProvider(i); } - using SkillProviderInterfacePrxMap = - std::map<std::string, skills::provider::dti::SkillProviderInterfacePrx>; - skills::manager::dto::SkillStatusUpdate SkillManagerComponentPluginUser::executeSkill( const skills::manager::dto::SkillExecutionRequest& info, diff --git a/source/RobotAPI/libraries/skills/provider/CMakeLists.txt b/source/RobotAPI/libraries/skills/provider/CMakeLists.txt index 5ccff912099114655eb96dd46c9e61966f677df7..fba16f3e70fe8849748369529001df24644ffc87 100644 --- a/source/RobotAPI/libraries/skills/provider/CMakeLists.txt +++ b/source/RobotAPI/libraries/skills/provider/CMakeLists.txt @@ -21,7 +21,6 @@ armarx_add_library( LambdaSkill.cpp SimpleSkill.cpp SimpleSpecializedSkill.cpp - SkillProxy.cpp SpecializedSkillProxy.cpp PeriodicSkill.cpp SpecializedSkill.cpp @@ -36,7 +35,6 @@ armarx_add_library( LambdaSkill.h SimpleSkill.h SimpleSpecializedSkill.h - SkillProxy.h SpecializedSkillProxy.h PeriodicSkill.h SpecializedSkill.h diff --git a/source/RobotAPI/libraries/skills/provider/PeriodicSkill.cpp b/source/RobotAPI/libraries/skills/provider/PeriodicSkill.cpp index 02ad14bc1e789ad62c9d13d9ece923f35fac7e15..197159cb79a4401ade9895b14936fc65a8bf3996 100644 --- a/source/RobotAPI/libraries/skills/provider/PeriodicSkill.cpp +++ b/source/RobotAPI/libraries/skills/provider/PeriodicSkill.cpp @@ -47,8 +47,10 @@ namespace armarx::skills { armarx::core::time::Metronome metronome(frequency); - while (not Skill::shouldSkillTerminate()) + while (true) { + this->throwIfSkillShouldTerminate(); + const auto res = stepOfSkill(); switch (res.status) { @@ -56,11 +58,11 @@ namespace armarx::skills // nothing to do here break; case ActiveOrTerminatedSkillStatus::Aborted: - return {TerminatedSkillStatus::Aborted, res.data}; + return MakeAbortedResult(); case ActiveOrTerminatedSkillStatus::Succeeded: - return {TerminatedSkillStatus::Succeeded, res.data}; + return MakeSucceededResult(res.data); case ActiveOrTerminatedSkillStatus::Failed: - return {TerminatedSkillStatus::Failed, res.data}; + return MakeFailedResult(); } const auto sleepDuration = metronome.waitForNextTick(); @@ -72,18 +74,8 @@ namespace armarx::skills } } - if (stopped) - { - return {TerminatedSkillStatus::Aborted, nullptr}; - } - - if (timeoutReached) - { - ARMARX_WARNING << "The skill " << getSkillId().toString() << " reached timeout!"; - return {TerminatedSkillStatus::Failed, nullptr}; - } - - throw skills::error::SkillException(__PRETTY_FUNCTION__, "Should not happen!"); + // never happens + return MakeSucceededResult(); } PeriodicSkill::StepResult diff --git a/source/RobotAPI/libraries/skills/provider/SkillProviderComponentPlugin.cpp b/source/RobotAPI/libraries/skills/provider/SkillProviderComponentPlugin.cpp index 1a5b0b0d19877cd00da786cc82fed4df690efecc..e5c169c04ff1ea7dc1ed5cf7ed59f136f1406b9c 100644 --- a/source/RobotAPI/libraries/skills/provider/SkillProviderComponentPlugin.cpp +++ b/source/RobotAPI/libraries/skills/provider/SkillProviderComponentPlugin.cpp @@ -120,7 +120,7 @@ namespace armarx::plugins ARMARX_CHECK(execId.skillId.isSkillSpecified()); const std::unique_lock l(skillExecutionsMutex); - if (skillExecutions.find(execId) != skillExecutions.end()) + if (skillExecutions.find(execId) == skillExecutions.end()) { ARMARX_WARNING << "Skill execution for skill '" + execId.skillId.toString() + "' not found!"; @@ -276,6 +276,21 @@ namespace armarx::plugins }); } + // wait until skill is constructed. This assures, that a status update exists. + while (true) + { + { + std::scoped_lock l(wrapper->skillStatusesMutex); + + if (wrapper->statusUpdate.hasBeenConstructed()) + { + break; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + } + return executionId; } @@ -320,11 +335,21 @@ namespace armarx::plugins return false; } - it->second.stopSkill(); + auto& runtime = it->second; + runtime.stopSkill(); - if (auto it = skillExecutions.find(executionId); it != skillExecutions.end()) + while (true) { - skillExecutions.erase(it); + { + std::scoped_lock l(runtime.skillStatusesMutex); + auto status = runtime.statusUpdate; + + if (status.hasBeenTerminated()) + { + break; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(20)); } return true; @@ -344,7 +369,7 @@ namespace armarx::plugins return false; } - std::thread([&]() { it->second.stopSkill(); }); + it->second.stopSkill(); return true; } diff --git a/source/RobotAPI/libraries/skills/provider/SkillProxy.cpp b/source/RobotAPI/libraries/skills/provider/SkillProxy.cpp deleted file mode 100644 index 50758fde754abe7434b677c53c54ff28a321430c..0000000000000000000000000000000000000000 --- a/source/RobotAPI/libraries/skills/provider/SkillProxy.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "SkillProxy.h" - -namespace armarx -{ - namespace skills - { - SkillProxy::SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, - const SkillID& skillId) : - manager(manager), - skillDescription(SkillDescription::FromIce( - manager->getSkillDescription(skillId.toManagerIce()).value())) - { - } - - SkillProxy::SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, - const SkillDescription& skillDesc) : - manager(manager), skillDescription(skillDesc) - { - ARMARX_CHECK(skillDesc.skillId.isFullySpecified()); - } - - TerminatedSkillStatusUpdate - SkillProxy::executeSkill(const std::string& executorName, const aron::data::DictPtr& params) - { - skills::manager::dto::SkillExecutionRequest req; - req.executorName = executorName; - req.parameters = params->toAronDictDTO(); - req.skillId = skillDescription.skillId.toManagerIce(); - - auto terminatingUpdateIce = manager->executeSkill(req); - return TerminatedSkillStatusUpdate::FromIce(terminatingUpdateIce); - } - - SkillExecutionID - SkillProxy::executeSkillAsync(const std::string& executorName, - const aron::data::DictPtr& params) - { - skills::manager::dto::SkillExecutionRequest req; - req.executorName = executorName; - req.parameters = params->toAronDictDTO(); - req.skillId = skillDescription.skillId.toManagerIce(); - - auto execReqIce = manager->executeSkillAsync(req); - return SkillExecutionID::FromIce(execReqIce); - } - - bool - SkillProxy::abortSkill(const SkillExecutionID& id) - { - auto r = manager->abortSkill(id.toManagerIce()); - return r.success; - } - - bool - SkillProxy::abortSkillAsync(const SkillExecutionID& id) - { - auto r = manager->abortSkillAsync(id.toManagerIce()); - return r.success; - } - - aron::data::DictPtr - SkillProxy::getDefaultParameters(const std::string& profileName) - { - if (profileName == "root") - { - return skillDescription.rootProfileDefaults; - } - // TODO @fabianPK - return nullptr; - } - } // namespace skills -} // namespace armarx diff --git a/source/RobotAPI/libraries/skills/provider/SkillProxy.h b/source/RobotAPI/libraries/skills/provider/SkillProxy.h deleted file mode 100644 index 8f0fb34be58e7d20fec5778795ead2a34ab80a3c..0000000000000000000000000000000000000000 --- a/source/RobotAPI/libraries/skills/provider/SkillProxy.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include <RobotAPI/libraries/skills/core/Skill.h> - -namespace armarx -{ - namespace skills - { - /* Manages the remote execution of a skill and converts the ice types */ - class SkillProxy : public armarx::Logging - { - public: - SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, - const SkillID& skillId); - SkillProxy(const manager::dti::SkillManagerInterfacePrx& manager, - const SkillDescription& skillDesc); - - // Provide a similar API as the skillprovider - TerminatedSkillStatusUpdate executeSkill(const std::string& executorName, - const aron::data::DictPtr& params = nullptr); - - SkillExecutionID executeSkillAsync(const std::string& executorName, - const aron::data::DictPtr& params = nullptr); - - bool abortSkill(const SkillExecutionID& executorName); - - bool abortSkillAsync(const SkillExecutionID& executorName); - - // Utiliy methods - aron::data::DictPtr getDefaultParameters(const std::string& profileName = "root"); - - protected: - manager::dti::SkillManagerInterfacePrx manager; - SkillDescription skillDescription; - }; - } // namespace skills -} // namespace armarx diff --git a/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.cpp b/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.cpp index 1074f549947ce61b4b96e3f698caab84a5149488..4f372c5f55247795f91d956f92d84eb6ac35ee77 100644 --- a/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.cpp +++ b/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.cpp @@ -1,4 +1,4 @@ -#include "SkillProxy.h" +#include "SpecializedSkillProxy.h" namespace armarx { diff --git a/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.h b/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.h index 37ecd5a7771f09f99d11c0513cdcf95fe7c5d8e0..4dd53171c4cf17c7298cf9ba59c364ddcf81eb8e 100644 --- a/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.h +++ b/source/RobotAPI/libraries/skills/provider/SpecializedSkillProxy.h @@ -1,6 +1,6 @@ #pragma once -#include "SkillProxy.h" +#include <RobotAPI/libraries/skills/core/SkillProxy.h> namespace armarx { @@ -27,9 +27,9 @@ namespace armarx // Utiliy methods AronT - getDefaultParameters(const std::string& profileName = "root") + getRootProfileParameters() { - auto dict = SkillProxy::getDefaultParameters(profileName); + auto dict = SkillProxy::getRootProfileParameters(); if (dict) { return AronT::FromAron(dict); diff --git a/source/RobotAPI/libraries/skills/provider/detail/SkillImplementationWrapper.cpp b/source/RobotAPI/libraries/skills/provider/detail/SkillImplementationWrapper.cpp index 4ac4757e2323a2d26adc6921e293b9062e0b449c..5c20567a2592e8e53c547e8dad8073fe50e779b5 100644 --- a/source/RobotAPI/libraries/skills/provider/detail/SkillImplementationWrapper.cpp +++ b/source/RobotAPI/libraries/skills/provider/detail/SkillImplementationWrapper.cpp @@ -65,6 +65,8 @@ namespace armarx const auto& skillName = skillId.skillName; const auto& providerId = *skillId.providerId; const auto& executorName = statusUpdate.executionId.executorName; + const auto& manager = + skills::manager::dti::SkillManagerInterfacePrx::checkedCast(callback_interface); ARMARX_INFO_S << "Executing skill: " << skillName; @@ -119,12 +121,10 @@ namespace armarx updateStatus(SkillStatus::Constructing); this->skill = this->factory.createSkill(providerId); - this->skill->executorName = executorName; - this->skill->manager = skills::manager::dti::SkillManagerInterfacePrx::checkedCast( - callback_interface); // ugly. Get managerPrx from manager! - - this->skill->callback = [&](const SkillStatus s, const armarx::aron::data::DictPtr& d) - { updateStatus(s, d); }; + this->skill->setExecutorName(executorName); + this->skill->setManager(manager); + this->skill->setCallback([&](const SkillStatus s, const armarx::aron::data::DictPtr& d) + { updateStatus(s, d); }); // set initial parameters that were attached to the execution request (only add as we are not sure whether some updates already arrived) skill->updateParameters(initial_aron_params);