From 61e453898bb2771f9c7bcfc0e1df51ba1baf27aa Mon Sep 17 00:00:00 2001 From: Mirko Waechter <mirko.waechter@kit.edu> Date: Mon, 8 Feb 2016 21:48:35 +0100 Subject: [PATCH] extended RobotStateComponent with a history of robot configurations, that can be used to query the robot state at any timepoint in the past (inside the size of the history) --- .../components/RobotState/CMakeLists.txt | 2 + .../RobotState/RobotStateComponent.cpp | 155 ++++++++++++++---- .../RobotState/RobotStateComponent.h | 10 +- .../RobotState/SharedRobotServants.h | 6 +- .../components/RobotState/test/CMakeLists.txt | 4 + .../RobotState/test/RobotStateTest.cpp | 103 ++++++++++++ source/RobotAPI/interface/core/RobotState.ice | 36 +++- 7 files changed, 281 insertions(+), 35 deletions(-) create mode 100644 source/RobotAPI/components/RobotState/test/CMakeLists.txt create mode 100644 source/RobotAPI/components/RobotState/test/RobotStateTest.cpp diff --git a/source/RobotAPI/components/RobotState/CMakeLists.txt b/source/RobotAPI/components/RobotState/CMakeLists.txt index 246213dd5..fcf6bab1c 100644 --- a/source/RobotAPI/components/RobotState/CMakeLists.txt +++ b/source/RobotAPI/components/RobotState/CMakeLists.txt @@ -25,3 +25,5 @@ set(LIB_HEADERS ) armarx_add_library("${LIB_NAME}" "${LIB_FILES}" "${LIB_HEADERS}" "${LIBS}") + +add_subdirectory(test) diff --git a/source/RobotAPI/components/RobotState/RobotStateComponent.cpp b/source/RobotAPI/components/RobotState/RobotStateComponent.cpp index 103191364..378150c76 100644 --- a/source/RobotAPI/components/RobotState/RobotStateComponent.cpp +++ b/source/RobotAPI/components/RobotState/RobotStateComponent.cpp @@ -38,7 +38,6 @@ using namespace Eigen; using namespace Ice; using namespace boost; -std::ofstream out_file; namespace armarx { @@ -58,6 +57,11 @@ namespace armarx void RobotStateComponent::onInitComponent() { + + historyLength = getProperty<int>("HistoryLength").getValue(); + history.clear(); + + relativeRobotFile = getProperty<std::string>("RobotFileName").getValue(); if (!ArmarXDataPath::getAbsolutePath(relativeRobotFile, robotFile)) @@ -123,7 +127,10 @@ namespace armarx this->_synchronizedPrx = SharedRobotInterfacePrx::uncheckedCast(getObjectAdapter()->addWithUUID(_sharedRobotServant)); ARMARX_INFO << "Started RobotStateComponent" << flush; - robotStateObs->setRobot(_synchronized); + if (robotStateObs) + { + robotStateObs->setRobot(_synchronized); + } } void RobotStateComponent::onDisconnectComponent() @@ -151,16 +158,118 @@ namespace armarx auto clone = this->_synchronized->clone(_synchronized->getName()); - SharedRobotInterfacePtr p = new SharedRobotServant(clone); + SharedRobotServantPtr p = new SharedRobotServant(clone); + p->setTimestamp(IceUtil::Time::microSeconds(_sharedRobotServant->getTimestamp()->timestamp)); auto result = getObjectAdapter()->addWithUUID(p); // virtal robot clone is buggy -> set global pose here p->setGlobalPose(new Pose(_synchronized->getGlobalPose())); return SharedRobotInterfacePrx::uncheckedCast(result); } + SharedRobotInterfacePrx RobotStateComponent::getRobotSnapshotAtTimestamp(Ice::Long time, const Current& current) + { + if (!_synchronized) + { + throw NotInitializedException("Shared Robot is NULL", "getRobotSnapshot"); + } + + VirtualRobot::RobotPtr clone = this->_synchronized->clone(_synchronized->getName()); + RobotStateConfig config; + if (!interpolate(time, config)) + { + ARMARX_WARNING << "Could not find or interpolate a robot state for time " << IceUtil::Time::microSeconds(time).toDateTime(); + return NULL; + } + + clone->setJointValues(config.jointMap); + clone->setGlobalPose(FramedPosePtr::dynamicCast(config.globalPose)->toEigen()); + SharedRobotServantPtr p = new SharedRobotServant(clone); + p->setTimestamp(IceUtil::Time::microSeconds(_sharedRobotServant->getTimestamp()->timestamp)); + auto result = getObjectAdapter()->addWithUUID(p); + // virtal robot clone is buggy -> set global pose here + p->setGlobalPose(new Pose(_synchronized->getGlobalPose())); + + + return SharedRobotInterfacePrx::uncheckedCast(result); + } + + + NameValueMap RobotStateComponent::getJointConfigAtTimestamp(Ice::Long timestamp, const Current&) const + { + RobotStateConfig config; + if (!interpolate(timestamp, config)) + { + ARMARX_WARNING << "Could not find or interpolate a robot state for time " << IceUtil::Time::microSeconds(timestamp).toDateTime(); + return NameValueMap(); + } + return config.jointMap; + } + + + RobotStateConfig RobotStateComponent::getRobotStateAtTimestamp(Long timestamp, const Current&) const + { + RobotStateConfig config; + if (!interpolate(timestamp, config)) + { + ARMARX_WARNING << "Could not find or interpolate a robot state for time " << IceUtil::Time::microSeconds(timestamp).toDateTime(); + return RobotStateConfig(); + } + return config; + } + + bool RobotStateComponent::interpolate(long time, RobotStateConfig& config) const + { + + auto it = history.lower_bound(time); + if (time > history.rbegin()->first) + { + config = history.rbegin()->second; + } + else if (it == history.end()) + { + return false; + } + else + { + config = it->second; + if (it->first != time) + { + // interpolate + auto prevIt = std::prev(it); + if (prevIt != history.end()) + { + long deltaT = it->first - prevIt->first; + float influenceNext = 1.0f - (float)(it->first - time) / deltaT; + float influencePrev = 1.0f - (float)(time - prevIt->first) / deltaT; + auto jointIt = prevIt->second.jointMap.begin(); + for (auto & joint : config.jointMap) + { + joint.second = joint.second * influenceNext + jointIt->second * influencePrev; + jointIt++; + } + config.timestamp = time; + ARMARX_CHECK_EXPRESSION(it->second.globalPose); + ARMARX_CHECK_EXPRESSION(prevIt->second.globalPose); + Eigen::Matrix4f globalPoseNext = FramedPosePtr::dynamicCast(it->second.globalPose)->toEigen(); + Eigen::Matrix4f globalPosePref = FramedPosePtr::dynamicCast(prevIt->second.globalPose)->toEigen(); + Eigen::Quaternionf rotNext(globalPoseNext.block<3, 3>(0, 0)); + Eigen::Quaternionf rotPrev(globalPosePref.block<3, 3>(0, 0)); + Eigen::Quaternionf rotNew = rotPrev.slerp(influenceNext, rotNext); + Eigen::Matrix4f globalPose; + globalPose.block<3, 3>(0, 0) = rotNew.toRotationMatrix(); + globalPose.block<3, 1>(0, 3) = globalPoseNext.block<3, 1>(0, 3) * influenceNext + + globalPosePref.block<3, 1>(0, 3) * influencePrev; + + } + } + } + return true; + } + void RobotStateComponent::reportJointAngles(const NameValueMap& jointAngles, bool aValueChanged, const Current& c) { // IceUtil::Time start = IceUtil::Time::now(); + auto timestamp = IceUtil::Time::now(); if (aValueChanged) { { @@ -185,9 +294,18 @@ namespace armarx if (_sharedRobotServant) { - _sharedRobotServant->setTimestamp(IceUtil::Time::now()); + _sharedRobotServant->setTimestamp(timestamp); } + history[timestamp.toMicroSeconds()] = {timestamp.toMicroSeconds(), + new FramedPose(_synchronized->getGlobalPose(), GlobalFrame, ""), + jointAngles + }; + + if (history.size() > historyLength) + { + history.erase(history.begin()); + } // ARMARX_VERBOSE << deactivateSpam(2) << "my duration: " << (IceUtil::Time::now() - start).toMicroSecondsDouble() << " ms"; @@ -204,7 +322,7 @@ namespace armarx auto packages = armarx::Application::GetProjectDependencies(); packages.push_back(Application::GetProjectName()); - for (const std::string& projectName : packages) + for (const std::string & projectName : packages) { if (projectName.empty()) { @@ -235,30 +353,6 @@ namespace armarx void RobotStateComponent::reportJointMotorTemperatures(const NameValueMap& jointMotorTemperatures, bool aValueChanged, const Current& c) {} void RobotStateComponent::reportJointStatuses(const NameStatusMap& jointStatuses, bool aValueChanged, const Current& c) {} - /*void RobotStateComponent::reportPlatformPose(float currentPlatformPositionX, float currentPlatformPositionY, float currentPlatformRotation, const Ice::Current& c) - { - if (!this->_synchronized->hasRobotNodeSet("Platform") && !this->_synchronized->hasRobotNode("Platform")) - { - return; - } - { - WriteLockPtr lock = _synchronized->getWriteLock(); - _synchronized->setJointValue("X_Platform", currentPlatformPositionX); - _synchronized->setJointValue("Y_Platform", currentPlatformPositionY); - _synchronized->setJointValue("Yaw_Platform", currentPlatformRotation); - _synchronized->applyJointValues(); - } - } - - void RobotStateComponent::reportNewTargetPose(float newPlatformPositionX, float newPlatformPositionY, float newPlatformRotation, const Ice::Current& c) - { - } - - void RobotStateComponent::reportPlatformVelocity(float currentPlatformVelocityX, float currentPlatformVelocityY, float currentPlatformVelocityRotation, const Ice::Current &c) - { - - }*/ - PropertyDefinitionsPtr RobotStateComponent::createPropertyDefinitions() { return PropertyDefinitionsPtr(new RobotStatePropertyDefinitions( @@ -285,3 +379,6 @@ namespace armarx } + + + diff --git a/source/RobotAPI/components/RobotState/RobotStateComponent.h b/source/RobotAPI/components/RobotState/RobotStateComponent.h index 617d938e6..91699e862 100644 --- a/source/RobotAPI/components/RobotState/RobotStateComponent.h +++ b/source/RobotAPI/components/RobotState/RobotStateComponent.h @@ -51,6 +51,7 @@ namespace armarx defineRequiredProperty<std::string>("RobotNodeSetName", "Set of nodes that is controlled by the KinematicUnit"); defineRequiredProperty<std::string>("RobotFileName", "Filename of VirtualRobot robot model (e.g. robot_model.xml)"); defineRequiredProperty<std::string>("AgentName", "Name of the agent for which the sensor values are provided"); + defineOptionalProperty<int>("HistoryLength", 10000, "Number of entries in the robot state history"); } }; @@ -82,7 +83,6 @@ namespace armarx { public: - /** * \return SharedRobotInterface proxy to the internal synchronized robot. */ @@ -120,6 +120,11 @@ namespace armarx return "RobotStateComponent"; } void setRobotStateObserver(RobotStateObserverPtr observer); + SharedRobotInterfacePrx getRobotSnapshotAtTimestamp(Ice::Long time, const Ice::Current& current); + NameValueMap getJointConfigAtTimestamp(Ice::Long, const Ice::Current&) const; + RobotStateConfig getRobotStateAtTimestamp(Ice::Long timestamp, const Ice::Current&) const; + + bool interpolate(long time, RobotStateConfig& config) const; protected: /** * Load and create a VirtualRobot::Robot instance from the RobotFileName @@ -150,9 +155,12 @@ namespace armarx SharedRobotInterfacePrx _synchronizedPrx; SharedRobotServantPtr _sharedRobotServant; VirtualRobot::RobotPtr _synchronized; + std::string robotFile; std::string relativeRobotFile; RobotStateObserverPtr robotStateObs; + std::map<long, RobotStateConfig> history; + size_t historyLength; }; diff --git a/source/RobotAPI/components/RobotState/SharedRobotServants.h b/source/RobotAPI/components/RobotState/SharedRobotServants.h index 845ed5465..178ca105a 100644 --- a/source/RobotAPI/components/RobotState/SharedRobotServants.h +++ b/source/RobotAPI/components/RobotState/SharedRobotServants.h @@ -49,7 +49,7 @@ namespace armarx */ class SharedRobotNodeServant : virtual public SharedRobotNodeInterface, - public SharedObjectBase + public SharedObjectBase { public: SharedRobotNodeServant(VirtualRobot::RobotNodePtr node /*,const Ice::Current & current = Ice::Current()*/); @@ -111,12 +111,12 @@ namespace armarx virtual PoseBasePtr getGlobalPose(const Ice::Current& current = Ice::Current()); NameValueMap getConfig(const Ice::Current& current = Ice::Current()); - virtual void setGlobalPose(const PoseBasePtr& pose, const Ice::Current& current); + virtual void setGlobalPose(const PoseBasePtr& pose, const Ice::Current& current = Ice::Current()); VirtualRobot::RobotPtr getRobot() const; void setTimestamp(const IceUtil::Time& updateTime); - TimestampBasePtr getTimestamp(const Ice::Current&) const; + TimestampBasePtr getTimestamp(const Ice::Current& = Ice::Current()) const; protected: VirtualRobot::RobotPtr _robot; boost::recursive_mutex m; diff --git a/source/RobotAPI/components/RobotState/test/CMakeLists.txt b/source/RobotAPI/components/RobotState/test/CMakeLists.txt new file mode 100644 index 000000000..2dfe68fa5 --- /dev/null +++ b/source/RobotAPI/components/RobotState/test/CMakeLists.txt @@ -0,0 +1,4 @@ +set(LIBS ${LIBS} RobotAPICore RobotAPIRobotStateComponent) + +armarx_add_test(RobotStateTest RobotStateTest.cpp "${LIBS}") + diff --git a/source/RobotAPI/components/RobotState/test/RobotStateTest.cpp b/source/RobotAPI/components/RobotState/test/RobotStateTest.cpp new file mode 100644 index 000000000..2c6d98938 --- /dev/null +++ b/source/RobotAPI/components/RobotState/test/RobotStateTest.cpp @@ -0,0 +1,103 @@ +/* +* 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/>. +* +* @package ArmarX::RobotAPI::Test +* @author Nils Adermann (naderman at naderman dot de) +* @date 2010 +* @copyright http://www.gnu.org/licenses/gpl-2.0.txt +* GNU General Public License +*/ + +#define BOOST_TEST_MODULE RobotAPI::FramedPose::Test +#define ARMARX_BOOST_TEST +#include <RobotAPI/Test.h> +#include <ArmarXCore/util/json/JSONObject.h> +#include <RobotAPI/libraries/core/FramedPose.h> +#include <RobotAPI/components/RobotState/RobotStateComponent.h> +#include <ArmarXCore/core/test/IceTestHelper.h> + +using namespace armarx; + +#include <ArmarXCore/core/application/Application.h> +#include <ArmarXCore/core/system/FactoryCollectionBase.h> +using namespace armarx; +class RobotStateComponentTestEnvironment +{ +public: + RobotStateComponentTestEnvironment(const std::string& testName, int registryPort = 11220, bool addObjects = true) + { + Ice::PropertiesPtr properties = Ice::createProperties(); + armarx::Application::LoadDefaultConfig(properties); + CMakePackageFinder finder("RobotAPI"); + ArmarXDataPath::addDataPaths({finder.getDataDir()}); + // If you need to set properties (optional): + properties->setProperty("ArmarX.RobotStateComponent.AgentName", "Armar3"); + properties->setProperty("ArmarX.RobotStateComponent.RobotNodeSetName", "Robot"); + properties->setProperty("ArmarX.RobotStateComponent.RobotFileName", "RobotAPI/robots/Armar3/ArmarIII.xml"); + + // The IceTestHelper starts all required Ice processes + _iceTestHelper = new IceTestHelper(registryPort, registryPort + 1); + _iceTestHelper->startEnvironment(); + + // The manager allows you to create new ArmarX components + _manager = new TestArmarXManager(testName, _iceTestHelper->getCommunicator(), properties); + if (addObjects) + { + // This is how you create components. + _rsc = _manager->createComponentAndRun<RobotStateComponent, RobotStateComponentInterfacePrx>("ArmarX", "RobotStateComponent"); + } + } + ~RobotStateComponentTestEnvironment() + { + _manager->shutdown(); + } + // In your tests, you can access your component through this proxy + RobotStateComponentInterfacePrx _rsc; + TestArmarXManagerPtr _manager; + IceTestHelperPtr _iceTestHelper; +}; +typedef boost::shared_ptr<RobotStateComponentTestEnvironment> RobotStateComponentTestEnvironmentPtr; + +BOOST_AUTO_TEST_CASE(RobotStateComponentHistoryTest) +//void func() +{ + RobotStateComponentTestEnvironment env("RSC"); + + NameValueMap joints; + while(env._manager->getObjectState("RobotStateComponent") < eManagedIceObjectStarted) + { + ARMARX_INFO_S << "Waiting"; + usleep(100000); + } + joints["Elbow L"] = 0; + env._rsc->reportJointAngles(joints, true); + auto t1 = env._rsc->getSynchronizedRobot()->getTimestamp()->timestamp; + usleep(10000); + joints["Elbow L"] = 1; + env._rsc->reportJointAngles(joints, true); + auto t2 = env._rsc->getSynchronizedRobot()->getTimestamp()->timestamp; + auto config = env._rsc->getJointConfigAtTimestamp(t1); + BOOST_CHECK(t2 > t1); + BOOST_CHECK_EQUAL(config["Elbow L"], 0); + config = env._rsc->getJointConfigAtTimestamp(t2); + BOOST_CHECK_EQUAL(config["Elbow L"], 1); + config = env._rsc->getJointConfigAtTimestamp(t2+1);// future timestamp -> latest values should be returned + BOOST_CHECK_EQUAL(config["Elbow L"], 1); + config = env._rsc->getJointConfigAtTimestamp((t1+t2)*0.5); // interpolated values in the middle + BOOST_CHECK_CLOSE(config["Elbow L"], 0.5f, 1); + config = env._rsc->getJointConfigAtTimestamp(t1+(t2-t1)*0.7); // interpolated values + ARMARX_INFO_S << "value at t=0.7%: " << config["Elbow L"]; + BOOST_CHECK_CLOSE(config["Elbow L"], 0.7f, 1); +} diff --git a/source/RobotAPI/interface/core/RobotState.ice b/source/RobotAPI/interface/core/RobotState.ice index d37226e2d..11bf27a06 100644 --- a/source/RobotAPI/interface/core/RobotState.ice +++ b/source/RobotAPI/interface/core/RobotState.ice @@ -41,6 +41,13 @@ module armarx string rootName; }; + struct RobotStateConfig + { + long timestamp; + FramedPoseBase globalPose; + NameValueMap jointMap; + }; + /** * The SharedRobotNodeInterface provides access to a limited amount of * VirtualRobot::RobotNode methods over the Ice network. @@ -133,8 +140,33 @@ module armarx /** * @return proxy to a copy of the shared robot with non updating joint values */ - SharedRobotInterface* getRobotSnapshot(string time) throws NotInitializedException; - + SharedRobotInterface* getRobotSnapshot(string deprecated) throws NotInitializedException; + + /** + * Create robot snapshot proxy from past timestamp. The time that can be gone back depends on the robotstatecomponent configuration. + * @return Snapshot + * + * */ + SharedRobotInterface* getRobotSnapshotAtTimestamp(long time) throws NotInitializedException; + + /** + * Return the joint values from past timestamp. The time that can be gone back depends on the robotstatecomponent configuration. + * @return Snapshot + * + * */ + ["cpp:const"] + idempotent + NameValueMap getJointConfigAtTimestamp(long time) throws NotInitializedException; + + /** + * Return the Robot configuration, including joint values and global pose of the root node, from past timestamp. The time that can be gone back depends on the robotstatecomponent configuration. + * @return Snapshot + * + * */ + ["cpp:const"] + idempotent + RobotStateConfig getRobotStateAtTimestamp(long time) throws NotInitializedException; + /** * @return the robot xml filename as specified in the configuration */ -- GitLab