LCOV - code coverage report
Current view: top level - apps/zaberLowLevel - zaberLowLevel.hpp (source / functions) Coverage Total Hit
Test: MagAOX Lines: 4.3 % 562 24
Test Date: 2026-04-15 19:34:29 Functions: 40.0 % 25 10

            Line data    Source code
       1              : /** \file zaberLowLevel.hpp
       2              :  * \brief The MagAO-X Low-Level Zaber Controller
       3              :  *
       4              :  * \ingroup zaberLowLevel_files
       5              :  */
       6              : 
       7              : #ifndef zaberLowLevel_hpp
       8              : #define zaberLowLevel_hpp
       9              : 
      10              : #include <iostream>
      11              : 
      12              : #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
      13              : #include "../../magaox_git_version.h"
      14              : 
      15              : typedef MagAOX::app::MagAOXApp<true> MagAOXAppT; // This needs to be before zaberStage.hpp for logging to work.
      16              : 
      17              : #include "zaberUtils.hpp"
      18              : #include "zaberStage.hpp"
      19              : #include "za_serial.h"
      20              : 
      21              : #define ZC_CONNECTED ( 0 )
      22              : #define ZC_ERROR ( -1 )
      23              : #define ZC_NOT_CONNECTED ( 10 )
      24              : 
      25              : /** \defgroup zaberLowLevel low-level zaber controller
      26              :  * \brief The low-level interface to a set of chained Zaber stages
      27              :  *
      28              :  * <a href="../handbook/operating/software/apps/zaberLowLevel.html">Application Documentation</a>
      29              :  *
      30              :  * \ingroup apps
      31              :  *
      32              :  */
      33              : 
      34              : /** \defgroup zaberLowLevel_files zaber low-level files
      35              :  * \ingroup zaberLowLevel
      36              :  */
      37              : 
      38              : namespace MagAOX
      39              : {
      40              : namespace app
      41              : {
      42              : 
      43              : class zaberLowLevel : public MagAOXAppT, public tty::usbDevice
      44              : {
      45              : 
      46              :     // Give the test harness access.
      47              :     friend class zaberLowLevel_test;
      48              : 
      49              :   protected:
      50              :     int m_numStages{ 0 };
      51              : 
      52              :     z_port m_port{ 0 };
      53              : 
      54              :     std::vector<zaberStage<zaberLowLevel>> m_stages;
      55              : 
      56              :     std::unordered_map<int, size_t>         m_stageAddress;
      57              :     std::unordered_map<std::string, size_t> m_stageSerial;
      58              :     std::unordered_map<std::string, size_t> m_stageName;
      59              : 
      60              :   public:
      61              :     /// Default c'tor.
      62              :     zaberLowLevel();
      63              : 
      64              :     /// D'tor, declared and defined for noexcept.
      65           34 :     ~zaberLowLevel() noexcept
      66           34 :     {
      67           34 :     }
      68              : 
      69              :     virtual void setupConfig();
      70              : 
      71              :     virtual void loadConfig();
      72              : 
      73              :     int connect();
      74              : 
      75              :     int loadStages( std::string &serialRes );
      76              : 
      77              :     /// Startup functions
      78              :     /** Sets up the INDI vars.
      79              :      *
      80              :      */
      81              :     virtual int appStartup();
      82              : 
      83              :     /// Implementation of the FSM for zaberLowLevel.
      84              :     virtual int appLogic();
      85              : 
      86              :     /// Implementation of the on-power-off FSM logic
      87              :     virtual int onPowerOff();
      88              : 
      89              :     /// Implementation of the while-powered-off FSM
      90              :     virtual int whilePowerOff();
      91              : 
      92              :     /// Do any needed shutdown tasks.  Currently nothing in this app.
      93              :     virtual int appShutdown();
      94              : 
      95              :   protected:
      96              :     /// Current state of the stage.
      97              :     pcf::IndiProperty m_indiP_curr_state;
      98              : 
      99              :     /// Maximum raw position of the stage.
     100              :     pcf::IndiProperty m_indiP_max_pos;
     101              : 
     102              :     /// Parked state of the stage.
     103              :     pcf::IndiProperty m_indiP_parked;
     104              : 
     105              :     /// Time of last homing for the state
     106              :     pcf::IndiProperty m_indiP_lastHomed;
     107              : 
     108              :     /// Current raw position of the stage.
     109              :     pcf::IndiProperty m_indiP_curr_pos;
     110              : 
     111              :     /// Current temperature of the stage.
     112              :     pcf::IndiProperty m_indiP_temp;
     113              : 
     114              :     /// Whether the stage has existing warnings.
     115              :     pcf::IndiProperty m_indiP_warn;
     116              : 
     117              :     /// Target raw position of the stage.
     118              :     pcf::IndiProperty m_indiP_tgt_pos;
     119              : 
     120              :     /// Command a stage to home.
     121              :     pcf::IndiProperty m_indiP_req_home;
     122              : 
     123              :     /// Command all stages to home.
     124              :     pcf::IndiProperty m_indiP_req_home_all;
     125              : 
     126              :     /// Command a stage to safely halt.
     127              :     pcf::IndiProperty m_indiP_req_halt;
     128              : 
     129              :     /// Command a stage to safely immediately halt.
     130              :     pcf::IndiProperty m_indiP_req_ehalt;
     131              : 
     132              :     /// Enable or disable a stages potentiometer
     133              :     pcf::IndiProperty m_indiP_knob_enable;
     134              : 
     135              :     /// Enable or disable a stages LED
     136              :     pcf::IndiProperty m_indiP_led_enable;
     137              : 
     138              :   public:
     139            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_tgt_pos );
     140            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_req_home );
     141            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_req_home_all );
     142            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_req_halt );
     143            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_req_ehalt );
     144            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_knob_enable );
     145            0 :     INDI_NEWCALLBACK_DECL( zaberLowLevel, m_indiP_led_enable );
     146              : };
     147              : 
     148          102 : zaberLowLevel::zaberLowLevel() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
     149              : {
     150           34 :     m_powerMgtEnabled = true;
     151              : 
     152           34 :     return;
     153            0 : }
     154              : 
     155            0 : void zaberLowLevel::setupConfig()
     156              : {
     157            0 :     tty::usbDevice::setupConfig( config );
     158            0 : }
     159              : 
     160            0 : void zaberLowLevel::loadConfig()
     161              : {
     162              : 
     163            0 :     this->m_baudRate = B115200; // default for Zaber stages.  Will be overridden by any config setting.
     164              : 
     165            0 :     int rv = tty::usbDevice::loadConfig( config );
     166              : 
     167            0 :     if( rv != 0 && rv != TTY_E_NODEVNAMES && rv != TTY_E_DEVNOTFOUND ) // Ignore error if not plugged in
     168              :     {
     169            0 :         log<software_error>( { rv, tty::ttyErrorString( rv ) } );
     170              :     }
     171              : 
     172            0 :     std::vector<std::string> sections;
     173              : 
     174            0 :     config.unusedSections( sections );
     175              : 
     176            0 :     if( sections.size() == 0 )
     177              :     {
     178            0 :         log<software_error>( { "No stages found" } );
     179            0 :         return;
     180              :     }
     181              : 
     182            0 :     for( size_t n = 0; n < sections.size(); ++n )
     183              :     {
     184            0 :         if( config.isSetUnused( mx::app::iniFile::makeKey( sections[n], "serial" ) ) )
     185              :         {
     186            0 :             m_stages.push_back( zaberStage<zaberLowLevel>( this ) );
     187              : 
     188            0 :             size_t idx = m_stages.size() - 1;
     189              : 
     190            0 :             m_stages[idx].name( sections[n] );
     191              : 
     192              :             // Get serial number from config.
     193            0 :             std::string tmp = m_stages[idx].serial(); // get default
     194            0 :             config.configUnused( tmp, mx::app::iniFile::makeKey( sections[n], "serial" ) );
     195            0 :             m_stages[idx].serial( tmp );
     196              : 
     197            0 :             m_stageName.insert( { m_stages[idx].name(), idx } );
     198            0 :             m_stageSerial.insert( { m_stages[idx].serial(), idx } );
     199            0 :         }
     200              :     }
     201            0 : }
     202              : 
     203            0 : int zaberLowLevel::connect()
     204              : {
     205            0 :     if( m_port > 0 )
     206              :     {
     207            0 :         int rv = za_disconnect( m_port );
     208            0 :         if( rv < 0 )
     209              :         {
     210            0 :             log<text_log>( "Error disconnecting from zaber system.", logPrio::LOG_ERROR );
     211              :         }
     212            0 :         m_port = 0;
     213              :     }
     214              : 
     215            0 :     if( m_port <= 0 )
     216              :     {
     217              : 
     218              :         int zrv;
     219              : 
     220              :         { // scope for elPriv
     221            0 :             elevatedPrivileges elPriv( this );
     222            0 :             zrv = za_connect( &m_port, m_deviceName.c_str() );
     223            0 :         }
     224              : 
     225            0 :         if( zrv != Z_SUCCESS )
     226              :         {
     227            0 :             if( m_port > 0 )
     228              :             {
     229            0 :                 za_disconnect( m_port );
     230            0 :                 m_port = 0;
     231              :             }
     232              : 
     233            0 :             if( !stateLogged() )
     234              :             {
     235            0 :                 log<software_error>( { "can not connect to zaber stage(s)" } );
     236              :             }
     237              : 
     238            0 :             return ZC_NOT_CONNECTED; // We aren't connected.
     239              :         }
     240              :     }
     241              : 
     242            0 :     if( m_port <= 0 )
     243              :     {
     244              :         // state(stateCodes::ERROR); //Should not get this here.  Probably means no device.
     245            0 :         log<text_log>( "can not connect to zaber stage(s): no port", logPrio::LOG_WARNING );
     246            0 :         return ZC_NOT_CONNECTED; // We aren't connected.
     247              :     }
     248              : 
     249            0 :     int rv = za_drain( m_port );
     250              : 
     251            0 :     if( rv != Z_SUCCESS )
     252              :     {
     253            0 :         log<software_error>( { rv, "error from za_drain" } );
     254            0 :         state( stateCodes::ERROR );
     255            0 :         return ZC_ERROR;
     256              :     }
     257              : 
     258              :     char buffer[256];
     259              : 
     260              :     //===== First renumber so they are unique.
     261            0 :     std::string renum = "/ renumber";
     262            0 :     int         nwr   = za_send( m_port, renum.c_str(), renum.size() );
     263              : 
     264            0 :     if( nwr == Z_ERROR_SYSTEM_ERROR )
     265              :     {
     266            0 :         log<text_log>( "Error sending renumber query to stages", logPrio::LOG_ERROR );
     267            0 :         state( stateCodes::ERROR );
     268            0 :         return ZC_ERROR;
     269              :     }
     270              : 
     271              :     //===== Drain the result
     272            0 :     rv = za_drain( m_port );
     273              : 
     274            0 :     if( rv != Z_SUCCESS )
     275              :     {
     276            0 :         log<software_error>( { rv, "error from za_drain" } );
     277            0 :         state( stateCodes::ERROR );
     278            0 :         return ZC_ERROR;
     279              :     }
     280              : 
     281              :     //======= Now find the stages
     282            0 :     std::string gss = "/ get system.serial";
     283            0 :     nwr             = za_send( m_port, gss.c_str(), gss.size() );
     284              : 
     285            0 :     if( nwr == Z_ERROR_SYSTEM_ERROR )
     286              :     {
     287            0 :         log<text_log>( "Error sending system.serial query to stages", logPrio::LOG_ERROR );
     288            0 :         state( stateCodes::ERROR );
     289            0 :         return ZC_ERROR;
     290              :     }
     291              : 
     292            0 :     std::string serialRes;
     293              :     while( 1 )
     294              :     {
     295            0 :         int nrd = za_receive( m_port, buffer, sizeof( buffer ) );
     296            0 :         if( nrd >= 0 )
     297              :         {
     298            0 :             buffer[nrd] = '\0';
     299            0 :             log<text_log>( std::string( "Received: " ) + buffer, logPrio::LOG_DEBUG );
     300            0 :             serialRes += buffer;
     301              :         }
     302            0 :         else if( nrd != Z_ERROR_TIMEOUT )
     303              :         {
     304            0 :             log<text_log>( "Error receiving from stages", logPrio::LOG_ERROR );
     305            0 :             state( stateCodes::ERROR );
     306            0 :             return ZC_ERROR;
     307              :         }
     308              :         else
     309              :         {
     310            0 :             log<text_log>( "TIMEOUT", logPrio::LOG_DEBUG );
     311            0 :             break; // Timeout ok.
     312              :         }
     313            0 :     }
     314              : 
     315            0 :     return loadStages( serialRes );
     316            0 : }
     317              : 
     318            0 : int zaberLowLevel::loadStages( std::string &serialRes )
     319              : {
     320            0 :     std::vector<int>         addresses;
     321            0 :     std::vector<std::string> serials;
     322              : 
     323            0 :     int rv = parseSystemSerial( addresses, serials, serialRes );
     324              : 
     325            0 :     if( rv < 0 )
     326              :     {
     327            0 :         log<software_error>( { errno, rv, "error in parseSystemSerial" } );
     328            0 :         state( stateCodes::ERROR );
     329            0 :         return ZC_ERROR;
     330              :     }
     331              :     else
     332              :     {
     333            0 :         log<text_log>( "Found " + std::to_string( addresses.size() ) + " stages." );
     334            0 :         m_stageAddress.clear(); // We clear this map before re-populating.
     335            0 :         for( size_t n = 0; n < addresses.size(); ++n )
     336              :         {
     337            0 :             if( m_stageSerial.count( serials[n] ) == 1 )
     338              :             {
     339            0 :                 m_stages[m_stageSerial[serials[n]]].deviceAddress( addresses[n] );
     340              : 
     341            0 :                 m_stageAddress.insert( { addresses[n], m_stageSerial[serials[n]] } );
     342            0 :                 log<text_log>( "stage @" + std::to_string( addresses[n] ) + " with s/n " + serials[n] +
     343            0 :                                " corresponds to " + m_stages[m_stageSerial[serials[n]]].name() );
     344              :             }
     345              :             else
     346              :             {
     347            0 :                 log<text_log>( "Unkown stage @" + std::to_string( addresses[n] ) + " with s/n " + serials[n],
     348              :                                logPrio::LOG_WARNING );
     349              :             }
     350              :         }
     351              : 
     352            0 :         for( size_t n = 0; n < m_stages.size(); ++n )
     353              :         {
     354            0 :             if( m_stages[n].deviceAddress() < 1 )
     355              :             {
     356              : 
     357            0 :                 log<text_log>(
     358            0 :                     std::format( "stage {} with s/n {} not found in system.", m_stages[n].name(), serials[n] ),
     359              :                     logPrio::LOG_ERROR );
     360            0 :                 state( state(), true );
     361              :             }
     362              :         }
     363              :     }
     364              : 
     365            0 :     return ZC_CONNECTED;
     366            0 : }
     367              : 
     368            1 : int zaberLowLevel::appStartup()
     369              : {
     370            1 :     if( state() == stateCodes::UNINITIALIZED )
     371              :     {
     372            1 :         log<text_log>( "In appStartup but in state UNINITIALIZED.", logPrio::LOG_CRITICAL );
     373            1 :         return -1;
     374              :     }
     375              : 
     376            0 :     if( m_stages.size() == 0 )
     377              :     {
     378            0 :         log<text_log>( "No stages configured.", logPrio::LOG_CRITICAL );
     379            0 :         return -1;
     380              :     }
     381              : 
     382            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_curr_state, "curr_state", pcf::IndiProperty::Text );
     383              : 
     384            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_max_pos, "max_pos", pcf::IndiProperty::Text );
     385              : 
     386            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_parked, "parked", pcf::IndiProperty::Number );
     387              : 
     388            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_lastHomed, "last_homed", pcf::IndiProperty::Number );
     389              : 
     390            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_curr_pos, "curr_pos", pcf::IndiProperty::Number );
     391              : 
     392            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_temp, "temp", pcf::IndiProperty::Number );
     393              : 
     394            0 :     REG_INDI_NEWPROP_NOCB( m_indiP_warn, "warning", pcf::IndiProperty::Switch );
     395            0 :     m_indiP_warn.setRule( pcf::IndiProperty::AnyOfMany );
     396              : 
     397            0 :     REG_INDI_NEWPROP( m_indiP_tgt_pos, "tgt_pos", pcf::IndiProperty::Number );
     398              : 
     399            0 :     REG_INDI_NEWPROP( m_indiP_req_home, "req_home", pcf::IndiProperty::Switch );
     400            0 :     m_indiP_req_home.setRule( pcf::IndiProperty::AtMostOne );
     401              : 
     402            0 :     CREATE_REG_INDI_NEW_REQUESTSWITCH( m_indiP_req_home_all, "home_all" );
     403              : 
     404            0 :     REG_INDI_NEWPROP( m_indiP_req_halt, "req_halt", pcf::IndiProperty::Switch );
     405            0 :     m_indiP_req_halt.setRule( pcf::IndiProperty::AtMostOne );
     406              : 
     407            0 :     REG_INDI_NEWPROP( m_indiP_req_ehalt, "req_ehalt", pcf::IndiProperty::Switch );
     408            0 :     m_indiP_req_ehalt.setRule( pcf::IndiProperty::AtMostOne );
     409              : 
     410            0 :     REG_INDI_NEWPROP( m_indiP_knob_enable, "knob_enable", pcf::IndiProperty::Switch );
     411            0 :     m_indiP_knob_enable.setPerm( pcf::IndiProperty::ReadWrite );
     412            0 :     m_indiP_knob_enable.setState( pcf::IndiProperty::Idle );
     413            0 :     m_indiP_knob_enable.setRule( pcf::IndiProperty::AtMostOne );
     414              : 
     415            0 :     REG_INDI_NEWPROP( m_indiP_led_enable, "led_enable", pcf::IndiProperty::Switch );
     416            0 :     m_indiP_led_enable.setPerm( pcf::IndiProperty::ReadWrite );
     417            0 :     m_indiP_led_enable.setState( pcf::IndiProperty::Idle );
     418            0 :     m_indiP_led_enable.setRule( pcf::IndiProperty::AtMostOne );
     419              : 
     420            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
     421              :     {
     422            0 :         m_indiP_curr_state.add( pcf::IndiElement( m_stages[n].name() ) );
     423              : 
     424            0 :         m_indiP_max_pos.add( pcf::IndiElement( m_stages[n].name() ) );
     425            0 :         m_indiP_max_pos[m_stages[n].name()] = -1;
     426              : 
     427            0 :         m_indiP_parked.add( pcf::IndiElement( m_stages[n].name() ) );
     428              : 
     429            0 :         m_indiP_lastHomed.add( pcf::IndiElement( m_stages[n].name() ) );
     430              : 
     431            0 :         m_indiP_curr_pos.add( pcf::IndiElement( m_stages[n].name() ) );
     432              : 
     433            0 :         m_indiP_temp.add( pcf::IndiElement( m_stages[n].name() ) );
     434              : 
     435            0 :         m_indiP_warn.add( pcf::IndiElement( m_stages[n].name() ) );
     436            0 :         m_indiP_warn[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
     437              : 
     438            0 :         m_indiP_tgt_pos.add( pcf::IndiElement( m_stages[n].name() ) );
     439              : 
     440            0 :         m_indiP_req_home.add( pcf::IndiElement( m_stages[n].name() ) );
     441            0 :         m_indiP_req_home[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
     442              : 
     443            0 :         m_indiP_req_halt.add( pcf::IndiElement( m_stages[n].name() ) );
     444            0 :         m_indiP_req_halt[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
     445              : 
     446            0 :         m_indiP_req_ehalt.add( pcf::IndiElement( m_stages[n].name() ) );
     447            0 :         m_indiP_req_ehalt[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
     448              : 
     449            0 :         m_indiP_knob_enable.add( pcf::IndiElement( m_stages[n].name() ) );
     450            0 :         m_indiP_knob_enable[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
     451              : 
     452            0 :         m_indiP_led_enable.add( pcf::IndiElement( m_stages[n].name() ) );
     453            0 :         m_indiP_led_enable[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
     454              : 
     455              :         // Now load last state from disk
     456            0 :         std::ifstream posIn;
     457            0 :         posIn.open( std::format( "{}/{}/{}", m_sysPath, m_configName, m_stages[n].name() ) );
     458              : 
     459            0 :         if( !posIn )
     460              :         {
     461            0 :             continue;
     462              :         }
     463              : 
     464            0 :         if( m_stages[n].readStateFile( posIn ) < 0 )
     465              :         {
     466            0 :             return log<software_critical, -1>( std::format( "error reading state file for {}", m_stages[n].name() ) );
     467              :         }
     468              : 
     469            0 :         m_indiP_curr_pos[m_stages[n].name()].set( m_stages[n].rawPos() );
     470            0 :         m_indiP_tgt_pos[m_stages[n].name()].set( m_stages[n].tgtPos() );
     471            0 :         m_indiP_parked[m_stages[n].name()].set( m_stages[n].parked() );
     472            0 :         m_indiP_lastHomed[m_stages[n].name()].set( m_stages[n].lastHomed() );
     473            0 :         m_indiP_max_pos[m_stages[n].name()].set( m_stages[n].maxPos() );
     474            0 :         m_indiP_knob_enable[m_stages[n].name()].set( m_stages[n].knobEnabled() ? pcf::IndiElement::On
     475              :                                                                                : pcf::IndiElement::Off );
     476            0 :         m_indiP_led_enable[m_stages[n].name()].set( m_stages[n].ledEnabled() ? pcf::IndiElement::On
     477              :                                                                              : pcf::IndiElement::Off );
     478            0 :     }
     479              : 
     480            0 :     return 0;
     481              : }
     482              : 
     483            0 : int zaberLowLevel::appLogic()
     484              : {
     485            0 :     if( state() == stateCodes::INITIALIZED )
     486              :     {
     487            0 :         log<text_log>( "In appLogic but in state INITIALIZED.", logPrio::LOG_CRITICAL );
     488            0 :         return -1;
     489              :     }
     490              : 
     491            0 :     if( state() == stateCodes::POWERON )
     492              :     {
     493            0 :         for( size_t i = 0; i < m_stages.size(); ++i )
     494              :         {
     495            0 :             updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "POWERON" ) );
     496              :         }
     497              : 
     498            0 :         state( stateCodes::NODEVICE );
     499              : 
     500            0 :         return 0; // go around once to give POWERON time to propagate
     501              :     }
     502              : 
     503            0 :     if( state() == stateCodes::NODEVICE )
     504              :     {
     505            0 :         for( size_t i = 0; i < m_stages.size(); ++i )
     506              :         {
     507            0 :             updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
     508              :         }
     509              : 
     510            0 :         int rv = tty::usbDevice::getDeviceName();
     511              : 
     512            0 :         if( rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES )
     513              :         {
     514            0 :             if( powerState() != 1 || powerStateTarget() != 1 )
     515              :             {
     516            0 :                 return 0; // means we're powering off
     517              :             }
     518              : 
     519            0 :             state( stateCodes::FAILURE );
     520              : 
     521            0 :             if( !stateLogged() )
     522              :             {
     523            0 :                 log<software_critical>( { rv, tty::ttyErrorString( rv ) } );
     524              :             }
     525            0 :             return -1;
     526              :         }
     527              : 
     528            0 :         if( rv == TTY_E_DEVNOTFOUND || rv == TTY_E_NODEVNAMES )
     529              :         {
     530            0 :             if( !stateLogged() )
     531              :             {
     532            0 :                 log<text_log>(
     533            0 :                     std::format( "USB Device {}:{}:{} not found in udev", m_idVendor, m_idProduct, m_serial ) );
     534              :             }
     535              : 
     536            0 :             return 0;
     537              :         }
     538              :         else
     539              :         {
     540            0 :             std::stringstream logs;
     541            0 :             log<text_log>( std::format(
     542            0 :                 "USB Device {}:{}:{} found in udev as {}", m_idVendor, m_idProduct, m_serial, m_deviceName ) );
     543              : 
     544            0 :             state( stateCodes::NOTCONNECTED );
     545              : 
     546            0 :             for( size_t i = 0; i < m_stages.size(); ++i )
     547              :             {
     548            0 :                 if( m_stages[i].deviceAddress() < 1 )
     549              :                 {
     550            0 :                     continue;
     551              :                 }
     552              : 
     553            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTCONNECTED" ) );
     554              :             }
     555              : 
     556            0 :             return 0; // we return to give the stage time to initialize the connection if this is a USB-FTDI power
     557              :                       // on/plug-in event.
     558            0 :         }
     559              :     }
     560              : 
     561            0 :     if( state() == stateCodes::NOTCONNECTED )
     562              :     {
     563            0 :         std::lock_guard<std::mutex> guard( m_indiMutex );
     564              : 
     565            0 :         int rv = connect();
     566              : 
     567            0 :         if( rv == ZC_CONNECTED )
     568              :         {
     569            0 :             state( stateCodes::CONNECTED );
     570              : 
     571            0 :             for( size_t i = 0; i < m_stages.size(); ++i )
     572              :             {
     573            0 :                 if( m_stages[i].deviceAddress() < 1 )
     574              :                 {
     575            0 :                     continue;
     576              :                 }
     577              : 
     578            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "CONNECTED" ) );
     579              :             }
     580              : 
     581            0 :             if( !stateLogged() )
     582              :             {
     583            0 :                 log<text_log>( "Connected to stage(s) on " + m_deviceName );
     584              :             }
     585              :         }
     586            0 :         else if( rv == ZC_NOT_CONNECTED )
     587              :         {
     588            0 :             return 0;
     589              :         }
     590            0 :     }
     591              : 
     592            0 :     if( state() == stateCodes::CONNECTED )
     593              :     {
     594            0 :         for( size_t i = 0; i < m_stages.size(); ++i )
     595              :         {
     596            0 :             if( m_stages[i].deviceAddress() < 1 )
     597              :             {
     598            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
     599            0 :                 continue; // Skip configured but not found stage
     600              :             }
     601              : 
     602            0 :             std::lock_guard<std::mutex> guard( m_indiMutex ); // Inside loop so INDI requests can steal it
     603              : 
     604            0 :             m_stages[i].enableKnob( m_port, false ); // Always disable the knob on startup
     605              : 
     606            0 :             m_stages[i].enableLED( m_port, false ); // Always disable the LEDs on startup
     607              : 
     608            0 :             m_stages[i].getMaxPos( m_port );
     609              : 
     610            0 :             updateIfChanged( m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos() );
     611              : 
     612              :             // First unpark if possible
     613            0 :             if( m_stages[i].unpark( m_port ) < 0 )
     614              :             {
     615            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     616              :                 {
     617            0 :                     return 0; // means we're powering off
     618              :                 }
     619              : 
     620            0 :                 log<software_error>();
     621            0 :                 state( stateCodes::ERROR );
     622            0 :                 return 0;
     623              :             }
     624              : 
     625              :             // Get warnings so first pass through has correct state for home/not-homed
     626            0 :             if( m_stages[i].getWarnings( m_port ) < 0 )
     627              :             {
     628            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     629              :                 {
     630            0 :                     return 0; // means we're powering off
     631              :                 }
     632              : 
     633            0 :                 log<software_error>();
     634            0 :                 state( stateCodes::ERROR );
     635            0 :                 return 0;
     636              :             }
     637            0 :         }
     638              : 
     639            0 :         state( stateCodes::READY );
     640              : 
     641            0 :         return 0;
     642              :     }
     643              : 
     644            0 :     if( state() == stateCodes::READY )
     645              :     {
     646              :         // Here we check complete stage state.
     647            0 :         for( size_t i = 0; i < m_stages.size(); ++i )
     648              :         {
     649            0 :             if( m_stages[i].deviceAddress() < 1 )
     650              :             {
     651            0 :                 continue; // Skip configured but not found stage
     652              :             }
     653              : 
     654            0 :             std::lock_guard<std::mutex> guard( m_indiMutex ); // Inside loop so INDI requests can steal it
     655              : 
     656            0 :             if( m_stages[i].getKnob( m_port ) < 0 )
     657              :             {
     658            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     659              :                 {
     660            0 :                     return 0; // means we're powering off
     661              :                 }
     662              : 
     663            0 :                 log<software_error>();
     664            0 :                 state( stateCodes::ERROR );
     665            0 :                 return 0;
     666              :             }
     667            0 :             updateSwitchIfChanged( m_indiP_knob_enable,
     668            0 :                                    m_stages[i].name(),
     669            0 :                                    ( m_stages[i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
     670              : 
     671            0 :             if( m_stages[i].getLED( m_port ) < 0 )
     672              :             {
     673            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     674              :                 {
     675            0 :                     return 0; // means we're powering off
     676              :                 }
     677              : 
     678            0 :                 log<software_error>();
     679            0 :                 state( stateCodes::ERROR );
     680            0 :                 return 0;
     681              :             }
     682            0 :             updateSwitchIfChanged( m_indiP_led_enable,
     683            0 :                                    m_stages[i].name(),
     684            0 :                                    ( m_stages[i].ledEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
     685              : 
     686            0 :             if( m_stages[i].getParked( m_port ) < 0 )
     687              :             {
     688            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     689              :                 {
     690            0 :                     return 0; // means we're powering off
     691              :                 }
     692              : 
     693            0 :                 log<software_error>();
     694            0 :                 state( stateCodes::ERROR );
     695            0 :                 return 0;
     696              :             }
     697              : 
     698            0 :             updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
     699            0 :             updateIfChanged( m_indiP_lastHomed, m_stages[i].name(), m_stages[i].lastHomed() );
     700              : 
     701            0 :             if( m_stages[i].updatePos( m_port ) < 0 )
     702              :             {
     703            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     704              :                 {
     705            0 :                     return 0; // means we're powering off
     706              :                 }
     707              : 
     708            0 :                 log<software_error>();
     709            0 :                 state( stateCodes::ERROR );
     710            0 :                 return 0;
     711              :             }
     712              : 
     713            0 :             updateIfChanged( m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos() );
     714            0 :             updateIfChanged( m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos() );
     715              : 
     716            0 :             if( m_stages[i].deviceStatus() == 'B' )
     717              :             {
     718            0 :                 if( m_stages[i].homing() )
     719              :                 {
     720            0 :                     updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "HOMING" ) );
     721              :                 }
     722              :                 else
     723              :                 {
     724            0 :                     updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "OPERATING" ) );
     725              :                 }
     726              :             }
     727            0 :             else if( m_stages[i].deviceStatus() == 'I' )
     728              :             {
     729            0 :                 if( m_stages[i].homing() )
     730              :                 {
     731            0 :                     log<software_error>( std::format( "stage {} idle but in "
     732              :                                                       "state homing. bug.",
     733            0 :                                                       m_stages[i].name() ) );
     734            0 :                     return 0;
     735              :                 }
     736              : 
     737            0 :                 if( m_stages[i].warnWR() )
     738              :                 {
     739            0 :                     updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTHOMED" ) );
     740              :                 }
     741              :                 else
     742              :                 {
     743            0 :                     if( !m_stages[i].parked() )
     744              :                     {
     745            0 :                         if( m_stages[i].park( m_port ) < 0 )
     746              :                         {
     747            0 :                             if( powerState() != 1 || powerStateTarget() != 1 )
     748              :                             {
     749            0 :                                 return 0; // means we're powering off
     750              :                             }
     751              : 
     752            0 :                             log<software_error>();
     753            0 :                             state( stateCodes::ERROR );
     754            0 :                             return 0;
     755              :                         }
     756              : 
     757            0 :                         std::ofstream posOut;
     758              : 
     759              :                         { // scope for priv
     760            0 :                             elevatedPrivileges ep( this );
     761            0 :                             posOut.open( std::format( "{}/{}/{}", m_sysPath, m_configName, m_stages[i].name() ) );
     762            0 :                         }
     763              : 
     764            0 :                         if( !posOut )
     765              :                         {
     766            0 :                             log<software_error>( std::format( "error opening state file for {}", m_stages[i].name() ) );
     767              :                         }
     768            0 :                         else if( m_stages[i].writeStateFile( posOut ) < 0 )
     769              :                         {
     770            0 :                             log<software_error>( std::format( "error writing state file for {}", m_stages[i].name() ) );
     771              :                         }
     772              : 
     773            0 :                         updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
     774            0 :                         updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "READY" ) );
     775            0 :                     }
     776              :                 }
     777              :             }
     778              :             else
     779              :             {
     780            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
     781              :             }
     782              : 
     783            0 :             if( m_stages[i].warn() )
     784              :             {
     785            0 :                 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::On );
     786              :             }
     787              :             else
     788              :             {
     789            0 :                 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off );
     790              :             }
     791              : 
     792            0 :             if( m_stages[i].updateTemp( m_port ) < 0 )
     793              :             {
     794            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     795              :                 {
     796            0 :                     return 0; // means we're powering off
     797              :                 }
     798              : 
     799            0 :                 log<software_error>();
     800            0 :                 state( stateCodes::ERROR );
     801            0 :                 return 0;
     802              :             }
     803            0 :             updateIfChanged( m_indiP_temp, m_stages[i].name(), m_stages[i].temp() );
     804              : 
     805            0 :             if( m_stages[i].getWarnings( m_port ) < 0 )
     806              :             {
     807            0 :                 if( powerState() != 1 || powerStateTarget() != 1 )
     808              :                 {
     809            0 :                     return 0; // means we're powering off
     810              :                 }
     811            0 :                 log<software_error>();
     812            0 :                 state( stateCodes::ERROR );
     813            0 :                 return 0;
     814              :             }
     815            0 :         }
     816              :     }
     817              : 
     818            0 :     if( state() == stateCodes::ERROR )
     819              :     {
     820            0 :         int rv = tty::usbDevice::getDeviceName();
     821            0 :         if( rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES )
     822              :         {
     823            0 :             if( powerState() != 1 || powerStateTarget() != 1 )
     824              :             {
     825            0 :                 return 0; // means we're powering off
     826              :             }
     827              : 
     828            0 :             state( stateCodes::FAILURE );
     829            0 :             for( size_t i = 0; i < m_stages.size(); ++i )
     830              :             {
     831            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "FAILURE" ) );
     832              :             }
     833            0 :             if( !stateLogged() )
     834              :             {
     835            0 :                 log<software_critical>( { rv, tty::ttyErrorString( rv ) } );
     836              :             }
     837            0 :             return rv;
     838              :         }
     839              : 
     840            0 :         if( rv == TTY_E_DEVNOTFOUND || rv == TTY_E_NODEVNAMES )
     841              :         {
     842            0 :             if( powerState() != 1 || powerStateTarget() != 1 )
     843              :             {
     844            0 :                 return 0; // means we're powering off
     845              :             }
     846              : 
     847            0 :             state( stateCodes::NODEVICE );
     848              : 
     849            0 :             for( size_t i = 0; i < m_stages.size(); ++i )
     850              :             {
     851            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
     852              :             }
     853              : 
     854            0 :             if( !stateLogged() )
     855              :             {
     856            0 :                 log<text_log>(
     857            0 :                     std::format( "USB Device {}:{}:{} not found in udev", m_idVendor, m_idProduct, m_serial ) );
     858              :             }
     859              : 
     860            0 :             return 0;
     861              :         }
     862              : 
     863            0 :         if( powerState() != 1 || powerStateTarget() != 1 )
     864              :         {
     865            0 :             return 0; // means we're powering off
     866              :         }
     867              : 
     868            0 :         state( stateCodes::FAILURE );
     869              : 
     870            0 :         for( size_t i = 0; i < m_stages.size(); ++i )
     871              :         {
     872            0 :             updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "FAILURE" ) );
     873              :         }
     874              : 
     875            0 :         log<software_critical>();
     876            0 :         log<text_log>( "Error NOT due to loss of USB connection.  I can't fix it myself.", logPrio::LOG_CRITICAL );
     877              :     }
     878              : 
     879            0 :     if( powerState() != 1 || powerStateTarget() != 1 )
     880              :     {
     881            0 :         return 0; // means we're powering off
     882              :     }
     883              : 
     884            0 :     if( state() == stateCodes::FAILURE )
     885              :     {
     886            0 :         return -1;
     887              :     }
     888              : 
     889            0 :     return 0;
     890              : }
     891              : 
     892            0 : inline int zaberLowLevel::onPowerOff()
     893              : {
     894            0 :     int rv = za_disconnect( m_port );
     895            0 :     if( rv < 0 )
     896              :     {
     897            0 :         log<text_log>( "Error disconnecting from zaber system.", logPrio::LOG_ERROR );
     898              :     }
     899              : 
     900            0 :     m_port = 0;
     901              : 
     902            0 :     std::lock_guard<std::mutex> lock( m_indiMutex );
     903              : 
     904            0 :     for( size_t i = 0; i < m_stages.size(); ++i )
     905              :     {
     906            0 :         m_stages[i].onPowerOff();
     907              : 
     908              :         // Publish the retained stage snapshot before advertising POWEROFF so
     909              :         // subscribers can consume the last known parked/position state first.
     910            0 :         updateIfChanged( m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos() );
     911            0 :         updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
     912            0 :         updateIfChanged( m_indiP_lastHomed, m_stages[i].name(), m_stages[i].lastHomed() );
     913            0 :         updateIfChanged( m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos() );
     914            0 :         updateIfChanged( m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos() );
     915            0 :         updateSwitchIfChanged( m_indiP_knob_enable,
     916            0 :                                m_stages[i].name(),
     917            0 :                                ( m_stages[i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
     918            0 :         updateSwitchIfChanged( m_indiP_led_enable,
     919            0 :                                m_stages[i].name(),
     920            0 :                                ( m_stages[i].ledEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
     921            0 :         updateIfChanged( m_indiP_temp, m_stages[i].name(), std::string( "" ) );
     922            0 :         updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "POWEROFF" ) );
     923            0 :         updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off );
     924              :     }
     925              : 
     926            0 :     return 0;
     927            0 : }
     928              : 
     929            0 : inline int zaberLowLevel::whilePowerOff()
     930              : {
     931            0 :     return 0;
     932              : }
     933              : 
     934            0 : inline int zaberLowLevel::appShutdown()
     935              : {
     936            0 :     for( size_t i = 0; i < m_stages.size(); ++i )
     937              :     {
     938            0 :         if( m_stages[i].deviceAddress() < 1 )
     939              :         {
     940            0 :             continue;
     941              :         }
     942              : 
     943            0 :         updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
     944              :     }
     945              : 
     946            0 :     return 0;
     947              : }
     948              : 
     949            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_tgt_pos )( const pcf::IndiProperty &ipRecv )
     950              : {
     951            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tgt_pos, ipRecv );
     952              : 
     953            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
     954              :     {
     955            0 :         if( ipRecv.find( m_stages[n].name() ) )
     956              :         {
     957            0 :             long tgt = ipRecv[m_stages[n].name()].get<long>();
     958            0 :             if( tgt >= 0 )
     959              :             {
     960            0 :                 if( m_stages[n].deviceAddress() < 1 )
     961              :                 {
     962            0 :                     return log<software_error, -1>( std::format(
     963            0 :                         "stage {} with with s/n {} not found in system.", m_stages[n].name(), m_stages[n].serial() ) );
     964              :                 }
     965              : 
     966            0 :                 std::lock_guard<std::mutex> guard( m_indiMutex );
     967              : 
     968            0 :                 if( m_stages[n].moveAbs( m_port, tgt ) < 0 )
     969              :                 {
     970            0 :                     return log<software_error, -1>( { "error from moveAbs for " + m_stages[n].name() } );
     971              :                 }
     972              : 
     973            0 :                 updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), m_stages[n].tgtPos() );
     974            0 :                 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
     975            0 :                 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string( "OPERATING" ) );
     976            0 :             }
     977              :         }
     978              :     }
     979              : 
     980            0 :     return 0;
     981              : }
     982              : 
     983            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_home )( const pcf::IndiProperty &ipRecv )
     984              : {
     985            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_home, ipRecv );
     986              : 
     987              :     // Make sure only one request is sent to avoid racing
     988            0 :     size_t stageno = std::numeric_limits<size_t>::max();
     989              : 
     990            0 :     bool found = false;
     991              : 
     992            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
     993              :     {
     994            0 :         if( ipRecv.find( m_stages[n].name() ) )
     995              :         {
     996            0 :             if( found )
     997              :             {
     998            0 :                 log<software_error>( { "more than one stage specified in req_home, rejecting request" } );
     999            0 :                 return -1;
    1000              :             }
    1001              : 
    1002            0 :             if( m_stages[n].deviceAddress() < 1 )
    1003              :             {
    1004            0 :                 return log<software_error, -1>( std::format( "stage {} with with "
    1005              :                                                              "s/n {} not found",
    1006            0 :                                                              m_stages[n].name(),
    1007            0 :                                                              m_stages[n].serial() ) );
    1008              :             }
    1009              : 
    1010            0 :             stageno = n;
    1011            0 :             found   = true;
    1012              :         }
    1013              :     }
    1014              : 
    1015            0 :     if( !found || stageno >= m_stages.size() )
    1016              :     {
    1017            0 :         log<software_error>( "no valid stage specified in req_home, rejecting request" );
    1018            0 :         return -1;
    1019              :     }
    1020              : 
    1021            0 :     if( ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
    1022              :     {
    1023            0 :         return log<software_warning, 0>( std::format( "request off for stage {} "
    1024              :                                                       "in req_home",
    1025            0 :                                                       m_stages[stageno].name() ) );
    1026              :     }
    1027              : 
    1028            0 :     std::lock_guard<std::mutex> guard( m_indiMutex );
    1029              : 
    1030            0 :     if( m_stages[stageno].homing() )
    1031              :     {
    1032            0 :         return log<software_warning, 0>( std::format( "stage {} is already "
    1033              :                                                       "homing in req_home",
    1034            0 :                                                       m_stages[stageno].name() ) );
    1035              :     }
    1036              : 
    1037            0 :     if( m_stages[stageno].home( m_port ) < 0 )
    1038              :     {
    1039            0 :         return log<software_error, -1>( std::format( "error from home for {}", m_stages[stageno].name() ) );
    1040              :     }
    1041              : 
    1042            0 :     updateIfChanged( m_indiP_tgt_pos, m_stages[stageno].name(), 0 );
    1043            0 :     updateIfChanged( m_indiP_parked, m_stages[stageno].name(), m_stages[stageno].parked() );
    1044            0 :     updateIfChanged( m_indiP_curr_state, m_stages[stageno].name(), std::string( "HOMING" ) );
    1045              : 
    1046            0 :     return 0;
    1047            0 : }
    1048              : 
    1049            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_home_all )( const pcf::IndiProperty &ipRecv )
    1050              : {
    1051            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_home_all, ipRecv );
    1052              : 
    1053            0 :     if( !ipRecv.find( "request" ) )
    1054              :     {
    1055            0 :         return 0;
    1056              :     }
    1057              : 
    1058            0 :     if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
    1059              :     {
    1060            0 :         for( size_t n = 0; n < m_stages.size(); ++n )
    1061              :         {
    1062            0 :             if( m_stages[n].deviceAddress() < 1 )
    1063              :             {
    1064            0 :                 continue;
    1065              :             }
    1066            0 :             std::lock_guard<std::mutex> guard( m_indiMutex );
    1067              : 
    1068            0 :             if( m_stages[n].homing() )
    1069              :             {
    1070            0 :                 continue;
    1071              :             }
    1072              : 
    1073            0 :             if( m_stages[n].home( m_port ) < 0 )
    1074              :             {
    1075            0 :                 return log<software_error, -1>( { "error from home for " + m_stages[n].name() } );
    1076              :             }
    1077              : 
    1078            0 :             updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), 0 );
    1079            0 :             updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
    1080            0 :             updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string( "HOMING" ) );
    1081            0 :         }
    1082              :     }
    1083              : 
    1084            0 :     return 0;
    1085              : }
    1086              : 
    1087            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_halt )( const pcf::IndiProperty &ipRecv )
    1088              : {
    1089            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_halt, ipRecv );
    1090              : 
    1091              :     // Make sure only one request is sent to avoid racing
    1092            0 :     size_t stageno = std::numeric_limits<size_t>::max();
    1093              : 
    1094            0 :     bool found = false;
    1095              : 
    1096            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
    1097              :     {
    1098            0 :         if( ipRecv.find( m_stages[n].name() ) )
    1099              :         {
    1100            0 :             if( found )
    1101              :             {
    1102            0 :                 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
    1103              :             }
    1104              : 
    1105            0 :             if( m_stages[n].deviceAddress() < 1 )
    1106              :             {
    1107            0 :                 return log<software_error, -1>( std::format( "stage {} with with "
    1108              :                                                              "s/n {} not present",
    1109            0 :                                                              m_stages[n].name(),
    1110            0 :                                                              m_stages[n].serial() ) );
    1111              :             }
    1112              : 
    1113            0 :             stageno = n;
    1114            0 :             found   = true;
    1115              :         }
    1116              :     }
    1117              : 
    1118            0 :     if( !found || stageno == std::numeric_limits<size_t>::max() )
    1119              :     {
    1120            0 :         return log<software_error, -1>( "no valid stage specified in req_halt, rejecting request" );
    1121              :     }
    1122              : 
    1123            0 :     if( ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
    1124              :     {
    1125            0 :         return log<software_warning, 0>( std::format( "request off for stage {} "
    1126              :                                                       "in req_halt",
    1127            0 :                                                       m_stages[stageno].name() ) );
    1128              :     }
    1129              : 
    1130            0 :     std::lock_guard<std::mutex> guard( m_indiMutex );
    1131              : 
    1132            0 :     if( m_stages[stageno].stop( m_port ) < 0 )
    1133              :     {
    1134            0 :         return log<software_error, -1>( std::format( "error from stop for {}", m_stages[stageno].name() ) );
    1135              :     }
    1136              : 
    1137            0 :     return 0;
    1138            0 : }
    1139              : 
    1140            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_ehalt )( const pcf::IndiProperty &ipRecv )
    1141              : {
    1142            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_ehalt, ipRecv );
    1143              : 
    1144              :     // Here we accept multiple ehalts all at once just in case.  It's an emergency~
    1145              :     // and we don't stop for errors
    1146            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
    1147              :     {
    1148            0 :         if( ipRecv.find( m_stages[n].name() ) )
    1149              :         {
    1150            0 :             if( ipRecv[m_stages[n].name()].getSwitchState() == pcf::IndiElement::On )
    1151              :             {
    1152            0 :                 if( m_stages[n].deviceAddress() < 1 )
    1153              :                 {
    1154            0 :                     log<software_error>( std::format( "stage {} with s/n {} "
    1155              :                                                       "not present",
    1156            0 :                                                       m_stages[n].name(),
    1157            0 :                                                       m_stages[n].serial() ) );
    1158            0 :                     continue;
    1159              :                 }
    1160              : 
    1161            0 :                 std::lock_guard<std::mutex> guard( m_indiMutex );
    1162              : 
    1163            0 :                 if( m_stages[n].estop( m_port ) < 0 )
    1164              :                 {
    1165            0 :                     log<software_error>( { "error from estop for " + m_stages[n].name() } );
    1166              :                 }
    1167            0 :             }
    1168              :         }
    1169              :     }
    1170              : 
    1171            0 :     return 0;
    1172              : }
    1173              : 
    1174            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_knob_enable )( const pcf::IndiProperty &ipRecv )
    1175              : {
    1176            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_knob_enable, ipRecv );
    1177              : 
    1178              :     // Make sure only one request is sent to avoid racing
    1179            0 :     size_t stageno = std::numeric_limits<size_t>::max();
    1180              : 
    1181            0 :     bool found = false;
    1182              : 
    1183            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
    1184              :     {
    1185            0 :         if( ipRecv.find( m_stages[n].name() ) )
    1186              :         {
    1187            0 :             if( found )
    1188              :             {
    1189            0 :                 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
    1190              :             }
    1191              : 
    1192            0 :             if( m_stages[n].deviceAddress() < 1 )
    1193              :             {
    1194            0 :                 return log<software_error, -1>( std::format( "stage {} with with "
    1195              :                                                              "s/n {} not present",
    1196            0 :                                                              m_stages[n].name(),
    1197            0 :                                                              m_stages[n].serial() ) );
    1198              :             }
    1199              : 
    1200            0 :             stageno = n;
    1201            0 :             found   = true;
    1202              :         }
    1203              :     }
    1204              : 
    1205            0 :     if( !found || stageno == std::numeric_limits<size_t>::max() )
    1206              :     {
    1207            0 :         return log<software_error, -1>( "no valid stage specified in req_knob, rejecting request" );
    1208              :     }
    1209              : 
    1210            0 :     bool enable_knob = ipRecv[m_stages[stageno].name()].getSwitchState() == pcf::IndiElement::On;
    1211              : 
    1212            0 :     std::lock_guard<std::mutex> guard( m_indiMutex );
    1213              : 
    1214            0 :     if( m_stages[stageno].enableKnob( m_port, enable_knob ) < 0 )
    1215              :     {
    1216            0 :         return log<software_error, -1>( std::format( "error from enable knob for {}", m_stages[stageno].name() ) );
    1217              :     }
    1218              : 
    1219            0 :     return 0;
    1220            0 : }
    1221              : 
    1222            3 : INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_led_enable )( const pcf::IndiProperty &ipRecv )
    1223              : {
    1224            3 :     INDI_VALIDATE_CALLBACK_PROPS( m_indiP_led_enable, ipRecv );
    1225              : 
    1226              :     // Make sure only one request is sent to avoid racing
    1227            0 :     size_t stageno = std::numeric_limits<size_t>::max();
    1228              : 
    1229            0 :     bool found = false;
    1230              : 
    1231            0 :     for( size_t n = 0; n < m_stages.size(); ++n )
    1232              :     {
    1233            0 :         if( ipRecv.find( m_stages[n].name() ) )
    1234              :         {
    1235            0 :             if( found )
    1236              :             {
    1237            0 :                 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
    1238              :             }
    1239              : 
    1240            0 :             if( m_stages[n].deviceAddress() < 1 )
    1241              :             {
    1242            0 :                 return log<software_error, -1>( std::format( "stage {} with with "
    1243              :                                                              "s/n {} not present",
    1244            0 :                                                              m_stages[n].name(),
    1245            0 :                                                              m_stages[n].serial() ) );
    1246              :             }
    1247              : 
    1248            0 :             stageno = n;
    1249            0 :             found   = true;
    1250              :         }
    1251              :     }
    1252              : 
    1253            0 :     if( !found || stageno == std::numeric_limits<size_t>::max() )
    1254              :     {
    1255            0 :         return log<software_error, -1>( "no valid stage specified in req_led, rejecting request" );
    1256              :     }
    1257              : 
    1258            0 :     bool enable_led = ipRecv[m_stages[stageno].name()].getSwitchState() == pcf::IndiElement::On;
    1259              : 
    1260            0 :     std::lock_guard<std::mutex> guard( m_indiMutex );
    1261              : 
    1262            0 :     if( m_stages[stageno].enableLED( m_port, enable_led ) < 0 )
    1263              :     {
    1264            0 :         return log<software_error, -1>( std::format( "error from enable led for {}", m_stages[stageno].name() ) );
    1265              :     }
    1266              : 
    1267            0 :     return 0;
    1268            0 : }
    1269              : 
    1270              : } // namespace app
    1271              : } // namespace MagAOX
    1272              : 
    1273              : #endif // zaberLowLevel_hpp
        

Generated by: LCOV version 2.0-1