LCOV - code coverage report
Current view: top level - apps/picoMotorCtrl - picoMotorCtrl.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 0.0 % 489 0
Test Date: 2026-01-03 21:03:39 Functions: 0.0 % 24 0

            Line data    Source code
       1              : /** \file picoMotorCtrl.hpp
       2              :   * \brief The MagAO-X Pico Motor Controller header file
       3              :   *
       4              :   * \ingroup picoMotorCtrl_files
       5              :   */
       6              : 
       7              : #ifndef picoMotorCtrl_hpp
       8              : #define picoMotorCtrl_hpp
       9              : 
      10              : #include <map>
      11              : 
      12              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      13              : #include "../../magaox_git_version.h"
      14              : 
      15              : /** \defgroup picoMotorCtrl
      16              :   * \brief The Pico Motor Controller application.
      17              :   *
      18              :   * Controls a multi-channel Newport pico motor controller.  Each motor gets its own thread.
      19              :   *
      20              :   * <a href="../handbook/operating/software/apps/picoMotorCtrl.html">Application Documentation</a>
      21              :   *
      22              :   * \ingroup apps
      23              :   *
      24              :   */
      25              : 
      26              : /** \defgroup picoMotorCtrl_files
      27              :   * \ingroup picoMotorCtrl
      28              :   */
      29              : 
      30              : 
      31              : 
      32              : namespace MagAOX
      33              : {
      34              : namespace app
      35              : {
      36              : 
      37            0 : int splitResponse( int & address,
      38              :                    std::string & response,
      39              :                    const std::string & fullResponse
      40              :                  )
      41              : {
      42            0 :    size_t carrot = fullResponse.find('>');
      43              : 
      44            0 :    if(carrot == std::string::npos)
      45              :    {
      46            0 :       address = 1;
      47            0 :       response = fullResponse;
      48            0 :       return 0;
      49              :    }
      50              : 
      51            0 :    if(carrot == 0)
      52              :    {
      53            0 :       address = 0;
      54            0 :       response = "";
      55            0 :       return -1;
      56              :    }
      57              : 
      58            0 :    if(carrot == fullResponse.size()-1)
      59              :    {
      60            0 :       address = 0;
      61            0 :       response = "";
      62            0 :       return -2;
      63              :    }
      64              : 
      65              :    try
      66              :    {
      67            0 :       address = std::stoi( fullResponse.substr(0,carrot));
      68            0 :       response = fullResponse.substr(carrot+1);
      69              :    }
      70            0 :    catch(...)
      71              :    {
      72            0 :       address = 0;
      73            0 :       response = "";
      74            0 :       return -3;
      75            0 :    }
      76              : 
      77            0 :    return 0;
      78              : 
      79              : }
      80              : 
      81              : /** MagAO-X application to control a multi-channel Newport Picomotor Controller.
      82              :   *
      83              :   * \todo need to recognize signals in tty polls and not return errors, etc.
      84              :   * \todo need to implement an onDisconnect() to update values to unknown indicators.
      85              :   * \todo need a frequency-dependent max amp facility.
      86              :   * \todo convert to ioDevice
      87              :   * \todo need telnet device, with optional username/password.
      88              :   *
      89              :   */
      90              : class picoMotorCtrl : public MagAOXApp<>, public dev::ioDevice, public dev::telemeter<picoMotorCtrl>
      91              : {
      92              : 
      93              :    friend class dev::telemeter<picoMotorCtrl>;
      94              : 
      95              :    typedef dev::telemeter<picoMotorCtrl> telemeterT;
      96              : 
      97              :    typedef long posT;
      98              : 
      99              :    struct motorChannel
     100              :    {
     101              :       picoMotorCtrl * m_parent {nullptr}; ///< A pointer to this for thread starting.
     102              : 
     103              :       std::string m_name; ///< The name of this channel, from the config section
     104              : 
     105              :       int m_address {1}; ///< The controller address, default is 1
     106              : 
     107              :       int m_channel {-1}; ///< The number of this channel, where the motor is plugged in
     108              : 
     109              :       int m_type {3}; ///< The motor type of this channel, default is 3
     110              : 
     111              :       std::vector<std::string> m_presetNames;
     112              :       std::vector<posT> m_presetPositions;
     113              : 
     114              :       posT m_currCounts {0}; ///< The current counts, the cumulative position
     115              : 
     116              :       bool m_doMove {false}; ///< Flag indicating that a move is requested.
     117              :       bool m_moving {false}; ///< Flag to indicate that we are actually moving
     118              : 
     119              :       pcf::IndiProperty m_property;
     120              :       pcf::IndiProperty m_indiP_presetName;
     121              : 
     122              :       std::thread * m_thread {nullptr}; /**< Thread for managing this channel.  A pointer to allow
     123              :                                               copying, but must be deleted in d'tor of parent.*/
     124              : 
     125              :       bool m_threadInit {true}; ///< Thread initialization flag.
     126              : 
     127              :       pid_t m_threadID {0}; ///< The ID of the thread.
     128              : 
     129              :       pcf::IndiProperty m_threadProp; ///< The property to hold the thread details.
     130              : 
     131              :       explicit motorChannel( picoMotorCtrl * p /**< [in] The parent point to set */) : m_parent(p)
     132              :       {
     133              :          m_thread = new std::thread;
     134              :       }
     135              : 
     136            0 :       motorChannel( picoMotorCtrl * p,     ///< [in] The parent point to set
     137              :                     const std::string & n, ///< [in] The name of this channel
     138              :                     int add,               ///< [in] The controller address
     139              :                     int ch,                ///< [in] The number of this channel
     140              :                     int type               ///< [in] The motor type of this channel
     141            0 :                   ) : m_parent(p), m_name(n), m_address(add), m_channel(ch), m_type(type)
     142              :       {
     143            0 :          m_thread = new std::thread;
     144            0 :       }
     145              : 
     146            0 :       motorChannel(const motorChannel &mc)
     147            0 :       {
     148            0 :         m_parent = mc.m_parent;
     149            0 :         m_name = mc.m_name;
     150            0 :         m_address = mc.m_address;
     151            0 :         m_channel = mc.m_channel;
     152            0 :         m_type = mc.m_type;
     153            0 :         m_presetNames = mc.m_presetNames;
     154            0 :         m_presetPositions = mc.m_presetPositions;
     155            0 :         m_currCounts = mc.m_currCounts;
     156            0 :         m_doMove = mc.m_doMove;
     157            0 :         m_moving = mc.m_moving;
     158            0 :         m_property = mc.m_property;
     159            0 :         m_indiP_presetName = mc.m_indiP_presetName;
     160            0 :         m_thread = mc.m_thread;
     161            0 :         m_threadInit = mc.m_threadInit;
     162            0 :         m_threadID =mc.m_threadID;
     163            0 :         m_threadProp = mc.m_threadProp;
     164            0 :       }
     165              :    };
     166              : 
     167              :    typedef std::map<std::string, motorChannel> channelMapT;
     168              : 
     169              :    /** \name Configurable Parameters
     170              :      * @{
     171              :      */
     172              : 
     173              :    std::string m_deviceAddr; ///< The device address
     174              :    std::string m_devicePort {"23"}; ///< The device port
     175              : 
     176              :    int m_nChannels {4}; ///< The number of motor channels total on the hardware.  Number of attached motors inferred from config.
     177              : 
     178              :    ///@}
     179              : 
     180              :    std::vector<int> m_addresses; ///< The unique controller addresses.
     181              : 
     182              :    channelMapT m_channels; ///< Map of motor names to channel.
     183              : 
     184              :    tty::telnetConn m_telnetConn; ///< The telnet connection manager
     185              : 
     186              :    ///Mutex for locking telnet communications.
     187              :    std::mutex m_telnetMutex;
     188              : 
     189              :    public:
     190              : 
     191              :    /// Default c'tor.
     192              :    picoMotorCtrl();
     193              : 
     194              :    /// D'tor, declared and defined for noexcept.
     195              :    ~picoMotorCtrl() noexcept;
     196              : 
     197              :    /// Setup the configuration system (called by MagAOXApp::setup())
     198              :    virtual void setupConfig();
     199              : 
     200              :    /// Implementation of loadConfig logic, separated for testing.
     201              :    /** This is called by loadConfig().
     202              :      */
     203              :    int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
     204              : 
     205              :    /// load the configuration system results (called by MagAOXApp::setup())
     206              :    virtual void loadConfig();
     207              : 
     208              :    /// Startup functions
     209              :    /** Setsup the INDI vars.
     210              :      *
     211              :      */
     212              :    virtual int appStartup();
     213              : 
     214              :    /// Implementation of the FSM
     215              :    /**
     216              :      * \returns 0 on no critical error
     217              :      * \returns -1 on an error requiring shutdown
     218              :      */
     219              :    virtual int appLogic();
     220              : 
     221              :    /// Implementation of the on-power-off FSM logic
     222              :    virtual int onPowerOff();
     223              : 
     224              :    /// Implementation of the while-powered-off FSM
     225              :    virtual int whilePowerOff();
     226              : 
     227              :    /// Do any needed shutdown tasks.
     228              :    virtual int appShutdown();
     229              : 
     230              :    /// Read the current channel counts from disk at startup
     231              :    /** Reads the counts from the file with the specified name in this apps sys directory.
     232              :      * Returns the file contents as a posT.
     233              :      */
     234              :    posT readChannelCounts(const std::string & chName);
     235              : 
     236              :    int writeChannelCounts( const std::string & chName,
     237              :                            posT counts
     238              :                          );
     239              : 
     240              :    /// Channel thread starter function
     241              :    static void channelThreadStart( motorChannel * mc /**< [in] the channel to start controlling */);
     242              : 
     243              :    /// Channel thread execution function
     244              :    /** Runs until m_shutdown is true.
     245              :      */
     246              :    void channelThreadExec( motorChannel * mc );
     247              : 
     248              : /** \name INDI
     249              :      * @{
     250              :      */
     251              : protected:
     252              : 
     253              :    //declare our properties
     254              :    std::vector<pcf::IndiProperty> m_indiP_counts;
     255              : 
     256              : 
     257              : public:
     258              :    /// The static callback function to be registered for relative position requests
     259              :    /** Dispatches to the handler, which then signals the relavent thread.
     260              :      *
     261              :      * \returns 0 on success.
     262              :      * \returns -1 on error.
     263              :      */
     264              :    static int st_newCallBack_picopos( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
     265              :                                       const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
     266              :                                     );
     267              : 
     268              :    /// The handler function for relative position requests, called by the static callback
     269              :    /** Signals the relavent thread.
     270              :      *
     271              :      * \returns 0 on success.
     272              :      * \returns -1 on error.
     273              :      */
     274              :    int newCallBack_picopos( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
     275              : 
     276              :    /// The static callback function to be registered for position presets
     277              :    /** Dispatches to the handler, which then signals the relavent thread.
     278              :      *
     279              :      * \returns 0 on success.
     280              :      * \returns -1 on error.
     281              :      */
     282              :    static int st_newCallBack_presetName( void * app, ///< [in] a pointer to this, will be static_cast-ed to this
     283              :                                       const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
     284              :                                     );
     285              : 
     286              :    /// The handler function for position presets, called by the static callback
     287              :    /** Signals the relavent thread.
     288              :      *
     289              :      * \returns 0 on success.
     290              :      * \returns -1 on error.
     291              :      */
     292              :    int newCallBack_presetName( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
     293              :    ///@}
     294              : 
     295              :    /** \name Telemeter Interface
     296              :      *
     297              :      * @{
     298              :      */
     299              :    int checkRecordTimes();
     300              : 
     301              :    int recordTelem( const telem_pico * );
     302              : 
     303              :    int recordPico( bool force = false );
     304              :    ///@}
     305              : 
     306              : };
     307              : 
     308            0 : picoMotorCtrl::picoMotorCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
     309              : {
     310            0 :    m_powerMgtEnabled = true;
     311            0 :    m_telnetConn.m_prompt = "\r\n";
     312            0 :    return;
     313            0 : }
     314              : 
     315            0 : picoMotorCtrl::~picoMotorCtrl() noexcept
     316              : {
     317              :    //Wait for each channel thread to exit, then delete it.
     318            0 :    for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
     319              :    {
     320            0 :       if(it->second.m_thread != nullptr)
     321              :       {
     322            0 :          if(it->second.m_thread->joinable()) it->second.m_thread->join();
     323            0 :          delete it->second.m_thread;
     324              :       }
     325              :    }
     326            0 : }
     327              : 
     328              : 
     329            0 : void picoMotorCtrl::setupConfig()
     330              : {
     331            0 :    config.add("device.address", "", "device.address", argType::Required, "device", "address", false, "string", "The controller IP address.");
     332            0 :    config.add("device.nChannels", "", "device.nChannels", argType::Required, "device", "nChannels", false, "int", "Number of motoro channels.  Default is 4.");
     333              : 
     334            0 :    dev::ioDevice::setupConfig(config);
     335              : 
     336            0 :    TELEMETER_SETUP_CONFIG( config );
     337              : 
     338            0 : }
     339              : 
     340              : #define PICOMOTORCTRL_E_NOMOTORS   (-5)
     341              : #define PICOMOTORCTRL_E_BADCHANNEL (-6)
     342              : #define PICOMOTORCTRL_E_DUPMOTOR   (-7)
     343              : #define PICOMOTORCTRL_E_INDIREG    (-20)
     344              : 
     345            0 : int picoMotorCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
     346              : {
     347              :    //Standard config parsing
     348            0 :    _config(m_deviceAddr, "device.address");
     349            0 :    _config(m_nChannels, "device.nChannels");
     350              : 
     351              : 
     352              :    // Parse the unused config options to look for motors
     353            0 :    std::vector<std::string> sections;
     354              : 
     355            0 :    _config.unusedSections(sections);
     356              : 
     357            0 :    if( sections.size() == 0 )
     358              :    {
     359            0 :       log<text_log>("No motors found in config.", logPrio::LOG_CRITICAL);
     360              : 
     361            0 :       return PICOMOTORCTRL_E_NOMOTORS;
     362              :    }
     363              : 
     364              :    //Now see if any unused sections have a channel keyword
     365            0 :    for(size_t i=0;i<sections.size(); ++i)
     366              :    {
     367            0 :       int channel = -1;
     368            0 :       _config.configUnused(channel, mx::app::iniFile::makeKey(sections[i], "channel" ) );
     369            0 :       if( channel == -1 )
     370              :       {
     371              :          //not a channel
     372            0 :          continue;
     373              :       }
     374              : 
     375            0 :       if(channel < 1 || channel > m_nChannels)
     376              :       {
     377            0 :          log<text_log>("Bad channel specificiation: " + sections[i] + " channel: " + std::to_string(channel), logPrio::LOG_CRITICAL);
     378              : 
     379            0 :          return PICOMOTORCTRL_E_BADCHANNEL;
     380              :       }
     381              : 
     382            0 :       int address = 1;
     383            0 :       _config.configUnused(address, mx::app::iniFile::makeKey(sections[i], "address" ) );
     384              : 
     385            0 :       if(address < 1)
     386              :       {
     387            0 :          log<text_log>("Bad channel specificiation: " + sections[i] + " address: " + std::to_string(address), logPrio::LOG_CRITICAL);
     388              : 
     389            0 :          return PICOMOTORCTRL_E_BADCHANNEL;
     390              :       }
     391              : 
     392            0 :       int type = 3;
     393            0 :       _config.configUnused(type, mx::app::iniFile::makeKey(sections[i], "type" ) );
     394              : 
     395            0 :       if(type < 1)
     396              :       {
     397            0 :          log<text_log>("Bad motor type specificiation: " + sections[i] + " type: " + std::to_string(type), logPrio::LOG_CRITICAL);
     398              : 
     399            0 :          return PICOMOTORCTRL_E_BADCHANNEL;
     400              :       }
     401              : 
     402              :       //Ok, valid channel.  Insert into map and check for duplicates.
     403            0 :       std::pair<channelMapT::iterator, bool> insert = m_channels.insert(std::pair<std::string, motorChannel>(sections[i], motorChannel(this,sections[i], address, channel, type)));
     404              : 
     405            0 :       if(insert.second == false)
     406              :       {
     407            0 :          log<text_log>("Duplicate motor specificiation: " + sections[i] + " " + std::to_string(channel), logPrio::LOG_CRITICAL);
     408            0 :          return PICOMOTORCTRL_E_DUPMOTOR;
     409              :       }
     410              :       else
     411              :       {
     412            0 :          _config.configUnused(insert.first->second.m_presetNames, mx::app::iniFile::makeKey(sections[i], "names" ));
     413            0 :          _config.configUnused(insert.first->second.m_presetPositions, mx::app::iniFile::makeKey(sections[i], "positions" ));
     414              :       }
     415              : 
     416              :       ///\todo extend to include address
     417            0 :       log<pico_channel>({sections[i], (uint8_t) channel});
     418              : 
     419            0 :       bool found = false;
     420            0 :       for(size_t n = 0; n < m_addresses.size(); ++n)
     421              :       {
     422            0 :          if(address == m_addresses[n])
     423              :          {
     424            0 :             found = true;
     425            0 :             break;
     426              :          }
     427              :       }
     428              : 
     429            0 :       if(!found)
     430              :       {
     431            0 :          m_addresses.push_back(address);
     432              :       }
     433              :    }
     434              : 
     435            0 :    TELEMETER_LOAD_CONFIG( config );
     436              : 
     437            0 :    return 0;
     438            0 : }
     439              : 
     440            0 : void picoMotorCtrl::loadConfig()
     441              : {
     442            0 :    if( loadConfigImpl(config) < 0)
     443              :    {
     444            0 :       log<text_log>("Error during config", logPrio::LOG_CRITICAL);
     445            0 :       m_shutdown = true;
     446              :    }
     447              : 
     448            0 :    if(dev::ioDevice::loadConfig(config) < 0)
     449              :    {
     450            0 :       log<text_log>("Error during ioDevice config", logPrio::LOG_CRITICAL);
     451            0 :       m_shutdown = true;
     452              :    }
     453              : 
     454              : 
     455              : 
     456            0 : }
     457              : 
     458            0 : int picoMotorCtrl::appStartup()
     459              : {
     460              :    ///\todo read state from disk to get current counts.
     461              : 
     462            0 :    for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
     463              :    {
     464            0 :       it->second.m_currCounts = readChannelCounts(it->second.m_name);
     465              : 
     466              : 
     467            0 :       createStandardIndiNumber( it->second.m_property, it->first+"_pos", std::numeric_limits<posT>::lowest(), std::numeric_limits<posT>::max(), static_cast<posT>(1), "%d", "Position", it->first);
     468            0 :       it->second.m_property["current"].set(it->second.m_currCounts);
     469            0 :       it->second.m_property["target"].set(it->second.m_currCounts);
     470            0 :       it->second.m_property.setState(INDI_IDLE);
     471              : 
     472            0 :       if( registerIndiPropertyNew( it->second.m_property, st_newCallBack_picopos) < 0)
     473              :       {
     474              :          #ifndef PICOMOTORCTRL_TEST_NOLOG
     475            0 :          log<software_error>({__FILE__,__LINE__});
     476              :          #endif
     477            0 :          return PICOMOTORCTRL_E_INDIREG;
     478              :       }
     479              : 
     480            0 :       if(it->second.m_presetNames.size() > 0)
     481              :       {
     482            0 :          if(createStandardIndiSelectionSw( it->second.m_indiP_presetName, it->first, it->second.m_presetNames) < 0)
     483              :          {
     484            0 :             log<software_critical>({__FILE__, __LINE__});
     485            0 :             return -1;
     486              :          }
     487            0 :          if( registerIndiPropertyNew( it->second.m_indiP_presetName, st_newCallBack_presetName) < 0)
     488              :          {
     489            0 :             log<software_error>({__FILE__,__LINE__});
     490            0 :             return -1;
     491              :          }
     492              :       }
     493              : 
     494              :       //Here we start each channel thread, with 0 R/T prio.
     495            0 :       threadStart( *it->second.m_thread, it->second.m_threadInit, it->second.m_threadID, it->second.m_threadProp, 0, "", it->second.m_name, &it->second, channelThreadStart);
     496              :    }
     497              : 
     498              :    //Install empty signal handler for USR1, which is used to interrupt sleeps in the channel threads.
     499              :    struct sigaction act;
     500              :    sigset_t set;
     501              : 
     502            0 :    act.sa_sigaction = &sigUsr1Handler;
     503            0 :    act.sa_flags = SA_SIGINFO;
     504            0 :    sigemptyset(&set);
     505            0 :    act.sa_mask = set;
     506              : 
     507            0 :    errno = 0;
     508            0 :    if( sigaction(SIGUSR1, &act, 0) < 0 )
     509              :    {
     510            0 :       std::string logss = "Setting handler for SIGUSR1 failed. Errno says: ";
     511            0 :       logss += strerror(errno);
     512              : 
     513            0 :       log<software_error>({__FILE__, __LINE__, errno, 0, logss});
     514              : 
     515            0 :       return -1;
     516            0 :    }
     517              : 
     518            0 :    TELEMETER_APP_STARTUP;
     519              : 
     520            0 :    return 0;
     521              : }
     522              : 
     523            0 : int picoMotorCtrl::appLogic()
     524              : {
     525            0 :    if( state() == stateCodes::POWERON)
     526              :    {
     527            0 :       if(!powerOnWaitElapsed()) return 0;
     528              : 
     529            0 :       state(stateCodes::NOTCONNECTED);
     530              :    }
     531              : 
     532            0 :    if(state() == stateCodes::NOTCONNECTED || state() == stateCodes::ERROR)
     533              :    {
     534            0 :       int rv = m_telnetConn.connect(m_deviceAddr, m_devicePort);
     535              : 
     536            0 :       if(rv == 0)
     537              :       {
     538            0 :          state(stateCodes::CONNECTED);
     539            0 :          m_telnetConn.noLogin();
     540              :       }
     541              :       else
     542              :       {
     543            0 :          if(powerState() != 1 || powerStateTarget() != 1) return 0;
     544              : 
     545            0 :          if(!stateLogged())
     546              :          {
     547            0 :             log<text_log>("Failed to connect on " + m_deviceAddr + ":" + m_devicePort);
     548              :          }
     549              : 
     550            0 :          return 0;
     551              :       }
     552              : 
     553              :    }
     554              : 
     555            0 :    if(state() == stateCodes::CONNECTED)
     556              :    {
     557              : 
     558            0 :       std::unique_lock<std::mutex> lock(m_telnetMutex);
     559              : 
     560              :       //First check the address 1 controller
     561            0 :       int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
     562            0 :       if(rv != TTY_E_NOERROR)
     563              :       {
     564            0 :          if(powerState() != 1 || powerStateTarget() != 1) return 0;
     565            0 :          log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     566            0 :          state(stateCodes::ERROR);
     567            0 :          return 0;
     568              :       }
     569              : 
     570            0 :       rv = m_telnetConn.read("\r\n", m_readTimeout, true);
     571            0 :       if(rv != TTY_E_NOERROR)
     572              :       {
     573            0 :          if(powerState() != 1 || powerStateTarget() != 1) return 0;
     574            0 :          log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     575            0 :          state(stateCodes::ERROR);
     576            0 :          return 0;
     577              :       }
     578              : 
     579            0 :       if(m_telnetConn.m_strRead.find("New_Focus") != std::string::npos)
     580              :       {
     581            0 :          log<text_log>("Connected to " + m_telnetConn.m_strRead + " at address 1");
     582              :       }
     583              :       else
     584              :       {
     585            0 :          if(powerState() != 1 || powerStateTarget() != 1) return 0;
     586            0 :          log<software_error>({__FILE__, __LINE__, "wrong response to IDN query at address 1"});
     587            0 :          state(stateCodes::ERROR);
     588            0 :          return 0;
     589              :       }
     590              : 
     591              :       //Now do a controller scan
     592            0 :       rv = m_telnetConn.write("SC1\r\n", m_writeTimeout); //Will adjust addresses.
     593            0 :       if(rv != TTY_E_NOERROR)
     594              :       {
     595            0 :          if(powerState() != 1 || powerStateTarget() != 1) return 0;
     596            0 :          log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     597            0 :          state(stateCodes::ERROR);
     598            0 :          return 0;
     599              :       }
     600              : 
     601              :       //And now do a motor scan
     602            0 :       rv = m_telnetConn.write("MC\r\n", m_writeTimeout);
     603            0 :       if(rv != TTY_E_NOERROR)
     604              :       {
     605            0 :          if(powerState() != 1 || powerStateTarget() != 1) return 0;
     606            0 :          log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     607            0 :          state(stateCodes::ERROR);
     608            0 :          return 0;
     609              :       }
     610              : 
     611            0 :       sleep(2); //Give time for controller scan to finish
     612              : 
     613            0 :       for(size_t n = 0; n < m_addresses.size(); ++n)
     614              :       {
     615            0 :          if(m_addresses[n] == 1) continue; //already done.
     616              : 
     617            0 :          std::string addprefix = std::to_string(m_addresses[n]) + ">";
     618              : 
     619            0 :          int rv = m_telnetConn.write(addprefix + "*IDN?\r\n", m_writeTimeout);
     620            0 :          if(rv != TTY_E_NOERROR)
     621              :          {
     622            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     623            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     624            0 :             state(stateCodes::ERROR);
     625            0 :             return 0;
     626              :          }
     627              : 
     628            0 :          rv = m_telnetConn.read("\r\n", m_readTimeout, true);
     629            0 :          if(rv != TTY_E_NOERROR)
     630              :          {
     631            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     632            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     633            0 :             state(stateCodes::ERROR);
     634            0 :             return 0;
     635              :          }
     636              : 
     637              :          int add;
     638            0 :          std::string resp;
     639              : 
     640            0 :          rv = splitResponse(add, resp, m_telnetConn.m_strRead);
     641            0 :          if(rv != 0)
     642              :          {
     643            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     644            0 :             log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
     645            0 :             state(stateCodes::ERROR);
     646            0 :             return 0;
     647              :          }
     648              : 
     649            0 :          if(add != m_addresses[n])
     650              :          {
     651            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     652            0 :             log<software_error>({__FILE__, __LINE__, "address did not match in response"});
     653            0 :             state(stateCodes::ERROR);
     654            0 :             return 0;
     655              :          }
     656              : 
     657            0 :          if(resp.find("New_Focus") != std::string::npos)
     658              :          {
     659            0 :             log<text_log>("Connected to " + resp + " at address " + std::to_string(m_addresses[n]));
     660              :          }
     661              :          else
     662              :          {
     663            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     664            0 :             log<software_error>({__FILE__, __LINE__, "wrong response to IDN query at address " + std::to_string(m_addresses[n])});
     665            0 :             state(stateCodes::ERROR);
     666            0 :             return 0;
     667              :          }
     668              : 
     669              :          //Now do a motor scan
     670            0 :          rv = m_telnetConn.write(addprefix + "MC\r\n", m_writeTimeout);
     671            0 :          if(rv != TTY_E_NOERROR)
     672              :          {
     673            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     674            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     675            0 :             state(stateCodes::ERROR);
     676            0 :             return 0;
     677              :          }
     678            0 :       }
     679              : 
     680            0 :       sleep(2); //This is to give time for motor scans to finish
     681              : 
     682              :       //Now check for each motor attached
     683            0 :       for(auto it=m_channels.begin(); it!=m_channels.end();++it)
     684              :       {
     685            0 :          std::string query = std::to_string(it->second.m_address) + ">" + std::to_string(it->second.m_channel) + "QM?";
     686              : 
     687            0 :          rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
     688            0 :          if(rv != TTY_E_NOERROR)
     689              :          {
     690            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     691            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     692            0 :             state(stateCodes::ERROR);
     693            0 :             return 0;
     694              :          }
     695              : 
     696            0 :          rv = m_telnetConn.read("\r\n", m_readTimeout, true);
     697            0 :          if(rv != TTY_E_NOERROR)
     698              :          {
     699            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     700            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     701            0 :             state(stateCodes::ERROR);
     702            0 :             return 0;
     703              :          }
     704              : 
     705              :          int add;
     706            0 :          std::string resp;
     707              : 
     708            0 :          rv = splitResponse(add, resp, m_telnetConn.m_strRead);
     709            0 :          if(rv != 0)
     710              :          {
     711            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     712            0 :             log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
     713            0 :             state(stateCodes::ERROR);
     714            0 :             return 0;
     715              :          }
     716              : 
     717            0 :          if(add != it->second.m_address)
     718              :          {
     719            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     720            0 :             log<software_error>({__FILE__, __LINE__, "address did not match in response"});
     721            0 :             state(stateCodes::ERROR);
     722            0 :             return 0;
     723              :          }
     724              : 
     725            0 :          int moType = std::stoi(resp);
     726            0 :          if(moType == 0)
     727              :          {
     728            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     729            0 :             log<text_log>("No motor connected on channel " + std::to_string(it->second.m_address) + "." + std::to_string(it->second.m_channel) + " [" + it->second.m_name + "]", logPrio::LOG_CRITICAL);
     730            0 :             state(stateCodes::FAILURE);
     731            0 :             return -1;
     732              :          }
     733            0 :          else if (moType != it->second.m_type)
     734              :          {
     735            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     736            0 :             std::string msg = "Wrong motor type connected on channel " + std::to_string(it->second.m_address) + ".";
     737            0 :             msg += std::to_string(it->second.m_channel) + " [" + it->second.m_name + "] ";
     738            0 :             msg += "expected " + std::to_string(it->second.m_type) + " ";
     739            0 :             msg += "got " + std::to_string(moType);
     740              : 
     741            0 :             log<text_log>(msg, logPrio::LOG_CRITICAL);
     742            0 :             state(stateCodes::FAILURE);
     743            0 :             return -1;
     744            0 :          }
     745            0 :       }
     746              : 
     747            0 :       state(stateCodes::READY);
     748              : 
     749            0 :       return 0;
     750            0 :    }
     751              : 
     752            0 :    if(state() == stateCodes::READY || state() == stateCodes::OPERATING)
     753              :    {
     754              :       //check connection
     755              :       {
     756            0 :          std::unique_lock<std::mutex> lock(m_telnetMutex);
     757              : 
     758            0 :          int rv = m_telnetConn.write("*IDN?\r\n", m_writeTimeout);
     759            0 :          if(rv != TTY_E_NOERROR)
     760              :          {
     761            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     762            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     763            0 :             state(stateCodes::ERROR);
     764            0 :             return 0;
     765              :          }
     766              : 
     767            0 :          rv = m_telnetConn.read("\r\n", m_readTimeout, true);
     768            0 :          if(rv != TTY_E_NOERROR)
     769              :          {
     770            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     771            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     772            0 :             state(stateCodes::ERROR);
     773            0 :             return 0;
     774              :          }
     775              : 
     776            0 :          if(m_telnetConn.m_strRead.find("New_Focus") == std::string::npos)
     777              :          {
     778            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     779              : 
     780            0 :             log<software_error>({__FILE__, __LINE__, "wrong response to IDN query"});
     781            0 :             state(stateCodes::ERROR);
     782            0 :             return 0;
     783              :          }
     784            0 :       }
     785              : 
     786              :       //Now check state of motors
     787            0 :       bool anymoving = false;
     788              : 
     789              :       //This is where we'd check for moving
     790            0 :       for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
     791              :       {
     792            0 :          std::unique_lock<std::mutex> lock(m_telnetMutex);
     793              : 
     794            0 :          std::string query = std::to_string(it->second.m_address) + ">" + std::to_string(it->second.m_channel) + "MD?";
     795              : 
     796            0 :          int rv = m_telnetConn.write(query + "\r\n", m_writeTimeout);
     797            0 :          if(rv != TTY_E_NOERROR)
     798              :          {
     799            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     800            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     801            0 :             state(stateCodes::ERROR);
     802            0 :             return 0;
     803              :          }
     804              : 
     805            0 :          rv = m_telnetConn.read("\r\n", m_readTimeout, true);
     806            0 :          if(rv != TTY_E_NOERROR)
     807              :          {
     808            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     809            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
     810            0 :             state(stateCodes::ERROR);
     811            0 :             return 0;
     812              :          }
     813              : 
     814              :          int add;
     815            0 :          std::string resp;
     816              : 
     817            0 :          rv = splitResponse(add, resp, m_telnetConn.m_strRead);
     818            0 :          if(rv != 0)
     819              :          {
     820            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     821            0 :             log<software_error>({__FILE__, __LINE__, "splitResponse returned " + std::to_string(rv)});
     822            0 :             state(stateCodes::ERROR);
     823            0 :             return 0;
     824              :          }
     825              : 
     826            0 :          if(add != it->second.m_address)
     827              :          {
     828            0 :             if(powerState() != 1 || powerStateTarget() != 1) return 0;
     829            0 :             log<software_error>({__FILE__, __LINE__, "address did not match in response"});
     830            0 :             state(stateCodes::ERROR);
     831            0 :             return 0;
     832              :          }
     833              : 
     834              :          //The check for moving here. With power off detection
     835            0 :          if(std::stoi(resp) == 0)
     836              :          {
     837            0 :             anymoving = true;
     838            0 :             it->second.m_moving = true;
     839              :          }
     840              :          else
     841              :          {
     842            0 :             it->second.m_moving = false;
     843              :          }
     844              : 
     845            0 :          if(it->second.m_moving == false && it->second.m_doMove == true)
     846              :          {
     847            0 :             it->second.m_currCounts = it->second.m_property["target"].get<long>();
     848            0 :             log<text_log>("moved " + it->second.m_name + " to " + std::to_string(it->second.m_currCounts) + " counts");
     849            0 :             it->second.m_doMove = false;
     850            0 :             recordPico(true);
     851              :          }
     852            0 :       }
     853              : 
     854            0 :       if(anymoving == false) state(stateCodes::READY);
     855            0 :       else state(stateCodes::OPERATING);
     856              : 
     857            0 :       for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
     858              :       {
     859            0 :          std::unique_lock<std::mutex> lock(m_indiMutex);
     860            0 :          if(it->second.m_moving) updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_BUSY);
     861            0 :          else updateIfChanged(it->second.m_property, "current", it->second.m_currCounts, INDI_IDLE);
     862              : 
     863            0 :          for(size_t n=0; n < it->second.m_presetNames.size(); ++n)
     864              :          {
     865            0 :             bool changed = false;
     866            0 :             if( it->second.m_currCounts == it->second.m_presetPositions[n])
     867              :             {
     868            0 :                if(it->second.m_indiP_presetName[it->second.m_presetNames[n]] == pcf::IndiElement::Off) changed = true;
     869            0 :                it->second.m_indiP_presetName[it->second.m_presetNames[n]] = pcf::IndiElement::On;
     870              :             }
     871              :             else
     872              :             {
     873            0 :                if(it->second.m_indiP_presetName[it->second.m_presetNames[n]] == pcf::IndiElement::On) changed = true;
     874            0 :                it->second.m_indiP_presetName[it->second.m_presetNames[n]] = pcf::IndiElement::Off;
     875              :             }
     876              : 
     877            0 :             if(changed) m_indiDriver->sendSetProperty(it->second.m_indiP_presetName);
     878              :          }
     879              : 
     880            0 :          if(writeChannelCounts(it->second.m_name, it->second.m_currCounts) < 0)
     881              :          {
     882            0 :             log<software_error>({__FILE__, __LINE__});
     883              :          }
     884            0 :       }
     885              : 
     886            0 :       TELEMETER_APP_LOGIC;
     887              : 
     888            0 :       return 0;
     889              :    }
     890              : 
     891              : 
     892            0 :    return 0;
     893              : }
     894              : 
     895            0 : int picoMotorCtrl::onPowerOff()
     896              : {
     897            0 :    return 0;
     898              : }
     899              : 
     900            0 : int picoMotorCtrl::whilePowerOff()
     901              : {
     902            0 :    return 0;
     903              : }
     904              : 
     905            0 : int picoMotorCtrl::appShutdown()
     906              : {
     907              :    //Shutdown and join the threads
     908            0 :    for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
     909              :    {
     910            0 :       if(it->second.m_thread->joinable())
     911              :       {
     912            0 :          pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
     913              :          try
     914              :          {
     915            0 :             it->second.m_thread->join(); //this will throw if it was already joined
     916              :          }
     917            0 :          catch(...)
     918              :          {
     919            0 :          }
     920              :       }
     921              :    }
     922              : 
     923            0 :    TELEMETER_APP_SHUTDOWN;
     924              : 
     925            0 :    return 0;
     926              : }
     927              : 
     928            0 : picoMotorCtrl::posT picoMotorCtrl::readChannelCounts(const std::string & chName)
     929              : {
     930            0 :    std::string statusDir = m_sysPath;
     931            0 :    statusDir += "/";
     932            0 :    statusDir += m_configName;
     933              : 
     934            0 :    std::string fileName = statusDir + "/" + chName;
     935              : 
     936            0 :    std::ifstream posIn;
     937            0 :    posIn.open( fileName );
     938              : 
     939            0 :    if(!posIn.good())
     940              :    {
     941            0 :       log<text_log>("no position file for " + chName + " found.  initializing to 0.");
     942            0 :       return 0;
     943              :    }
     944              : 
     945              :    long pos;
     946            0 :    posIn >> pos;
     947              : 
     948            0 :    posIn.close();
     949              : 
     950            0 :    log<text_log>("initializing " + chName + " to " + std::to_string(pos));
     951              : 
     952            0 :    return pos;
     953            0 : }
     954              : 
     955            0 : int picoMotorCtrl::writeChannelCounts( const std::string & chName,
     956              :                                        posT counts
     957              :                                      )
     958              : {
     959            0 :    std::string statusDir = m_sysPath;
     960            0 :    statusDir += "/";
     961            0 :    statusDir += m_configName;
     962              : 
     963            0 :    std::string fileName = statusDir + "/" + chName;
     964              : 
     965            0 :    elevatedPrivileges ep(this);
     966              : 
     967            0 :    std::ofstream posOut;
     968            0 :    posOut.open( fileName );
     969              : 
     970            0 :    if(!posOut.good())
     971              :    {
     972            0 :       log<text_log>("could not open counts file for " + chName + " -- can not store position.", logPrio::LOG_ERROR);
     973            0 :       return -1;
     974              :    }
     975              : 
     976            0 :    posOut << counts;
     977              : 
     978            0 :    posOut.close();
     979              : 
     980            0 :    return 0;
     981            0 : }
     982              : 
     983            0 : void picoMotorCtrl::channelThreadStart( motorChannel * mc )
     984              : {
     985            0 :    mc->m_parent->channelThreadExec(mc);
     986            0 : }
     987              : 
     988            0 : void picoMotorCtrl::channelThreadExec( motorChannel * mc)
     989              : {
     990              :    //Get the thread PID immediately so the caller can return.
     991            0 :    mc->m_threadID = syscall(SYS_gettid);
     992              : 
     993              :    //Wait for initialization to complete.
     994            0 :    while( mc->m_threadInit == true && m_shutdown == 0)
     995              :    {
     996            0 :       sleep(1);
     997              :    }
     998              : 
     999              :    //Now begin checking for state change request.
    1000            0 :    while(!m_shutdown)
    1001              :    {
    1002              :       //If told to move and not moving, start a move
    1003            0 :       if(mc->m_doMove && !mc->m_moving && (state() == stateCodes::READY || state() == stateCodes::OPERATING))
    1004              :       {
    1005            0 :          long dr = mc->m_property["target"].get<long>() - mc->m_currCounts;
    1006              : 
    1007            0 :          recordPico(true);
    1008            0 :          std::unique_lock<std::mutex> lock(m_telnetMutex);
    1009            0 :          state(stateCodes::OPERATING);
    1010            0 :          mc->m_moving = true;
    1011            0 :          log<text_log>("moving " + mc->m_name + " by " + std::to_string(dr) + " counts");
    1012              : 
    1013            0 :          std::string comm = std::to_string(mc->m_address) + ">" + std::to_string(mc->m_channel) + "PR" + std::to_string(dr);
    1014              : 
    1015            0 :          int rv = m_telnetConn.write(comm + "\r\n", m_writeTimeout);
    1016            0 :          if(rv != TTY_E_NOERROR)
    1017              :          {
    1018            0 :             if(powerState() != 1 || powerStateTarget() != 1) //about to get POWEROFF
    1019              :             {
    1020            0 :                sleep(1);
    1021            0 :                continue;
    1022              :             }
    1023            0 :             log<software_error>({__FILE__, __LINE__, tty::ttyErrorString(rv)});
    1024            0 :             state(stateCodes::ERROR);
    1025              :          }
    1026            0 :       }
    1027            0 :       else if( !(state() == stateCodes::READY || state() == stateCodes::OPERATING))
    1028              :       {
    1029            0 :          mc->m_doMove = false; //In case a move is requested when not able to move
    1030              :       }
    1031              : 
    1032            0 :       sleep(1);
    1033              :    }
    1034              : 
    1035              : 
    1036            0 : }
    1037              : 
    1038              : 
    1039            0 : int picoMotorCtrl::st_newCallBack_picopos( void * app,
    1040              :                                            const pcf::IndiProperty &ipRecv
    1041              :                                          )
    1042              : {
    1043            0 :    return static_cast<picoMotorCtrl*>(app)->newCallBack_picopos(ipRecv);
    1044              : }
    1045              : 
    1046            0 : int picoMotorCtrl::newCallBack_picopos( const pcf::IndiProperty &ipRecv )
    1047              : {
    1048              : 
    1049              :    //Search for the channel
    1050            0 :    std::string propName = ipRecv.getName();
    1051            0 :    size_t nend = propName.rfind("_pos");
    1052              : 
    1053            0 :    if(nend == std::string::npos)
    1054              :    {
    1055            0 :       log<software_error>({__FILE__, __LINE__, "Channel without _pos received"});
    1056            0 :       return -1;
    1057              :    }
    1058              : 
    1059            0 :    std::string chName = propName.substr(0, nend);
    1060            0 :    channelMapT::iterator it = m_channels.find(chName);
    1061              : 
    1062            0 :    if(it == m_channels.end())
    1063              :    {
    1064            0 :       log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
    1065            0 :       return -1;
    1066              :    }
    1067              : 
    1068            0 :    if(it->second.m_doMove == true)
    1069              :    {
    1070            0 :       log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
    1071            0 :       return 0;
    1072              :    }
    1073              : 
    1074              :    //Set the target element, and the doMove flag, and then signal the thread.
    1075              :    {//scope for mutex
    1076            0 :       std::unique_lock<std::mutex> lock(m_indiMutex);
    1077              : 
    1078              :       long counts; //not actually used
    1079            0 :       if(indiTargetUpdate( it->second.m_property, counts, ipRecv, true) < 0)
    1080              :       {
    1081            0 :          return log<software_error,-1>({__FILE__,__LINE__});
    1082              :       }
    1083            0 :    }
    1084              : 
    1085            0 :    it->second.m_doMove= true;
    1086              : 
    1087            0 :    pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
    1088              : 
    1089            0 :    return 0;
    1090            0 : }
    1091              : 
    1092            0 : int picoMotorCtrl::st_newCallBack_presetName( void * app,
    1093              :                                              const pcf::IndiProperty &ipRecv
    1094              :                                             )
    1095              : {
    1096            0 :    return static_cast<picoMotorCtrl*>(app)->newCallBack_presetName (ipRecv);
    1097              : }
    1098              : 
    1099            0 : int picoMotorCtrl::newCallBack_presetName( const pcf::IndiProperty &ipRecv )
    1100              : {
    1101            0 :    channelMapT::iterator it = m_channels.find(ipRecv.getName());
    1102              : 
    1103            0 :    if(it == m_channels.end())
    1104              :    {
    1105            0 :       log<software_error>({__FILE__, __LINE__, "Unknown channel name received"});
    1106            0 :       return -1;
    1107              :    }
    1108              : 
    1109            0 :    if(it->second.m_doMove == true)
    1110              :    {
    1111            0 :       log<text_log>("channel " + it->second.m_name + " is already moving", logPrio::LOG_WARNING);
    1112            0 :       return 0;
    1113              :    }
    1114              : 
    1115            0 :    long counts = -1e10;
    1116              : 
    1117              :    size_t i;
    1118            0 :    for(i=0; i< it->second.m_presetNames.size(); ++i)
    1119              :    {
    1120            0 :       if(!ipRecv.find(it->second.m_presetNames[i])) continue;
    1121              : 
    1122            0 :       if(ipRecv[it->second.m_presetNames[i]].getSwitchState() == pcf::IndiElement::On)
    1123              :       {
    1124            0 :          if(counts != -1e10)
    1125              :          {
    1126            0 :             log<text_log>("More than one preset selected", logPrio::LOG_ERROR);
    1127            0 :             return -1;
    1128              :          }
    1129              : 
    1130            0 :          counts = it->second.m_presetPositions[i];
    1131            0 :          std::cerr << "selected: " << it->second.m_presetNames[i] << " " << counts << "\n";
    1132              :       }
    1133              :    }
    1134              : 
    1135              :    //Set the target element, and the doMove flag, and then signal the thread.
    1136              :    {//scope for mutex
    1137            0 :       std::unique_lock<std::mutex> lock(m_indiMutex);
    1138              : 
    1139            0 :       it->second.m_property["target"].set(counts);
    1140            0 :    }
    1141              : 
    1142            0 :    it->second.m_doMove= true;
    1143              : 
    1144            0 :    pthread_kill(it->second.m_thread->native_handle(), SIGUSR1);
    1145              : 
    1146            0 :    return 0;
    1147              : }
    1148              : 
    1149            0 : int picoMotorCtrl::checkRecordTimes()
    1150              : {
    1151            0 :    return telemeterT::checkRecordTimes(telem_pico());
    1152              : }
    1153              : 
    1154            0 : int picoMotorCtrl::recordTelem( const telem_pico * )
    1155              : {
    1156            0 :    return recordPico(true);
    1157              : }
    1158              : 
    1159            0 : int picoMotorCtrl::recordPico( bool force )
    1160              : {
    1161            0 :    static std::vector<int64_t> lastpos(m_nChannels, std::numeric_limits<long>::max());
    1162              : 
    1163            0 :    bool changed = false;
    1164            0 :    for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
    1165              :    {
    1166            0 :       if(it->second.m_currCounts != lastpos[it->second.m_channel-1]) changed = true;
    1167              :    }
    1168              : 
    1169            0 :    if( changed || force )
    1170              :    {
    1171            0 :       for(channelMapT::iterator it = m_channels.begin(); it != m_channels.end(); ++ it)
    1172              :       {
    1173            0 :          lastpos[it->second.m_channel-1] = it->second.m_currCounts;
    1174              :       }
    1175              : 
    1176            0 :       telem<telem_pico>(lastpos);
    1177              :    }
    1178              : 
    1179            0 :    return 0;
    1180              : }
    1181              : 
    1182              : } //namespace app
    1183              : } //namespace MagAOX
    1184              : 
    1185              : #endif //picoMotorCtrl_hpp
        

Generated by: LCOV version 2.0-1