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
|