Skip to content
Snippets Groups Projects
Forked from Software / ArmarX / RobotAPI
924 commits behind the upstream repository.
RobotUnitModuleControllerManagement.cpp 33.66 KiB
/*
 * 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    RobotAPI::ArmarXObjects::RobotUnit
 * @author     Raphael Grimm ( raphael dot grimm at kit dot edu )
 * @date       2018
 * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
 *             GNU General Public License
 */


#include "RobotUnitModuleControllerManagement.h"

#include <ArmarXCore/core/ArmarXManager.h>
#include <ArmarXCore/core/util/OnScopeExit.h>
#include <ArmarXCore/core/util/algorithm.h>
#include <ArmarXCore/util/CPPUtility/trace.h>

#include <RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerBase.h>
#include <RobotAPI/components/units/RobotUnit/NJointControllers/NJointControllerRegistry.h>
#include <RobotAPI/components/units/RobotUnit/RobotUnitModules/RobotUnitModuleControlThreadDataBuffer.h>
#include <RobotAPI/components/units/RobotUnit/RobotUnitModules/RobotUnitModuleDevices.h>
#include <RobotAPI/components/units/RobotUnit/RobotUnitModules/RobotUnitModulePublisher.h>
#include <RobotAPI/components/units/RobotUnit/RobotUnitModules/RobotUnitModuleRobotData.h>

namespace armarx::RobotUnitModule
{
    template <class Cont>
    static Ice::StringSeq
    GetNonNullNames(const Cont& c)
    {
        Ice::StringSeq result;
        result.reserve(c.size());
        for (const auto& e : c)
        {
            if (e)
            {
                result.emplace_back(e->getName());
            }
        }
        return result;
    }

    /**
     * \brief This class allows minimal access to private members of \ref NJointControllerBase in a sane fashion for \ref ControllerManagement.
     * \warning !! DO NOT ADD ANYTHING IF YOU DO NOT KNOW WAHT YOU ARE DOING! IF YOU DO SOMETHING WRONG YOU WILL CAUSE UNDEFINED BEHAVIOUR !!
     */
    class NJointControllerAttorneyForControllerManagement
    {
        friend class ControllerManagement;

        static void
        SetRequested(const NJointControllerBasePtr& nJointCtrl, bool requested)
        {
            nJointCtrl->isRequested = requested;
        }
    };
} // namespace armarx::RobotUnitModule

namespace armarx::RobotUnitModule
{
    Ice::StringSeq
    ControllerManagement::getRequestedNJointControllerNames(const Ice::Current&) const
    {
        ARMARX_TRACE;
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        return GetNonNullNames(_module<ControlThreadDataBuffer>().copyRequestedNJointControllers());
    }

    Ice::StringSeq
    ControllerManagement::getActivatedNJointControllerNames(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        return GetNonNullNames(_module<ControlThreadDataBuffer>().getActivatedNJointControllers());
    }

    void
    ControllerManagement::checkNJointControllerClassName(const std::string& className) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        if (!NJointControllerRegistry::has(className))
        {
            std::stringstream ss;
            ss << "Requested controller class '" << className
               << "' unknown! Known classes:" << NJointControllerRegistry::getKeys()
               << " (If this class exists in a different lib then load it in the property "
                  "definitions of the RT-unit. DO NOT load it via "
                  "loadLibFromPath(path) or loadLibFromPackage(package, lib)) (see "
                  "https://git.h2t.iar.kit.edu/sw/armarx-integration/robots/armar7/documentation/-/"
                  "issues/85)";
            ARMARX_ERROR << ss.str();
            throw InvalidArgumentException{ss.str()};
        }
    }

    std::vector<NJointControllerBasePtr>
    ControllerManagement::getNJointControllersNotNull(const std::vector<std::string>& names) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        std::vector<NJointControllerBasePtr> ctrl;
        ctrl.reserve(names.size());
        for (const auto& name : names)
        {
            ctrl.emplace_back(getNJointControllerNotNull(name));
        }
        return ctrl;
    }

    const NJointControllerBasePtr&
    ControllerManagement::getNJointControllerNotNull(const std::string& name) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        auto it = nJointControllers.find(name);
        if (it == nJointControllers.end())
        {
            std::stringstream ss;
            ss << "RobotUnit: there is no NJointControllerBase with name '" << name
               << "'. Existing NJointControllers are: " << getNJointControllerNames();
            throw InvalidArgumentException{ss.str()};
        }
        if (!it->second)
        {
            std::stringstream ss;
            ss << "RobotUnit: The NJointControllerBase with name '" << name
               << "'. Is a nullptr! This should never be the case (invariant)!  \nMap:\n"
               << nJointControllers;
            ARMARX_FATAL << ss.str();
            throw InvalidArgumentException{ss.str()};
        }
        return it->second;
    }

    void
    ControllerManagement::deleteNJointController(const NJointControllerBasePtr& ctrl)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deleteNJointControllers(std::vector<NJointControllerBasePtr>{ctrl});
    }

    StringNJointControllerPrxDictionary
    ControllerManagement::getAllNJointControllers(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        std::map<std::string, NJointControllerBasePtr> nJointControllersCopy;
        {
            auto guard = getGuard();
            //copy to keep lock retention time low
            nJointControllersCopy = nJointControllers;
        }
        StringNJointControllerPrxDictionary result;
        for (const auto& pair : nJointControllersCopy)
        {
            result[pair.first] =
                NJointControllerInterfacePrx::uncheckedCast(pair.second->getProxy(-1, true));
        }
        return result;
    }

    NJointControllerInterfacePrx
    ControllerManagement::createNJointController(const std::string& className,
                                                 const std::string& instanceName,
                                                 const NJointControllerConfigPtr& config,
                                                 const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        //no lock required
        return NJointControllerInterfacePrx::uncheckedCast(
            createNJointController(className, instanceName, config, true, false)
                ->getProxy(-1, true));
    }

    NJointControllerInterfacePrx
    ControllerManagement::createNJointControllerFromVariantConfig(
        const std::string& className,
        const std::string& instanceName,
        const StringVariantBaseMap& variants,
        const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        //no lock required
        checkNJointControllerClassName(className);
        if (!NJointControllerRegistry::get(className)->hasRemoteConfiguration())
        {
            std::stringstream ss;
            ss << "Requested controller class '" << className << "' allows no remote configuration"
               << NJointControllerRegistry::getKeys()
               << " (Implement 'static WidgetDescription::WidgetPtr " << className
               << "::GenerateConfigDescription()'"
               << " and 'static NJointControllerConfigPtr " << className
               << "::GenerateConfigFromVariants(const StringVariantBaseMap&)' to allow remote "
                  "configuration";
            ARMARX_ERROR << ss.str();
            throw InvalidArgumentException{ss.str()};
        }
        return createNJointController(
            className,
            instanceName,
            NJointControllerRegistry::get(className)->GenerateConfigFromVariants(variants),
            Ice::emptyCurrent /*to select ice overload*/);
    }

    NJointControllerInterfacePrx
    ControllerManagement::createOrReplaceNJointController(const std::string& className,
                                                          const std::string& instanceName,
                                                          const NJointControllerConfigPtr& config,
                                                          const Ice::Current&)
    {
        ARMARX_TRACE;
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        {
            ARMARX_TRACE;
            auto guard = getGuard();
            if (getNJointController(instanceName))
            {
                ARMARX_TRACE;
                deactivateAndDeleteNJointController(instanceName);
            }
        }
        ARMARX_TRACE;
        while (getNJointController(instanceName))
        {
            ARMARX_TRACE;
            if (isShuttingDown())
            {
                ARMARX_TRACE;
                return nullptr;
            }
            ARMARX_INFO << "wiating until controller '" << instanceName << "' is deleted";
        }
        while (getArmarXManager()->getIceManager()->isObjectReachable(instanceName))
        {
            ARMARX_TRACE;
            if (isShuttingDown())
            {
                ARMARX_TRACE;
                return nullptr;
            }
            ARMARX_INFO << "wiating until controller '" << instanceName << "' is removed from ice";
        }
        return NJointControllerInterfacePrx::uncheckedCast(
            createNJointController(className, instanceName, config, true, false)
                ->getProxy(-1, true));
    }

    const NJointControllerBasePtr&
    ControllerManagement::createNJointController(const std::string& className,
                                                 const std::string& instanceName,
                                                 const NJointControllerConfigPtr& config,
                                                 bool deletable,
                                                 bool internal)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        if (instanceName.empty())
        {
            ARMARX_ERROR << "The instance name is empty! (give a unique name)";
            throw InvalidArgumentException{"The instance name is empty! (give a unique name)"};
        }
        //check if we would be able to create the class
        checkNJointControllerClassName(className);
        auto& factory = NJointControllerRegistry::get(className);

        //check if the instance name is already in use
        if (nJointControllers.count(instanceName))
        {
            std::stringstream ss;
            ss << "There already is a controller instance with the name '" << instanceName
               << "'. Use a different instance name instead."
               << " Other used instance names are " << getNJointControllerNames();
            ARMARX_ERROR << ss.str();
            throw InvalidArgumentException{ss.str()};
        }

        //create the controller
        ARMARX_CHECK_EXPRESSION(factory);
        NJointControllerBasePtr nJointCtrl =
            factory->create(this, config, controllerCreateRobot, deletable, internal, instanceName);
        ARMARX_CHECK_NOT_EQUAL(nJointCtrl->getControlDeviceUsedIndices().size(), 0)
            << "The NJointControllerBase '" << nJointCtrl->getName()
            << "' uses no ControlDevice! (It has to use at least one)";

        getArmarXManager()->addObject(nJointCtrl, instanceName, false, false);
        nJointControllers[instanceName] = std::move(nJointCtrl);
        _module<Publisher>().getRobotUnitListenerProxy()->nJointControllerCreated(instanceName);
        return nJointControllers.at(instanceName);
    }

    bool
    ControllerManagement::loadLibFromPath(const std::string& path, const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        ARMARX_WARNING << "Do not use this function as it has implications on the RT thread (see "
                        "https://git.h2t.iar.kit.edu/sw/armarx-integration/robots/armar7/"
                        "documentation/-/issues/85)";
        const bool result = getArmarXManager()->loadLibFromPath(path);
        ARMARX_INFO << "loadLibFromPath('" << path << "') -> " << result;
        return result;
    }

    bool
    ControllerManagement::loadLibFromPackage(const std::string& package,
                                             const std::string& lib,
                                             const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        ARMARX_WARNING << "Do not use this function as it has implications on the RT thread (see "
                        "https://git.h2t.iar.kit.edu/sw/armarx-integration/robots/armar7/"
                        "documentation/-/issues/85)";
        const bool result = getArmarXManager()->loadLibFromPackage(package, lib);
        ARMARX_INFO << "loadLibFromPackage('" << package << "', '" << lib << "') -> " << result;
        return result;
    }

    Ice::StringSeq
    ControllerManagement::getNJointControllerClassNames(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        return NJointControllerRegistry::getKeys();
    }

    Ice::StringSeq
    ControllerManagement::getNJointControllerNames(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        return getMapKeys(nJointControllers);
    }

    std::vector<std::string>
    ControllerManagement::getNJointControllerNames(
        const std::vector<NJointControllerBasePtr>& ctrls) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        std::vector<std::string> result;
        result.reserve(ctrls.size());
        for (const auto& ctrl : ctrls)
        {
            if (ctrl)
            {
                result.emplace_back(ctrl->getInstanceName());
            }
        }
        return result;
    }

    void
    ControllerManagement::activateNJointController(const std::string& name, const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        activateNJointControllers(getNJointControllersNotNull({name}));
    }

    void
    ControllerManagement::activateNJointControllers(const Ice::StringSeq& names,
                                                    const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        activateNJointControllers(getNJointControllersNotNull(names));
    }

    void
    ControllerManagement::activateNJointController(const NJointControllerBasePtr& ctrl)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        activateNJointControllers(std::vector<NJointControllerBasePtr>{ctrl});
    }

    void
    ControllerManagement::activateNJointControllers(
        const std::vector<NJointControllerBasePtr>& ctrlsToActVec)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        if (ctrlsToActVec.empty())
        {
            return;
        }
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        //if not activate them
        std::set<NJointControllerBasePtr, std::greater<NJointControllerBasePtr>> ctrlsToAct{
            ctrlsToActVec.begin(), ctrlsToActVec.end()};
        ARMARX_CHECK_EXPRESSION(!ctrlsToAct.count(nullptr));
        //check if all already active
        if (std::all_of(ctrlsToActVec.begin(),
                        ctrlsToActVec.end(),
                        [](const NJointControllerBasePtr& ctrl)
                        { return ctrl->isControllerActive(); }))
        {
            return;
        }
        //get already requested
        const auto ctrlVector = _module<ControlThreadDataBuffer>().copyRequestedNJointControllers();
        std::set<NJointControllerBasePtr, std::greater<NJointControllerBasePtr>>
            ctrlsAlreadyRequested{ctrlVector.begin(), ctrlVector.end()};
        ctrlsAlreadyRequested.erase(nullptr);
        //check for conflict
        std::vector<char> inuse;
        //check requested controllers
        {
            auto r = NJointControllerBase::AreNotInConflict(ctrlsToAct.begin(), ctrlsToAct.end());
            if (!r)
            {
                std::stringstream ss;
                ss << "activateNJointControllers: requested controllers are in "
                      "conflict!\ncontrollers:\n"
                   << getNJointControllerNames(ctrlsToActVec);
                ARMARX_ERROR << ss.str();
                throw InvalidArgumentException{ss.str()};
            }
            inuse = std::move(*r);
        }
        ARMARX_DEBUG << "all requested controllers are conflict free" << std::flush;
        auto printInUse = ARMARX_STREAM_PRINTER
        {
            for (const auto c : inuse)
            {
                out << (c ? 1 : 0);
            }
        };
        ARMARX_DEBUG << "inuse field (request)\n" << printInUse;
        //add already active controllers if they are conflict free
        {
            if (ctrlsAlreadyRequested.empty())
            {
                ARMARX_DEBUG << "no already requested NJointControllers";
            }
            for (const NJointControllerBasePtr& nJointCtrl : ctrlsAlreadyRequested)
            {
                if (ctrlsToAct.count(nJointCtrl))
                {
                    continue;
                }
                auto r = nJointCtrl->isNotInConflictWith(inuse);
                if (r)
                {
                    ARMARX_DEBUG << "keeping  already requested NJointControllerBase '"
                                 << nJointCtrl->getInstanceName()
                                 << "' in   list of requested controllers";
                    ctrlsToAct.insert(nJointCtrl);
                    inuse = std::move(*r);
                }
                else
                {
                    ARMARX_INFO << "removing already requested NJointControllerBase '"
                                << nJointCtrl->getInstanceName()
                                << "' from list of requested controllers";
                }
            }
            ARMARX_DEBUG << "inuse field (all)\n" << printInUse;
        }
        _module<ControlThreadDataBuffer>().setActivateControllersRequest(ctrlsToAct);
    }

    void
    ControllerManagement::deactivateNJointController(const std::string& name, const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deactivateNJointControllers(getNJointControllersNotNull({name}));
    }

    void
    ControllerManagement::deactivateNJointControllers(const Ice::StringSeq& names,
                                                      const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deactivateNJointControllers(getNJointControllersNotNull(names));
    }

    void
    ControllerManagement::deactivateNJointController(const NJointControllerBasePtr& ctrl)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deactivateNJointControllers(std::vector<NJointControllerBasePtr>{ctrl});
    }

    void
    ControllerManagement::deactivateNJointControllers(
        const std::vector<NJointControllerBasePtr>& ctrlsDeacVec)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        if (ctrlsDeacVec.empty())
        {
            return;
        }
        const auto ctrlVector = _module<ControlThreadDataBuffer>().copyRequestedNJointControllers();
        std::set<NJointControllerBasePtr, std::greater<NJointControllerBasePtr>> ctrls{
            ctrlVector.begin(), ctrlVector.end()};
        const std::size_t ctrlsNum = ctrls.size();
        for (const auto& nJointCtrlToDeactivate : ctrlsDeacVec)
        {
            ctrls.erase(nJointCtrlToDeactivate);
        }
        if (ctrls.size() == ctrlsNum)
        {
            return;
        }
        _module<ControlThreadDataBuffer>().setActivateControllersRequest(ctrls);
    }

    void
    ControllerManagement::deleteNJointController(const std::string& name, const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deleteNJointControllers(getNJointControllersNotNull({name}));
    }

    void
    ControllerManagement::deleteNJointControllers(const Ice::StringSeq& names, const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deleteNJointControllers(getNJointControllersNotNull(names));
    }

    void
    ControllerManagement::switchNJointControllerSetup(const Ice::StringSeq& newSetup,
                                                      const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        auto ctrlsToActVec =
            getNJointControllersNotNull(newSetup); //also checks if these controllers exist
        _module<ControlThreadDataBuffer>().setActivateControllersRequest(
            {ctrlsToActVec.begin(), ctrlsToActVec.end()});
    }

    void
    ControllerManagement::deleteNJointControllers(
        const std::vector<NJointControllerBasePtr>& ctrlsToDelVec)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        if (ctrlsToDelVec.empty())
        {
            return;
        }
        //check if all can be deleted
        for (const auto& nJointCtrl : ctrlsToDelVec)
        {
            if (!nJointCtrl->isDeletable())
            {
                throw LogicError{"The NJointControllerBase '" + nJointCtrl->getInstanceName() +
                                 "' can't be deleted since this operation is not allowed for this "
                                 "controller! (no NJointControllerBase was deleted)"};
            }
            if (nJointCtrl->isControllerActive() || nJointCtrl->isControllerRequested())
            {
                throw LogicError{"The NJointControllerBase '" + nJointCtrl->getInstanceName() +
                                 "' can't be deleted since it is active or requested! (no "
                                 "NJointControllerBase was deleted)"};
            }
        }
        for (const auto& nJointCtrl : ctrlsToDelVec)
        {
            const auto name = nJointCtrl->getInstanceName();
            //deletion is done in a different thread since this call may be done by the controller (protection against use after free)
            nJointControllersToBeDeleted[name] = std::move(nJointCtrl);
            nJointControllers.erase(name);
            ARMARX_VERBOSE << "added NJointControllerBase '" << name << "' to be deleted";
        }
    }

    void
    ControllerManagement::deactivateAndDeleteNJointController(const std::string& name,
                                                              const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deactivateAndDeleteNJointControllers(getNJointControllersNotNull({name}));
    }

    void
    ControllerManagement::deactivateAndDeleteNJointControllers(const Ice::StringSeq& names,
                                                               const Ice::Current&)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deactivateAndDeleteNJointControllers(getNJointControllersNotNull(names));
    }

    void
    ControllerManagement::deactivateAndDeleteNJointController(const NJointControllerBasePtr& ctrl)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        deactivateAndDeleteNJointControllers({ctrl});
    }

    void
    ControllerManagement::deactivateAndDeleteNJointControllers(
        const std::vector<NJointControllerBasePtr>& ctrlsToDelVec)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        if (ctrlsToDelVec.empty())
        {
            return;
        }
        deactivateNJointControllers(ctrlsToDelVec);
        while (std::any_of(ctrlsToDelVec.begin(),
                           ctrlsToDelVec.end(),
                           [](const NJointControllerBasePtr& ctrl)
                           { return ctrl->isControllerActive(); }))
        {
            if (isShuttingDown())
            {
                return;
            }
            std::this_thread::sleep_for(std::chrono::microseconds{100});
        }
        deleteNJointControllers(ctrlsToDelVec);
    }

    NJointControllerClassDescription
    ControllerManagement::getNJointControllerClassDescription(const std::string& className,
                                                              const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        while (getRobotUnitState() == RobotUnitState::InitializingComponent ||
               getRobotUnitState() == RobotUnitState::InitializingDevices)
        {
            //this phase should only last short so busy waiting is ok
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        checkNJointControllerClassName(className);
        NJointControllerClassDescription data;
        data.className = className;
        if (NJointControllerRegistry::get(className)->hasRemoteConfiguration())
        {
            data.configDescription =
                NJointControllerRegistry::get(className)->GenerateConfigDescription(
                    controllerCreateRobot,
                    _module<Devices>().getControlDevicesConstPtr(),
                    _module<Devices>().getSensorDevicesConstPtr());
        }
        return data;
    }

    NJointControllerClassDescriptionSeq
    ControllerManagement::getNJointControllerClassDescriptions(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        std::size_t tries = 200;
        while (getRobotUnitState() == RobotUnitState::InitializingComponent ||
               getRobotUnitState() == RobotUnitState::InitializingDevices)
        {
            //this phase should only last short so busy waiting is ok
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
            if (!--tries)
            {
                throw RuntimeError{"RobotUnit::getNJointControllerClassDescriptions: it took too "
                                   "long to for the unit to get in a valid state"};
            }
        }
        auto guard = getGuard();
        NJointControllerClassDescriptionSeq r;
        r.reserve(NJointControllerRegistry::getKeys().size());
        for (const auto& key : NJointControllerRegistry::getKeys())
        {
            r.emplace_back(getNJointControllerClassDescription(key));
        }
        return r;
    }

    NJointControllerInterfacePrx
    ControllerManagement::getNJointController(const std::string& name, const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        NJointControllerBasePtr ctrl;
        {
            auto guard = getGuard();
            auto it = nJointControllers.find(name);
            if (it == nJointControllers.end())
            {
                return nullptr;
            }
            ctrl = it->second;
        }
        return NJointControllerInterfacePrx::uncheckedCast(ctrl->getProxy(-1, true));
    }

    NJointControllerStatus
    ControllerManagement::getNJointControllerStatus(const std::string& name,
                                                    const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        throwIfDevicesNotReady(__FUNCTION__);
        return getNJointControllerNotNull(name)->getControllerStatus();
    }

    NJointControllerStatusSeq
    ControllerManagement::getNJointControllerStatuses(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        auto guard = getGuard();
        if (!areDevicesReady())
        {
            return {};
        }
        NJointControllerStatusSeq r;
        r.reserve(nJointControllers.size());
        for (const auto& nJointCtrl : nJointControllers)
        {
            r.emplace_back(nJointCtrl.second->getControllerStatus());
        }
        return r;
    }

    NJointControllerDescription
    ControllerManagement::getNJointControllerDescription(const std::string& name,
                                                         const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        NJointControllerBasePtr nJointCtrl;
        {
            auto guard = getGuard();
            throwIfDevicesNotReady(__FUNCTION__);
            nJointCtrl = getNJointControllerNotNull(name);
        }
        return nJointCtrl->getControllerDescription();
    }

    NJointControllerDescriptionSeq
    ControllerManagement::getNJointControllerDescriptions(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        std::map<std::string, NJointControllerBasePtr> nJointControllersCopy;
        {
            auto guard = getGuard();
            if (!areDevicesReady())
            {
                return {};
            }
            nJointControllersCopy = nJointControllers;
        }
        NJointControllerDescriptionSeq r;
        r.reserve(nJointControllersCopy.size());
        for (const auto& nJointCtrl : nJointControllersCopy)
        {
            r.emplace_back(nJointCtrl.second->getControllerDescription());
        }
        return r;
    }

    NJointControllerDescriptionWithStatus
    ControllerManagement::getNJointControllerDescriptionWithStatus(const std::string& name,
                                                                   const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        NJointControllerBasePtr nJointCtrl;
        {
            auto guard = getGuard();
            throwIfDevicesNotReady(__FUNCTION__);
            nJointCtrl = getNJointControllerNotNull(name);
        }
        return nJointCtrl->getControllerDescriptionWithStatus();
    }

    NJointControllerDescriptionWithStatusSeq
    ControllerManagement::getNJointControllerDescriptionsWithStatuses(const Ice::Current&) const
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        std::map<std::string, NJointControllerBasePtr> nJointControllersCopy;
        {
            auto guard = getGuard();
            if (!areDevicesReady())
            {
                return {};
            }
            nJointControllersCopy = nJointControllers;
        }
        NJointControllerDescriptionWithStatusSeq r;
        r.reserve(nJointControllersCopy.size());
        for (const auto& nJointCtrl : nJointControllersCopy)
        {
            r.emplace_back(nJointCtrl.second->getControllerDescriptionWithStatus());
        }
        return r;
    }

    void
    ControllerManagement::removeNJointControllers(
        std::map<std::string, NJointControllerBasePtr>& ctrls,
        bool blocking,
        RobotUnitListenerPrx l)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        for (auto& n2NJointCtrl : ctrls)
        {
            NJointControllerBasePtr& nJointCtrl = n2NJointCtrl.second;
            if (blocking)
            {
                ARMARX_VERBOSE << "deleted NJointControllerBase " << n2NJointCtrl.first;
                getArmarXManager()->removeObjectBlocking(nJointCtrl->getName());
            }
            else
            {
                ARMARX_VERBOSE << "deleted NJointControllerBase " << n2NJointCtrl.first;
                getArmarXManager()->removeObjectBlocking(nJointCtrl->getName());
            }
            if (l)
            {
                l->nJointControllerDeleted(n2NJointCtrl.first);
            }
        }
        ctrls.clear();
    }

    void
    ControllerManagement::removeNJointControllersToBeDeleted(bool blocking, RobotUnitListenerPrx l)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        removeNJointControllers(nJointControllersToBeDeleted, blocking, l);
    }

    void
    ControllerManagement::_preFinishRunning()
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        //NJoint queued for deletion (some could still be in the queue)
        ARMARX_DEBUG << "remove NJointControllers queued for deletion";
        removeNJointControllersToBeDeleted();
        ARMARX_DEBUG << "remove NJointControllers queued for deletion...done";
        //NJoint
        ARMARX_DEBUG << "remove NJointControllers";
        removeNJointControllers(nJointControllers);
        ARMARX_DEBUG << "remove NJointControllers...done";
    }

    void
    ControllerManagement::_postFinishRunning()
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        nJointControllers.clear();
    }

    ControllerManagement::~ControllerManagement()
    {
    }

    void
    ControllerManagement::_preOnInitRobotUnit()
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        controllerCreateRobot = _module<RobotData>().cloneRobot();
    }

    void
    ControllerManagement::updateNJointControllerRequestedState(
        const std::set<NJointControllerBasePtr>& request)
    {
        throwIfInControlThread(BOOST_CURRENT_FUNCTION);
        ARMARX_DEBUG << "set requested state for NJoint controllers";
        for (const auto& name2NJoint : nJointControllers)
        {
            NJointControllerAttorneyForControllerManagement::SetRequested(
                name2NJoint.second, request.count(name2NJoint.second));
        }
    }
} // namespace armarx::RobotUnitModule