diff --git a/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.cpp b/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.cpp index 786ca15e36b15d16ebc008c165d19ffd132d6ce9..358954ae904939c74c8c9c943e7dc00e2a6ef4e2 100644 --- a/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.cpp +++ b/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.cpp @@ -2,20 +2,27 @@ #include <cstring> #include <filesystem> +#include <map> #include <optional> #include <string> #include <vector> +#include <sys/inotify.h> +#include <sys/types.h> +#include <unistd.h> + #include <SimoxUtility/algorithm/string/string_tools.h> -#include <VirtualRobot/Grasping/Grasp.h> -#include <VirtualRobot/Grasping/GraspSet.h> -#include <VirtualRobot/ManipulationObject.h> +#include <VirtualRobot/Grasping/Grasp.h> // IWYU pragma: keep +#include <VirtualRobot/Grasping/GraspSet.h> // IWYU pragma: keep +#include <VirtualRobot/ManipulationObject.h> // IWYU pragma: keep #include <VirtualRobot/VirtualRobot.h> #include <VirtualRobot/XML/ObjectIO.h> #include <ArmarXCore/core/exceptions/LocalException.h> #include <ArmarXCore/core/exceptions/local/ExpressionException.h> #include <ArmarXCore/core/logging/Logging.h> +#include <ArmarXCore/core/services/tasks/TaskUtil.h> +#include <ArmarXCore/core/time/Clock.h> #include <ArmarXCore/core/util/StringHelpers.h> #include "RobotAPI/libraries/ArmarXObjects/ObjectFinder.h" @@ -26,6 +33,8 @@ #include <RobotAPI/libraries/armem/server/MemoryToIceAdapter.h> #include <RobotAPI/libraries/armem_grasping/aron/KnownGraspCandidate.aron.generated.h> +#include <linux/limits.h> + namespace armarx::armem::grasping::segment { @@ -41,183 +50,189 @@ namespace armarx::armem::grasping::segment Base::init(); loadMemory(); + + installFileWatcher(); } std::optional<arondto::KnownGraspInfo> KnownGraspProviderSegment::knownGraspInfoFromObjectInfo(const ObjectInfo& info) { - std::string objectClassName = info.className(); - auto fileLocInfo = info.file(".xml", "_Grasps"); - std::filesystem::path graspFilePath = fileLocInfo.absolutePath; + return knownGraspInfoFromFile(GraspFileInfo::FromObjectInfo(info)); + } + + std::optional<arondto::KnownGraspInfo> + KnownGraspProviderSegment::knownGraspInfoFromFile(const GraspFileInfo& graspFileInfo) + { - if (std::filesystem::is_regular_file(graspFilePath)) + const std::filesystem::path graspFilePath = graspFileInfo.fileLocInfo.absolutePath; + if (not std::filesystem::is_regular_file(graspFilePath)) { - ARMARX_INFO << "loading " << graspFilePath; - try - { - auto manipulationObject = - VirtualRobot::ObjectIO::loadManipulationObject(graspFilePath); + return std::nullopt; // file does not exist + } - if (manipulationObject == nullptr) - { - ARMARX_WARNING << "Invalid file content: " << graspFilePath; - return std::nullopt; - } + ARMARX_INFO << "loading " << graspFilePath; + try + { + const std::string objectClassName = graspFileInfo.objectId.className(); - arondto::KnownGraspInfo ret; - ret.correspondingObject.memoryName = "Object"; - ret.correspondingObject.coreSegmentName = "Class"; - ret.correspondingObject.providerSegmentName = "PriorKnowledgeData"; - ret.correspondingObject.entityName = info.idStr(); - ret.xml.package = fileLocInfo.package; - ret.xml.path = fileLocInfo.relativePath; + auto manipulationObject = VirtualRobot::ObjectIO::loadManipulationObject(graspFilePath); - for (const VirtualRobot::GraspSetPtr& graspSet : - manipulationObject->getAllGraspSets()) - { - ARMARX_CHECK_NOT_NULL(graspSet); + if (manipulationObject == nullptr) + { + ARMARX_WARNING << "Invalid file content: " << graspFilePath; + return std::nullopt; + } - arondto::KnownGraspSet retGraspSet; + arondto::KnownGraspInfo ret; + ret.correspondingObject.memoryName = "Object"; + ret.correspondingObject.coreSegmentName = "Class"; + ret.correspondingObject.providerSegmentName = "PriorKnowledgeData"; + ret.correspondingObject.entityName = graspFileInfo.objectId.str(); + ret.xml.package = graspFileInfo.fileLocInfo.package; + ret.xml.path = graspFileInfo.fileLocInfo.relativePath; - retGraspSet.name = graspSet->getName(); + for (const VirtualRobot::GraspSetPtr& graspSet : manipulationObject->getAllGraspSets()) + { + ARMARX_CHECK_NOT_NULL(graspSet); - retGraspSet.robot = simox::alg::split(graspSet->getRobotType(), " ").front(); + arondto::KnownGraspSet retGraspSet; - retGraspSet.endeffector = graspSet->getEndEffector(); + retGraspSet.name = graspSet->getName(); - VirtualRobot::GraspSetPtr preGraspSet = [&graspSet]() - { - std::vector<VirtualRobot::GraspPtr> preGrasps; - for (const auto& grasp : graspSet->getGrasps()) - { - // check if grasp is a prepose by checking if grasp name ends with "_Prepose" - if (simox::alg::ends_with(grasp->getName(), PREPOSE_SUFFIX)) - { - preGrasps.emplace_back(grasp); - } + retGraspSet.robot = simox::alg::split(graspSet->getRobotType(), " ").front(); - // everything else is assumed to be a grasp - } + retGraspSet.endeffector = graspSet->getEndEffector(); - // create a new grasp set with the preposes - VirtualRobot::GraspSetPtr preGraspSet = graspSet->clone(); - preGraspSet->removeAllGrasps(); // just keep the EEF info etc - for (const auto& preGrasp : preGrasps) + VirtualRobot::GraspSetPtr preGraspSet = [&graspSet]() + { + std::vector<VirtualRobot::GraspPtr> preGrasps; + for (const auto& grasp : graspSet->getGrasps()) + { + // check if grasp is a prepose by checking if grasp name ends with "_Prepose" + if (simox::alg::ends_with(grasp->getName(), PREPOSE_SUFFIX)) { - preGraspSet->addGrasp(preGrasp); + preGrasps.emplace_back(grasp); } - return preGraspSet; - }(); - - - ARMARX_DEBUG << VAROUT(preGraspSet->getSize()); + // everything else is assumed to be a grasp + } - // remove all preposes from the grasp set - for (const auto& preGrasp : preGraspSet->getGrasps()) + // create a new grasp set with the preposes + VirtualRobot::GraspSetPtr preGraspSet = graspSet->clone(); + preGraspSet->removeAllGrasps(); // just keep the EEF info etc + for (const auto& preGrasp : preGrasps) { - graspSet->removeGrasp(preGrasp); + preGraspSet->addGrasp(preGrasp); } + return preGraspSet; + }(); - for (const VirtualRobot::GraspPtr& grasp : graspSet->getGrasps()) - { - ARMARX_CHECK_NOT_NULL(grasp); - arondto::KnownGrasp retGrasp; + ARMARX_DEBUG << VAROUT(preGraspSet->getSize()); - retGrasp.name = grasp->getName(); - retGrasp.quality = grasp->getQuality(); - retGrasp.creator = grasp->getCreationMethod(); - retGrasp.pose = grasp->getTransformation(); - retGrasp.prepose.reset(); + // remove all preposes from the grasp set + for (const auto& preGrasp : preGraspSet->getGrasps()) + { + graspSet->removeGrasp(preGrasp); + } - // check if grasp has a prepose by checking if grasp name ends with "_Prepose" - { - const std::string prePoseName = retGrasp.name + PREPOSE_SUFFIX; - ARMARX_DEBUG << "Checking for prepose '" << prePoseName << "' ..."; + for (const VirtualRobot::GraspPtr& grasp : graspSet->getGrasps()) + { + ARMARX_CHECK_NOT_NULL(grasp); - if (preGraspSet->hasGrasp(prePoseName)) - { - retGrasp.prepose = - preGraspSet->getGrasp(prePoseName)->getTransformation(); + arondto::KnownGrasp retGrasp; - // remove the prepose from the set as it found its match - preGraspSet->removeGrasp(preGraspSet->getGrasp(prePoseName)); + retGrasp.name = grasp->getName(); + retGrasp.quality = grasp->getQuality(); + retGrasp.creator = grasp->getCreationMethod(); + retGrasp.pose = grasp->getTransformation(); + retGrasp.prepose.reset(); + // check if grasp has a prepose by checking if grasp name ends with "_Prepose" + { + const std::string prePoseName = retGrasp.name + PREPOSE_SUFFIX; - ARMARX_DEBUG << "Found prepose `" + prePoseName + "` for grasp '" - << retGrasp.name << "' in set '" << retGraspSet.name - << "' for obj '" << objectClassName << "' with pose \n" - << retGrasp.prepose.value(); - } - } + ARMARX_DEBUG << "Checking for prepose '" << prePoseName << "' ..."; - // check if grasp has a prepose for a grasp with a specific name, e.g., "XY_Grasp" (GRASP_OPTIONAL_SUFFIX) - if ( // not retGrasp.prepose.has_value() and - simox::alg::ends_with(retGrasp.name, GRASP_OPTIONAL_SUFFIX)) + if (preGraspSet->hasGrasp(prePoseName)) { - const std::string prePoseName = - retGrasp.name.substr( - 0, retGrasp.name.size() - std::strlen(GRASP_OPTIONAL_SUFFIX)) + - PREPOSE_SUFFIX; + retGrasp.prepose = + preGraspSet->getGrasp(prePoseName)->getTransformation(); - ARMARX_DEBUG << "Checking for prepose '" << prePoseName << "' ..."; - - if (preGraspSet->hasGrasp(prePoseName)) - { - retGrasp.prepose = - preGraspSet->getGrasp(prePoseName)->getTransformation(); + // remove the prepose from the set as it found its match + preGraspSet->removeGrasp(preGraspSet->getGrasp(prePoseName)); - // remove the prepose from the set as it found its match - preGraspSet->removeGrasp(preGraspSet->getGrasp(prePoseName)); - ARMARX_DEBUG << "Found prepose `" + prePoseName + "` for grasp '" - << retGrasp.name << "' in set '" << retGraspSet.name - << "' for obj '" << objectClassName << "' with pose \n" - << retGrasp.prepose.value(); - } + ARMARX_DEBUG << "Found prepose `" + prePoseName + "` for grasp '" + << retGrasp.name << "' in set '" << retGraspSet.name + << "' for obj '" << objectClassName << "' with pose \n" + << retGrasp.prepose.value(); } - - ARMARX_DEBUG << "Found grasp '" << retGrasp.name << "' in set '" - << retGraspSet.name << "' for obj '" << objectClassName - << "' with pose \n" - << retGrasp.pose; - - retGraspSet.grasps.push_back(retGrasp); } - // Now, check if there are any preposes left in the set. This should not have happened. - if (preGraspSet->getSize() > 0) + // check if grasp has a prepose for a grasp with a specific name, e.g., "XY_Grasp" (GRASP_OPTIONAL_SUFFIX) + if ( // not retGrasp.prepose.has_value() and + simox::alg::ends_with(retGrasp.name, GRASP_OPTIONAL_SUFFIX)) { - ARMARX_WARNING << "Found " << preGraspSet->getSize() - << " preposes in the grasp set '" << retGraspSet.name - << "' for obj '" << objectClassName - << "' that do not have a corresponding grasp!"; - for (const auto& preGrasp : preGraspSet->getGrasps()) + const std::string prePoseName = + retGrasp.name.substr( + 0, retGrasp.name.size() - std::strlen(GRASP_OPTIONAL_SUFFIX)) + + PREPOSE_SUFFIX; + + ARMARX_DEBUG << "Checking for prepose '" << prePoseName << "' ..."; + + if (preGraspSet->hasGrasp(prePoseName)) { - ARMARX_WARNING << "Prepose '" << preGrasp->getName(); + retGrasp.prepose = + preGraspSet->getGrasp(prePoseName)->getTransformation(); + + // remove the prepose from the set as it found its match + preGraspSet->removeGrasp(preGraspSet->getGrasp(prePoseName)); + + ARMARX_DEBUG << "Found prepose `" + prePoseName + "` for grasp '" + << retGrasp.name << "' in set '" << retGraspSet.name + << "' for obj '" << objectClassName << "' with pose \n" + << retGrasp.prepose.value(); } } - ARMARX_CHECK(ret.graspSets.count(retGraspSet.robot + "/" + retGraspSet.name) == - 0) - << "The grasp set `" << retGraspSet.robot + "/" + retGraspSet.name - << "` was defined twice!"; + ARMARX_DEBUG << "Found grasp '" << retGrasp.name << "' in set '" + << retGraspSet.name << "' for obj '" << objectClassName + << "' with pose \n" + << retGrasp.pose; - ret.graspSets[retGraspSet.robot + "/" + retGraspSet.name] = retGraspSet; + retGraspSet.grasps.push_back(retGrasp); } - return ret; - } - catch (...) - { - ARMARX_WARNING << graspFilePath << " is not a manipulation object!" - << GetHandledExceptionString(); - return std::nullopt; + + // Now, check if there are any preposes left in the set. This should not have happened. + if (preGraspSet->getSize() > 0) + { + ARMARX_WARNING << "Found " << preGraspSet->getSize() + << " preposes in the grasp set '" << retGraspSet.name + << "' for obj '" << objectClassName + << "' that do not have a corresponding grasp!"; + for (const auto& preGrasp : preGraspSet->getGrasps()) + { + ARMARX_WARNING << "Prepose '" << preGrasp->getName(); + } + } + + ARMARX_CHECK(ret.graspSets.count(retGraspSet.robot + "/" + retGraspSet.name) == 0) + << "The grasp set `" << retGraspSet.robot + "/" + retGraspSet.name + << "` was defined twice!"; + + ret.graspSets[retGraspSet.robot + "/" + retGraspSet.name] = retGraspSet; } + return ret; + } + catch (...) + { + ARMARX_WARNING << graspFilePath << " is not a manipulation object!" + << GetHandledExceptionString(); + return std::nullopt; } - return std::nullopt; } void @@ -268,4 +283,152 @@ namespace armarx::armem::grasping::segment ARMARX_WARNING << "Got errors for commit: " << result.allErrorMessages(); } } + + void + KnownGraspProviderSegment::installFileWatcher() + { + // FIXME improve this implementation. Merge it with the code above + + // load data from prior knowledge + const ObjectFinder objectFinder; + const std::vector<ObjectInfo> infos = objectFinder.findAllObjects(false); + + const bool autoReloadSceneSnapshotsOnFileChange = true; // FIXME param + + if (autoReloadSceneSnapshotsOnFileChange) + { + int inotifyFd; + + // init inotify + { + + inotifyFd = inotify_init(); + if (inotifyFd == -1) + { + ARMARX_WARNING << "inotify_init failed"; + } + } + + std::map<int, GraspFileInfo> wds; + + // set up inotify watchers + for (const auto& info : infos) + { + const auto graspFileInfo = GraspFileInfo::FromObjectInfo(info); + + if (std::filesystem::exists(graspFileInfo.fileLocInfo.absolutePath)) + { + auto wd = inotify_add_watch( + inotifyFd, graspFileInfo.fileLocInfo.absolutePath.c_str(), IN_MODIFY); + if (wd == -1) + { + ARMARX_WARNING << "inotify_add_watch for grasp file: " + << graspFileInfo.fileLocInfo.absolutePath << "` failed."; + } + + ARMARX_INFO << "inotify_add_watch for grasp file: " + << graspFileInfo.fileLocInfo.absolutePath << "` added."; + wds.emplace(wd, graspFileInfo); + } + else + { + ARMARX_WARNING << "Faild to add file watcher for grasp file: `" + << graspFileInfo.fileLocInfo.absolutePath << "`."; + } + } + + ARMARX_INFO << "Set up " << wds.size() << " inotify events."; + + fileWatcherTask = new SimpleRunningTask<>( + [this, inotifyFd, wds]() + { + ObjectFinder objectFinder; + + constexpr std::size_t BUF_LEN = (10 * (sizeof(inotify_event) + NAME_MAX + 1)); + char buf[BUF_LEN]; + + for (;;) + { + ssize_t numRead; + + numRead = read(inotifyFd, buf, BUF_LEN); + if (numRead == 0) + { + ARMARX_WARNING << "read() from inotify fd returned 0!"; + } + + if (numRead == -1) + { + ARMARX_VERBOSE << "read"; + } + + ARMARX_DEBUG << VAROUT(numRead); + + for (char* p = buf; p < buf + numRead;) + { + auto* event = reinterpret_cast<inotify_event*>(p); + + const auto& graspFileInfo = wds.at(event->wd); + ARMARX_INFO << "File changed: " + << VAROUT(graspFileInfo.fileLocInfo.absolutePath); + + p += sizeof(struct inotify_event) + event->len; + + const bool lockMemory = true; + + // read updated grasp file and store it in the memory + { + const MemoryID providerID = + segmentPtr->id().withProviderSegmentName( + objectFinder.getPackageName()); + + // create commit from new file content + Commit commit; + { + if (auto knownGraspCandidate = + knownGraspInfoFromFile(graspFileInfo); + knownGraspCandidate) + { + EntityUpdate& update = commit.add(); + update.entityID = + providerID.withEntityName(graspFileInfo.objectId.str()); + update.entityID.timestamp = update.arrivedTime = + update.referencedTime = update.sentTime = + armarx::Clock::Now(); + + update.instancesData = {knownGraspCandidate->toAron()}; + + ARMARX_DEBUG + << VAROUT(knownGraspCandidate->graspSets.size()); + for (const auto& gs : knownGraspCandidate->graspSets) + { + ARMARX_DEBUG << VAROUT(gs.second.grasps.size()); + for (const auto& grasp : gs.second.grasps) + { + ARMARX_DEBUG << VAROUT(grasp.name); + } + } + } + } + + // now store commit in memory + if (not commit.updates.empty()) + { + ARMARX_VERBOSE << "Storing update in memory"; + + const auto result = iceMemory.commit(commit); + if (not result.allSuccess()) + { + ARMARX_WARNING << "Got errors for commit: " + << result.allErrorMessages(); + } + } + } + } + } + }); + + fileWatcherTask->start(); + } + } } // namespace armarx::armem::grasping::segment diff --git a/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.h b/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.h index 36504338a877c42932c646521bb0e9c556ab7cc4..e70f675b3a2a552f3713f8050f51f6549164a53e 100644 --- a/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.h +++ b/source/RobotAPI/libraries/armem_grasping/server/KnownGraspProviderSegment.h @@ -1,7 +1,14 @@ #pragma once -#include <RobotAPI/libraries/ArmarXObjects/ObjectFinder.h> -#include <RobotAPI/libraries/armem/server/segment/SpecializedSegment.h> +#include <filesystem> +#include <optional> + +#include <ArmarXCore/core/services/tasks/TaskUtil.h> + +#include "RobotAPI/libraries/ArmarXObjects/ObjectID.h" +#include "RobotAPI/libraries/ArmarXObjects/ObjectInfo.h" +#include "RobotAPI/libraries/armem/server/forward_declarations.h" +#include "RobotAPI/libraries/armem/server/segment/SpecializedProviderSegment.h" #include <RobotAPI/libraries/armem_grasping/aron/KnownGraspCandidate.aron.generated.h> namespace armarx::armem::grasping::segment @@ -19,6 +26,27 @@ namespace armarx::armem::grasping::segment void loadMemory(); std::optional<arondto::KnownGraspInfo> knownGraspInfoFromObjectInfo(const ObjectInfo&); + struct GraspFileInfo + { + PackageFileLocation fileLocInfo; + ObjectID objectId; + + static GraspFileInfo FromObjectInfo(const ObjectInfo& objectInfo) + { + const std::string objectClassName = objectInfo.className(); + PackageFileLocation fileLocInfo = objectInfo.file(".xml", "_Grasps"); + + return GraspFileInfo{.fileLocInfo = fileLocInfo, .objectId = objectInfo.id()}; + } + }; + + std::optional<arondto::KnownGraspInfo> + knownGraspInfoFromFile(const GraspFileInfo& graspFileInfo); + + void installFileWatcher(); + + SimpleRunningTask<>::pointer_type fileWatcherTask; + public: static const constexpr char* CORE_SEGMENT_NAME = "KnownGraspCandidate"; static const constexpr char* PROVIDER_SEGMENT_NAME = "PriorKnowledgeData";