/*
* $Header: https://oberon.oapd.inaf.it/shins/trunk/shins/src/iif/Service/LtcsIifServiceWorker.cc 3983 2022-07-04 08:43:50Z depa $
*
*/

/* Code adapted from LINC-NIRVANA */

/**
* @file
* @since 2011-08-30
* @author kittmann
*/

#include <unistd.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include <cmath>
#include <cstdlib>
#include <cstdio>
// #include <thread>

#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/dom/DOM.hpp>
#include <xercesc/framework/StdOutFormatTarget.hpp>
#include <xercesc/framework/LocalFileFormatTarget.hpp>

#include <Nice/Application.h>
#include <Nice/Log.h>
#include <Basda/Basdard.h>
#include <Basda/ServiceState.h>

#include <Ltcs/Iif/Iif2fitsKw.h>
#include <Ltcs/Iif/IifStar.h>
#include <Ltcs/Iif/IifSide.h>
#include <Ltcs/Iif/IifStation.h>
#include <LtcsIifServiceWorker.h>
#include <Ltcs/Iif/PropertyName.h>
#include <Ltcs/Iif/ServiceWorkerInternalData.h>
#include <Ltcs/Iif/ServiceWorkerException.h>
#include <LbtoIIFInterface.h>
#include <LbtoIIFInterfaceResult.h>

#include <boost/exception/diagnostic_information.hpp>

#undef LOG_NAME
#define LOG_NAME m_service->name()

#ifndef DR2D
/** conversion radians to degrees. Same as in sofam.h. */
#define DR2D 57.2957795130823208767981548141
#endif

std::string tilde = " ~ ";

/* If one is in desparate need to use the old Com-2 telescope interface with single ICE...
* one may define this preprocessor variable.
* @since 2017-12-08
* #define LN_TCS_SINGLE_ICE */

using namespace lbto;
using namespace xercesc;

/*!*********************
* @param _t The time to be waited. */
int Ltcs::IifServiceWorker::DeviceAsyncImpl::asyncWait(const Nice::Time & _t)
{
    return m_service->timedWaitForNewState(_t);
}

/*!*********************
* @param _commandName
* @param _percent */
int Ltcs::IifServiceWorker::DeviceAsyncImpl::asyncStatus(const std::string & _commandName, int _percent)
{
    m_service->Basda::ServiceWorker::sendStateStatus(_commandName, _percent);
    return 0;
}

/*!*********************
* @param _commandName
* @param _percent */
int Ltcs::IifServiceWorker::DeviceCmdAsyncImpl::asyncStatus(const std::string & _commandName, int _percent)
{
    U6_LOG(__FUNCTION__);
    if(_percent > -2)
    {
        Ltcs::IifServiceWorker::DeviceAsyncImpl::m_commandName = _commandName;
        Ltcs::IifServiceWorker::DeviceAsyncImpl::m_percent = _percent;
        Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->cmdAction(this->m_percent, this->m_commandName);
    }

    // While a [i] command is executed, a [o] command can be called in parallel.
    // When the [o] command is done, it set back the completion bar to 0%(DONE)
    // and the completion of the [i] command is not displayed any more
    //(especially the ping pong of the motToHome command).
    // The following IF checks the whether a [o] command was executed while
    // a [i] command is running and sets back the completion of the [i] command
    // by overwriting the global CMD completion with the running command completion
    if(Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->m_completion
        != Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->m_cmd->get<Nice::Completion>())
    {
        Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->cmdAction(this->m_percent, this->m_commandName);
    }

    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getRA();
    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getDEC();
    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getParallacticAngle();
    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getUTC();
    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getLST();
    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getAlt();
    Ltcs::IifServiceWorker::DeviceAsyncImpl::m_service->getAz();

    return 0;
}

#undef LOG_NAME
#define LOG_NAME name()

/*!**************************************
* @brief ctor
* @param _config The TAN configuration node. */
Ltcs::IifServiceWorker::IifServiceWorker(const Nice::JointPtr & _config)
    : Ltcs::IifServiceWorkerSkeleton(_config), dd2keys(), m_fMonit(), m_fFtpl(),
      m_iifSidReg(Ltcs::SIDE_NONE), m_iifSidCfg(Ltcs::SIDE_NONE), m_keys(), m_ddents(), m_scat()
{
    U6_LOG("########################### - - " << __FUNCTION__ << " - - ###########################");
    U6_LOG("########################### - - " << __FUNCTION__ << " - - ###########################");
    initXml();
    m_kfpHash[0].clear();
    m_kfpHash[1].clear();

    bool warning = true;
    try
    {
#ifdef LN_TCS_SINGLE_ICE
        m_iif = 0;
#else
        m_iif[0] = m_iif[1] = 0;
#endif

        m_config->getWithDefault<Nice::Endpoint>(Ltcs::Iif::Svc::PropertyName::connectionAdapter(), Nice::Endpoint::fromString("Name=Factory,Port=10000"), warning);
        m_config->getWithDefault<Nice::Time>(Ltcs::Iif::Svc::PropertyName::connectionTimeout(), Nice::Time::seconds(5), warning);

        // To be modified for SHARK-NIR
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName(), "SHARK_LTCS_SDX_IIF", warning);
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgInstrumentID(), "SHARK", warning);
        // m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName(), "LBTI_LTCS_SDX_IIF", warning);
        // m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgInstrumentID(), "LBTI", warning);
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgFocalStation(), "bentGregorianCenter", warning);

        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgCommandSide(), "left", warning);

        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::cfgRotatorMode(), "ROTATOR_IDLE", warning);

        Basda::ServiceWorker::config()->Nice::Joint::getWithDefault<Nice::Time>(Ltcs::Iif::Svc::PropertyName::cfgInterval(), Nice::Time::seconds(10));

        // create status attributes
        // the VAR.SERVICE_NAME is needed for the interface method readServiceName()
        Basda::ServiceWorker::config()->Nice::Joint::getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::serviceName(),
                                                                                 Basda::ServiceWorker::config()->Nice::Joint::getAtNode<std::string>("NAME"));

        m_config->Nice::Joint::getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::connectionAdapter(), "not defined", warning);

        m_config->Nice::Joint::getWithDefault<Nice::Time>(Ltcs::Iif::Svc::PropertyName::cfgInterval(), Nice::Time::seconds(10), warning);

        /* three values concerning number and density and order of the getRotatorPolynomials polynomials */
        m_config->Nice::Joint::getWithDefault<int>(Ltcs::Iif::Svc::PropertyName::cfgNumberOfPolynomials(), 7200, warning);

        m_config->Nice::Joint::getWithDefault<double>(Ltcs::Iif::Svc::PropertyName::cfgSamplingRate(), 1.0, warning);

        m_config->Nice::Joint::getWithDefault<int>(Ltcs::Iif::Svc::PropertyName::cfgPolynomialOrder(), 2, warning);

        m_config->Nice::Joint::getWithDefault<Nice::SeqSeqDouble>(Ltcs::Iif::Svc::PropertyName::varPolynomials(), Nice::SeqSeqDouble());

        // create status attributes
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varRA(), "not defined");
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varDEC(), "not defined");

        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varLST(), "");
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varUTC(), "");
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varHA(), "");
        m_config->getWithDefault<double>(Ltcs::Iif::Svc::PropertyName::varPA(), -9999.0);
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varALTString(), "not defined");
        m_config->getWithDefault<std::string>(Ltcs::Iif::Svc::PropertyName::varAZString(), "not defined");
        m_config->getWithDefault<double>(Ltcs::Iif::Svc::PropertyName::varALT(), -9999.0);
        m_config->getWithDefault<double>(Ltcs::Iif::Svc::PropertyName::varAZ(), -9999.0);
        m_config->getWithDefault<double>(Ltcs::Iif::Svc::PropertyName::varEpoch(), 2000.0);
#if 1
        Basda::ServiceWorkerSkeleton::cmd()->Nice::Property::getAttributeWithDefault<bool>(Ltcs::Iif::Svc::AttributeName::isReachable(), false);
#else
        m_config->getWithDefault<std::bool>(Ltcs::Iif::Svc::PropertyName::isReachable(), false);
#endif

        Nice::Application::distribute(Basda::ServiceWorker::config()->node("CFG"));
        Nice::Application::distribute(Basda::ServiceWorker::config()->node("CMD"));
        Nice::Application::distribute(Basda::ServiceWorker::config()->node("VAR"));
        Nice::Application::distribute(Basda::ServiceWorker::config()->node("NAME"));
        Nice::Application::distribute(Basda::ServiceWorker::config(), false);
    }
    catch(Ice::ConnectionRefusedException & e)
    {
        F_THROW("IIF Simulation Server not found.");
    }
    catch(const Nice::Exception & ex)
    {
        E_LOG(ex);
    }
    catch(const std::exception & ex)
    {
        E_LOG(ex.what());
    }
    catch(...)
    {
        E_LOG("unknown error");
    }
} /* ctor */

/*!************************************
* @brief dtor.
* Closes the communicator.
*/
Ltcs::IifServiceWorker::~IifServiceWorker()
{
    U6_LOG(__FUNCTION__);
    deinit();
} /* dtor */

/*!**********************************
* @brief Initialize property tree.
* Create a proxy for the given instrument and focal station according to the configured standards.  */
void Ltcs::IifServiceWorker::init()
{
    U6_LOG("init-> " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));

    try
    {
        /// Ice communicator
        Ice::CommunicatorPtr m_communicator;

        Ice::InitializationData initData;

        initData.properties = Ice::createProperties();
        initData.properties->setProperty("Ice.Override.ConnectTimeout", "1000");
#if 0
        // Maximum transfer per call of ice is 1MByte(default is 1024,
        // unit is 1KByte) needed for cameras with big images
        initData.properties->setProperty("Ice.MessageSizeMax", "65536");
        initData.properties->setProperty("Ice.MessageSizeMax", "1388134400");
#endif

        m_communicator = Ice::initialize(initData);

        U9_LOG("connect to Factory -t:" + m_config->getAtNode<Nice::Endpoint>(Ltcs::Iif::Svc::PropertyName::connectionAdapter()).toString())

        m_factory = lbto::FactoryPrx::checkedCast(
            m_communicator->stringToProxy("Factory -t:" + m_config->getAtNode<Nice::Endpoint>(Ltcs::Iif::Svc::PropertyName::connectionAdapter()).toIceEndpoint()));

        if(!m_factory)
        {
            std::string errorMsg("IIF Server not found.");
            E_LOG(errorMsg)
            E_THROW(errorMsg);
        }

        /* Get a proxy object for this instrument.
        * If the proxy already exist, the arbitrator will
        * link the proper IIF instance with it.
        * Otherwise, it will create a new IIF instance for this client.
        * fstation is "bentGregorianBack right" for example. */
        std::string fstation = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgFocalStation());

        /* this is SHARK in our case... */
        std::string instru = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgInstrumentID());

        /* commandSide is "both", "left" or "right" */
        std::string commandSide = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgCommandSide());

        m_iifSidReg = m_iifSidCfg = Ltcs::IifSide(commandSide);
        I_LOG("init " << m_iifSidCfg.toString())

#ifdef LN_TCS_SINGLE_ICE
        /* inflexible interface used up to Com-3 */
        m_iif = m_factory->create(m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName()), fstation, instru);

        if(!m_iif)
        {
            E_THROW("Invalid proxy: IIF Factory fails to create new instance " + fstation + ". Probably wrong combin. of Focal Station and InstrumentID. Check logs.");
        }
#else

        /* split interface handling modes with one side set to NONE or parasitic mode. */
        const Ltcs::IifStation gregor(fstation);
        U9_LOG(m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName()) << " ~ " << fstation << " ~ " << instru << " ~ " << commandSide << " ~ " << gregor.toString() << " ~ " << m_iifSidCfg.toIntIdx());
        switch(m_iifSidCfg.s)
        {
        case Ltcs::SIDE_BOTH:
//            m_iif[0] = m_factory->create(instru + "_LTCS_SX_IIF", gregor.toString() + " left", instru);
            m_iif[0] = m_factory->create(instru + "_LTCS_SX_IIF", gregor.toString() + " both", instru);

            if(!m_iif[0])
            {
                E_THROW("Invalid proxy: IIF Factory fails to create new instance " + fstation + " left. Probably wrong combination of Focal Station and InstrumentID.");
            }
            else
            {
                U9_LOG("Proxy name : " << (instru + "_LTCS_SX_IIF") << "; Focal Station : " << (gregor.toString() + " left"));
            }

//            m_iif[1] = m_factory->create(instru + "_LTCS_DX_IIF", gregor.toString() + " right", instru);
            m_iif[1] = m_factory->create(instru + "_LTCS_DX_IIF", gregor.toString() + " both", instru);

            if(!m_iif[1])
            {
                E_THROW("Invalid proxy: IIF Factory fails to create new instance " + fstation + " right. Probably wrong combination of Focal Station and InstrumentID.");
            }
            else
            {
                U9_LOG("Proxy name : " << (instru + "_LTCS_DX_IIF") << "; Focal Station : " << (gregor.toString() + " right"));
            }

            break;
        default :
            U7_LOG("m_iif : " << m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName()) << tilde << fstation << tilde << instru);
            m_iif[m_iifSidCfg.toIntIdx()] = m_factory->create(m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName()), fstation, instru);
            if(m_iif[m_iifSidCfg.toIntIdx()])
            {
                U9_LOG("Proxy name : " << m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgProxyName()) << "; Focal Station : " << fstation << " ~ " << instru << " ~ " << commandSide << " ~ " << m_iifSidCfg.toIntIdx());
            }

            if(!m_iif[m_iifSidCfg.toIntIdx()])
            {
                E_THROW("Invalid proxy: IIF Factory fails to create new instance " + fstation + ". Probably wrong combination of Focal Station and InstrumentID. Check logs.");
            }
        }
#endif

        I_LOG("IIF client list: ");
        std::string connectionList = m_factory->listConnections();
//        connectionList.erase(std::find(connectionList.begin(), connectionList.end(), '\n'));
        I_LOG(connectionList);

        /* construct the full path of the m_fMonit file name in compliance with liifSnapshotGUI */
        ostringstream sdir;
        sdir << getenv("INSROOT") << "/var/log/IifSnapshot";
        m_fMonit = sdir.str();

        /* construct the full path of the m_fFtpl file name in compliance with the GEIRS parser */
        ostringstream tdir;
        tdir << getenv("INSROOT") << "/var/log/ltcsSnapshot.tpl";
        m_fFtpl = tdir.str();

        status();
    }
    catch(const Ice::ConnectionRefusedException & ex)
    {
        setReachable(false);
        E_THROW(::Basda::ServiceInitException, "Can't connect to IIF interface.");
    }
    catch(const Basda::Dev::OpenException & ex)
    {
        setReachable(false);
        E_THROW(::Basda::ServiceInitException, ex, "Can't initiate service " << m_name << ".");
    }
    catch(const Basda::Dev::InitException & ex)
    {
        setReachable(false);
        E_THROW(::Basda::ServiceInitException, ex, "Can't initiate service " << m_name << ".");
    }
    catch(...)
    {
        setReachable(false);
        E_LOG(__FUNCTION__ << " ~ " << isIifReachable());
        throw;
    }

    Basda::ServiceWorker::sendStateStatus("INIT", -1);
} /* init */

/****************************
* Remove the entry of this factory and destroy the ICE communicator. */
void Ltcs::IifServiceWorker::deinit()
{
    U6_LOG(__FUNCTION__ << " ~ reset-> " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));
    ///close the communicator
#ifdef LN_TCS_SINGLE_ICE
    if(m_iif)
    {
        try
        {
            m_factory->destroy(m_iif);
        }
        catch(Ice::ConnectionRefusedException & e)
        {
            // iif service is offline
        }

        if(m_communicator)
            m_communicator->destroy();
    }
#else
    for(int s = 0; s < 2; s++)
    {
        if(m_iif[s])
        {
            try
            {
                m_factory->destroy(m_iif[s]);
            }
            catch(Ice::ConnectionRefusedException & e)
            {
                // iif service is offline
            }
        }
    }

    if(m_communicator)
        m_communicator->destroy();
#endif

    Basda::ServiceWorker::sendStateStatus("RESET", -1);
} /* deinit */

/****************************
* @brief activate the interface. */
void Ltcs::IifServiceWorker::activate()
{
    U6_LOG("activate ~ " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));

    lbto::result res = iifAuthorize();
    logResults(res, "Authorize");
    if(res.rescode != lbto::Result::SUCCESS)
    {
        /* to have a sucessful 'make check' after merging with the pang service, this E_THROW is changed to a W_LOG, RJM 2016-12-06
        * also: the pang service does not need authorization...
        W_LOG("We are not authorized to control one or both sides of the telescope. This authorization is done by the TO through the IIF GUI.")
        * We don't use E_THROW because all the useful monitoring does not need authorization!
        E_THROW(::Basda::ServiceActivateException,
                "We are not authorized to control the telescope. This authorization is done by the TO through the IIF GUI.") */
        W_LOG("We are not authorized to control one or both sides of the telescope. This authorization is done by the TO through the IIF GUI.")
    }

    N_LOG("Service started");
    Basda::ServiceWorker::sendStateStatus("ACTIV", -1);
} /* activate */

/****************************
* @brief deactivate the interface */
void Ltcs::IifServiceWorker::deactivate()
{
    U6_LOG("deactivate ~ " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));
    Basda::ServiceWorker::sendStateStatus("DEACT", -1);
} /* deactivate */

/****************************
* @brief shuts down the interface */
void Ltcs::IifServiceWorker::terminate()
{
    U6_LOG(__FUNCTION__ << " ~  " << Basda::ServiceWorker::state());
    Basda::ServiceWorker::sendStateStatus("EXIT", -1);
} /* terminate */

/*****************************/
void Ltcs::IifServiceWorker::errorOffline()
{
    F_LOG(__FUNCTION__ << " ~  Error in state: offline. Stay offline!")
    deactivate();
    deinit();
    Basda::ServiceWorker::setState(Basda::OFFLINE);
} /* errorOffline */

/*****************************/
void Ltcs::IifServiceWorker::errorStandby()
{
    F_LOG(__FUNCTION__ << " ~  Error in state: standby. Go offline!")
    deactivate();
    deinit();
    Basda::ServiceWorker::setState(Basda::OFFLINE);
} /* errorStandby */

/*****************************/
void Ltcs::IifServiceWorker::errorOnline()
{
    F_LOG(__FUNCTION__ << " ~  Error in state: online. Go offline!")
    deactivate();
    deinit();
    Basda::ServiceWorker::setState(Basda::OFFLINE);
} /* errorOnline */

/*****************************/
void Ltcs::IifServiceWorker::errorWorking()
{
    F_LOG(__FUNCTION__ << " ~  Error in state: working. Go offline!")
    deactivate();
    deinit();
    Basda::ServiceWorker::setState(Basda::OFFLINE);
}

/*****************************/
void Ltcs::IifServiceWorker::idleOffline()
{
    U6_LOG(__FUNCTION__)
    bool rc = Basda::ServiceWorker::timedWaitForNewState(Nice::Time::milliSeconds(30000));
    if(!rc)
        Basda::ServiceWorker::setState(Basda::STANDBY);
}

/*****************************/
void Ltcs::IifServiceWorker::idleStandby()
{
    U6_LOG(__FUNCTION__ << " ~  " << Basda::ServiceWorker::name() << " -- STANDBY IDLE -- " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));

    Nice::Time period = Basda::ServiceWorker::config()->Nice::Joint::getWithDefault<Nice::Time>(Ltcs::Iif::Svc::PropertyName::cfgInterval(), Nice::Time::seconds(300));

    const bool rc = Basda::ServiceWorker::timedWaitForNewState(period); // returns after period; false -> timed out

    if(false && !rc)
    {
        snapshot();
        toKVPairFile();
    }
}

/****************************
* @brief Periodically called after IIF.SVC.CFG.INTERVAL */
void Ltcs::IifServiceWorker::idleOnline()
{
    U6_LOG(__FUNCTION__ << " ~ " << Basda::ServiceWorker::name() << " -- ONLINE IDLE -- " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));

    Nice::Time period = Basda::ServiceWorker::config()->Nice::Joint::getWithDefault<Nice::Time>(Ltcs::Iif::Svc::PropertyName::cfgInterval(), Nice::Time::seconds(300));

    const bool rc = Basda::ServiceWorker::timedWaitForNewState(period); // returns after period; false -> timed out

    if(false && !rc)
    {
        snapshot();
        toKVPairFile();
    }
}


/****************************
* @brief does nothing      */
void Ltcs::IifServiceWorker::abort()
{
    U6_LOG(__FUNCTION__ << " ~ " << Basda::ServiceWorker::name() << " abort")
}

/****************************
* @brief does nothing      */
void Ltcs::IifServiceWorker::stop()
{
    U6_LOG(__FUNCTION__ << " ~ " << Basda::ServiceWorker::name() << " stop")
}

/****************************
* @brief read a single value via the IIF interface.
*
* @param ddnam Name in the data dictionary.
*  It is required that the string value returned by the IIF for that case
*  is the standard decimal representation!
* @param propname Property name, where the value is stored.
* @return the IIF value parsed as a double value.
* @warning If we have registered both sides of the telescope, the SX(left) side is queried. */
double Ltcs::IifServiceWorker::getDouble(const std::string & ddnam, const std::string & propnam)
{
//    U6_LOG(__FUNCTION__);
    double ret = 0;
    try
    {
#ifdef LN_TCS_SINGLE_ICE
        if(m_iif == 0)
        {
            E_LOG("No telescope proxy initialized");
            /* todo: return NaN */
            return ret;
        }
#else
        if(m_iif[0] == 0 && m_iif[1] == 0)
        {
            E_LOG("No telescope proxy initialized");
            /* todo: return NaN */
            return ret;
        }
#endif

        lbto::SeqDD ddentries;
        lbto::DDstruct dd;
        dd.DDname = ddnam;
        ddentries.push_back(dd);

        lbto::result res = iifGetParameter(ddentries);
        if(res.rescode == lbto::Result::SUCCESS)
        {
            std::stringstream ss;
            U7_LOG("ddnam : " << ddnam << ";propnam : " << propnam << " -> res.resmsg[1] : \"" << res.resmsg[1] << "\"");
            ss << res.resmsg[1];
            ss >> ret;
            m_config->setAtNode<std::string>(propnam, res.resmsg[1]);
            // m_cmd->attribute(propnam)->set<double>(ret);
        }
        else
        {
            E_LOG(propnam << " not found");
        }
    }

    catch(const Nice::Exception & ex)
    {
        setReachable(false);
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        E_LOG(log_msg);
    }
    catch(const IceUtil::Exception & ex)
    {
        setReachable(false);
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        E_LOG(log_msg);
    }
    catch(const std::exception & ex)
    {
        setReachable(false);
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        E_LOG(log_msg);
    }
    catch(...)
    {
        setReachable(false);
        cmdError(Nice::LoggerObject());
        if(worker()->terminated()) throw;
        E_LOG("Unknown error");
    }
    return ret;
} /* getDouble */

#if 0
/****************************
* @param ddnam
* @param propname
* @todo not implemented
*/
int Ltcs::IifServiceWorker::getInt(const std::string & ddnam, const std::string & propnam)
{
    W_LOG(__FUNCTION__ << " ~ not implemented");
    return 0;
} /* getInt */

/****************************
* @param ddnam
* @param propname
* @todo not implemented
*/
std::string Ltcs::IifServiceWorker::getString(const std::string & ddnam, const std::string & propnam)
{
    W_LOG(__FUNCTION__ << " ~ not implemented");
    return "Null";
} /* getString */
#endif /* 0 */


/*!***********************************
* @brief retrieve a generic parameter from the IIF.
* @param ddnam Associated name of the data dictionary
* @param propnam The Nice property name
* @param lognam string to be shown in the logs.
*    No log is created if this is NULL.
* @return the value of the IIF in the string format(as returned).
* @since 2016-11-11
* @author R. J. Mathar
*/
template <typename TTYPE>
std::string Ltcs::IifServiceWorker::getAny(const char * ddnam, std::string propnam, const char * lognam)
{
    U6_LOG(__FUNCTION__ << " ~ " << ddnam << " ~ " << propnam << " ~ " << lognam);
    std::string val;
    try
    {
#ifdef LN_TCS_SINGLE_ICE
        if(m_iif == 0)
        {
            E_LOG("No telescope proxy initialized");
            return std::string("NOTSPECIFIED");
        }
#else
        if(m_iif[0] == 0 && m_iif[1] == 0)
        {
            E_LOG("No telescope proxy initialized");
            return std::string("NOTSPECIFIED");
        }
#endif

        lbto::SeqDD ddentries;
        lbto::DDstruct dd;
        dd.DDname = ddnam;

        ddentries.push_back(dd);
        lbto::result res = iifGetParameter(ddentries);

        if(lognam)
            logResults(res, lognam);

        if(res.rescode == lbto::Result::SUCCESS)
        {
            val = res.resmsg[1];
            m_config->setAtNode<TTYPE>(propnam, val);
            // m_cmd->attribute(propnam)->set<TTYPE>(val);
        }

    }
    catch(const Nice::Exception & ex)
    {
        setReachable(false);
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        E_LOG(propnam << " " <<  log_msg);
    }
    catch(const IceUtil::Exception & ex)
    {
        setReachable(false);
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        E_LOG(propnam << " " <<  log_msg);
    }
    catch(const std::exception & ex)
    {
        setReachable(false);
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        E_LOG(propnam << " " << log_msg);
    }
    catch(...)
    {
        setReachable(false);
        cmdError(Nice::LoggerObject());
        if(worker()->terminated())
            throw;
        E_LOG(propnam << " " <<  "Unknown error");
    }
    return val;
} /* getAny */

/*!**************************
* @brief Obtain the RA from the iif interface.
* @return a string of the "HH:MM:SS.sss" format.
*/
std::string Ltcs::IifServiceWorker::getRA()
{
    U6_LOG(__FUNCTION__);
    return getAny<std::string>("MountAchievedRAHMS", Ltcs::Iif::Svc::PropertyName::varRA(), __func__);
} /* getRA */

/*!**************************************
* @brief read the right ascension from the IIF interface.
* @return the value in radians.
* @since 2016-11-11
* @author R. J. Mathar
*/
double Ltcs::IifServiceWorker::getRAradians()
{
    U6_LOG(__FUNCTION__);
    /* RA in a stringified HH:MM:SS.sss format */
    const std::string ra = getRA();

    /* convert to degrees and return as radians */
    return hexToDeg(ra) * M_PI/180.0;
} /* getRAradians */


/*!****************************
* @brief read the declination from the IIF ICE interface.
* @return A string of the +-DD:MM.SS.ssss format.
* @since 2016-11-11 forwarded bo getAny<std::string>(...)
*/
std::string Ltcs::IifServiceWorker::getDEC()
{
    U6_LOG(__FUNCTION__);
    return getAny<std::string>("MountAchievedDECDMS", Ltcs::Iif::Svc::PropertyName::varDEC(), __func__);
} /* getDEC */

/*!*********************************
* @param hdms a string of the form HH:MM::ss[.sss] or [+-]DD:MM:ss[.sss]
*
*  The two colons are mandatory. The sign in front of the second format is mandatory.
*  There must be no sign in front of the first format.
*  Re-implements Nice::DMS::fromString() .
* @return the value converted from hours or degrees to degrees.
* @since 2016-12-14
*/
double Ltcs::IifServiceWorker::hexToDeg(const std::string & hdms)
{
//    U6_LOG(__FUNCTION__);
    int deg, min;
    double sec;
    sscanf(hdms.c_str(),"%d:%d:%lf", &deg, &min, &sec);

    /* postive value in degrees, ignoring the sign of the deg component */
    double decInRad = abs(deg) + min/60.0 + sec/3600.0;

    /* bring sign back in. Note that comparision of deg with zero is not a good
    * idea because a declination starting with minus zero would lose the minus sign.
    * Return value in degrees. If the value starts with a sign, it already
    * is in degrees, otherwise it was hours and needs a factor 15 = 360/24 . */
    if(hdms.find('-') != std::string::npos)
        decInRad *= -1;
    else if(hdms.find('+') != std::string::npos)
        ;
    else
        decInRad *= 15.0;

    return decInRad;
} /* hexToDeg */

/*!**************************************
* @brief read the declination from the IIF interface.
* @return the value in radians.
* @since 2016-11-11
* @author R. J. Mathar
*/
double Ltcs::IifServiceWorker::getDECradians()
{
    U6_LOG(__FUNCTION__);
    /* declination in a stringified +-DD:MM:SS.sss format */
    const std::string dec = getDEC();

    /* convert to degrees and return as radians */
    return hexToDeg(dec) * M_PI/180.0;
}

/*************************
* @brief Read the parallactic angle from the IIF.
* @return The angle in units of degrees. */
double Ltcs::IifServiceWorker::getParallacticAngle()
{
//    U6_LOG(__FUNCTION__);
    return getDouble("ParAngle", Ltcs::Iif::Svc::PropertyName::varPA());
}

/*************************
* @brief Read the azimuth angle from the IIF.
* @return The angle in units of radians.
* @since 2016-12-13 */
double Ltcs::IifServiceWorker::getAz()
{
    U6_LOG(__FUNCTION__);
    return getDouble("MountAchievedAZ", Ltcs::Iif::Svc::PropertyName::varAZ());
}

/*************************
* @brief Read the altitude angle from the IIF.
* @return The angle in units of radians.
* @since 2016-12-13*/
double Ltcs::IifServiceWorker::getAlt()
{
    U6_LOG(__FUNCTION__);
    return getDouble("MountAchievedALT", Ltcs::Iif::Svc::PropertyName::varALT());
}

/*************************
* @brief Read the current EPOCH from the IIF.
* @return The epoch in units of UTC years
* @since 2016-12-13*/
double Ltcs::IifServiceWorker::getEpoch()
{
    U6_LOG(__FUNCTION__);
    return getDouble("Epoch", Ltcs::Iif::Svc::PropertyName::varEpoch());
}

/*!******************
* @brief read the univeral time coordinated from the IIF interface.
* @return a string of the "HH:mm:ss.ssss" format
* @since 2016-11-11 Forwarded to getAny<std::string>(...)
* @warning In the TCS simulation on linc2.mpia.de the value may be off by 1 minute(NTP synchronized?) */
std::string Ltcs::IifServiceWorker::getUTC()
{
    U6_LOG(__FUNCTION__);
    return getAny<std::string>("UTC", Ltcs::Iif::Svc::PropertyName::varUTC(), __func__);
}

/*!**********************
* @brief read the local siderial time from the IIF interface.
* @return A string in the HH:MM:SS.sss format.
* @since 2016-11-11 Forwarded to getAny<std::string>(...) */
std::string Ltcs::IifServiceWorker::getLST()
{
    U6_LOG(__FUNCTION__);
    return getAny<std::string>("LST", Ltcs::Iif::Svc::PropertyName::varLST(), __func__);
}

/*!******************
* @brief read the hour angle from the IIF interface.
* @since 2016-11-11 Forwarded to getAny<std::string>(...) */
double Ltcs::IifServiceWorker::getHourAngle()
{
    U6_LOG(__FUNCTION__);
    return getDouble("MountAchievedHA", Ltcs::Iif::Svc::PropertyName::varHA());
}


//std::string Ltcs::IifServiceWorker::getHourAngleHms()
//{
//    U6_LOG(__FUNCTION__);
//    return getAny<std::string>("MountAchievedHAHMS", Ltcs::Iif::Svc::PropertyName::varHourAngleHms(), __func__);
//}

/*!*********************************
* @brief read RA, DEC and some other principal variables in one shot
*  If the time defined by cfgInterval has been reached, call snapshot(),
*  otherwise do nothing, assuming that data are recent.
* @since 2016-12-13 */
void Ltcs::IifServiceWorker::poll()
{
    U6_LOG(__FUNCTION__);
    /* the most suitable time stamp is the one of the VAR.EPOCH,
    * because that is the one that is probably only updated from within snapshot.
    * The others may more at random be updated by difference clients, GUI's and so on. */
    Nice::Date prevRead = m_config->node(Ltcs::Iif::Svc::PropertyName::varEpoch(),false)->leaf()->timestamp();
    Nice::Time pollIntvl = m_config->getAtNode<Nice::Time>(Ltcs::Iif::Svc::PropertyName::cfgInterval());
    if(prevRead + pollIntvl < Nice::Date::now())
        /* previous touch of the Epoch variable was more than CFG.INTERVAL in the past,
        * so re-read the values from the IIF interface. */
        snapshot();
    U8_LOG(1);
} /* poll */

/*!*********************************
* @brief read all DD variables of interest in one shot.
*  The only purpose of this is to be faster than calling getRA(), getDEC() etc in sequence.
*  The string data are pushed into the properties in the same format as with
*  the other getXXX functions.
* @since 2016-12-13
* @since 2017-05-22 use XML configuration to fill m_keys[] */
void Ltcs::IifServiceWorker::snapshot()
{
    U6_LOG(__FUNCTION__ << " ~ Bypassed");
    return;
    try
    {
#ifdef LN_TCS_SINGLE_ICE
        if(m_iif == 0)
        {
            E_LOG("No telescope proxy initialized");
            return;
        }
#else
        if(m_iif[0] == 0 && m_iif[1] == 0)
        {
            E_LOG("No telescope proxy initialized");
            return;
        }
#endif

        /* for units etc see http://wiki.lbto.org/pub/Software/SWRelBuild2016B/DDNames */
        double d;

        /* read the parameters that have been XML-configured into m_keys[] */
        m_iifSidReg = getParam();

        std::stringstream ss;

        /* In addition duplicate(important) values that are a property in the property tree */
        for(unsigned int k = 0; k < m_keys.size(); k++)
        {
            ss.str("");
            ss.clear();
            if(m_keys[k].ddName == "MountAchievedRAHMS")
            {
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varRA(), m_keys[k].val);
//                I_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "MountAchievedDECDMS")
            {
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varDEC(), m_keys[k].val);
//                I_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "ParAngle")
            {
                ss << m_keys[k].val;
                ss >> d;
                m_config->setAtNode<double>(Ltcs::Iif::Svc::PropertyName::varPA(), d);
//                U4_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "UTC")
            {
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varUTC(), m_keys[k].val);
//                U4_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "LST")
            {
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varLST(), m_keys[k].val);
//                U4_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "MountAchievedALT")
            {
                ss << m_keys[k].val;
                ss >> d;
                m_config->setAtNode<double>(Ltcs::Iif::Svc::PropertyName::varALT(), d);
                Nice::DMS dms;
                /* the following setup of varALTSTring is basically superflous becuase it's again
                *  target in the MuntAchievecALDTMS further down. So only if that MountAchievedALTDMS
                *  is not included in the xml file, this here sets a value that will be included
                *  in the iif-GUI. */
                dms.setRadians(d);
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varALTString(), dms.toDMSString());
//                U4_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "MountAchievedALTDMS")
            {
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varALTString(), m_keys[k].val);
                // U4_LOG(m_keys[k].val);
//                I_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "MountAchievedAZ")
            {
                ss << m_keys[k].val;
                ss >> d;
                m_config->setAtNode<double>(Ltcs::Iif::Svc::PropertyName::varAZ(), d);
                //dms.setRadians(d);
                //m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varAZString(), dms.toDMSString());
//                U4_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "MountAchievedAZDMS")
            {
                m_config->setAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varAZString(), m_keys[k].val);
//                I_LOG(m_keys[k].toKVPairString());
            }
            else if(m_keys[k].ddName == "Epoch")
            {
                ss << m_keys[k].val;
                ss >> d;
                m_config->setAtNode<double>(Ltcs::Iif::Svc::PropertyName::varEpoch(), d);
//                U4_LOG(m_keys[k].toKVPairString());
            }
        }
    }
    catch(const Nice::Exception & ex)
    {
        E_LOG(ex);
    }
    catch(const IceUtil::Exception & ex)
    {
        E_LOG(ex);
    }
    catch(const std::exception & ex)
    {
        E_LOG(ex.what());
    }
    catch(...)
    {
        cmdError(Nice::LoggerObject());
        if(worker()->terminated())
            throw;
        E_LOG("Unknown error");
    }
} /* snapshot */

/*!*********************************
* @brief Flag whether one or both sides of the telescope are on source.
* The function polls the R_IIFOnSource or S_IIFOnSource keyword of the IIF
* associated with the side LINC has registered with. If these values are
* 1, the function returns.
* @return true if the source is acquired, false if not.
* @since 2017-06-08 */
bool Ltcs::IifServiceWorker::isSlewing()
{
    U6_LOG(__FUNCTION__);
    try
    {
#ifdef LN_TCS_SINGLE_ICE
        if(m_iif == 0)
        {
            E_LOG("No telescope proxy initialized");
            return true;
        }
#else
        if(m_iif[0] == 0 && m_iif[1] == 0)
        {
            E_LOG("No telescope proxy initialized");
            return true;
        }
#endif
        /* update local copy of DD, the m_keys[] */
        poll();

        /* figure out whether to look for R_IIFOnSource or L_IIFOnSource */
        Ltcs::IifSide station(getLINCstation());

        bool ons[2] = { false, false};

        /* Search for the two [LR]_IIFONSource m_keys in the DD */
        for(unsigned int k = 0; k < m_keys.size(); k++)
        {
            if(m_keys[k].ddName == "L_IIFOnSource")
                ons[0] =(m_keys[k].val == "1") ? true  : false;
            else if(m_keys[k].ddName == "R_IIFOnSource")
                ons[1] =(m_keys[k].val == "1") ? true  : false;
        }
        // std:cerr <<__FILE__ << " " << __LINE__ << " " << station.toString() << " onsleft " << ons[0] << " onsright " << ons[1] << std::endl;

        /* if we have one or two sides , require that the associated value
        * is 1(true) at that side. If we are not registered on any side,
        * don't require any specific value to leave the loop. */
        if(station.s == Ltcs::SIDE_LEFT)
        {
            return ! ons[0];
        }
        else if(station.s == Ltcs::SIDE_RIGHT)
        {
            return ! ons[1];
        }
        else if(station.s == Ltcs::SIDE_BOTH)
        {
            return !(ons[0] && ons[1]);
        }
        else
            /* for SIDE_NONE, we return true meaning basically that there was
            * no requirement of acquiring the star and that is fulfilled. */
            return false;
    }
    catch(const Nice::Exception & ex)
    {
        E_LOG(ex);
    }
    catch(const IceUtil::Exception & ex)
    {
        E_LOG(ex);
    }
    catch(const std::exception & ex)
    {
        E_LOG(ex.what());
    }
    catch(...)
    {
        cmdError(Nice::LoggerObject());
        if(worker()->terminated())
            throw;
        E_LOG("Unknown error");
    }
    return true;
} /* isSlewing */


lbto::result Ltcs::IifServiceWorker::iifSyncPresetTelescope(double leftRotAngle, const std::string & leftRotMode, const std::string & leftTelescopeMode, double rightRotAngle, const std::string & rightRotMode, const std::string & rightTelescopeMode)
{
    U6_LOG(__FUNCTION__);
    lbto::result res;
    int counter = 0;

    if((m_iifSidCfg.s == Ltcs::SIDE_BOTH && (m_iif[0] && m_iif[1])) || (m_iifSidCfg.s == Ltcs::SIDE_LEFT && m_iif[0]))
    {
        int right_interface = 1;
        if(m_iifSidCfg.s == Ltcs::SIDE_LEFT)right_interface = 0;
        try
        {
            counter++;
            res = m_iif[0]->BinocularControl("SYNCPRESET");
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }

            counter++;
            Ice::AsyncResultPtr prleft = m_iif[0]->begin_PresetTelescope(leftRotAngle, leftRotMode, leftTelescopeMode, "left");

            counter++;
            res = m_iif[right_interface]->BinocularControl("SYNCPRESET");
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }

            counter++;
            Ice::AsyncResultPtr prright = m_iif[right_interface]->begin_PresetTelescope(rightRotAngle, rightRotMode, rightTelescopeMode, "right");

            counter++;
            res = m_iif[0]->end_PresetTelescope(prleft);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
            counter++;
            res = m_iif[right_interface]->end_PresetTelescope(prright);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
        }
        catch(...)
        {
            E_LOG(__FUNCTION__ << " ~ " << counter);
        }
    }
    U8_LOG(__FUNCTION__);
    return res;
}



/*!**********
* @brief Initiate telescope slewing and tracking.
* @param[in] cat The catalogue to be used.
* @author R. J. Mathar
* @since 2017-03-17 */
std::string Ltcs::IifServiceWorker::setPresetTelescopeCat(const lbto::SeqPos2 & stars)
{
    U6_LOG(__FUNCTION__ << " ~ Preset Telescope " << stars.size() << " stars")
    for(unsigned int s = 0; s < stars.size(); s++)
    {
        I_LOG(stars[s])
    }
    Basda::ServiceWorkerSkeleton::cmdStart("setPresetTelescope");

    /* ensure that the isSlewing() results will be "true" in the meantime, even if polling
    * generates slow updates. This is a flag to the derotators that would start too early... */
    for(unsigned int k = 0; k < m_keys.size(); k++)
    {
        if(m_keys[k].ddName == "L_IIFOnSource" || m_keys[k].ddName == "R_IIFOnSource")
            m_keys[k].val = "0";
    }

    std::string ret;
    try
    {
#ifdef LN_TCS_SINGLE_ICE
        if(m_iif == 0)
        {
            E_LOG("No telescope proxy initialized");
            return std::string("PreSetTelescop result status: Error");
        }
#else
        if(m_iif[0] == 0 && m_iif[1] == 0)
        {
            E_LOG("No telescope proxy initialized");
            return std::string("PreSetTelescop result status: Error");
        }
#endif

        /* this is "left" or "right" or perhaps "both"
        * todo: actually a preset should be done with the mount without regard to
        * the command side(!) */
        std::string commandSide = m_iifSidCfg.toString();

        /* This should always be 'POSITION' in the LN case
        * In the telescope operator's GUI this means one would start in the "slewing"
        * and end up in the "velocity mode". */
        std::string rotatorMode = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgRotatorMode());

        I_LOG("command side " << commandSide)

        lbto::result res = iifSetStars2(stars);
        logResults(res,"SetStars2");

        /* save the catalog for further use by clients who want to
        * get pointing coordinates during the forthcoming run */
        m_scat = stars;

        /* indicate that the m_kfpHash is obsolete: invalidate the hashed version */
        m_kfpHash[0].clear();
        m_kfpHash[1].clear();

#ifdef LN_TCS_SINGLE_ICE
        /* old version: we must be authorized on exactly the same sides
        * as in our configuration(while starting the iif proxy...) */
        res = iifPresetTelescope(0.0, rotatorMode, "TRACK", commandSide);
#else /* LN_TCS_SINGLE_ICE */
        /* scenarios: m_iifSidReg = m_iifSidCfg = both; use synchronized preset
        * m_iifSidReg = m_iifSidCfg = left or right, use asynchronous preset
        * m_iifSidReg <> m_iifSidCfg = left or right
        * if(m_iifSidReg.s == m_iifSidCfg.s && m_iifSidReg.s == Ltcs::SIDE_BOTH) */
        if(m_iifSidReg.s == m_iifSidCfg.s)
        {
            /* This handles the case where(i) we're authorized on both sides and also registered as "both" in the iif/ICE,
            * or(ii) authorized on one side and registered as "both" or only that side in the iif/ICE..
            * We cannot just send in order
            * res = m_iif[0]->BinocularControl("SYNCPRESET");
            * res = m_iif[0]->PresetTelescope(0.0, rotatorMode, "TRACK", "left");
            * res = m_iif[1]->BinocularControl("SYNCPRESET");
            * res = m_iif[1]->PresetTelescope(0.0, rotatorMode, "TRACK", "right");
            * because the miif[0]Preset does not return before the second preset
            * has been sent. So we must parallelize the two operations... */
#if 0
            /* explicitly threaded version */
            res = m_iif[0]->BinocularControl("SYNCPRESET");
            std::thread prleft(iifPresetTelescope2,std::ref(m_iif[0]),0.0, rotatorMode, std::string("TRACK"), std::string("left"));
            res = m_iif[1]->BinocularControl("SYNCPRESET");
            std::thread prright(iifPresetTelescope2,std::ref(m_iif[1]),0.0, rotatorMode, std::string("TRACK"), std::string("right"));
            prleft.join();
            prright.join();
#else /* 0 */
            /* version using the ICE-type of splitting the commands with callbacks */
            res = m_iif[0]->BinocularControl("SYNCPRESET");
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }

            Ice::AsyncResultPtr prleft = m_iif[0]->begin_PresetTelescope(0.0, rotatorMode, "TRACK", "left");

            res = m_iif[1]->BinocularControl("SYNCPRESET");
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }

            Ice::AsyncResultPtr prright = m_iif[1]->begin_PresetTelescope(0.0, rotatorMode, "TRACK", "right");

            res = m_iif[0]->end_PresetTelescope(prleft);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
            res = m_iif[1]->end_PresetTelescope(prright);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
#endif /* 0 */
        }
        else if((m_iifSidReg.s == SIDE_LEFT && m_iifSidCfg.s == SIDE_RIGHT) || (m_iifSidReg.s == SIDE_RIGHT && m_iifSidCfg.s == SIDE_LEFT))
        {
            E_LOG("Iif configuration " + m_iifSidCfg.toString() + " but TCS assigned " +  m_iifSidReg.toString())
        }
        else
        {
            /* this is the case where we're authorized on one side and also
            * registered as either "left" or "right" but not "both" */
            res = iifPresetTelescope(0.0, rotatorMode, "TRACK", commandSide);
        }
#endif /* LN_TCS_SINGLE_ICE */

        logResults(res,"PresetTelescope");
    }
    catch(const Nice::Exception & ex)
    {
        setReachable(false);
        E_LOG(ex);
    }
    catch(const IceUtil::Exception & ex)
    {
        setReachable(false);
        E_LOG(ex);
    }
    catch(const std::exception & ex)
    {
        setReachable(false);
        E_LOG(ex.what());
    }
    catch(...)
    {
        setReachable(false);
        cmdError(Nice::LoggerObject());
        if(worker()->terminated()) throw;
        E_LOG("Unknown error");
    }
    return ret;
}

/*!**********
* @param[in] star The point on the sky to be reached.
*    See LbtoIIFIngterface.ice for the structures position and position2
* @author R. J. Mathar
* @since 2017-01-21 */
std::string Ltcs::IifServiceWorker::setPresetTelescopeStar(const struct lbto::position2 & star)
{
    U6_LOG(__FUNCTION__);
    /* build a star catalogue with a single star and call the
    * interface that uses a full catalogue for setup */
    lbto::SeqPos2 cat;
    cat.push_back(star);

    return setPresetTelescopeCat(cat);
} /* setPresetTelescope */

/*!**********
* @brief Preset the telescope to some Ra/Dec position(considered epoch 2000).
* @param _raH The hour component of RA. Range 0-23.
* @param _raMin The minute component of RA. Range 0-59.
* @param _raSec The seconds component of RA. Range 0-60.
* @param _decDeg The degree component of DEC. Range -90 to +90.
* @param _decMin The minute component of DEC(generally unsigned positive value)
* @param _decSec The seconds component of DEC(generally unsigned positive value)
*  To instantiate small but negative declinations in the range from 0 to minus 1
*  degrees, where _decDeg =0 and perhaps also _decMin=0, use a negative
*  value of at least one of the three _decDeg, _decMin or _decSec. This
*  means the absolute value of the declination angle is the sum of the three
*  positive values |_decDeg|+|_decMin|/60+|_decSec|/3600, and the
*  sign is negative if at least one of the _decDeg, _decMin and _decSec
*  is negative. */
std::string Ltcs::IifServiceWorker::setPresetTelescope(int _raH, int _raMin, double _raSec, int _decDeg, int _decMin, double _decSec)
{
    U6_LOG(__FUNCTION__);
    /* construct the two angles of RA and DEC */
    Nice::DMS ra;
    ra.setH(_raH, _raMin, _raSec);

    Nice::DMS dec;
    dec.setD(_decDeg, _decMin, _decSec);

    struct lbto::position2 star;
    star.coord1 = ra.radians();
    star.coord2 = dec.radians();
    star.epoch = 2000;
    star.equinox = "J2000";
    star.type = "SIDEREAL";
    star.system = "RADEC";

    return setPresetTelescopeStar(star);
} /* setPresetTelescope */

/*!*****************************************
* @param offx first coordinate in units of radians or seconds(depending on _coordSys)
* @param offy second coordinate in units of radians.
* @param _coordSys The coordinate system of the first two parameters, like "RADEC" or "DETXY"
* @param _side "left" or "right" "both"
* @since 2017-10-31
* @return */
std::string Ltcs::IifServiceWorker::setOffsetGuiding(double offx, double offy, const std::string & _coordSys, const std::string & _side)
{
    U6_LOG(__FUNCTION__);
    std::string ret;
#ifdef LN_TCS_SINGLE_ICE
    if(m_iif == 0)
    {
        E_LOG("No telescope proxy initialized");
        return std::string("OffsetGuiding result status: Error");
    }
#else
    if(m_iif[0] == 0 && m_iif[1] == 0)
    {
        E_LOG("No telescope proxy initialized");
        return std::string("OffsetGuiding result status: Error");
    }
#endif

    try
    {
        U9_LOG("OFFX : " << offx << "; OFFY : " << offy << "; COORD SYS : " << _coordSys << "; SIDE : " << _side);

        lbto::result res;
#ifdef LN_TCS_SINGLE_ICE
        res = iifOffsetGuiding(0.0, offx, offy, _coordSys, _side);
#else
        Ltcs::IifSide reqSi(_side);
        if(m_iifSidReg.s == m_iifSidCfg.s && m_iifSidReg.s == Ltcs::SIDE_BOTH)
        {
            /* purely sequential does not work because OffSetGuiding waits before returning
            m_iif[reqSi.toIntIdx()]->BinocularControl("SYNCOFFSET");
            m_iif[reqSi.toIntIdx()]->OffsetGuiding(0.0, offx, offy, _coordSys, _side);
            m_iif[1-reqSi.toIntIdx()]->BinocularControl("SYNCOFFSET");
            m_iif[1-reqSi.toIntIdx()]->OffsetGuiding(0.0, 0., 0., _coordSys, _side); */
            const int sActive = reqSi.toIntIdx();
            const int sPassive = 1 - sActive;
            /* opposite side(which does not send an offset....) */
            const std::string opSide = (reqSi.s == Ltcs::SIDE_LEFT) ? "right" : "left";
            m_iif[sActive]->BinocularControl("SYNCOFFSET");
            Ice::AsyncResultPtr prActive = m_iif[sActive]->begin_OffsetGuiding(0.0, offx, offy, _coordSys, _side);
            m_iif[sPassive]->BinocularControl("SYNCOFFSET");
            Ice::AsyncResultPtr prPassive = m_iif[sPassive]->begin_OffsetGuiding(0.0, 0., 0., _coordSys, opSide.c_str());
            res = m_iif[sActive]->end_OffsetGuiding(prActive);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
            res = m_iif[sPassive]->end_OffsetGuiding(prPassive);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
        }
        else
        {
            /* instrument configured one-sided and telescope has authorized that side or both */
            res = iifOffsetGuiding(0.0, offx, offy, _coordSys, _side);
        }
#endif
        if(res.rescode != lbto::Result::SUCCESS)
        {
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                W_LOG(res.resmsg[m])
            }
            ret = res.resmsg[1];
        }
        else
            ret = res.resmsg[0];

        logResults(res,"OffsetGuiding");
        //error could be: [OffsetGuiding execution failed: Requested side not allowed for this instrument / focal station combination]

    }
    catch(const Nice::Exception & ex)
    {
        setReachable(false);
        E_LOG(ex);
    }
    catch(const IceUtil::Exception & ex)
    {
        setReachable(false);
        E_LOG(ex);
    }
    catch(const std::exception & ex)
    {
        setReachable(false);
        E_LOG(ex.what());
    }
    catch(...)
    {
        setReachable(false);
        cmdError(Nice::LoggerObject());
        if(worker()->terminated()) throw;
        E_LOG("Unknown error");
    }
    return ret;
} /* setOffsetGuiding */


lbto::result Ltcs::IifServiceWorker::iifSyncOffsetPointing(double leftOffX,  double leftOffY,  const std::string & leftCoordSys,  const std::string & leftMoveType,
                                                           double rightOffX, double rightOffY, const std::string & rightCoordSys, const std::string & rightMoveType)
{
    U6_LOG(__FUNCTION__);
    lbto::result res;
    int counter = 0;

    if((m_iifSidCfg.s == Ltcs::SIDE_BOTH && (m_iif[0] && m_iif[1])) || (m_iifSidCfg.s == Ltcs::SIDE_LEFT && m_iif[0]))
    {
        int right_interface = 1;
        if(m_iifSidCfg.s == Ltcs::SIDE_LEFT)right_interface = 0;
        try
        {
            counter++;
            double rotatorOffset = 0.0;
            res = m_iif[0]->BinocularControl("SYNCOFFSET");
            logResults(res, "BinocularControl left");
            counter++;
            Ice::AsyncResultPtr OffLeft = m_iif[0]->begin_OffsetPointing2(rotatorOffset, leftOffX, leftOffY, leftCoordSys, leftMoveType, "left");

            counter++;
            res = m_iif[right_interface]->BinocularControl("SYNCOFFSET");
            logResults(res, "BinocularControl right");

            counter++;
            Ice::AsyncResultPtr OffRight = m_iif[right_interface]->begin_OffsetPointing2(rotatorOffset, rightOffX, rightOffY, rightCoordSys, rightMoveType, "right");

            counter++;
            res = m_iif[0]->end_OffsetPointing2(OffLeft);
            logResults(res, "OffsetPointing2 left");

            counter++;
            res = m_iif[right_interface]->end_OffsetPointing2(OffRight);
            logResults(res, "OffsetPointing2 right");
        }
        catch(...)
        {
            E_LOG(__FUNCTION__ << " ~ " << counter);
            std::string what = boost::current_exception_diagnostic_information();
            std::string toErase = "std::exception::what:";
            size_t pos = what.find(toErase);
            if(pos != std::string::npos)what.erase(pos, toErase.length());
            what.erase(std::remove(what.begin(), what.end(), '\n'), what.end());
            std::stringstream log_msg;
            log_msg << what;
            E_LOG(log_msg.str());
        }
    }
    U8_LOG(__FUNCTION__);
    return res;
}


/*!*****************************************
* @param offx first coordinate in units of radians or seconds(depending on _coordSys)
* @param offy second coordinate in units of radians.
* @param _coordSys The coordinate system of the first two parameters, "RADEC" or "DETXY". Currently only "RADEC" and "DETXY" are supported, not "AZALT"
* @param _moveType relative or absolute flag, "REL", "ABS", "RELCS", "ABSCS"
* @param _side "left" or "right" "both"
* @since 2017-05-30 use iifOffsetPointing2, not iifOffsetPointing.
* @return */
std::string Ltcs::IifServiceWorker::setOffsetPointing(double offx, double offy, const std::string & _coordSys, const std::string & _moveType, const std::string & _side)
{
    U6_LOG(__FUNCTION__);
    std::string ret;
#ifdef LN_TCS_SINGLE_ICE
    if(m_iif == 0)
    {
        E_LOG("No telescope proxy initialized");
        return std::string("OffsetPointing result status: Error");
    }
#else
    if(m_iif[0] == 0 && m_iif[1] == 0)
    {
        E_LOG("No telescope proxy initialized");
        return std::string("OffsetPointing result status: Error");
    }
#endif

    try
    {
        U9_LOG("OFFX : " << offx << "; OFFY : " << offy << "; COORD SYS : " << _coordSys << " ; moveType : " << _moveType << "; SIDE : " << _side);

#ifdef LN_TCS_SINGLE_ICE
        lbto::result res = iifOffsetPointing2(0.0, offx, offy, _coordSys, _moveType, _side);
#else
        Ltcs::IifSide reqSi(_side);
        lbto::result res;
        if(m_iifSidReg.s == m_iifSidCfg.s && m_iifSidReg.s == Ltcs::SIDE_BOTH)
        {
            const int sActive = reqSi.toIntIdx();
            const int sPassive = 1-sActive;
            /* opposite side(which does not send an offset....)
            */
            const std::string opSide =(reqSi.s == Ltcs::SIDE_LEFT) ? "right" : "left";
            m_iif[sActive]->BinocularControl("SYNCOFFSET");
            Ice::AsyncResultPtr prActive =
                m_iif[sActive]->begin_OffsetPointing2(0.0, offx, offy, _coordSys, _moveType, _side);
            m_iif[sPassive]->BinocularControl("SYNCOFFSET");
            Ice::AsyncResultPtr prPassive =
                m_iif[sPassive]->begin_OffsetPointing2(0.0, 0., 0., _coordSys, _moveType, opSide.c_str());
            res = m_iif[sActive]->end_OffsetPointing2(prActive);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
            res = m_iif[sPassive]->end_OffsetPointing2(prPassive);
            for(unsigned int m = 0; m < res.resmsg.size(); m++)
            {
                I_LOG(res.resmsg[m])
            }
        }
        else
        {
            /* instrument configured one-sided and telescope has authorized that side or both */
            res = iifOffsetPointing2(0.0, offx, offy, _coordSys, _moveType, _side);
        }
#endif

        if(res.rescode != lbto::Result::SUCCESS)
            ret = res.resmsg[1];
        else
            ret = res.resmsg[0];

        logResults(res,"OffsetPointing");
        //error could be: [OffsetPointing execution failed: Requested side not allowed for this instrument / focal station combination]
    }
    catch(const Nice::Exception & ex)
    {
        setReachable(false);
        E_LOG(ex);
    }
    catch(const IceUtil::Exception & ex)
    {
        setReachable(false);
        E_LOG(ex);
    }
    catch(const std::exception & ex)
    {
        setReachable(false);
        E_LOG(ex.what());
    }
    catch(...)
    {
        setReachable(false);
        cmdError(Nice::LoggerObject());
        if(worker()->terminated()) throw;
        E_LOG("Unknown error");
    }
    return ret;
} /* setOffsetPointing */

/*********************************
* @brief query a set of rotator polynomials from the IIF
* @param _starttime start time of the first polynomial in MJD
* @param _count Number of polynomial pieces.
* @param _interval Length of each time interval, should be positive.
* @param _order Order of the polynomial, non-negative.
* @param _side "left" or "right" or "both"
*   The case of "both" lets the result become the arithmetic mean of both sides.
* @since 2017-05-27 Take the mean of the two polynmial ladders if side == "both". */
Nice::SeqSeqDouble Ltcs::IifServiceWorker::getRotatorPolynomials(double _starttime, int _count, double _interval, int _order, const std::string & _side)
{
    U6_LOG(__FUNCTION__ << " ~ Requesting " << _count << " on " << _side << " polynomials starting " << _starttime << " MJD");
    Nice::SeqSeqDouble polynomials;
#ifdef LN_TCS_SINGLE_ICE
    if(m_iif == 0)
    {
        E_LOG("No telescope proxy initialized");
        return polynomials;
    }
#else
    if(m_iif[0] == 0 && m_iif[1] == 0)
    {
        E_LOG("No telescope proxy initialized");
        return polynomials;
    }
#endif

#if 0
    /* fixed configuration... replaced by command line argument in May 2017 */
    std::string _side = m_config->Nice::Joint::getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgCommandSide());
#endif

    lbto::result res = iifGetRotatorPolynomials(_starttime, _count, _interval, _order, _side);
    I_LOG("Got polynomials starting " << _starttime << " MJD " << res.rescode);

    if(res.rescode == lbto::Result::SUCCESS)
    {
        I_LOG("Got polynomials starting " << _starttime << " MJD ");
        if(_side != "both")
        {
            // first entry "GetRotatorPolynomials result status: OK 0 0" is skiped..
            for(std::size_t i = 1; i < res.resmsg.size(); i++)
            {
                std::stringstream ss;
                double time, angle, v, aby2;
                ss << res.resmsg[i];
                Nice::SeqDouble polynom;
                ss >> time >> angle;
                polynom.push_back(time);
                polynom.push_back(angle);
                if(_order > 0)
                {
                    ss >> v;
                    polynom.push_back(v);
                }
                if(_order > 1)
                {
                    ss >> aby2;
                    polynom.push_back(aby2);
                }
                polynomials.push_back(polynom);
            }
        }
        else
        {
            /* if messag size is 7, for example , the status [0] is skipped
            * and [1] combined with [4], [2] with [5] and [3] with [6].
            * [1..3] refer to SX, [4..6] to DX then. */
            const int stride = res.resmsg.size() / 2;
            for(std::size_t i = 1; i + stride < res.resmsg.size(); i++)
            {
                std::stringstream ss[2];
                ss[0] << res.resmsg[i];
                ss[1] << res.resmsg[i + stride];

                double time[2], angle[2];
                ss[0] >> time[0] >> angle[0];
                ss[1] >> time[1] >> angle[1];

                Nice::SeqDouble polynom;
                polynom.push_back(0.5*(time[0]+time[1]));
                polynom.push_back(0.5*(angle[0]+angle[1]));
                if(_order > 0)
                {
                    double v[2];
                    ss[0] >> v[0];
                    ss[1] >> v[1];
                    polynom.push_back(0.5*(v[0]+v[1]));
                }
                if(_order > 1)
                {
                    double aby2[2];
                    ss[0] >> aby2[0];
                    ss[1] >> aby2[1];
                    polynom.push_back(0.5*(aby2[0]+aby2[1]));
                }
                polynomials.push_back(polynom);
            }
        }
    }
    else
    {
        logResults(res,"GetRotatorPolynomials");
        setReachable(false);
        if(res.resmsg.size() >= 2)
        {
            E_THROW(Ltcs::RotatorPolynomialException, res.resmsg[1]);
        }
        else
        {
            E_THROW(Ltcs::RotatorPolynomialException, "Error while asking for rotator polynomials.");
        }
    }
    return polynomials;
} /* getRotatorPolynomials */

/*********************************
* @brief query a set of rotator polynomials from the IIF
* @param _startTime Start time of the trajectory.
*  The other parameters are read from the LBCS configuration file.
* @return The sequence of polynomials. */
Nice::SeqSeqDouble Ltcs::IifServiceWorker::getRotatorPolynomials(Nice::Date _startTime)
{
    U6_LOG(__FUNCTION__);
    Nice::SeqSeqDouble polynomials;
#ifdef LN_TCS_SINGLE_ICE
    if(m_iif == 0)
    {
        E_LOG("No telescope proxy initialized");
        return polynomials;
    }
#else
    if(m_iif[0] == 0 && m_iif[1] == 0)
    {
        E_LOG("No telescope proxy initialized");
        return polynomials;
    }
#endif

    std::string _side = m_config->Nice::Joint::getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::cfgCommandSide());

    int numberOfPolynomials = m_config->Nice::Joint::getAtNode<int>(Ltcs::Iif::Svc::PropertyName::cfgNumberOfPolynomials());

    double samplingRate = m_config->Nice::Joint::getAtNode<double>(Ltcs::Iif::Svc::PropertyName::cfgSamplingRate());

    int polynomialOrder = m_config->Nice::Joint::getAtNode<int>(Ltcs::Iif::Svc::PropertyName::cfgPolynomialOrder());

    polynomials = getRotatorPolynomials(_startTime.toMJD(), numberOfPolynomials, samplingRate, polynomialOrder,_side);

    if((int)polynomials.size() < numberOfPolynomials)
    {
        W_LOG("Number of polynomials received(" << polynomials.size() << ") is less than the number requested(" << numberOfPolynomials << ")!")
    }

    m_config->Nice::Joint::setAtNode<Nice::SeqSeqDouble>(Ltcs::Iif::Svc::PropertyName::varPolynomials(), polynomials);

    return polynomials;
} /* getRotatorPolynomials */

/*********************************
* @brief Obtain focal plane coordinates for the stars previously send with the preset.
* @param _side The side of the telescope, "left" or "right" or "both".
* @return The sequence of points, both point coordinates in units of mm.
* @since 2017-05-30
* @author R. J. Mathar */
Nice::SeqPoint Ltcs::IifServiceWorker::getKFPCoordinates(const std::string & _side)
{
    U6_LOG(__FUNCTION__);
    Nice::SeqPoint kfp;
#ifdef LN_TCS_SINGLE_ICE
    if(m_iif == 0)
    {
        E_LOG("No telescope proxy initialized");
        return kfp;
    }
#else
    if(m_iif[0] == 0 && m_iif[1] == 0)
    {
        E_LOG("No telescope proxy initialized");
        return kfp;
    }
#endif

    /* if we don't have a star catalog, we don't have a basis to query positions,
    * and we return an empty list. */
    if(m_scat.size() == 0)
        return kfp;

    /* right ascension constructed with an argument value that is in degrees. See operator::SkyPoint() */
    const Nice::DMS r(getRAradians()/Nice::DMS::DegToRad);
    /* declination constructed with an argument value that is in degrees. */
    const Nice::DMS d(getDECradians()/Nice::DMS::DegToRad);
    /* current pointing of the telescope constructed by the current ra and dec */
    Ltcs::IifStar sCur(r,d);

    /* if distance between current pointing and the poining in m_scat stored with the previous preset
    * is too large, we assume that the telescope operator has sent a different
    * preset meanwhile, bypassing licsPreset(); so we return an empty list.
    * The value of the comparision should not be too tight, because this may be randomized
    * by precession and proper motion. take 1/2 degree as a rather random threshold here. */
    if(sCur.distance(m_scat[0]) > 0.5)
        return kfp;

    /* return an empty list if the request is for "none" or for "both", which does not make sense here. */
    const Ltcs::IifSide s(_side);
    const int sideIdx = s.toIntIdx();
    if(sideIdx < 0)
        return kfp;

    /* if there is a hashed version of a previous request, take that one to avoid
    * the lengthy computations within the TCS, which otherwise would be done twice per side. */
    if(m_kfpHash[sideIdx].size() > 0)
        return m_kfpHash[sideIdx];

    m_kfpHash[sideIdx].clear();
    /* empty list(should trigger that the IIF considers the same target list as known internally,
    * but doesn't)
    * const lbto::SeqPos2 list;
    * lbto::result res = iifGetKFPCoordinates2(list, _side); */
    lbto::result res = iifGetKFPCoordinates2(m_scat, _side);

    if(res.rescode == lbto::Result::SUCCESS)
    {
        /* convert result structure to a sequence of Nice::points
        * Skip the first line with the OK message */
        for(int m=1; m <(int) res.resmsg.size(); m++)
        {
            /* convert string in the message lint to data pair(x,y) */
            double x, y;
            std::istringstream reslin(res.resmsg[m]);
            reslin >> x >> y;
            m_kfpHash[sideIdx].push_back(Nice::Point(x,y));
        }
    }
    else
    {
        logResults(res, "iifGetKFPCoordinates2");
    }

    return m_kfpHash[sideIdx];
} /* getKFPCoordinates */

/*********************************
* @brief Read the star catalog that was submitted with the most recent preset.
* @return The sequence of stars from the last setPresetTelescopeCat()
*   If the TO submitted another preset in the mean time, this list will be empty.
* @since 2017-06-28
* @author R. J. Mathar */
lbto::SeqPos2 Ltcs::IifServiceWorker::getPresetStars()
{
    U6_LOG(__FUNCTION__);
    /* empty star catalog to start with */
    lbto::SeqPos2 stars;

    if(m_scat.size() == 0)
        return stars;

    /* right ascension constructed with an argument value that is in degrees. See operator::SkyPoint() */
    const Nice::DMS r(getRAradians()/Nice::DMS::DegToRad);
    /* declination constructed with an argument value that is in degrees. */
    const Nice::DMS d(getDECradians()/Nice::DMS::DegToRad);
    /* current pointing of the telescope constructed by the current ra and dec */
    Ltcs::IifStar sCur(r,d);

    /* if distance between current pointing and the poining in m_scat stored with the previous preset
    * is too large, we assume that the telescope operator has sent a different
    * preset meanwhile, bypassing licsPreset(); so we return an empty list.
    * The value of the comparision should not be too tight, because this may be randomized
    * by precession and proper motion. take 1/2 degree as a rather random threshold here. */
    if(sCur.distance(m_scat[0]) > 0.5)
        return stars;
    else
        return m_scat;

} /* getPresetStars */


/*******************************/
void Ltcs::IifServiceWorker::setReachable(bool _reachable)
{
    U6_LOG(__FUNCTION__ << tilde << _reachable);
    /* isReachable introduced by LtcsIifServiceWorker.basda */
    Basda::ServiceWorkerSkeleton::cmd()->Nice::Property::attribute(Ltcs::Iif::Svc::AttributeName::isReachable())->Nice::Property::set<bool>(_reachable);
}

/* this is the same as setReachable(true), which is clearer */
bool Ltcs::IifServiceWorker::isReachable()
{
    U6_LOG(__FUNCTION__);
    bool reachable = true;
    Basda::ServiceWorkerSkeleton::cmd()->Nice::Property::attribute(Ltcs::Iif::Svc::AttributeName::isReachable())->Nice::Property::set<bool>(reachable);
    return reachable;
}

/* this is true if reachable */
bool Ltcs::IifServiceWorker::isIifReachable()
{
//    U6_LOG(__FUNCTION__);
    bool reachable = Basda::ServiceWorkerSkeleton::cmd()->Nice::Property::attribute(Ltcs::Iif::Svc::AttributeName::isReachable())->Nice::Property::get<bool>();
    return reachable;
}

/*********************************/
void Ltcs::IifServiceWorker::status()
{
    U6_LOG(__FUNCTION__);
    snapshot();
    setReachable(true);
} /* status */

Basda::VinoFitsCardBox Ltcs::IifServiceWorker::getFits(const double & _pixscal)
{
    U6_LOG(__FUNCTION__);
    return Ltcs::IifServiceWorker::getFitsWithWcs(_pixscal);
} /* getFits */

/********************************
* @brief collect a standard set of FITS header cards
* @param ctrpx The x and y pixel(1-based FITS) of the center of the pointing.
* @param pixscal The pixel scale in degrees per pixel.
* @param wcsAng The correlated angle in the WCS matrix in degrees.
*   If this is NaN, the WCW matrix will not be written.
*   This should effectively be the parallactic angle minus the beam rotation angle
*  (the angle is positive if the image is rotated ccw, mathematically positive, by the derotation service)
*   minus any offset implied by the camera mount or beam rotation reference zero.
*   The beam rotation angle is two times the K-mirror angle for the Pcam and HWS camera.
*   See LN-MPIA-MAN-ICS-010 .*/
Basda::VinoFitsCardBox Ltcs::IifServiceWorker::getFitsWithWcs(const double pixscal, const double ctrpx[2], const double wcsAng)
{
    U6_LOG(__FUNCTION__);
    /* keep values up-to-date */
    poll();

    /* TwiceAsNice/basda-vino/src/Service/Camera/VinoFitsWriterServiceWorker.cc
    * see TwiceAsNice/basda-vino/src/Interface/Basda/Vino/Image.cc
    * see TwiceAsNice/basda-vino/src/Interface/Basda/Vino/FitsCard.h */

    Basda::VinoFitsCardBox fbox;

    /* in a loop over the things gathered in snapshot() convert them to the
    * standard FITS cards. basda-vino/src/Interface/Basda/Vino/FitsCard.cc . */

    fbox.set("ORIGIN", "MGIO-LBT","mgio.arizona.edu Mt. Graham Intl. Observ.");
    fbox.set("INSTRUME", "SHARK-NIR","SHARKNIR");
    fbox.set("OBSERVAT", "LBTO","www.lbto.org Large Binocular Telescope, AZ");

    std::stringstream ss;

    /* RA and DEC in degrees */
    double radec[2];

    /* construct OBJECT string from RA and DEC hex-dec representations */
    std::string iif = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varRA());
    radec[0] = hexToDeg(iif);
    /* skip colons... precision one digit after the dot */
    iif = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varDEC());
    radec[1] = hexToDeg(iif);
    fbox.set("OBJECT", getObject(),"synthesized from RA and DEC");

    if(m_iifSidReg.s == Ltcs::SIDE_NONE)
        fbox.set("TELESCOP", "LBT","");
    else if(m_iifSidReg.s == Ltcs::SIDE_LEFT)
        fbox.set("TELESCOP", "LBT-SX","");
    else if(m_iifSidReg.s == Ltcs::SIDE_RIGHT)
        fbox.set("TELESCOP", "LBT-DX","");
    else
        fbox.set("TELESCOP", "LBT-SDX","");

    /* main part: copy all individual entries of m_keys[] in their VinoFitsCard representation */
    for(unsigned int k = 0; k < m_keys.size(); k++)
        fbox.set(m_keys[k].getFits());

    /* further entries, which are not in the DD of the IIF */

    fbox.set("REFERENC", "doi:10.1117/12.2055495","bibliographic reference");
    if(pixscal > 0.)
        fbox.set("PIXSCALE", pixscal,"[deg/pix]");
    fbox.set("OBSGEO-L", -109.889064,"[deg] geod. longit. 2015A&A..574A..36R");
    fbox.set("OBSGEO-B", 32.701309,"[deg] geod. latit. 2015A&A..574A..36R");
    fbox.set("OBSGEO-H", 3221,"[m] above sea level 2015A&A..574A..36R");

    ss << __FILE__ << " " << __func__;
    fbox.set("CREATOR", ss.str(),"Software compiled " __DATE__);
    ss.str("");
    ss.clear();

    /* for LN at least we have no extensions */

    /* if wcsAng is not NaN, add the standard 2x2 WCS matrix */
    if(! isnan(wcsAng) && pixscal >0.)
    {
        fbox.set("CUNIT1", "deg","doi:10.1051/0004-6361:20021326");
        fbox.set("CUNIT2", "deg","doi:10.1051/0004-6361:20021326");
        fbox.set("CTYPE1", "RA---TAN","doi:10.1051/0004-6361:20021326");
        fbox.set("CTYPE2", "DEC--TAN","doi:10.1051/0004-6361:20021326");
        fbox.set("CRPIX1", ctrpx[0],"doi:10.1051/0004-6361:20021326");
        fbox.set("CRPIX2", ctrpx[1],"doi:10.1051/0004-6361:20021326");
        fbox.set("CRVAL1", radec[0],"doi:10.1051/0004-6361:20021326");
        fbox.set("CRVAL2", radec[1],"doi:10.1051/0004-6361:20021326");
        double sinCos[2];
        sinCos[0] = sin(wcsAng/DR2D);
        sinCos[1] = cos(wcsAng/DR2D);
        fbox.set("CD1_1", -pixscal*sinCos[1],"doi:10.1051/0004-6361:20021326");
        fbox.set("CD1_2", pixscal*sinCos[0],"doi:10.1051/0004-6361:20021326");
        fbox.set("CD2_1", pixscal*sinCos[0],"doi:10.1051/0004-6361:20021326");
        fbox.set("CD2_2", pixscal*sinCos[1],"doi:10.1051/0004-6361:20021326");
    }

    I_LOG(fbox);
    return fbox;
} /* getFitsWithWcs */

/*!**************************
* @brief generate a line in the log files
* @param _res The response/result from the IIF.
* @param _cmd The command that generated the response */
void Ltcs::IifServiceWorker::logResults(const lbto::result _res, const std::string _cmd)
{
    if(_res.rescode == lbto::Result::SUCCESS)
    {
        U9_LOG(_cmd << " command status: SUCCESS");
    }
    else if(_res.rescode == lbto::Result::WARNING)
    {
        W_LOG(_cmd << " command status: WARNING");
    }
    else
    {
        E_LOG(_cmd << " command status: Failed");
    }

    for(unsigned int i = 0; i < _res.resmsg.size(); i++)
    {
        U9_LOG(_cmd << ": " << _res.resmsg[i]);
    }
} /* logResults */

/*!****************************************
* @brief Assembly of the list of dictionary entries.
*  This fills the m_ddents[] and dd2keys[] vectors supposed the m_keys[] are already instantiated.
* @param station 0=None, 1=left, 2= right, 3=both
*   This acts like a filter on whether the L_ or R_ entries are considered.
* @since 2014-03-24
* @author Richard J. Mathar 
* modified for shark-nir - Fulvio  */
void Ltcs::IifServiceWorker::initDD(const Ltcs::IifSide station)
{
    U6_LOG(__FUNCTION__);
    DDstruct dd;

    m_ddents.clear();
    dd2keys.clear();
    std::stringstream log_msg;
    for(unsigned int k = 0; k < m_keys.size(); k++)
    {
        Ltcs::Iif2fitsKw & d = m_keys[k];
        bool keep = true;
        if(d.ddName.find("R_") == 0 && (station.s == Ltcs::SIDE_LEFT || station.s == Ltcs::SIDE_NONE))
            keep =false;

        if(keep)
        {
            log_msg << d.ddName << " ~ ";
            dd.DDname = d.ddName;
            m_ddents.push_back(dd);
            dd2keys.push_back(k);
        }
    }
    U6_LOG(__FUNCTION__ << " ~ " << log_msg.str());

} /* initDD */

/*!****************************************
* @brief Read the XML configuration file to create a sequence of interesting m_keys.
*
* For each entry in the file that is not marked by use="false", we generate a
* m_keys[] entry, and two of them(L_*, R_*) if the dictionary name starts with S_.
* @since 2014-03-24
* @author Richard J. Mathar */
void Ltcs::IifServiceWorker::initXml()
{
    U6_LOG(__FUNCTION__);
    /* configure the m_keys set by parsing the XML file */
    try
    {
        XMLPlatformUtils::Initialize();
    }
    catch(const XMLException& toCatch)
    {
        return;
    }

    XMLCh tempStr[100];
    XMLString::transcode("",tempStr,99);
    DOMImplementation* impl = DOMImplementationRegistry::getDOMImplementation(tempStr);

    //LocalFileInputSource fs = new LocalFileInputSource(fname);
    DOMLSParser * prs;
    try
    {
        prs =  impl->createLSParser(DOMImplementationLS::MODE_SYNCHRONOUS,0);
    }
    catch(DOMException ex)
    {
//        const XMLCh* exm= ex.getMessage();
//        wcout << *exm << endl;
        W_LOG(ex.getMessage());
        return;
    }

    /* search the ltcs.iif-fits.xml configuration file */
    ostringstream sdir;
    if(getenv("INSROOT_ETC_PATH"))
    {
        string ipath(getenv("INSROOT_ETC_PATH"));
        sdir << ipath;

        /* search in the colon separated directories and the ltcs subdirectories
        * The point is that some ltcs/test checks use only a flat etc subdirectory view.
        * todo: this should be a function of the Nice::FileByPath class. */
        size_t col1= 0;
        size_t col2= ipath.find(":",col1);
        while(col2 != string::npos)
        {
            sdir << ":" << ipath.substr(col1,col2-col1) << "/tcs_iif";
            col1 = col2+1;
            col2= ipath.find(":",col1);
        }
        sdir << ":" << ipath.substr(col1) << "/tcs_iif";
    }
    else
    {
        sdir << ":$INSROOT/etc/tcs_iif";
    }
    Nice::FileByPath fdir;
    fdir.setSearchPath(sdir.str());

    /* construct the full path of the XML cfg file name */
    std::string xmlName = fdir.find("ltcs.iif-fits.xml");

    DOMDocument*   doc = prs->parseURI(xmlName.c_str());
    DOMElement*   root = doc->getDocumentElement();

    XMLString::transcode("dic", tempStr, 99);
    DOMNodeList * gD = root->getElementsByTagName(tempStr);

    for(unsigned int i = 0; i < gD->getLength(); i++)
    {
        try
        {
            DOMNode *gi = gD->item(XMLSize_t(i));
            const XMLCh* cpt = gi->getNodeName();
            //const XMLCh* cpt = gi->getLocalName();
            //cout << __FILE__ << " " << __LINE__ << " item " << i << " tpe " << gi->getNodeType() << endl;
            //char * cptstr = XMLString::transcode(cpt);
            //cout << "nodname " << cptstr <<endl;

            DOMNamedNodeMap *gservAtt = gi->getAttributes();

            XMLString::transcode("dd", tempStr, 99);
            DOMAttr *gservPt =(DOMAttr*) gservAtt->getNamedItem(tempStr);
            cpt = gservPt->getValue();
            ostringstream dd;
            dd << XMLString::transcode(cpt);

            // cout << __FILE__ << " " << __LINE__ << " " << i << " " << dd.str() << endl;
            XMLString::transcode("unit", tempStr, 99);
            gservPt =(DOMAttr*) gservAtt->getNamedItem(tempStr);
            cpt = gservPt->getValue();
            ostringstream un;
            un << XMLString::transcode(cpt);

            XMLString::transcode("type", tempStr, 99);
            gservPt =(DOMAttr*) gservAtt->getNamedItem(tempStr);
            cpt = gservPt->getValue();
            ostringstream typ;
            typ << XMLString::transcode(cpt);

            XMLString::transcode("com", tempStr, 99);
            gservPt =(DOMAttr*) gservAtt->getNamedItem(tempStr);
            cpt = gservPt->getValue();
            ostringstream comm;
            comm << XMLString::transcode(cpt);

            XMLString::transcode("keyw", tempStr, 99);
            gservPt =(DOMAttr*) gservAtt->getNamedItem(tempStr);
            ostringstream key;
            if(gservPt)
            {
                cpt = gservPt->getValue();
                key << XMLString::transcode(cpt);
            }
            else
                /* not specified.. assume the key is a copy of the data dictionary name
                * No need to turn it uppercase here, because the FITS scanner will convert... */
                key << dd.str();

            /* the attribtues may specify use="false", or use="cmt" or use ="true", where the default is true */
            bool use = true;
            short bits = 0;
            XMLString::transcode("use", tempStr, 99);
            gservPt =(DOMAttr*) gservAtt->getNamedItem(tempStr);
            if(gservPt)
            {
                ostringstream useAsFits;
                cpt = gservPt->getValue();
                useAsFits << XMLString::transcode(cpt);
                if(useAsFits.str() == "false")
                    use = false;
                else if(useAsFits.str() == "cmt")
                    bits = 1;
            }

            if(use)
            {
                /* split/replicate the S_xxx keywords into left and right */
                string ddstr = dd.str();
                if(ddstr.find("S_") == 0)
                {
                    string keystr = key.str();
                    if(keystr == ddstr)
                    {
                        /* cases where the keyw= had not been specified in the XML file */
                        Ltcs::Iif2fitsKw kL(ddstr.replace(0,1,"L"), typ.str(), keystr.replace(0,1,"L"), un.str(), comm.str(), bits);
                        m_keys.push_back(kL);
                        Ltcs::Iif2fitsKw kR(ddstr.replace(0,1,"R"), typ.str(), keystr.replace(0,1,"R"), un.str(), comm.str(), bits);
                        m_keys.push_back(kR);
                    }
                    else
                    {
                        /* dont switch first letter if the key was explicitly specified ... */
                        Ltcs::Iif2fitsKw kL(ddstr.replace(0,1,"L"), typ.str(), keystr, un.str(), comm.str(), bits);
                        m_keys.push_back(kL);
                        Ltcs::Iif2fitsKw kR(ddstr.replace(0,1,"R"), typ.str(), keystr, un.str(), comm.str(), bits);
                        m_keys.push_back(kR);
                    }
                }
                else
                {
                    /* common/joined keyword of SX and DX sides */
                    Ltcs::Iif2fitsKw k(ddstr, typ.str(), key.str(), un.str(), comm.str(), bits);
                    m_keys.push_back(k);
                }
            }
        }
        catch(XMLException & ex)
        {
//            const XMLCh* exm= ex.getMessage();
//            wcout << *exm << endl;
            W_LOG(ex.getMessage());
        }
    }

    doc->release();
    XMLPlatformUtils::Terminate();

} /* Ltcs::IifServiceWorker::initXml */

/** Split the total set of keywords with a single GetParam() into chunks of that size */
#define IIF_GETPARM_CHUNK 20

/*!****************************************
* @brief obtain parameters information from the IIF interface.
* @return station 0=None, 1=left, 2= right, 3=both
*  This value is the current assignment by the telescope interface.
* @since 2016-10-12
* @author Richard J. Mathar */
Ltcs::IifSide Ltcs::IifServiceWorker::getParam()
{
    U6_LOG(__FUNCTION__);
    Ltcs::IifSide station(Ltcs::SIDE_NONE);

    /* need to start to succeed(must be done on laos2 due to *.cfg restrictions)
    * rcbasdard.sh start ltcs.iifDummyLocalhost-svr --CONFIG=ltcs/ltcs.iifDummyLocalhost-svr.cfg
    * rcbasdard.sh stop ltcs.iifDummyLocalhost-svr */
    try
    {
        /* figure out whether LINC is on the SX or DX or no side. */
        station = Ltcs::IifSide(getLINCstation());

        /* fill m_ddents and dd2keys with the keywords of interest for that station */
        initDD(station);

        /** Split the full set of dictionary entries into chunks because
        * it appears that there is some undocumented upper limit in the
        * number of keywords handled by the GetParameter() function at a time. */

        for(unsigned int chunk = 0; chunk < m_ddents.size()/IIF_GETPARM_CHUNK; chunk++)
        {
            SeqDD ddsubset;
            for(unsigned int i = 0; i < IIF_GETPARM_CHUNK && i + chunk*IIF_GETPARM_CHUNK < m_ddents.size(); i++)
            {
                ddsubset.push_back(m_ddents[i + chunk*IIF_GETPARM_CHUNK]);
            }

            result res = iifGetParameter(ddsubset);
            /* message size will usually be 1+IIF_GETPARAM_CHUNK because the
            * [0] will say GetParameter result status: Error or GetParameter result status: OK */
            for(unsigned int i = 0; i < res.resmsg.size(); i++)
            {
                string & val = res.resmsg[i];
                /* if we err in the dictionary, a "not found: LMTTimeStamp etc are reported
                * Otherwise we have 0 or 1(boolean), floating point constants, in exponential
                * format, nan(lowercase) or strings */
                if(val.find("Not found")  != string::npos  || val.find("nan") != string::npos || i == 0)
                    continue;
                else
                {
                    /* i=1 corresponds to ddsubset[i-1], to m_ddents[(i-1)+chunk*IIF_CHUNK]
                    * and to m_keys[dd2keys[(i-1)+chunk*IIF_CHUNK]]
                    * cerr <<__FILE__ << " " << __LINE__ << " " << i << " " << val << "|" <<  std::endl; */
                    m_keys[dd2keys[i - 1 + chunk*IIF_GETPARM_CHUNK]].val = val;
                    time(& m_keys[dd2keys[i - 1 + chunk*IIF_GETPARM_CHUNK]].updt);
                    // std::cerr << m_keys[dd2keys[i-1+chunk*IIF_GETPARM_CHUNK]].toString() << std::endl;
                    // std::cerr << m_keys[dd2keys[i-1+chunk*IIF_GETPARM_CHUNK]].toKVPairString() << std::endl;
                }
            }
        }
    }
    catch(Ice::Exception & ex)
    {
        std::string log_msg = ex.what();
        std::remove(log_msg.begin(), log_msg.end(), '\n');
        U9_LOG("COMMENT : " << log_msg);
    }
    catch(const char * msg)
    {
        U9_LOG("COMMENT : " << msg);
    }

    return station;
} /* getParam */

/** Figure out whether LINC is on the L or R side or not on any
* @param iif The IIF proxy to use for the getParam().
* @return "none", "left", "right" or "both" depending on whether LINC is not a {LR}_Instrument, Left or Right or both.
* @since 2016-10-13 
* @ changed for shark-nir */
std::string Ltcs::IifServiceWorker::getLINCstation()
{
    U6_LOG(__FUNCTION__);
    m_ddents.clear();
    dd2keys.clear();

    DDstruct dd;
    dd.DDname = "L_Instrument";
    m_ddents.push_back(dd);

    int station = 0;
    result res = iifGetParameter(m_ddents);
    for(unsigned int i = 1; i < res.resmsg.size(); i++)
    {
        string & val = res.resmsg[i];
        /* if we err in the dictionary, a "not found: LMTTimeStamp etc are reported
        * Otherwise we have 0 or 1(boolean), floating point constants, nan or strings */
        if(val.find("SHARK") != string::npos )
        // if(val.find("SHARK-NIR") != string::npos || val.find("LBTI")  != string::npos) // changed since SHARK is being authorized as LBTI
            /* LINC on left(SX) =1, on the right(DX) =2, both =3 */
            station += i;
    }

    switch(station)
    {
    case 0:
        return "none";
    case 1:
        return "left";
    default:
        return "error";
    }
} /* getLINCstation */

/*!****************************************
* @brief Dump the DD entries for use in the licsGUI and GEIRS FITS headers.
*
* @since 2017-05-22
* @since 2017-06-23 generate also the FITS template file for GEIRS
* @author Richard J. Mathar */
void Ltcs::IifServiceWorker::toKVPairFile()
{
    U6_LOG(__FUNCTION__ << " ~ Bypassed ~ " << m_fMonit.c_str() << " ~ " << m_fFtpl.c_str());
    return;
    /* write to file  in overwrite mode */
    ofstream f(m_fMonit.c_str(),ios_base::trunc);
    if(f)
    {
        for(unsigned int k = 0; k < m_keys.size(); k++)
            f << m_keys[k].toKVPairString() << endl; // that would add carriage returns even for the empty specs..
    }
#if 0
    else
        std::cerr << "Could not open " << m_fMonit << std::dendl;
#endif

    f.close();

    /* write to file  in overwrite mode */
    ofstream f2(m_fFtpl.c_str(), ios_base::trunc);
    if(f2)
    {
        f2 << "ORIGIN = 'MGIO-LBT' / mgio.arizona.edu Mt. Graham Intl. Observ." << std::endl;
        f2 << "INSTRUME = 'LINC-NIRVANA' / www.mpia.de/LINC LBT Interfer. Camera" << std::endl;
        f2 << "OBSERVAT = 'LBTO' / www.lbto.org Large Binocular Telescope, AZ" << std::endl;
        f2 << "OBJECT = '" << getObject() << "' / synthesized from RA and DEC" << std::endl;
        f2 << "CREATOR = \'" << __FILE__ << " " << __func__ << "' / compiled " __DATE__ << std::endl;
        for(unsigned int k = 0; k < m_keys.size(); k++)
        {
            /* there is an implicit 160 byte limit in getkey.c. of cfitsio 3.420
            *(a sort of bug). Make sure our lines are not longer than that. There is another
            * constant 140  which also seems to play a role...*/
            f2 << m_keys[k].toString().substr(0,139) << endl;
        }
    }
#if 0
    else
        std::cerr << "Could not open " << m_fFtpl << std::endl;
#endif

    f2.close();

    /* copy the file to the lircs and laos or lsys2(backup machines) directories
    * $CAMTMP/geirsPhduAdd.nirvana_4 as fixed by the GEIRS standard configuration */
    ostringstream cmd;
    cmd << "scp " << m_fFtpl.c_str() << " lircs:GEIRS/tmp/geirsPhduAdd.nirvana_4 >& /dev/null &";
    U7_LOG(cmd.str());
    system(cmd.str().c_str());

    cmd.str("");
    cmd.clear();
    cmd << "scp " << m_fFtpl.c_str() << " lsys2:GEIRS/tmp/geirsPhduAdd.nirvana_4 >& /dev/null &";
    U7_LOG(cmd.str());
    system(cmd.str().c_str());

} /* toKVPairFile */

/*!****************************************
* @brief Generate an OBJECT string from pointing coordinates
*
* @since 2017-11-28
* @author Richard J. Mathar */
std::string Ltcs::IifServiceWorker::getObject()
{
    U6_LOG(__FUNCTION__);
    /* right ascension constructed with an argument value that is in degrees. */
    const Nice::DMS r(getRAradians()/Nice::DMS::DegToRad);
    /* declination constructed with an argument value that is in degrees. */
    const Nice::DMS d(getDECradians()/Nice::DMS::DegToRad);
    const Ltcs::IifStar s(r,d);
    return s.object();
} /* getObject */


/*!***************************************
* @brief Transform the current pointing into a SkyPoint object.
*
* This asks the IIF for the current(ra/dec) pair and generates a SkyPoint object.
* This may be used with the SkyPoint member functions then for example to generate Alt/Az
* without asking again the IIF(at least in the limit of neglecting refraction etc.)
*
* Note that the azimuth convention(North=0, East=+90 deg) seems to be
* the same for the LBT IIF queries and for the SkyPoint member functions.
* So one does not need to add 180 degrees or construct the complement with respect to 180
* to us map both conventions. See also https://cgit.kde.org/kstars.git/tree/kstars/skyobjects/skypoint.cpp .
* @return Sky coordinates RA and DEC embodied by a SkyPoint object.
* @since 2016-11-16 */
Ltcs::IifServiceWorker::operator Nice::SkyPoint()
{
    U6_LOG(__FUNCTION__);
#if 0
    /* right ascension as a string.
    * This here is a show stopper: someone commented the DMS::fromString(),
    * so we cannot start from the stringified/hex IIF results. */
    std::string RA = m_config->getAtNode<std::string>(Ltcs::Iif::Svc::PropertyName::varRightAscension());
#endif
    /* right ascension constructed with an argument value that is in degrees. */
    const Nice::DMS r(getRAradians()/Nice::DMS::DegToRad);
    /* declination constructed with an argument value that is in degrees. */
    const Nice::DMS d(getDECradians()/Nice::DMS::DegToRad);

    return Nice::SkyPoint(r,d);
} /* operator() */

/** @brief Acquire the reference star.
* @param repointFlag flag to request the telescope to repoint.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifAcquireRefAO(bool repointFlag, const std::string & side)
{
    U6_LOG(__FUNCTION__);
    I_LOG(" repointFlag " << repointFlag << " side " << side);

#ifdef LN_TCS_SINGLE_ICE
    return m_iif->AcquireRefAO(repointFlag, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->AcquireRefAO(repointFlag, side);
#endif
}

/** @brief Check authorization for control the telescope.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifAuthorize()
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->Authorize();
#else
    return m_iif[m_iifSidReg.toIntIdx()]->Authorize();
#endif
}

/** @brief Check authorization for control the telescope.
* @return Result object
* @since 2022-12-22 */
lbto::result Ltcs::IifServiceWorker::iifAuthorize2(const std::string & side)
{
    U6_LOG(__FUNCTION__);
    lbto::result res;
    Ltcs::IifSide s(side);
    if(m_iifSidCfg.s == Ltcs::SIDE_BOTH && m_iif[s.toIntIdx()])
    {
        res = m_iif[s.toIntIdx()]->Authorize();
    }
    else if(m_iif[m_iifSidReg.toIntIdx()])
    {
        res = m_iif[m_iifSidReg.toIntIdx()]->Authorize();
    }
    return res;
}

/** @brief BinocularControl clears the target information in the PCS.
* @param flag Preset control flag: CLEARPRESET, SYNCPRESET, SYNCOFFSET, ADJUSTBALANCE.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifBinocularControl(const std::string & flag)
{
    U6_LOG(__FUNCTION__ << " ~ flag : "<< flag);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->BinocularControl(flag);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->BinocularControl(flag);
#endif
}

/** @brief Check the reference star position.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifCheckRefAO(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->CheckRefAO(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->CheckRefAO(side);
#endif
}

/** @brief Removes the hotspot from the system.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifClearHotspot()
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ClearHotspot();
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ClearHotspot();
#endif
}

/** @brief Removes the non-sidereal object from the system.
* @param override non-sidereal override flag
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifClearNonSidereal(bool bOverride)
{
    U6_LOG(__FUNCTION__ << " ~  override : " << bOverride);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ClearNonSidereal(bOverride);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ClearNonSidereal(bOverride);
#endif
}

/** @brief Removes the offset from the system.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifClearOffset()
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ClearOffset();
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ClearOffset();
#endif
}

/** @brief Removes the AO reference star from the system.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifClearReference()
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ClearReference();
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ClearReference();
#endif
}

/** @brief Removes the selected target and guide stars from the system.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifClearStars()
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ClearStars();
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ClearStars();
#endif
}

/** @brief Send 672 mode corrections to AO.
* @param modes modes list
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifCorrectModesAO(const lbto::SeqModes & modes, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ modes : TBD");// << modes);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->CorrectModesAO(modes,side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->CorrectModesAO(modes,side);
#endif
}

/** @brief Use this command to get focal plane positions for LN AO reference objects.
* @param list the list of stars.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifGetKFPCoordinates(const lbto::SeqPos & list, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars ");
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->GetKFPCoordinates(list, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->GetKFPCoordinates(list, side);
#endif
}

/** @brief Use this command to get focal plane positions for LN AO reference objects.
* @param list the list of stars.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifGetKFPCoordinates2(const lbto::SeqPos2 & list, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars ");
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->GetKFPCoordinates2(list, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->GetKFPCoordinates2(list, side);
#endif
}

/** @brief Reads a block of entries from the data dictionary in one shot.
* @brief If FITS information is defined here, it will be included in the FITS headers(only used by the IRTC client).
* @param list DD entry list
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifGetParameter(const lbto::SeqDD & _dd)
{
    U6_LOG(__FUNCTION__ );
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->GetParameter(_dd);
#else
    lbto::result res;
    try
    {
        res = m_iif[m_iifSidReg.toIntIdx()]->GetParameter(_dd);
    }
    catch(Basda::ServiceIsBusyException)
    {
        E_LOG(__FUNCTION__);
    }
    catch(...)
    {
        std::string what = boost::current_exception_diagnostic_information();
        std::string toErase = "std::exception::what:";
        size_t pos = what.find(toErase);
        if(pos != std::string::npos)what.erase(pos, toErase.length());
        what.erase(std::remove(what.begin(), what.end(), '\n'), what.end());
        std::stringstream log_msg;
        log_msg << what;
        E_LOG(log_msg.str());
    }
    return res;
#endif
}

/** @brief Use this command to get rotator polynomials.
* @param start the start time in MJD(UT days)
* @param count the number of points in the trajectory
* @param interval the time between points(seconds)
* @param order the desired polynomial order(0,1,2)
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27
* @todo get a realistic local sidereal time via sofa */
lbto::result Ltcs::IifServiceWorker::iifGetRotatorPolynomials(double start, int count, double interval, int order, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ start : " << start << "; count : " << count);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->GetRotatorPolynomials(start, count, interval, order, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->GetRotatorPolynomials(start, count, interval, order, side);
#endif
}

/** @brief Use this command to get the rotator trajectory for the LBC.
* @param seconds the number of seconds in the trajectory
* @param interval the time between points
* @param start the start time in MJD
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifGetRotatorTrajectory(double seconds, double interval, double start, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ seconds : " << seconds << "; interval : " << interval << "; start : " << start << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->GetRotatorTrajectory(seconds, interval,start, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->GetRotatorTrajectory(seconds, interval,start, side);
#endif
}

/** @brief Use this command to log a message inside the TCS's logging system.
* @param event an event name that the instrument desires to log
* @param description the descriptive text that the instrument desires to log
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifLogEvent(const std::string & event, const std::string & description)
{
    U6_LOG(__FUNCTION__ << " ~ " << event << " " << description);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->LogEvent(event, description);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->LogEvent(event, description);
#endif
}

/** @brief This command is to provide some control over the use of the rotator cablewrap.
* @param azflag flag to control AZ wrap.
* @param rotflag flag to control rotator wrap.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifMaximizeWrapTime(bool azflag, bool rotflag, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ azflag : " << azflag << "; rotflag : " << rotflag << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->MaximizeWrapTime(azflag, rotflag, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->MaximizeWrapTime(azflag, rotflag, side);
#endif
}

/** @brief Modify AO.
* @param NModes the number of corrected modes.
* @param Freq  the CCD integration frequency.
* @param Nbins  the CCD binning.
* @param TTMod  Tip-Tilt internal mirror modulation.
* @param F1spec the selected position of filter wheel #1.
* @param F2spec the selected position of filter wheel #2.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifModifyAO(int NModes, double Freq, int Nbins, double TTmod, const std::string & F1spec, const std::string & F2spec, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ Nmodes :" << NModes << "; Freq : " << Freq << "; Nbins : " << Nbins << "; TTmod : " << TTmod);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ModifyAO(NModes, Freq, Nbins, TTmod, F1spec, F2spec, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ModifyAO(NModes, Freq, Nbins, TTmod, F1spec, F2spec, side);
#endif
}

/** @brief This command combines the functionality of MoveXY, TipTilt, StepFocus and MoveFocus.
* @param x movement in x [millimeter]
* @param y movement in y [millimeter]
* @param z movement in z [millimeter]
* @param rx rotation in x [microradian]
* @param ry rotation in y [microradian]
* @param rz rotation in z [microradian]
* @param dflag  6 bits with a bit for each of the preceding 6 variables. Bit 0 enables X, bit 1 enables Y, bit 2 enables Z, and so on.
* @param movetype movement type: ABS, REL
* @param ope Optical element: DEFAULT, M1, M2, M3, M1M2, M1M3, M2M3, M1M2M3
* @param time lookahead time for the collimation correction [sec]
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifMove(double x, double y, double z,
                                             double rx, double ry, double rz,
                                             int dflag, const std::string & movetype,
                                             const std::string & ope, int time,
                                             const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ x : " << x << "; y :" << y << "; z : " << z << "; rx :" << rx << "; ry : " << ry << "; rz :" << rz << "; dflag : " << dflag);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->Move(x, y, z, rx, ry, rz, dflag, movetype, ope, time, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->Move(x, y, z, rx, ry, rz, dflag, movetype, ope, time, side);
#endif
}

/** @brief Moves an OPE to a new absolute position z to adjust or to define a new focus  position.
* @param absPos distance to move the OPE [millimeter]
* @param ope Optical element :  M1 or M2 or M3 or M1M2
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifMoveFocus(double absPos, const std::string & ope, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ absPos " << absPos << " ope " << ope << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->MoveFocus(absPos, ope, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->MoveFocus(absPos, ope, side);
#endif
}

/** @brief MoveXY moves an OPE in X or Y direction, relative to the current position.
* @param xmov movement in x [millimeter]
* @param ymov movement in y [millimeter]
* @param ope Optical element: M1 or M2
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifMoveXY(double xmov, double ymov, const std::string & ope, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  xmov " << xmov << " ymov " << ymov << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->MoveXY(xmov, ymov, ope, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->MoveXY(xmov, ymov, ope, side);
#endif
}

/** @brief MoveXYZ moves the primary and secondary together in X, Y and Z direction.
* @param xmov movement in x [millimeter]
* @param ymov movement in y [millimeter]
* @param zmov movement in z [millimeter]
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifMoveXYZ(double xmov, double ymov, double zmov, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  xmov " << xmov << " ymov " << ymov << " zmov " << zmov << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->MoveXYZ(xmov, ymov, zmov, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->MoveXYZ(xmov, ymov, zmov, side);
#endif
}

/** @brief OffsetGuiding moves the telescope a small distance without changing the value of the pointing coordinates.
* @param rotangle Rotator angle [radian]
* @param offx Offset in X [radian/pixels]
* @param offy Offset in Y [radian/pixels]
* @param _coordsys Coordinate system: RADEC, AZALT, DETXY, GALACTIC
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifOffsetGuiding(double angle, double offx, double offy, const std::string & _coordsys, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  offx " << offx << " offy " << offy << " " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->OffsetGuiding(angle, offx, offy, _coordsys, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->OffsetGuiding(angle, offx, offy, _coordsys, side);
#endif
}

/** @brief OffsetPointing moves the telescope a small distance, setting the value
* @brief of the telescope pointing coordinates to the new position.
* @param rotangle Rotator angle in the range -2*pi to 2*pi [radian]
* @param offx Offset in X [radian or seconds]
* @param offy Offset in Y [radian]
* @param coordsys Coordinate system: RADEC, AZALT, DETXY, GALACTIC
* @param ope Optical element : DEFAULT, M1, M2, M3, HEXAPOD, MOUNT
*    This is a dummy parameter which will be ignored.
* @param np New position flag.
*    This is a dummy parameter which will be ignored.
* @param movetype movement type to apply: "REL" or "ABS" meaning Relative / Absolute.
*    A "CS" can be appended for RADEC or GALACTIC to indicate a coordinate system offset.
* @param side the telescope side this command applies to. "left" or "right" or "both"
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifOffsetPointing(double _rotangle,
                                                       double _offx,
                                                       double _offy,
                                                       const std::string & _coordsys,
                                                       const std::string & _ope,
                                                       const std::string & _np,
                                                       const std::string & _movetype,
                                                       const std::string & _side)
{                                       
    U6_LOG(__FUNCTION__ << " ~  _rotangle : " << _rotangle << "; _offx : " << _offx
          << "; _offy : " << _offy << "; _coordsys : " << _coordsys << "; _ope : " << _ope << "; _np : " << _np << "; _movetype : " << _movetype << "; _side : " << _side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->OffsetPointing(_rotangle, _offx, _offy, _coordsys, _ope, _np, _movetype, _side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->OffsetPointing(_rotangle, _offx, _offy, _coordsys, _ope, _np, _movetype, _side);
#endif

}

/** @brief OffsetPointing moves the telescope a small distance, setting the value
* @brief of the telescope pointing coordinates to the new position.
* @param _rotangle Rotator offset [radian]
* @param _offx Offset in X [radians or seconds]
* @param _offy Offset in Y [radian]
* @param _coordsys Coordinate system: RADEC, AZALT, DETXY, GALACTIC
* @param _movetype movement type to apply: "REL", "RELCS", "ABS", "ABSCS"
* @param _side the side this command applies to. "left" "right" or "both".
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifOffsetPointing2(double _rotangle,
                                                        double _offx,
                                                        double _offy,
                                                        const std::string & _coordsys,
                                                        const std::string & _movetype,
                                                        const std::string & _side)
{
    U6_LOG(__FUNCTION__ << " ~ _rotangle : " << _rotangle << "; _offx : " << _offx << "; _offy : " << _offy << "; _coordsys : " << _coordsys << "; _movetype : " << _movetype << "; _side : " << _side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->OffsetPointing2(_rotangle, _offx, _offy, _coordsys, _movetype, _side);
#else
    lbto::result res;
    res = m_iif[m_iifSidReg.toIntIdx()]->OffsetPointing2(_rotangle, _offx, _offy, _coordsys, _movetype, _side);
    U8_LOG(__FUNCTION__);
    return res;
#endif
}


// lbto::result Ltcs::IifServiceWorker::iifOffsetPointing22(double _rotangle,
//                                                         double _offx,
//                                                         double _offy,
//                                                         const std::string & _coordsys,
//                                                         const std::string & _movetype,
//                                                         const std::string & _side)
// {
//     U6_LOG(__FUNCTION__ << " ~ _rotangle : " << _rotangle << "; _offx : " << _offx << "; _offy : " << _offy << "; _coordsys : " << _coordsys << "; _movetype : " << _movetype << "; _side : " << _side);
// #ifdef LN_TCS_SINGLE_ICE
//     return m_iif->OffsetPointing2(_rotangle, _offx, _offy, _coordsys, _movetype, _side);
// #else
//     Ltcs::IifSide s(_side);
//     lbto::result res;
//     if((m_iifSidCfg.s == Ltcs::SIDE_BOTH && (m_iif[0] && m_iif[1])) || (m_iifSidCfg.s == Ltcs::SIDE_LEFT && m_iif[0]))
//     {
//         if(m_iifSidCfg.s == Ltcs::SIDE_BOTH)
//             res = m_iif[s.toIntIdx()]->OffsetPointing2(_rotangle, _offx, _offy, _coordsys, _movetype, _side);
//         else
//             res = m_iif[0]->OffsetPointing2(_rotangle, _offx, _offy, _coordsys, _movetype, _side);
//     }
//     U8_LOG(__FUNCTION__);
//     return res;
// #endif
// }


/** @brief OffsetXY AO.
* @param DeltaX the X offset.
* @param DeltaY the Y offset.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifOffsetXYAO(double DeltaX, double DeltaY, const std::string & side)
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->OffsetXYAO(DeltaX, DeltaY, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->OffsetXYAO(DeltaX, DeltaY, side);
#endif
}

/** @brief OffsetZ AO.
* @param DeltaZ the Z offset.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifOffsetZAO(double DeltaZ, const std::string & side)
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->OffsetZAO(DeltaZ, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->OffsetZAO(DeltaZ, side);
#endif
}

/** @brief Temporarily suspends the current AO operation.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifPauseAO(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->PauseAO(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->PauseAO(side);
#endif
}

/** @brief Temporarily suspends the current guiding operation.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifPauseGuiding(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->PauseGuiding(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->PauseGuiding(side);
#endif
}

/** @brief Preset AO.
* @param AOMode the AO operation mode.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifPresetAO(const std::string & AOMode, const std::string & side)
{
    U6_LOG(__FUNCTION__ << "  AOMode : " << AOMode << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->PresetAO(AOMode, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->PresetAO(AOMode, side);
#endif
}

/** @brief Request flat shape for AO.
* @param flat the flat name.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifPresetFlatAO(const std::string & flat, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " flat : " << flat << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->PresetFlatAO(flat, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->PresetFlatAO(flat, side);
#endif
}

/** @brief PresetTelescope slew the telescope into position in order to begin an observation cycle.
* @param rotangle initial rotator angle [radian]
* @param rotmode rotator mode: POSITION, PARALLACTIC, NATIVE, GRAVITATIONAL, IDLE.
* @param mode Operation mode: STATIC, TRACK, ACQUIRE, GUIDE, ADAPTIVE, ACTIVE, INTERFEROMETRIC.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifPresetTelescope(double rotangle,
                                                        const std::string & rotmode,
                                                        const std::string & mode,
                                                        const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ rotangle : " << rotangle << "; rotmode : " << rotmode << "; mode : " << mode << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->PresetTelescope(rotangle, rotmode, mode, side);
#else
    U6_LOG(__FUNCTION__);
    lbto::result result;

    try
    {
        U6_LOG(__FUNCTION__);
        result = m_iif[m_iifSidReg.toIntIdx()]->PresetTelescope(rotangle, rotmode, mode, side);
        U6_LOG(__FUNCTION__ << " ~ " << side);
    }
    catch(Ice::ConnectionTimeoutException err)
    {
        E_LOG(__FUNCTION__ << " ~ ICE Timeout");
    }
    catch(...)
    {
        std::string what = boost::current_exception_diagnostic_information();
        std::string toErase = "std::exception::what:";
        size_t pos = what.find(toErase);
        if(pos != std::string::npos)what.erase(pos, toErase.length());
        what.erase(std::remove(what.begin(), what.end(), '\n'), what.end());
        std::stringstream log_msg;
        log_msg << what;
        E_LOG(log_msg.str());
    }
    return result;
#endif
}

#if 0
/** @brief PresetTelescope slew the telescope into position in order to begin an observation cycle.
* @param rotangle initial rotator angle [radian]
* @param rotmode rotator mode: POSITION, PARALLACTIC, NATIVE, GRAVITATIONAL, IDLE.
* @param mode Operation mode: STATIC, TRACK, ACQUIRE, GUIDE, ADAPTIVE, ACTIVE, INTERFEROMETRIC.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifPresetTelescope2(lbto::IIFServerPrx m_iif,double rotangle,
                const std::string rotmode,
                const std::string mode,
                const std::string side)
{
    std::cerr <<__FILE__ << " " << __LINE__ << " " << side << std::endl;
    return m_iif->PresetTelescope(rotangle, rotmode, mode, side);
    //std::cerr <<__FILE__ << " " << __LINE__ << " " << side << std::endl;
}
#endif

/** @brief Refine AO.
* @param method the method used for refinement.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRefineAO(const std::string & method, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ method " << method << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RefineAO(method, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RefineAO(method, side);
#endif
}

/** @brief remove the IIF object */
void Ltcs::IifServiceWorker::iifRemove()
{
    U6_LOG(__FUNCTION__);
}


/** @brief Resumes suspended operation after a PauseAO.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifResumeAO(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  side :" << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ResumeAO(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ResumeAO(side);
#endif
}

/** @brief Resumes suspended guiding operation.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifResumeGuiding(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->ResumeGuiding(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->ResumeGuiding(side);
#endif
}

/** @brief Rotates the primary and the secondary mirror around a common point.
* @param z rotation point [millimeter]
* @param angle rotation angle [radian]
* @param direction direction angle [radian]
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotateCommon(double z, double angle, double direction, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  z : " << z << "; angle : " << angle << "; direction : " << direction << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotateCommon(z, angle, direction, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotateCommon(z, angle, direction, side);
#endif
}

/** @brief Use this command to to rotate the primary about a point on axis.
* @param distance the distance above the mirror about which to rotate
* @param angle the rotation angle
* @param direction the direction in which to rotate
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotatePrimary(double distance, double angle, double direction, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  distance : " << distance << "; angle : " << angle << "; direction : " << direction << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotatePrimary(distance, angle, direction, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotatePrimary(distance, angle, direction, side);
#endif
}

/** @brief the tertiary mirror is rotated to adjust the incoming beam angle for  the instrument.
* @param angle rotation angle [radian]
* @param movetype movement type: ABS, REL
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotateZ(double angle, const std::string & movetype, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  angle : " << angle << "; movetype : " << movetype << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotateZ(angle, movetype, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotateZ(angle, movetype, side);
#endif
}

/** @brief If the rotator is tracking or slewing, this command makes it stop moving and hold the position.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotHold(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotHold(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotHold(side);
#endif
}

/** @brief Readies the cable wrap and rotator.
* @param enable flag to ready/unready the rotator.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotReady(bool enable, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  enable : " << enable << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotReady(enable, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotReady(enable, side);
#endif
}

/** @brief Makes the rotator move to the specified angle in the specified coordinate frame and hold at that position.
* @param angle [radian]
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotServicePosition(double angle, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  angle " << angle << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotServicePosition(angle, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotServicePosition(angle, side);
#endif
}

/** @brief Makes rotator begin tracking according to the polynomial stream it is currently receiving from the PCS.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRotTrack(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RotTrack(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RotTrack(side);
#endif
}

/** @brief Do AcquireRefAO, StartAO.
* @param repointFlag flag to request the telescope to repoint in AcquireRefAO.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifRunAO(const std::string & repointFlag, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  repointFlag : " << repointFlag << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->RunAO(repointFlag, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->RunAO(repointFlag, side);
#endif
}

/** @brief Sends an array of 28 Zernike polynomial coefficients to be compensated by the actuators
* @brief of the primary or secondary mirror.
* @param wfs coefficients list
* @param OPE Optical element: M1, M2
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSendWavefront(const lbto::SeqWF & wfs, const std::string & OPE, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  OPE : " << OPE << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SendWavefront(wfs, OPE, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SendWavefront(wfs, OPE, side);
#endif
}

/** @brief Use this command to set the AGw filter.
* @param filterNumber the number of the filter.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetAGWFilter(int filterNumber, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  filterNumber : " << filterNumber << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetAGWFilter(filterNumber, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetAGWFilter(filterNumber, side);
#endif
}

/** @brief Use this command to set the AGw filter.
* @param factor
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetGuidingBinning(int factor, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  factor : " << factor << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetGuidingBinning(factor, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetGuidingBinning(factor, side);
#endif
}

/** @brief Sets guider hotspot.
* @param coord1 coordinate x [pixel].
* @param coord2 coordinate y [pixel].
* @param movetype the move type, REL or ABS.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetGuidingHotspot(double coord1, double coord2, const std::string & movetype, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  coord1 : " << coord1 << "; coord2 : " << coord2 << "; movetype : " << movetype << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetGuidingHotspot(coord1, coord2, movetype, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetGuidingHotspot(coord1, coord2, movetype, side);
#endif
}


/** @brief Set the hotspot coordinates to be used by PresetTelescope command
* @param coord1 Coordinate x [pixel]
* @param coord2 Coordinate y [pixel]
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetHotspot(double _offx, double _offy)
{
    U6_LOG(__FUNCTION__ << " ~  offx : " << _offx << "; offy : " << _offy);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetHotspot(_offx, _offy);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetHotspot(_offx, _offy);
#endif
}

/** @brief Set the non-sidereal values to be used by PresetTelescope
* @param target non-sidereal target
* @param override non-sidereal override flag
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetNonSidereal(const lbto::nonsidereal & target, bool bOverride)
{
    U6_LOG(__FUNCTION__ << " ~  value1 : " << target.value1 << "; value2 : " << target.value2 << "; override : " << bOverride);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetNonSidereal(target, bOverride);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetNonSidereal(target, bOverride);
#endif
}

/** @brief Set the offset coordinates to be used by PresetTelescope
* @param _offx Coordinate x [radian/pixel]
* @param _offy Coordinate y [radian/pixel]
* @param _coordsys Coordinate system: RADEC, ALTAZ, DETXY, GALACTIC.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetOffset(double _offx, double _offy, const std::string & _coordsys)
{
    U6_LOG(__FUNCTION__ << " ~ _offx : " << _offx << "; _offy : " << _offy << "; _coordsys : " << _coordsys);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetOffset(_offx, _offy, _coordsys);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetOffset(_offx, _offy, _coordsys);
#endif
}

/** @brief Set the offset coordinates to be used by PresetTelescope
* @param coord1 Coordinate x [radian/pixel]
* @param coord2 Coordinate y [radian/pixel]
* @param system Coordinate system: RADEC, ALTAZ, DETXY, GALACTIC.
* @param movetype
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetOffset2(double coord1, double coord2, const std::string & system, const std::string & movetype)
{
    U6_LOG(__FUNCTION__ << " ~  coord1 : " << coord1 << "; coord2 : " << coord2 << "; system : " << system << "; movetype:  " << movetype);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetOffset2(coord1, coord2, system, movetype);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetOffset2(coord1, coord2, system, movetype);
#endif
}

/** @brief Sets the values of the specified data dictionary entries on the TCS in one shot.
* @param list DD entry list with entry-value pairs
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetParameter(const lbto::SeqDD & list)
{
    U6_LOG(__FUNCTION__);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetParameter(list);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetParameter(list);
#endif
}

/** @brief SetPMTerm sets a singel term in the pointing model.
* @param _name name of term(only "CA" and "IE" are allowed)
* @param value value of term
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetPMTerm(const std::string & _name, double value, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  name " << _name << " value " << value << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetPMTerm(_name, value, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetPMTerm(_name, value, side);
#endif
}

/** @brief SetPMTerm sets a singel term in the pointing model.
* @param _name name of term(only "CA" and "IE" are allowed)
* @param value value of term
* @param movetype
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetPMTerm2(const std::string & _name, double value, const std::string & movetype, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  name : " << _name << "; value : " << value << "; movetype : " << movetype << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetPMTerm2(_name, value, movetype, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetPMTerm2(_name, value, movetype, side);
#endif
}

/** @brief Sets the AO reference star
* @param list List of objects.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetReference(const lbto::SeqPos & list)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars");
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetReference(list);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetReference(list);
#endif
}

/** @brief Sets the AO reference star
* @param list List of objects.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetReference2(const lbto::SeqPos2 & list)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars");
    U6_LOG(__FUNCTION__ << " ~ " << Basda::ServiceWorker::name() << " ~ " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetReference2(list);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetReference2(list);
#endif
}

/** @brief Sets the AO reference star specifiyng the side
  * @param list List of objects.
  * @param side side where to set the reference
  * @return Result object
  * @since 2022-12-20 */
lbto::result Ltcs::IifServiceWorker::iifSetReference22(const lbto::SeqPos2 & list, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars");
    U6_LOG(__FUNCTION__ << " ~ " << Basda::ServiceWorker::name() << " ~ " << Basda::ServiceWorker::state() << "::" << Basda::ServiceWorker::config()->leaf("STATE"));

    Ltcs::IifSide s(side);
    lbto::result res;
    if(m_iifSidCfg.s == Ltcs::SIDE_BOTH) // two interfaces have been created
    {
        res = m_iif[s.toIntIdx()]->SetReference2(list);
    }
    else
    {
        res = m_iif[m_iifSidCfg.toIntIdx()]->SetReference2(list);
    }

    return res;
}


/** @brief Sets the target, and eventually, the guide stars.(first element on the list is used as target)
* @param list List of objects.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetStars(const lbto::SeqPos & _stars)
{
    U6_LOG(__FUNCTION__ << " ~ " << _stars.size() << " stars");
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetStars(_stars);
#else
    lbto::result res;
    for(int s = 0; s < 2; s++)
        if(m_iif[s])
            res = m_iif[s]->SetStars(_stars);
    return res;
#endif
}

/** @brief Sets the target, and eventually, the guide stars.(first element on the list is used as target)
* @param list List of objects.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetStars2(const lbto::SeqPos2 & list)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars");
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetStars2(list);
#else
    lbto::result res;
    for(int s = 0; s < 2; s++)
        if(m_iif[s])
            res = m_iif[s]->SetStars2(list);
    return res;
#endif
}

/** @brief Sets the target, and eventually, the guide stars.(first element on the list is used as target)
* @param list List of objects.
* @return Result object
* @since 2022-12-22 */
lbto::result Ltcs::IifServiceWorker::iifSetStars22(const lbto::SeqPos2 & list, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ " << list.size() << " stars");
    lbto::result res;
    Ltcs::IifSide s(side);
    if(m_iifSidCfg.s == Ltcs::SIDE_BOTH) // two interfaces have been created
    {
        if(m_iif[s.toIntIdx()])
            res = m_iif[s.toIntIdx()]->SetStars2(list);
    }
    else
    {
        if(m_iif[m_iifSidCfg.toIntIdx()])
            res = m_iif[m_iifSidCfg.toIntIdx()]->SetStars2(list);
    }
    return res;
}

/** @brief Set the target information to be used by PresetTelescope. Deprecated better using SetStars or SetStars2
* @param coord1 Coordinate x [radian]
* @param coord2 Coordinate y [radian]
* @param coordsys Coordinate system: RADEC, AZALT, GALACTIC
* @param epoch
* @param wl Target wavelength
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifSetTarget(double coord1, double coord2, const std::string & coordsys, double epoch, float wl)
{
    U6_LOG(__FUNCTION__ << " ~ coord1 : " << coord1 << "; coord2 : " << coord2 << "; coordsys : " << coordsys << "; epoch : " << epoch << "; wl : " << wl);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->SetTarget(coord1, coord2, coordsys, epoch, wl);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->SetTarget(coord1, coord2, coordsys, epoch, wl);
#endif
}

/** @brief Use this command to set the standby level.
* @param level the standby level
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifStandby(int level, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ level : " << level << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->Standby(level, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->Standby(level, side);
#endif
}

/** @brief Start AO operation.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifStartAO(const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~  side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->StartAO(side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->StartAO(side);
#endif
}

/** @brief StepFocus moves the respective focus position, by moving the OPE
* @brief a given distance in the direction of of the telescope's Z axis.
* @param relPos distance to move the OPE [millimeter]
* @param ope Optical element :  M1 or M2 or M3 or M1M2
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifStepFocus(double relPos, const std::string & ope, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ relPost : " << relPos << "; ope : " << ope << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->StepFocus(relPos, ope, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->StepFocus(relPos, ope, side);
#endif
}

/** @brief Stop AO operation.
* @param reason the reason for stopping.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifStopAO(const std::string & reason, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ reason " << reason << " side " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->StopAO(reason, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->StopAO(reason, side);
#endif
}

/** @brief TipTilt moves an OPE in tip or tilt direction, relative to the current position.
* @param xrot rotation in x [microradian]
* @param yrot rotation in y [microradian]
* @param ope Optical element: M1 or M2 or M3
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifTipTilt(double xrot, double yrot, const std::string & ope, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ xrot " << xrot << " yrot " << yrot << " ope " << ope << " side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->TipTilt(xrot, yrot, ope, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->TipTilt(xrot, yrot, ope, side);
#endif
}

/** @brief Update the non-sidereal values
* @param RArate the new RArate
* @param DECrate the new DECrate
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifUpdateNonSiderealTarget(double RArate, double DECrate)
{
    U6_LOG(__FUNCTION__ << " ~ RArate : "<< RArate << "; DECrate : " << DECrate);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->UpdateNonSiderealTarget(RArate, DECrate);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->UpdateNonSiderealTarget(RArate, DECrate);
#endif
}

/** @brief This command allows the user to use the current sky or focal plane location achieved via the
* @brief OffsetPointing command and update the previous values; To be used in conjunction with the OffsetPointing cmd.
* @param offset_coord the offset coordinate system.
* @param side the side this command applies to.
* @return Result object
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifUpdatePointingReference(const std::string & offset_coord, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ offset_coord : " << offset_coord << "; side : " << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->UpdatePointingReference(offset_coord, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->UpdatePointingReference(offset_coord, side);
#endif
}


/** @brief Updates the target effective wavelength without issuing a new preset.
* @param wavelength new target wavelength. Range is [0.3, 15.0] [microns]
* @param side the side this command applies to.
* @return Result object.
* @since 2017-05-27 */
lbto::result Ltcs::IifServiceWorker::iifUpdateTargetWavelength(float wavelength, const std::string & side)
{
    U6_LOG(__FUNCTION__ << " ~ wavelength : " << wavelength << "; side :" << side);
#ifdef LN_TCS_SINGLE_ICE
    return m_iif->UpdateTargetWavelength(wavelength, side);
#else
    return m_iif[m_iifSidReg.toIntIdx()]->UpdateTargetWavelength(wavelength, side);
#endif
}
