Skip to content
Snippets Groups Projects
NJointCartesianWaypointController.cpp 19.98 KiB
#include "NJointCartesianWaypointController.h"

#include <iomanip>

#include <ArmarXCore/util/CPPUtility/trace.h>

#include <RobotAPI/components/units/RobotUnit/ControlTargets/ControlTarget1DoFActuator.h>
#include <RobotAPI/components/units/RobotUnit/Devices/SensorDevice.h>
#include <RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerRegistry.h>
#include <RobotAPI/components/units/RobotUnit/SensorValues/SensorValue1DoFActuator.h>
#include <RobotAPI/components/units/RobotUnit/SensorValues/SensorValueForceTorque.h>
#include <RobotAPI/components/units/RobotUnit/util/ControlThreadOutputBuffer.h>
#include <RobotAPI/interface/visualization/DebugDrawerInterface.h>

namespace armarx
{
    std::ostream&
    operator<<(std::ostream& out, const NJointCartesianWaypointControllerRuntimeConfig& cfg)
    {
        out << "maxPositionAcceleration     " << cfg.wpCfg.maxPositionAcceleration << '\n'
            << "maxOrientationAcceleration  " << cfg.wpCfg.maxOrientationAcceleration << '\n'
            << "maxNullspaceAcceleration    " << cfg.wpCfg.maxNullspaceAcceleration << '\n'

            << "kpJointLimitAvoidance       " << cfg.wpCfg.kpJointLimitAvoidance << '\n'
            << "jointLimitAvoidanceScale    " << cfg.wpCfg.jointLimitAvoidanceScale << '\n'

            << "thresholdPositionNear       " << cfg.wpCfg.thresholdPositionNear << '\n'
            << "thresholdPositionReached    " << cfg.wpCfg.thresholdPositionReached << '\n'
            << "maxPosVel                   " << cfg.wpCfg.maxPosVel << '\n'
            << "kpPos                       " << cfg.wpCfg.kpPos << '\n'

            << "thresholdOrientationNear    " << cfg.wpCfg.thresholdOrientationNear << '\n'
            << "thresholdOrientationReached " << cfg.wpCfg.thresholdOrientationReached << '\n'
            << "maxOriVel                   " << cfg.wpCfg.maxOriVel << '\n'
            << "kpOri                       " << cfg.wpCfg.kpOri << '\n'

            << "forceThreshold              " << cfg.forceThreshold << '\n';
        return out;
    }

    NJointControllerRegistration<NJointCartesianWaypointController>
        registrationControllerNJointCartesianWaypointController(
            "NJointCartesianWaypointController");

    std::string
    NJointCartesianWaypointController::getClassName(const Ice::Current&) const
    {
        return "NJointCartesianWaypointController";
    }

    NJointCartesianWaypointController::NJointCartesianWaypointController(
        RobotUnit*,
        const NJointCartesianWaypointControllerConfigPtr& config,
        const VirtualRobot::RobotPtr&)
    {
        ARMARX_CHECK_NOT_NULL(config);

        //robot
        {
            _rtRobot = useSynchronizedRtRobot();
            ARMARX_CHECK_NOT_NULL(_rtRobot);

            _rtRns = _rtRobot->getRobotNodeSet(config->rns);
            ARMARX_CHECK_NOT_NULL(_rtRns) << "No rns " << config->rns;
            _rtTcp = _rtRns->getTCP();
            ARMARX_CHECK_NOT_NULL(_rtTcp) << "No tcp in rns " << config->rns;

            _rtRobotRoot = _rtRobot->getRootNode();
        }
        //sensor
        {
            if (!config->ftName.empty())
            {
                const auto svalFT = useSensorValue<SensorValueForceTorque>(config->ftName);
                ARMARX_CHECK_NOT_NULL(svalFT) << "No sensor value of name " << config->ftName;
                _rtForceSensor = &(svalFT->force);
                _rtTorqueSensor = &(svalFT->force);

                const auto sdev = peekSensorDevice(config->ftName);
                ARMARX_CHECK_NOT_NULL(sdev);

                const auto reportFrameName = sdev->getReportingFrame();
                ARMARX_CHECK_EXPRESSION(!reportFrameName.empty())
                    << VAROUT(sdev->getReportingFrame());

                _rtFT = _rtRobot->getRobotNode(reportFrameName);
                ARMARX_CHECK_NOT_NULL(_rtFT)
                    << "No sensor report frame '" << reportFrameName << "' in robot";
            }
        }
        //ctrl
        {
            _rtJointVelocityFeedbackBuffer =
                Eigen::VectorXf::Zero(static_cast<int>(_rtRns->getSize()));

            VirtualRobot::RobotNodePtr referenceFrame = nullptr;
            if (!config->referenceFrameName.empty())
            {
                referenceFrame = _rtRobot->getRobotNode(config->referenceFrameName);
                ARMARX_CHECK_NOT_NULL(referenceFrame)
                    << "No node with name '" << config->referenceFrameName
                    << "' for referenceFrame in robot";
            }

            _rtWpController = std::make_unique<CartesianWaypointController>(
                _rtRns,
                _rtJointVelocityFeedbackBuffer,
                config->runCfg.wpCfg.maxPositionAcceleration,
                config->runCfg.wpCfg.maxOrientationAcceleration,
                config->runCfg.wpCfg.maxNullspaceAcceleration,
                nullptr,
                referenceFrame);

            _rtWpController->setConfig({});

            for (size_t i = 0; i < _rtRns->getSize(); ++i)
            {
                std::string jointName = _rtRns->getNode(i)->getName();
                auto ct = useControlTarget<ControlTarget1DoFActuatorVelocity>(
                    jointName, ControlModes::Velocity1DoF);
                auto sv = useSensorValue<SensorValue1DoFActuatorVelocity>(jointName);
                ARMARX_CHECK_NOT_NULL(ct);
                ARMARX_CHECK_NOT_NULL(sv);

                _rtVelTargets.emplace_back(&(ct->velocity));
                _rtVelSensors.emplace_back(&(sv->velocity));
            }
        }
        //visu
        {
            _tripRt2NonRtRobotGP.getWriteBuffer().setIdentity();
            _tripRt2NonRtRobotGP.commitWrite();
        }
    }

    void
    NJointCartesianWaypointController::rtPreActivateController()
    {
        _publishIsAtTarget = false;
        _rtForceOffset = Eigen::Vector3f::Zero();

        _publishErrorPos = 0;
        _publishErrorOri = 0;
        _publishErrorPosMax = 0;
        _publishErrorOriMax = 0;
        _publishForceThreshold = _rtForceThreshold;

        //feedback
        {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
            for (std::size_t idx = 0; idx < _rtVelSensors.size(); ++idx)
            {
                const float* ptr = _rtVelSensors[idx];
                _rtJointVelocityFeedbackBuffer(idx) = *ptr;
            }
            _rtWpController->setCurrentJointVelocity(_rtJointVelocityFeedbackBuffer);
#pragma GCC diagnostic pop
        }
    }

    void
    NJointCartesianWaypointController::rtRun(const IceUtil::Time& sensorValuesTimestamp,
                                             const IceUtil::Time& timeSinceLastIteration)
    {
        auto& rt2nonrtBuf = _tripRt2NonRt.getWriteBuffer();

        if (_tripBufWpCtrl.updateReadBuffer())
        {
            ARMARX_RT_LOGF_IMPORTANT("updates in tripple buffer");
            auto& r = _tripBufWpCtrl._getNonConstReadBuffer();
            if (r.cfgUpdated)
            {
                r.cfgUpdated = false;
                _rtWpController->setConfig(r.cfg.wpCfg);
                _rtForceThreshold = r.cfg.forceThreshold;
                _publishForceThreshold = _rtForceThreshold;
                _rtOptimizeNullspaceIfTargetWasReached = r.cfg.optimizeNullspaceIfTargetWasReached;
                _rtForceThresholdInRobotRootZ = r.cfg.forceThresholdInRobotRootZ;
                _publishForceThresholdInRobotRootZ = r.cfg.forceThresholdInRobotRootZ;
                ARMARX_RT_LOGF_IMPORTANT("updated the controller config");
            }
            if (r.wpsUpdated)
            {
                _rtStopConditionReached = false;
                _publishIsAtTarget = false;
                r.wpsUpdated = false;
                ARMARX_RT_LOGF_IMPORTANT("updated waypoints (# = %u)", r.wps.size());
                _publishWpsNum = r.wps.size();
                _rtHasWps = !r.wps.empty();
                _rtWpController->swapWaypoints(r.wps);
                if (r.cfg.skipToClosestWaypoint)
                {
                    _rtWpController->skipToClosestWaypoint(r.skipRad2mmFactor);
                }
            }
            _publishIsAtForceLimit = false;
        }
        //run
        bool reachedTarget = false;
        bool reachedFtLimit = false;

        if (_rtForceSensor)
        {
            const Eigen::Vector3f ftInRoot =
                _rtFT->getTransformationTo(_rtRobotRoot).topLeftCorner<3, 3>().transpose() *
                (*_rtForceSensor);
            rt2nonrtBuf.ft.force = ftInRoot;
            rt2nonrtBuf.ft.torque = *_rtTorqueSensor;
            rt2nonrtBuf.ft.timestampInUs = sensorValuesTimestamp.toMicroSeconds();
            rt2nonrtBuf.ftInRoot = _rtFT->getPoseInRootFrame();

            Eigen::Vector3f ftVal = ftInRoot;
            if (_rtForceThresholdInRobotRootZ)
            {
                ftVal(0) = 0;
                ftVal(1) = 0;
            }
            if (_setFTOffset)
            {
                _rtForceOffset = ftVal;
                _setFTOffset = false;
                _publishIsAtForceLimit = false;
            }
            rt2nonrtBuf.ftUsed = ftVal;

            const float currentForce = std::abs(ftVal.norm() - _rtForceOffset.norm());

            reachedFtLimit = (_rtForceThreshold >= 0) && currentForce > _rtForceThreshold;
            _publishForceCurrent = currentForce;
            _publishIsAtForceLimit = _publishIsAtForceLimit || reachedFtLimit;
        }

        _publishWpsCur = _rtHasWps ? _rtWpController->currentWaypointIndex : 0;

        rt2nonrtBuf.rootPose = _rtRobot->getGlobalPose();
        rt2nonrtBuf.tcp = _rtTcp->getPoseInRootFrame();
        if (_rtHasWps)
        {
            const float errorPos = _rtWpController->getPositionError();
            const float errorOri = _rtWpController->getOrientationError();

            _publishErrorPos = errorPos;
            _publishErrorOri = errorOri;
            if (!_rtWpController->isLastWaypoint())
            {
                _publishErrorPosMax = _rtWpController->thresholdPositionNear;
                _publishErrorOriMax = _rtWpController->thresholdOrientationNear;
            }
            else
            {
                _publishErrorPosMax = _rtWpController->thresholdPositionReached;
                _publishErrorOriMax = _rtWpController->thresholdOrientationReached;
                reachedTarget = (errorPos < _rtWpController->thresholdPositionReached) &&
                                (errorOri < _rtWpController->thresholdOrientationReached);
            }
            rt2nonrtBuf.tcpTarg = _rtWpController->getCurrentTarget();
        }
        else
        {
            rt2nonrtBuf.tcpTarg = rt2nonrtBuf.tcp;
        }
        _rtStopConditionReached = _rtStopConditionReached || reachedFtLimit ||
                                  (reachedTarget && !_rtOptimizeNullspaceIfTargetWasReached);
        _publishIsAtTarget = _publishIsAtTarget || reachedTarget;
        if (!_rtHasWps || _rtStopConditionReached)
        {
            setNullVelocity();
        }
        else
        {
            const Eigen::VectorXf& goal =
                _rtWpController->calculate(timeSinceLastIteration.toSecondsDouble());
            //write targets
            ARMARX_CHECK_EQUAL(static_cast<std::size_t>(goal.size()), _rtVelTargets.size());
            for (std::size_t idx = 0; idx < _rtVelTargets.size(); ++idx)
            {
                float* ptr = _rtVelTargets[idx];
                *ptr = goal(idx);
            }
        }
        _tripRt2NonRt.commitWrite();

        _tripRt2NonRtRobotGP.getWriteBuffer() = _rtRobot->getGlobalPose();
        _tripRt2NonRtRobotGP.commitWrite();
    }

    void
    NJointCartesianWaypointController::rtPostDeactivateController()
    {
    }

    bool
    NJointCartesianWaypointController::hasReachedTarget(const Ice::Current&)
    {
        return _publishIsAtTarget;
    }

    void
    NJointCartesianWaypointController::setConfig(
        const NJointCartesianWaypointControllerRuntimeConfig& cfg,
        const Ice::Current&)
    {
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.cfgUpdated = true;
        w.cfg = cfg;
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "set new config\n" << cfg;
    }

    void
    NJointCartesianWaypointController::setWaypoints(const std::vector<Eigen::Matrix4f>& wps,
                                                    const Ice::Current&)
    {
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.wpsUpdated = true;
        w.wps = wps;
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "set new waypoints\n" << wps;
    }

    void
    NJointCartesianWaypointController::setWaypoint(const Eigen::Matrix4f& wp, const Ice::Current&)
    {
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.wpsUpdated = true;
        w.wps.clear();
        w.wps.emplace_back(wp);
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "set new waypoint\n" << wp;
    }

    void
    NJointCartesianWaypointController::setWaypointAx(const Ice::FloatSeq& data, const Ice::Current&)
    {
        Eigen::Matrix4f wp(data.data());
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.wpsUpdated = true;
        w.wps.clear();
        w.wps.emplace_back(wp);
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "set new waypoint\n" << wp;
    }

    void
    NJointCartesianWaypointController::setConfigAndWaypoints(
        const NJointCartesianWaypointControllerRuntimeConfig& cfg,
        const std::vector<Eigen::Matrix4f>& wps,
        const Ice::Current&)
    {
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.wpsUpdated = true;
        w.cfgUpdated = true;
        w.cfg = cfg;
        w.wps = wps;
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "set new config\n" << cfg << " and new waypoints\n" << wps;
    }

    void
    NJointCartesianWaypointController::setConfigAndWaypoint(
        const NJointCartesianWaypointControllerRuntimeConfig& cfg,
        const Eigen::Matrix4f& wp,
        const Ice::Current&)
    {
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.wpsUpdated = true;
        w.cfgUpdated = true;
        w.cfg = cfg;
        w.wps.clear();
        w.wps.emplace_back(wp);
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "set new config\n" << cfg << "and a new waypoint\n" << wp;
    }

    void
    NJointCartesianWaypointController::setNullVelocity()
    {
        for (auto ptr : _rtVelTargets)
        {
            *ptr = 0;
        }
    }

    bool
    NJointCartesianWaypointController::hasReachedForceLimit(const Ice::Current&)
    {
        ARMARX_CHECK_NOT_NULL(_rtForceSensor);
        return _publishIsAtForceLimit;
    }

    FTSensorValue
    NJointCartesianWaypointController::getFTSensorValue(const Ice::Current&)
    {
        ARMARX_CHECK_NOT_NULL(_rtForceSensor);
        std::lock_guard g{_tripRt2NonRtMutex};
        return _tripRt2NonRt.getUpToDateReadBuffer().ft;
    }

    void
    NJointCartesianWaypointController::setCurrentFTAsOffset(const Ice::Current&)
    {
        ARMARX_CHECK_NOT_NULL(_rtForceSensor);
        _setFTOffset = true;
    }

    void
    NJointCartesianWaypointController::setVisualizationRobotGlobalPose(const Eigen::Matrix4f& p,
                                                                       const Ice::Current&)
    {
        ; // No longer used ...
    }
    void
    NJointCartesianWaypointController::resetVisualizationRobotGlobalPose(const Ice::Current&)
    {
        ; // No longer used ...
    }

    void
    NJointCartesianWaypointController::stopMovement(const Ice::Current&)
    {
        std::lock_guard g{_tripBufWpCtrlMut};
        auto& w = _tripBufWpCtrl.getWriteBuffer();
        w.wpsUpdated = true;
        w.cfgUpdated = false;
        w.wps.clear();
        _tripBufWpCtrl.commitWrite();
        ARMARX_IMPORTANT << "stop movement by setting 0 waypoints";
    }

    void
    NJointCartesianWaypointController::onPublish(const SensorAndControl&,
                                                 const DebugDrawerInterfacePrx& drawer,
                                                 const DebugObserverInterfacePrx& obs)
    {
        const std::size_t wpsNum = _publishWpsNum;
        const std::size_t wpsCur = wpsNum ? _publishWpsCur + 1 : 0;

        const float errorPos = _publishErrorPos;
        const float errorOri = _publishErrorOri;
        const float errorPosMax = _publishErrorPosMax;
        const float errorOriMax = _publishErrorOriMax;
        const float forceThreshold = _publishForceThreshold;
        const float forceCurrent = _publishForceCurrent;

        {
            StringVariantBaseMap datafields;
            datafields["WpNum"] = new Variant(static_cast<int>(wpsNum));
            datafields["WpCur"] = new Variant(static_cast<int>(wpsCur));

            datafields["errorPos"] = new Variant(errorPos);
            datafields["errorOri"] = new Variant(errorOri);
            datafields["errorPosMax"] = new Variant(errorPosMax);
            datafields["errorOriMax"] = new Variant(errorOriMax);

            datafields["forceThreshold"] = new Variant(forceThreshold);
            datafields["forceCurrent"] = new Variant(forceCurrent);

            obs->setDebugChannel(getInstanceName(), datafields);
        }

        ARMARX_INFO << deactivateSpam(1) << std::setprecision(4) << "At waypoint " << wpsCur
                    << " / " << wpsNum << " (last " << _publishIsAtTarget << ", ft limit "
                    << _publishIsAtForceLimit << ", perror " << errorPos << " / " << errorPosMax
                    << ", oerror " << errorOri << " / " << errorOriMax << ')';

        {
            std::lock_guard g{_tripRt2NonRtMutex};
            const auto& buf = _tripRt2NonRt.getUpToDateReadBuffer();

            const Eigen::Matrix4f fakeGP = _tripRt2NonRtRobotGP.getUpToDateReadBuffer();
            const Eigen::Matrix4f gp = std::isfinite(fakeGP(0, 0)) ? fakeGP : buf.rootPose;

            if (buf.tcp != buf.tcpTarg)
            {
                const Eigen::Matrix4f gtcp = gp * buf.tcp;
                const Eigen::Matrix4f gtcptarg = gp * buf.tcpTarg;
                drawer->setPoseVisu(getName(), "tcp", new Pose(gtcp));
                drawer->setPoseVisu(getName(), "tcptarg", new Pose(gtcptarg));
                drawer->setLineVisu(getName(),
                                    "tcp2targ",
                                    new Vector3(Eigen::Vector3f{gtcp.topRightCorner<3, 1>()}),
                                    new Vector3(Eigen::Vector3f{gtcptarg.topRightCorner<3, 1>()}),
                                    3,
                                    {0, 0, 1, 1});
            }
            else
            {
                drawer->removePoseVisu(getName(), "tcp");
                drawer->removePoseVisu(getName(), "tcptarg");
                drawer->removeLineVisu(getName(), "tcp2targ");
            }
            if (_rtForceSensor)
            {
                const Eigen::Matrix4f gft = gp * buf.ftInRoot;
                const Vector3Ptr pos = new Vector3{Eigen::Vector3f{gft.topRightCorner<3, 1>()}};
                drawer->setArrowVisu(getName(),
                                     "force",
                                     pos,
                                     new Vector3{buf.ft.force.normalized()},
                                     {1, 1, 0, 1},
                                     buf.ft.force.norm(),
                                     2.5);
                drawer->setArrowVisu(getName(),
                                     "forceUsed",
                                     pos,
                                     new Vector3{buf.ftUsed.normalized()},
                                     {1, 0.5, 0, 1},
                                     buf.ftUsed.norm(),
                                     2.5);
            }
        }
    }

    int
    NJointCartesianWaypointController::getCurrentWaypointIndex(const Ice::Current&)
    {
        return _publishWpsCur;
    }
} // namespace armarx