LCOV - code coverage report
Current view: top level - libMagAOX/app/dev - outletController.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 52.0 % 252 131
Test Date: 2026-04-15 19:34:29 Functions: 71.4 % 21 15

            Line data    Source code
       1              : /** \file outletController.hpp
       2              :  * \author Jared R. Males
       3              :  * \brief Declares and defines a power control device framework in the MagAOXApp context
       4              :  *
       5              :  * \ingroup
       6              :  *
       7              :  */
       8              : 
       9              : #ifndef app_outletController_hpp
      10              : #define app_outletController_hpp
      11              : 
      12              : #include <ImageStreamIO/ImageStreamIO.h>
      13              : 
      14              : #include <mx/app/application.hpp>
      15              : #include <mx/sys/timeUtils.hpp>
      16              : 
      17              : #include "../../INDI/libcommon/IndiProperty.hpp"
      18              : #include "../../libMagAOX/libMagAOX.hpp"
      19              : #include "../indiUtils.hpp"
      20              : 
      21              : 
      22              : #define OUTLET_STATE_UNKNOWN (-1)
      23              : #define OUTLET_STATE_OFF (0)
      24              : #define OUTLET_STATE_INTERMEDIATE (1)
      25              : #define OUTLET_STATE_ON (2)
      26              : 
      27              : 
      28              : #define OUTLET_E_NOOUTLETS (-10)
      29              : #define OUTLET_E_NOCHANNELS (-15)
      30              : #define OUTLET_E_NOVALIDCH (-20)
      31              : 
      32              : namespace MagAOX
      33              : {
      34              : namespace app
      35              : {
      36              : namespace dev
      37              : {
      38              : 
      39              : /// A generic outlet controller
      40              : /** Controls a set of outlets on a device, such as A/C power outlets or digital outputs.
      41              :   * The outlets are organized into channels, which could be made up of multiple outlets.
      42              :   *
      43              :   * derivedT must be a MagAOXApp, and additionally it must implement the functions
      44              :   * \code
      45              :     int turnOutletOn( int outletNum );
      46              : 
      47              :     int turnOutletOff( int outletNum );
      48              : 
      49              :     int updateOutletState( int outletNum );
      50              :   * \endcode
      51              :   * and optionally
      52              :   * \code
      53              :     int updateOutletStates();
      54              :     \endcode
      55              :   *
      56              :   * Other requirements:
      57              :   * - call `setNumberOfOutlets` in the derived class constructor
      58              :   *
      59              :   *
      60              :   * \tparam derivedT specifies a MagAOXApp parent base class which is accessed with a `static_cast` (downcast)
      61              :   *         to perform various methods.
      62              :   *
      63              :   * \ingroup appdev
      64              :   *
      65              :   *
      66              :   */
      67              : template<class derivedT>
      68              : struct outletController
      69              : {
      70              :    bool m_firstOne {false}; ///< Flag is true if the first outlet is numbered 1, otherwise assumes starting at 0.
      71              : 
      72              :    double m_stateDelay {0}; ///< Delay to wait after changing states before allowing a new command.
      73              : 
      74              :    std::vector<int> m_outletStates; /**< The current states of each outlet.  These MUST be updated by derived 
      75              :                                          classes in the overridden \ref updatedOutletState.*/
      76              : 
      77              :    pcf::IndiProperty m_indiP_outletStates; ///< Indi Property to show individual outlet states.
      78              : 
      79              :    /// Structure containing the specification of one channel.
      80              :    /** A channel may include more than one outlet, may specify the order in which
      81              :      * outlets are turned on and/or off, and may specify a delay between turning outlets on
      82              :      * and/or off.
      83              :      */
      84              :    struct channelSpec
      85              :    {
      86              :       std::vector<size_t> m_outlets; ///< The outlets in this channel
      87              : 
      88              :       std::vector<size_t> m_onOrder; ///< [optional] The order in which outlets are turned on.  This contains the indices of m_outlets, not the outlet numbers of the device.
      89              :       std::vector<size_t> m_offOrder; ///< [optional] The order in which outlets are turned off.  This contains the indices of m_outlets, not the outlet numbers of the device.
      90              : 
      91              :       std::vector<unsigned> m_onDelays; ///< [optional] The delays between outlets in a multi-oultet channel.  The first entry is always ignored.  The second entry is the dealy between the first and second outlet, etc.
      92              :       std::vector<unsigned> m_offDelays; ///< [optional] The delays between outlets in a multi-oultet channel.  The first entry is always ignored.  The second entry is the dealy between the first and second outlet, etc.
      93              : 
      94              :       timespec m_stateTime {0,0}; ///< The time of the last state change
      95              : 
      96              :       pcf::IndiProperty m_indiP_prop;
      97              : 
      98              :       std::mutex * m_mutex {nullptr};
      99              : 
     100              :    };
     101              : 
     102              :    /// The map of channel specifications, which can be accessed by their names.
     103              :    std::unordered_map<std::string, channelSpec> m_channels;
     104              : 
     105              :    std::vector<std::mutex *> m_channelMutexes;
     106              : 
     107              :    /// An INDI property which pulishes the times of the last state change for each channel 
     108              :    pcf::IndiProperty m_indiP_stateTimes;
     109              : 
     110              :    /// An INDI property which publishes the outlets associated with each channel.  Useful for GUIs, etc.
     111              :    pcf::IndiProperty m_indiP_chOutlets;
     112              : 
     113              :    /// An INDI property which publishes the total on delay for each channel.  Useful for GUIs, etc.
     114              :    pcf::IndiProperty m_indiP_chOnDelays;
     115              : 
     116              :    /// An INDI property which publishes the total off delay for each channel.  Useful for GUIs, etc.
     117              :    pcf::IndiProperty m_indiP_chOffDelays;
     118              : 
     119              :    ~outletController();
     120              : 
     121              :    ///Setup an application configurator for an outletController
     122              :    /** This is currently a no-op
     123              :      *
     124              :      * \returns 0 on success
     125              :      * \returns -1 on failure
     126              :      */
     127              :    int setupConfig( mx::app::appConfigurator & config /**< [in] an application configuration to setup */);
     128              : 
     129              :    /// Load the [channel] sections from an application configurator
     130              :    /** Any "unused" section from the config parser is analyzed to determine if it is a channel specification.
     131              :      * If it contains the `outlet` or `outlets` keyword, then it is considered a channel. `outlet` and `outlets`
     132              :      * are equivalent, and specify the one or more device outlets included in this channel (i.e. this may be a vector
     133              :      * value entry).
     134              :      *
     135              :      * This function then looks for `onOrder` and `offOrder` keywords, which specify the order outlets are turned
     136              :      * on or off by their indices in the vector specified by the `outlet`/`outlets` keyword (i.e not the outlet numbers).
     137              :      *
     138              :      * Next it looks for `onDelays` and `offDelays`, which specify the delays between outlet operations in milliseconds.
     139              :      * The first entry is always ignored, then the second entry specifies the delay between the first and second outlet
     140              :      * operation, etc.
     141              :      *
     142              :      * An example config file section is:
     143              :      \verbatim
     144              :      [sue]           #this channel will be named sue
     145              :      outlets=4,5     #this channel uses outlets 4 and 5
     146              :      onOrder=1,0     #outlet 5 will be turned on first
     147              :      offOrder=0,1    #Outlet 4 will be turned off first
     148              :      onDelays=0,150  #a 150 msec delay between outlet turn on
     149              :      offDelays=0,345 #a 345 msec delay between outlet turn off
     150              :      \endverbatim
     151              :      *
     152              :      * \returns 0 on success
     153              :      * \returns -1 on failure
     154              :      */
     155              :    int loadConfig( mx::app::appConfigurator & config /**< [in] an application configuration from which to load values */);
     156              : 
     157              :    /// Sets the number of outlets.  This should be called by the derived class constructor.
     158              :    /**
     159              :      * \returns 0 on success
     160              :      * \returns -1 on failure
     161              :      */
     162              :    int setNumberOfOutlets( int numOuts /**< [in] the number of outlets to allocate */);
     163              : 
     164              :    /// Get the currently stored outlet state, without updating from device.
     165              :    int outletState( int outletNum );
     166              : 
     167              :    /// Get the states of all outlets from the device.
     168              :    /** The default implementation for-loops through each outlet, calling \ref updateOutletState.
     169              :      * Can be re-implemented in derived classes to update the outlet states.
     170              :      *
     171              :      * \returns 0 on success.
     172              :      * \returns -1 on error.
     173              :      */
     174              :    virtual int updateOutletStates();
     175              : 
     176              :    /// Get the number of channels
     177              :    /**
     178              :      * \returns the number of entries in m_channels.
     179              :      */
     180              :    size_t numChannels();
     181              : 
     182              :    /// Get the vector of outlet indices for a channel.
     183              :    /** Mainly used for testing.
     184              :      *
     185              :      * \returns the m_outlets member of the channelSpec specified by its name.
     186              :      */
     187              :    std::vector<size_t> channelOutlets( const std::string & channel /**< [in] the name of the channel */);
     188              : 
     189              :    /// Get the vector of outlet on orders for a channel.
     190              :    /** Mainly used for testing.
     191              :      *
     192              :      * \returns the m_onOrder member of the channelSpec specified by its name.
     193              :      */
     194              :    std::vector<size_t> channelOnOrder( const std::string & channel /**< [in] the name of the channel */);
     195              : 
     196              :    /// Get the vector of outlet off orders for a channel.
     197              :    /** Mainly used for testing.
     198              :      *
     199              :      * \returns the m_offOrder member of the channelSpec specified by its name.
     200              :      */
     201              :    std::vector<size_t> channelOffOrder( const std::string & channel /**< [in] the name of the channel */);
     202              : 
     203              :    /// Get the vector of outlet on delays for a channel.
     204              :    /** Mainly used for testing.
     205              :      *
     206              :      * \returns the m_onDelays member of the channelSpec specified by its name.
     207              :      */
     208              :    std::vector<unsigned> channelOnDelays( const std::string & channel /**< [in] the name of the channel */);
     209              : 
     210              :    /// Get the vector of outlet off delays for a channel.
     211              :    /** Mainly used for testing.
     212              :      *
     213              :      * \returns the m_offDelays member of the channelSpec specified by its name.
     214              :      */
     215              :    std::vector<unsigned> channelOffDelays( const std::string & channel /**< [in] the name of the channel */);
     216              : 
     217              :    /// Get the state of a channel.
     218              :    /**
     219              :      * \returns OUTLET_STATE_UNKNOWN if the state is not known
     220              :      * \returns OUTLET_STATE_OFF if the channel is off (all outlets off)
     221              :      * \returns OUTLET_STATE_INTERMEDIATE if outlets are intermediate or not in the same state
     222              :      * \returns OUTLET_STATE_ON if channel is on (all outlets on)
     223              :      */
     224              :    int channelState( const std::string & channel /**< [in] the name of the channel */);
     225              : 
     226              :    /// Turn a channel on.
     227              :    /** This implements the outlet order and delay logic.
     228              :      *
     229              :      * \returns 0 on success.
     230              :      * \returns -1 on error.
     231              :      */
     232              :    int turnChannelOn( const std::string & channel /**< [in] the name of the channel to turn on*/);
     233              : 
     234              :    /// Turn a channel off.
     235              :    /** This implements the outlet order and delay logic.
     236              :      *
     237              :      * \returns 0 on success.
     238              :      * \returns -1 on error.
     239              :      */
     240              :    int turnChannelOff( const std::string & channel /**< [in] the name of the channel to turn on*/);
     241              : 
     242              : 
     243              :    /** \name INDI Setup
     244              :      *@{
     245              :      */
     246              : 
     247              :    /// The static callback function to be registered for the channel properties.
     248              :    /**
     249              :      * \returns 0 on success.
     250              :      * \returns -1 on error.
     251              :      */
     252              :    static int st_newCallBack_channels( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
     253              :                                        const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
     254              :                                      );
     255              : 
     256              :    /// The callback called by the static version, to actually process the new request.
     257              :    /**
     258              :      * \returns 0 on success.
     259              :      * \returns -1 on error.
     260              :      */
     261              :    int newCallBack_channels( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
     262              : 
     263              :    /// Setup the INDI properties for this device controller
     264              :    /** This should be called in the `appStartup` function of the derived MagAOXApp.
     265              :      * 
     266              :      * \returns 0 on success.
     267              :      * \returns -1 on error.
     268              :      */
     269              :    int appStartup();
     270              : 
     271              :    /// Setup the INDI properties for this device controller
     272              :    /** This should be called in the `appStartup` function of the derived MagAOXApp.
     273              :      *  
     274              :      * \deprecated
     275              :      *
     276              :      * \returns 0 on success.
     277              :      * \returns -1 on error.
     278              :      */
     279              :    [[deprecated("use appStartup() instead")]]
     280              :    int setupINDI();
     281              : 
     282              :    /// Update the INDI properties for this device controller
     283              :    /** You should call this after updating the outlet states.
     284              :      * It is not called automatically.
     285              :      *
     286              :      * \returns 0 on success.
     287              :      * \returns -1 on error.
     288              :      */
     289              :    int updateINDI();
     290              : 
     291              :    ///@}
     292              : 
     293              : 
     294              : private:
     295          112 :    derivedT & derived()
     296              :    {
     297          112 :       return *static_cast<derivedT *>(this);
     298              :    }
     299              : };
     300              : 
     301              : template<class derivedT>
     302           31 : outletController<derivedT>::~outletController()
     303              : {
     304          105 :    for(auto & it : m_channels)
     305              :    {
     306           74 :       it.second.m_mutex = nullptr;
     307              :    }
     308              : 
     309          105 :    for(size_t n = 0; n < m_channelMutexes.size(); ++n)
     310              :    {
     311           74 :       if(m_channelMutexes[n])
     312              :       {
     313           74 :          delete m_channelMutexes[n];
     314              :       }
     315              :    }
     316           62 : }
     317              : 
     318              : 
     319              : template<class derivedT>
     320           29 : int outletController<derivedT>::setupConfig( mx::app::appConfigurator & config )
     321              : {
     322              :    static_cast<void>(config);
     323              : 
     324           29 :    return 0;
     325              : }
     326              : 
     327              : template<class derivedT>
     328           29 : int outletController<derivedT>::loadConfig( mx::app::appConfigurator & config )
     329              : {
     330           29 :    if( m_outletStates.size() == 0) return OUTLET_E_NOOUTLETS;
     331              : 
     332              :    //Get the "unused" sections.
     333           29 :    std::vector<std::string> sections;
     334              : 
     335           29 :    config.unusedSections(sections);
     336              : 
     337           29 :    if( sections.size() == 0 ) return OUTLET_E_NOCHANNELS;
     338              : 
     339              :    //Now see if any are channels, which means they have an outlet= or outlets= entry
     340           29 :    std::vector<std::string> chSections;
     341              : 
     342          103 :    for(size_t i=0;i<sections.size(); ++i)
     343              :    {
     344          222 :       if( config.isSetUnused( mx::app::iniFile::makeKey(sections[i], "outlet" ))
     345          172 :               || config.isSetUnused( mx::app::iniFile::makeKey(sections[i], "outlets" )) )
     346              :       {
     347           74 :          chSections.push_back(sections[i]);
     348              :       }
     349              :    }
     350              : 
     351           29 :    if( chSections.size() == 0 ) return OUTLET_E_NOVALIDCH;
     352              : 
     353              :    //Now configure the channels.
     354          177 :    for(size_t n = 0; n < chSections.size(); ++n)
     355              :    {
     356           74 :       m_channels.emplace( chSections[n] , channelSpec());
     357              : 
     358              :       //---- Set outlets ----
     359           74 :       std::vector<size_t> outlets;
     360          148 :       if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "outlet" )))
     361              :       {
     362          124 :          config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n], "outlet" ) );
     363              :       }
     364              :       else
     365              :       {
     366           24 :          config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n], "outlets" ) );
     367              :       }
     368              : 
     369           74 :       if(outlets.size() == 0)
     370              :       {
     371            0 :          return derivedT::template log<software_error,-1>( std::format("no outlets in Channel ""{} is not valid", chSections[n]));
     372              :       }
     373              : 
     374              :       //Subtract one if the device numbers from 1.
     375          190 :       for(size_t k=0;k<outlets.size(); ++k)
     376              :       {
     377              :          ///\todo test this error
     378          116 :          if( (int) outlets[k] - m_firstOne < 0 || (int) outlets[k] - m_firstOne > (int) m_outletStates.size())
     379              :          {
     380            0 :             return derivedT::template log<software_error,-1>( std::format("Outlet {} in Channel ""{} is not valid", outlets[k], chSections[n]), logPrio::LOG_ERROR);
     381              :             
     382              :          }
     383              : 
     384          116 :          outlets[k] -= m_firstOne;
     385              :       }
     386              : 
     387           74 :       m_channels[chSections[n]].m_outlets = outlets;
     388              : 
     389              :       //---- Set optional configs ----
     390          148 :       if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "onOrder" )))
     391              :       {
     392           34 :          std::vector<size_t> onOrder;
     393           34 :          config.configUnused( onOrder, mx::app::iniFile::makeKey(chSections[n], "onOrder" ) );
     394              : 
     395           34 :          if(onOrder.size() != m_channels[chSections[n]].m_outlets.size())
     396              :          {
     397            0 :             return derivedT::template log<software_error,-1>("onOrder must be same ""size as outlets.  In Channel " + chSections[n]);
     398              :          }
     399              : 
     400           34 :          m_channels[chSections[n]].m_onOrder = onOrder;
     401           34 :       }
     402              : 
     403          148 :       if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "offOrder" )))
     404              :       {
     405           26 :          std::vector<size_t> offOrder;
     406           26 :          config.configUnused( offOrder, mx::app::iniFile::makeKey(chSections[n], "offOrder" ) );
     407              : 
     408           26 :          if(offOrder.size() != m_channels[chSections[n]].m_outlets.size())
     409              :          {
     410            0 :             return derivedT::template log<software_error,-1>("offOrder must be same ""size as outlets.  In Channel " + chSections[n]);
     411              :          }
     412              : 
     413           26 :          m_channels[chSections[n]].m_offOrder = offOrder;
     414           26 :       }
     415              : 
     416          148 :       if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "onDelays" )))
     417              :       {
     418           16 :          std::vector<unsigned> onDelays;
     419           16 :          config.configUnused( onDelays, mx::app::iniFile::makeKey(chSections[n], "onDelays" ) );
     420              : 
     421           16 :          if(onDelays.size() != m_channels[chSections[n]].m_outlets.size())
     422              :          {
     423            0 :             return derivedT::template log<software_error,-1>("onDelays must be same ""size as outlets.  In Channel " + chSections[n]);
     424              :          }
     425              : 
     426           16 :          m_channels[chSections[n]].m_onDelays = onDelays;
     427           16 :       }
     428              : 
     429          148 :       if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "offDelays" )))
     430              :       {
     431           16 :          std::vector<unsigned> offDelays;
     432           16 :          config.configUnused( offDelays, mx::app::iniFile::makeKey(chSections[n], "offDelays" ) );
     433              : 
     434           16 :          if(offDelays.size() != m_channels[chSections[n]].m_outlets.size())
     435              :          {
     436            0 :             return derivedT::template log<software_error,-1>("offDelays must be same ""size as outlets.  In Channel " + chSections[n]);
     437              :          }
     438              : 
     439           16 :          m_channels[chSections[n]].m_offDelays = offDelays;
     440           16 :       }
     441              :    }
     442              : 
     443           29 :    m_channelMutexes.resize(m_channels.size(), nullptr);
     444           29 :    size_t n = 0;
     445          103 :    for(auto & it : m_channels)
     446              :    {
     447           74 :       m_channelMutexes[n] = new std::mutex;
     448              : 
     449           74 :       it.second.m_mutex = m_channelMutexes[n];
     450              : 
     451           74 :       ++n;
     452              : 
     453              :    }
     454              : 
     455           29 :    return 0;
     456           29 : }
     457              : 
     458              : template<class derivedT>
     459           31 : int outletController<derivedT>::setNumberOfOutlets( int numOuts )
     460              : {
     461           31 :    m_outletStates.resize(numOuts, -1);
     462           31 :    return 0;
     463              : }
     464              : 
     465              : template<class derivedT>
     466          540 : int outletController<derivedT>::outletState( int outletNum )
     467              : {
     468          540 :    return m_outletStates[outletNum];
     469              : }
     470              : 
     471              : template<class derivedT>
     472            0 : int outletController<derivedT>::updateOutletStates()
     473              : {
     474            0 :    for(size_t n=0; n<m_outletStates.size(); ++n)
     475              :    {
     476            0 :       int rv = derived().updateOutletState(n);
     477            0 :       if(rv < 0) 
     478              :       {
     479            0 :          derivedT::template log<software_error>({0, rv, std::format("error updating outlet {}", n)});
     480            0 :          return rv;
     481              :       }
     482              :    }
     483              : 
     484            0 :    return 0;
     485              : }
     486              : 
     487              : template<class derivedT>
     488            8 : size_t outletController<derivedT>::numChannels()
     489              : {
     490            8 :    return m_channels.size();
     491              : }
     492              : 
     493              : template<class derivedT>
     494           24 : std::vector<size_t> outletController<derivedT>::channelOutlets( const std::string & channel )
     495              : {
     496           24 :    return m_channels[channel].m_outlets;
     497              : }
     498              : 
     499              : template<class derivedT>
     500           24 : std::vector<size_t> outletController<derivedT>::channelOnOrder( const std::string & channel )
     501              : {
     502           24 :    return m_channels[channel].m_onOrder;
     503              : }
     504              : 
     505              : template<class derivedT>
     506           24 : std::vector<size_t> outletController<derivedT>::channelOffOrder( const std::string & channel )
     507              : {
     508           24 :    return m_channels[channel].m_offOrder;
     509              : }
     510              : 
     511              : template<class derivedT>
     512           24 : std::vector<unsigned> outletController<derivedT>::channelOnDelays( const std::string & channel )
     513              : {
     514           24 :    return m_channels[channel].m_onDelays;
     515              : }
     516              : 
     517              : template<class derivedT>
     518           24 : std::vector<unsigned> outletController<derivedT>::channelOffDelays( const std::string & channel )
     519              : {
     520           24 :    return m_channels[channel].m_offDelays;
     521              : }
     522              : 
     523              : template<class derivedT>
     524          256 : int outletController<derivedT>::channelState( const std::string & channel )
     525              : {
     526          256 :    int st = outletState(m_channels[channel].m_outlets[0]);
     527              : 
     528          404 :    for( size_t n = 1; n < m_channels[channel].m_outlets.size(); ++n )
     529              :    {
     530          148 :       if( st != outletState(m_channels[channel].m_outlets[n]) ) st = 1;
     531              :    }
     532              : 
     533          256 :    return st;
     534              : }
     535              : 
     536              : template<class derivedT>
     537           34 : int outletController<derivedT>::turnChannelOn( const std::string & channel )
     538              : {
     539           34 :    std::unique_lock<std::mutex> channelGuard;
     540           34 :    if(m_channels[channel].m_mutex != nullptr)
     541              :    {
     542           34 :       channelGuard = std::unique_lock<std::mutex>(*m_channels[channel].m_mutex);
     543              :    }
     544              :    else 
     545              :    {
     546            0 :       std::cerr << "mutex nullptr\n";
     547              :    }
     548              : 
     549              :    #ifndef OUTLET_CTRL_TEST_NOINDI
     550            0 :    indi::updateIfChanged(m_channels[channel].m_indiP_prop, "target", std::string("On"), derived().m_indiDriver, INDI_BUSY );
     551              :    #endif
     552              : 
     553              :    //Take no other action if already on
     554           34 :    if(channelState(channel) == OUTLET_STATE_ON)
     555              :    {
     556            0 :       return 0;
     557              :    }
     558              : 
     559              :    timespec now;
     560           34 :    clock_gettime(CLOCK_ISIO, &now);
     561           34 :    if( m_stateDelay > 0 && ( (1.0*now.tv_sec + now.tv_nsec/1e9) - (1.0*m_channels[channel].m_stateTime.tv_sec + m_channels[channel].m_stateTime.tv_nsec/1e9) < m_stateDelay))
     562              :    {
     563            0 :       return 0;
     564              :    }
     565              : 
     566              :    //If order is specified, get first outlet number
     567           34 :    size_t n = 0;
     568           34 :    if( m_channels[channel].m_onOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_onOrder[0];
     569              : 
     570              :    //turn on first outlet.
     571           34 :    if( derived().turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
     572              :    {
     573            0 :       return derivedT::template log<software_error, -1>(std::format("error turning on outlet {}",n));
     574              :    }
     575              : 
     576           34 :    derivedT::template log<outlet_state>({ (uint8_t) (n  + m_firstOne), 2});
     577              : 
     578              :    //Now do the rest
     579           56 :    for(size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
     580              :    {
     581              :       //If order is specified, get next outlet number
     582           22 :       n=i;
     583           22 :       if( m_channels[channel].m_onOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_onOrder[i];
     584              : 
     585              :       //Delay if specified
     586           22 :       if( m_channels[channel].m_onDelays.size() == m_channels[channel].m_outlets.size() )
     587              :       {
     588            4 :          mx::sys::milliSleep(m_channels[channel].m_onDelays[i]);
     589              :       }
     590              : 
     591              :       //turn on next outlet
     592              : 
     593           22 :       if( derived().turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
     594              :       {
     595            0 :          return derivedT::template log<software_error, -1>(std::format("error turning on outlet {}", n));
     596              :       }
     597              : 
     598           22 :       derivedT::template log<outlet_state>({ (uint8_t) (n  + m_firstOne ), 2});
     599              :    
     600              :    }
     601              : 
     602           34 :    derivedT::template log<outlet_channel_state>({ channel, 2});
     603              :    
     604           34 :    if(clock_gettime(CLOCK_ISIO, &m_channels[channel].m_stateTime) < 0)
     605              :    {
     606            0 :       return derivedT::template log<software_error,-1>({errno, 0, "clock_gettime"});
     607              :    }
     608              : 
     609              :    #ifndef OUTLET_CTRL_TEST_NOINDI
     610            0 :    indi::updateIfChanged(m_indiP_stateTimes, channel, m_channels[channel].m_stateTime.tv_sec, derived().m_indiDriver, INDI_IDLE );
     611              :    #endif
     612              : 
     613           34 :    return 0;
     614           34 : }
     615              : 
     616              : template<class derivedT>
     617           34 : int outletController<derivedT>::turnChannelOff( const std::string & channel )
     618              : {
     619           34 :    std::unique_lock<std::mutex> channelGuard;
     620           34 :    if(m_channels[channel].m_mutex != nullptr)
     621              :    {
     622           34 :       channelGuard = std::unique_lock<std::mutex>(*m_channels[channel].m_mutex);
     623              :    }
     624              :    else 
     625              :    {
     626            0 :       std::cerr << "mutex nullptr\n";
     627              :    }
     628              : 
     629              :    #ifndef OUTLET_CTRL_TEST_NOINDI
     630            0 :    indi::updateIfChanged(m_channels[channel].m_indiP_prop, "target", std::string("Off"), derived().m_indiDriver, INDI_BUSY );
     631              :    #endif
     632              : 
     633              :    //Take no other action if already off
     634           34 :    if(channelState(channel) == OUTLET_STATE_OFF)
     635              :    {
     636            0 :       return 0;
     637              :    }
     638              : 
     639              :    timespec now;
     640           34 :    clock_gettime(CLOCK_ISIO, &now);
     641           34 :    if( m_stateDelay > 0 && ((1.0*now.tv_sec + now.tv_nsec/1e9) - (1.0*m_channels[channel].m_stateTime.tv_sec + m_channels[channel].m_stateTime.tv_nsec/1e9) < m_stateDelay))
     642              :    {
     643            0 :       return 0;
     644              :    }
     645              : 
     646              :    //If order is specified, get first outlet number
     647           34 :    size_t n = 0;
     648           34 :    if( m_channels[channel].m_offOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_offOrder[0];
     649              : 
     650              :    //turn off first outlet.
     651           34 :    if( derived().turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
     652              :    {
     653            0 :       return derivedT::template log<software_error, -1>(std::format("error turning off outlet {}", n));
     654              :    }
     655              : 
     656           34 :    derivedT::template log<outlet_state>({ (uint8_t) (n  + m_firstOne), 0});
     657              : 
     658              :    //Now do the rest
     659           56 :    for(size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
     660              :    {
     661              :       //If order is specified, get next outlet number
     662           22 :       n=i;
     663           22 :       if( m_channels[channel].m_offOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_offOrder[i];
     664              : 
     665              :       //Delay if specified
     666           22 :       if( m_channels[channel].m_offDelays.size() == m_channels[channel].m_outlets.size() )
     667              :       {
     668            4 :          mx::sys::milliSleep(m_channels[channel].m_offDelays[i]);
     669              :       }
     670              : 
     671              :       //turn off next outlet
     672           22 :       if( derived().turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
     673              :       {
     674            0 :          return derivedT::template log<software_error, -1>(std::format("error turning off outlet {}", n));
     675              :       }
     676              : 
     677           22 :       derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
     678              :    }
     679              : 
     680           34 :    derivedT::template log<outlet_channel_state>({ channel, 0});
     681              : 
     682           34 :    if(clock_gettime(CLOCK_ISIO, &m_channels[channel].m_stateTime) < 0)
     683              :    {
     684            0 :       return derivedT::template log<software_error,-1>({errno, 0, "clock_gettime"});
     685              :    }
     686              : 
     687              :    #ifndef OUTLET_CTRL_TEST_NOINDI
     688            0 :    indi::updateIfChanged(m_indiP_stateTimes, channel, m_channels[channel].m_stateTime.tv_sec, derived().m_indiDriver, INDI_IDLE );
     689              :    #endif
     690              : 
     691           34 :    return 0;
     692           34 : }
     693              : 
     694              : template<class derivedT>
     695            0 : int outletController<derivedT>::st_newCallBack_channels( void * app,
     696              :                                                          const pcf::IndiProperty &ipRecv
     697              :                                                        )
     698              : {
     699            0 :    return static_cast<derivedT *>(app)->newCallBack_channels(ipRecv);
     700              : }
     701              : 
     702              : template<class derivedT>
     703            0 : int outletController<derivedT>::newCallBack_channels( const pcf::IndiProperty &ipRecv )
     704              : {
     705              :    //Check if we're in state READY before doing anything
     706            0 :    if(derived().state() != stateCodes::READY)
     707              :    {
     708            0 :       return derivedT::template log<software_error, -1>("can't change outlet state when not READY");
     709              :    }
     710              : 
     711              :    //Interogate ipRecv to figure out which channel it is.
     712              :    //And then call turn on or turn off based on requested state.
     713            0 :    std::string name = ipRecv.getName();
     714              : 
     715            0 :    std::string state, target;
     716              : 
     717            0 :    if(ipRecv.find("state"))
     718              :    {
     719            0 :       state = ipRecv["state"].get<std::string>();
     720              :    }
     721              : 
     722            0 :    if(ipRecv.find("target"))
     723              :    {
     724            0 :       target = ipRecv["target"].get<std::string>();
     725              :    }
     726              : 
     727            0 :    if( target == "" ) target = state;
     728              : 
     729            0 :    target = mx::ioutils::toUpper(target);
     730              : 
     731              : 
     732            0 :    if( target == "ON" )
     733              :    {
     734            0 :       return turnChannelOn(name);
     735              :    }
     736              : 
     737            0 :    if(target == "OFF")
     738              :    {
     739            0 :       return turnChannelOff(name);
     740              :    }
     741              : 
     742            0 :    return 0;
     743            0 : }
     744              : 
     745              : template<class derivedT>
     746            0 : int outletController<derivedT>::appStartup()
     747              : {
     748            0 :    m_indiP_stateTimes = pcf::IndiProperty(pcf::IndiProperty::Number);
     749            0 :    m_indiP_stateTimes.setDevice(derived().configName()); 
     750            0 :    m_indiP_stateTimes.setName("stateTimes");
     751            0 :    m_indiP_stateTimes.setPerm(pcf::IndiProperty::ReadOnly);
     752            0 :    m_indiP_stateTimes.setState(pcf::IndiProperty::Idle);
     753              : 
     754            0 :    if(derived().registerIndiPropertyReadOnly(m_indiP_stateTimes) < 0)
     755              :    {
     756            0 :       return derivedT::template log<software_error, -1>();
     757              :    }
     758              : 
     759              :    //Register the static INDI properties
     760            0 :    m_indiP_chOutlets = pcf::IndiProperty(pcf::IndiProperty::Text);
     761            0 :    m_indiP_chOutlets.setDevice(derived().configName());
     762            0 :    m_indiP_chOutlets.setName("channelOutlets");
     763            0 :    m_indiP_chOutlets.setPerm(pcf::IndiProperty::ReadOnly);
     764            0 :    m_indiP_chOutlets.setState(pcf::IndiProperty::Idle);
     765              : 
     766            0 :    if(derived().registerIndiPropertyReadOnly(m_indiP_chOutlets) < 0)
     767              :    {
     768            0 :       return derivedT::template log<software_error, -1>();
     769              :    }
     770              : 
     771            0 :    m_indiP_chOnDelays = pcf::IndiProperty (pcf::IndiProperty::Number);
     772            0 :    m_indiP_chOnDelays.setDevice(derived().configName());
     773            0 :    m_indiP_chOnDelays.setName("channelOnDelays");
     774            0 :    m_indiP_chOnDelays.setPerm(pcf::IndiProperty::ReadOnly);
     775            0 :    m_indiP_chOnDelays.setState(pcf::IndiProperty::Idle);
     776              : 
     777            0 :    if(derived().registerIndiPropertyReadOnly(m_indiP_chOnDelays) < 0)
     778              :    {
     779            0 :       return derivedT::template log<software_error, -1>();
     780              :    }
     781              : 
     782            0 :    m_indiP_chOffDelays = pcf::IndiProperty (pcf::IndiProperty::Number);
     783            0 :    m_indiP_chOffDelays.setDevice(derived().configName());
     784            0 :    m_indiP_chOffDelays.setName("channelOffDelays");
     785            0 :    m_indiP_chOffDelays.setPerm(pcf::IndiProperty::ReadOnly);
     786            0 :    m_indiP_chOffDelays.setState(pcf::IndiProperty::Idle);
     787              : 
     788            0 :    if(derived().registerIndiPropertyReadOnly(m_indiP_chOffDelays) < 0)
     789              :    {
     790            0 :       return derivedT::template log<software_error, -1>();
     791              :    }
     792              : 
     793              :    //Create channel properties and register callback.
     794            0 :    for(auto it = m_channels.begin(); it != m_channels.end(); ++it)
     795              :    {
     796            0 :       it->second.m_indiP_prop = pcf::IndiProperty (pcf::IndiProperty::Text);
     797            0 :       it->second.m_indiP_prop.setDevice(derived().configName());
     798            0 :       it->second.m_indiP_prop.setName(it->first);
     799            0 :       it->second.m_indiP_prop.setPerm(pcf::IndiProperty::ReadWrite);
     800            0 :       it->second.m_indiP_prop.setState( pcf::IndiProperty::Idle );
     801              : 
     802              :       //add elements 'state' and 'target'
     803            0 :       it->second.m_indiP_prop.add (pcf::IndiElement("state"));
     804            0 :       it->second.m_indiP_prop.add (pcf::IndiElement("target"));
     805              : 
     806            0 :       if( derived().registerIndiPropertyNew( it->second.m_indiP_prop, st_newCallBack_channels) < 0)
     807              :       {
     808            0 :          return derivedT::template log<software_error, -1>();
     809              :       }
     810              : 
     811              :       //Load values into the static INDI properties
     812            0 :       m_indiP_stateTimes.add(pcf::IndiElement(it->first));
     813            0 :       m_indiP_stateTimes[it->first].set(0);
     814              : 
     815            0 :       m_indiP_chOutlets.add(pcf::IndiElement(it->first));
     816            0 :       std::string os = std::format("{}", it->second.m_outlets[0]);
     817            0 :       for(size_t i=1;i< it->second.m_outlets.size();++i) os += std::format(",{}",it->second.m_outlets[i]);
     818            0 :       m_indiP_chOutlets[it->first].set(os);
     819              : 
     820            0 :       m_indiP_chOnDelays.add(pcf::IndiElement(it->first));
     821            0 :       double sum=0;
     822            0 :       for(size_t i=0;i< it->second.m_onDelays.size();++i) sum += it->second.m_onDelays[i];
     823            0 :       m_indiP_chOnDelays[it->first].set(sum);
     824              : 
     825            0 :       m_indiP_chOffDelays.add(pcf::IndiElement(it->first));
     826            0 :       sum=0;
     827            0 :       for(size_t i=0;i< it->second.m_offDelays.size();++i) sum += it->second.m_offDelays[i];
     828            0 :       m_indiP_chOffDelays[it->first].set(sum);
     829              : 
     830              :    }
     831              : 
     832              :    //Register the outletStates INDI property, and add an element for each outlet.
     833            0 :    m_indiP_outletStates = pcf::IndiProperty (pcf::IndiProperty::Text);
     834            0 :    m_indiP_outletStates.setDevice(derived().configName());
     835            0 :    m_indiP_outletStates.setName("outlet");
     836            0 :    m_indiP_outletStates.setPerm(pcf::IndiProperty::ReadWrite);
     837            0 :    m_indiP_outletStates.setState( pcf::IndiProperty::Idle );
     838              : 
     839            0 :    if( derived().registerIndiPropertyReadOnly(m_indiP_outletStates) < 0)
     840              :    {
     841            0 :       return derivedT::template log<software_error, -1>();
     842              :    }
     843              : 
     844            0 :    for(size_t i=0; i< m_outletStates.size(); ++i)
     845              :    {
     846            0 :       m_indiP_outletStates.add (pcf::IndiElement(std::to_string(i+m_firstOne)));
     847              :    }
     848              : 
     849            0 :    return 0;
     850              : }
     851              : 
     852              : template<class derivedT>
     853            0 : int outletController<derivedT>::setupINDI()
     854              : {
     855            0 :    return appStartup();
     856              : }
     857              : 
     858              : std::string stateIntToString(int st);
     859              : 
     860              : template<class derivedT>
     861            0 : int outletController<derivedT>::updateINDI()
     862              : {
     863            0 :    if( !derived().m_indiDriver ) return 0;
     864              : 
     865              :    //Publish outlet states (only bother if they've changed)
     866            0 :    for(size_t i=0; i< m_outletStates.size(); ++i)
     867              :    {
     868            0 :       indi::updateIfChanged(m_indiP_outletStates, std::to_string(i+m_firstOne), stateIntToString(m_outletStates[i]), derived().m_indiDriver);
     869              :    }
     870              : 
     871              :    //Publish channel states (only bother if they've changed)
     872            0 :    for(auto it = m_channels.begin(); it != m_channels.end(); ++it)
     873              :    {
     874            0 :       std::string state = stateIntToString( channelState( it->first ));
     875              : 
     876            0 :       indi::updateIfChanged( it->second.m_indiP_prop, "state", state, derived().m_indiDriver );
     877              :    }
     878              : 
     879              : 
     880              : 
     881            0 :    return 0;
     882              : }
     883              : 
     884              : } //namespace dev
     885              : } //namespace app
     886              : } //namespace MagAOX
     887              : 
     888              : #endif //app_outletController_hpp
        

Generated by: LCOV version 2.0-1