API
 
Loading...
Searching...
No Matches
zaberLowLevel.hpp
Go to the documentation of this file.
1/** \file zaberLowLevel.hpp
2 * \brief The MagAO-X Low-Level Zaber Controller
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup zaberLowLevel_files
6 */
7
8#ifndef zaberLowLevel_hpp
9#define zaberLowLevel_hpp
10
11#include <iostream>
12
13#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
14#include "../../magaox_git_version.h"
15
16typedef MagAOX::app::MagAOXApp<true> MagAOXAppT; // This needs to be before zaberStage.hpp for logging to work.
17
18#include "zaberUtils.hpp"
19#include "zaberStage.hpp"
20#include "za_serial.h"
21
22#define ZC_CONNECTED ( 0 )
23#define ZC_ERROR ( -1 )
24#define ZC_NOT_CONNECTED ( 10 )
25
26/** \defgroup zaberLowLevel low-level zaber controller
27 * \brief The low-level interface to a set of chained Zaber stages
28 *
29 * <a href="../handbook/operating/software/apps/zaberLowLevel.html">Application Documentation</a>
30 *
31 * \ingroup apps
32 *
33 */
34
35/** \defgroup zaberLowLevel_files zaber low-level files
36 * \ingroup zaberLowLevel
37 */
38
39namespace MagAOX
40{
41namespace app
42{
43
44/// The low-level ASCII-protocol Zaber controller.
45/**
46 * This app manages a daisy-chained ASCII Zaber bus and keeps its discovery,
47 * recovery, and INDI reporting behavior aligned with the binary-protocol app.
48 *
49 * \ingroup zaberLowLevel
50 */
52{
53
54 // Give the test harness access.
55 friend class zaberLowLevel_test;
56
57 protected:
58 /** \name Stage Mapping - Data
59 *
60 * @{
61 */
62 /// Number of configured stages.
63 int m_numStages{ 0 };
64
65 /// Connected ASCII protocol port.
67
68 /// Stage helpers in configuration order.
69 std::vector<zaberStage<zaberLowLevel>> m_stages;
70
71 /// Map from ASCII device address to configured stage index.
72 std::unordered_map<int, size_t> m_stageAddress;
73
74 /// Map from configured serial number to configured stage index.
75 std::unordered_map<std::string, size_t> m_stageSerial;
76
77 /// Map from configured stage name to configured stage index.
78 std::unordered_map<std::string, size_t> m_stageName;
79
80 /// Whether the active connection has completed an initial discovery pass.
82 ///@}
83
84 public:
85 /// Default constructor.
87
88 /// Destructor, declared and defined for noexcept.
92
93 /// Set up application configuration.
94 virtual void setupConfig();
95
96 /// Load application configuration.
97 virtual void loadConfig();
98
99 /// Connect to the ASCII-protocol stage chain and discover configured devices.
100 int connect();
101
102 /// Apply a parsed `system.serial` snapshot to the configured stages.
103 int loadStages( std::string &serialRes /**< [in] the raw response to `/ get system.serial` */ );
104
105 /// Refresh discovery on an already-connected ASCII bus.
107
108 /// Reset the active ASCII connection bookkeeping.
109 int resetConnection();
110
111 /// Recover from an ASCII-transport error without terminating the app.
112 int recoverFromError( bool devicePresent /**< [in] True if the USB tty still exists in udev. */ );
113
114 /// Set up the INDI properties and restore retained stage state.
115 virtual int appStartup();
116
117 /// Execute the main FSM for `zaberLowLevel`.
118 virtual int appLogic();
119
120 /// Handle the transition into the powered-off state.
121 virtual int onPowerOff();
122
123 /// Execute the powered-off loop.
124 virtual int whilePowerOff();
125
126 /// Perform any shutdown tasks before exit.
127 virtual int appShutdown();
128
129 protected:
130 /** \name INDI Stage State - Data
131 *
132 * @{
133 */
134 /// Current state of the stage.
135 pcf::IndiProperty m_indiP_curr_state;
136
137 /// Maximum raw position of the stage.
138 pcf::IndiProperty m_indiP_max_pos;
139
140 /// Parked state of the stage.
141 pcf::IndiProperty m_indiP_parked;
142
143 /// Time of last homing for the stage.
144 pcf::IndiProperty m_indiP_lastHomed;
145
146 /// Current raw position of the stage.
147 pcf::IndiProperty m_indiP_curr_pos;
148
149 /// Current temperature of the stage.
150 pcf::IndiProperty m_indiP_temp;
151
152 /// Whether the stage has existing warnings.
153 pcf::IndiProperty m_indiP_warn;
154
155 /// Target raw position of the stage.
156 pcf::IndiProperty m_indiP_tgt_pos;
157
158 /// Command a stage to home.
159 pcf::IndiProperty m_indiP_req_home;
160
161 /// Command all stages to home.
162 pcf::IndiProperty m_indiP_req_home_all;
163
164 /// Command a stage to safely halt.
165 pcf::IndiProperty m_indiP_req_halt;
166
167 /// Command a stage to safely immediately halt.
168 pcf::IndiProperty m_indiP_req_ehalt;
169
170 /// Enable or disable a stage's potentiometer.
171 pcf::IndiProperty m_indiP_knob_enable;
172
173 /// Enable or disable a stage's LED.
174 pcf::IndiProperty m_indiP_led_enable;
175 ///@}
176
177 public:
178 /** \name INDI Stage State
179 *
180 * @{
181 */
189 ///@}
190};
191
192zaberLowLevel::zaberLowLevel() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
193{
194 m_powerMgtEnabled = true;
195
196 return;
197}
198
203
205{
206
207 this->m_baudRate = B115200; // default for Zaber stages. Will be overridden by any config setting.
208
209 int rv = tty::usbDevice::loadConfig( config );
210
211 if( rv != 0 && rv != TTY_E_NODEVNAMES && rv != TTY_E_DEVNOTFOUND ) // Ignore error if not plugged in
212 {
214 }
215
216 std::vector<std::string> sections;
217
218 config.unusedSections( sections );
219
220 if( sections.size() == 0 )
221 {
222 log<software_error>( { "No stages found" } );
223 return;
224 }
225
226 for( size_t n = 0; n < sections.size(); ++n )
227 {
228 if( config.isSetUnused( mx::app::iniFile::makeKey( sections[n], "serial" ) ) )
229 {
230 m_stages.push_back( zaberStage<zaberLowLevel>( this ) );
231
232 size_t idx = m_stages.size() - 1;
233
234 m_stages[idx].name( sections[n] );
235
236 // Get serial number from config.
237 std::string tmp = m_stages[idx].serial(); // get default
238 config.configUnused( tmp, mx::app::iniFile::makeKey( sections[n], "serial" ) );
239 m_stages[idx].serial( tmp );
240
241 m_stageName.insert( { m_stages[idx].name(), idx } );
242 m_stageSerial.insert( { m_stages[idx].serial(), idx } );
243 }
244 }
245}
246
248{
249 if( m_port > 0 )
250 {
251 int rv = za_disconnect( m_port );
252 if( rv < 0 )
253 {
254 log<text_log>( "Error disconnecting from zaber system.", logPrio::LOG_ERROR );
255 }
256 m_port = 0;
257 }
258
259 if( m_port <= 0 )
260 {
261
262 int zrv;
263
264 { // scope for elPriv
265 elevatedPrivileges elPriv( this );
266 zrv = za_connect( &m_port, m_deviceName.c_str() );
267 }
268
269 if( zrv != Z_SUCCESS )
270 {
271 if( m_port > 0 )
272 {
274 m_port = 0;
275 }
276
277 if( !stateLogged() )
278 {
279 log<software_error>( { "can not connect to zaber stage(s)" } );
280 }
281
282 return ZC_NOT_CONNECTED; // We aren't connected.
283 }
284 }
285
286 if( m_port <= 0 )
287 {
288 // state(stateCodes::ERROR); //Should not get this here. Probably means no device.
289 log<text_log>( "can not connect to zaber stage(s): no port", logPrio::LOG_WARNING );
290 return ZC_NOT_CONNECTED; // We aren't connected.
291 }
292
293 int rv = za_drain( m_port );
294
295 if( rv != Z_SUCCESS )
296 {
297 log<software_error>( { rv, "error from za_drain" } );
299 return ZC_ERROR;
300 }
301
302 char buffer[256];
303
304 //===== First renumber so they are unique.
305 std::string renum = "/ renumber";
306 int nwr = za_send( m_port, renum.c_str(), renum.size() );
307
309 {
310 log<text_log>( "Error sending renumber query to stages", logPrio::LOG_ERROR );
312 return ZC_ERROR;
313 }
314
315 //===== Drain the result
316 rv = za_drain( m_port );
317
318 if( rv != Z_SUCCESS )
319 {
320 log<software_error>( { rv, "error from za_drain" } );
322 return ZC_ERROR;
323 }
324
325 //======= Now find the stages
326 std::string gss = "/ get system.serial";
327 nwr = za_send( m_port, gss.c_str(), gss.size() );
328
330 {
331 log<text_log>( "Error sending system.serial query to stages", logPrio::LOG_ERROR );
333 return ZC_ERROR;
334 }
335
336 std::string serialRes;
337 while( 1 )
338 {
339 int nrd = za_receive( m_port, buffer, sizeof( buffer ) );
340 if( nrd >= 0 )
341 {
342 buffer[nrd] = '\0';
343 log<text_log>( std::string( "Received: " ) + buffer, logPrio::LOG_DEBUG );
344 serialRes += buffer;
345 }
346 else if( nrd != Z_ERROR_TIMEOUT )
347 {
348 log<text_log>( "Error receiving from stages", logPrio::LOG_ERROR );
350 return ZC_ERROR;
351 }
352 else
353 {
354 log<text_log>( "TIMEOUT", logPrio::LOG_DEBUG );
355 break; // Timeout ok.
356 }
357 }
358
359 {
360 std::vector<int> addresses;
361 std::vector<std::string> serials;
362
364 if( rv == ZUTILS_E_BADSERIAL )
365 {
366 log<text_log>( "Ignoring inconclusive system.serial snapshot during stage activity.", logPrio::LOG_DEBUG );
367 return ZC_CONNECTED;
368 }
369 }
370
371 return loadStages( serialRes );
372}
373
374int zaberLowLevel::loadStages( std::string &serialRes )
375{
376 std::vector<int> addresses;
377 std::vector<std::string> serials;
378 std::vector<int> oldAddresses;
380 size_t oldPresentCount = 0;
381
382 oldAddresses.reserve( m_stages.size() );
383 for( size_t n = 0; n < m_stages.size(); ++n )
384 {
385 oldAddresses.push_back( m_stages[n].deviceAddress() );
386 if( m_stages[n].deviceAddress() > 0 )
387 {
389 }
390 }
391
393
394 if( rv < 0 )
395 {
396 log<software_error>( { errno, rv, "error in parseSystemSerial" } );
398 return ZC_ERROR;
399 }
400 else
401 {
403 {
404 log<text_log>( "Found " + std::to_string( addresses.size() ) + " stages." );
405 }
406
407 m_stageAddress.clear(); // We clear this map before re-populating.
408
409 for( size_t n = 0; n < m_stages.size(); ++n )
410 {
411 m_stages[n].deviceAddress( -1 );
412 }
413
414 for( size_t n = 0; n < addresses.size(); ++n )
415 {
416 if( m_stageSerial.count( serials[n] ) == 1 )
417 {
418 const size_t stageIndex = m_stageSerial[serials[n]];
419
420 m_stages[stageIndex].deviceAddress( addresses[n] );
421
422 m_stageAddress.insert( { addresses[n], stageIndex } );
423 if( firstDiscoveryPass || stageIndex >= oldAddresses.size() ||
425 {
426 log<text_log>( "stage @" + std::to_string( addresses[n] ) + " with s/n " + serials[n] +
427 " corresponds to " + m_stages[stageIndex].name() );
428 }
429 }
430 else
431 {
432 log<text_log>( "Unkown stage @" + std::to_string( addresses[n] ) + " with s/n " + serials[n],
434 }
435 }
436
437 for( size_t n = 0; n < m_stages.size(); ++n )
438 {
439 if( m_stages[n].deviceAddress() < 1 )
440 {
441 if( firstDiscoveryPass || n >= oldAddresses.size() || oldAddresses[n] > 0 )
442 {
443 log<text_log>( std::format( "stage {} with s/n {} not found in system.",
444 m_stages[n].name(),
445 m_stages[n].serial() ),
447 state( state(), true );
448 }
449 }
450 }
451 }
452
454
455 return ZC_CONNECTED;
456}
457
459{
460 if( m_port <= 0 )
461 {
462 return ZC_NOT_CONNECTED;
463 }
464
465 int rv = za_drain( m_port );
466
467 if( rv != Z_SUCCESS )
468 {
469 log<software_error>( { rv, "error from za_drain" } );
471 return ZC_ERROR;
472 }
473
474 char buffer[256];
475 std::string gss = "/ get system.serial";
476 int nwr = za_send( m_port, gss.c_str(), gss.size() );
477
479 {
480 log<text_log>( "Error sending system.serial query to stages", logPrio::LOG_ERROR );
482 return ZC_ERROR;
483 }
484
485 std::string serialRes;
486 while( 1 )
487 {
488 int nrd = za_receive( m_port, buffer, sizeof( buffer ) );
489 if( nrd >= 0 )
490 {
491 buffer[nrd] = '\0';
492 log<text_log>( std::string( "Received: " ) + buffer, logPrio::LOG_DEBUG );
493 serialRes += buffer;
494 }
495 else if( nrd != Z_ERROR_TIMEOUT )
496 {
497 log<text_log>( "Error receiving from stages", logPrio::LOG_ERROR );
499 return ZC_ERROR;
500 }
501 else
502 {
503 log<text_log>( "TIMEOUT", logPrio::LOG_DEBUG );
504 break; // Timeout ok.
505 }
506 }
507
508 return loadStages( serialRes );
509}
510
512{
513 if( m_port > 0 )
514 {
515 int rv = za_disconnect( m_port );
516 if( rv < 0 )
517 {
518 log<text_log>( "Error disconnecting from zaber system.", logPrio::LOG_ERROR );
519 }
520 }
521
522 m_port = 0;
524
525 return 0;
526}
527
528int zaberLowLevel::recoverFromError( bool devicePresent )
529{
531
532 if( devicePresent )
533 {
535 }
536 else
537 {
539 }
540
541 for( size_t i = 0; i < m_stages.size(); ++i )
542 {
543 if( devicePresent && m_stages[i].deviceAddress() > 0 )
544 {
545 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTCONNECTED" ) );
546 }
547 else
548 {
549 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
550 }
551 }
552
553 return 0;
554}
555
557{
559 {
560 log<text_log>( "In appStartup but in state UNINITIALIZED.", logPrio::LOG_CRITICAL );
561 return -1;
562 }
563
564 if( m_stages.size() == 0 )
565 {
566 log<text_log>( "No stages configured.", logPrio::LOG_CRITICAL );
567 return -1;
568 }
569
570 REG_INDI_NEWPROP_NOCB( m_indiP_curr_state, "curr_state", pcf::IndiProperty::Text );
571
572 REG_INDI_NEWPROP_NOCB( m_indiP_max_pos, "max_pos", pcf::IndiProperty::Text );
573
574 REG_INDI_NEWPROP_NOCB( m_indiP_parked, "parked", pcf::IndiProperty::Number );
575
576 REG_INDI_NEWPROP_NOCB( m_indiP_lastHomed, "last_homed", pcf::IndiProperty::Number );
577
578 REG_INDI_NEWPROP_NOCB( m_indiP_curr_pos, "curr_pos", pcf::IndiProperty::Number );
579
580 REG_INDI_NEWPROP_NOCB( m_indiP_temp, "temp", pcf::IndiProperty::Number );
581
582 REG_INDI_NEWPROP_NOCB( m_indiP_warn, "warning", pcf::IndiProperty::Switch );
583 m_indiP_warn.setRule( pcf::IndiProperty::AnyOfMany );
584
585 REG_INDI_NEWPROP( m_indiP_tgt_pos, "tgt_pos", pcf::IndiProperty::Number );
586
587 REG_INDI_NEWPROP( m_indiP_req_home, "req_home", pcf::IndiProperty::Switch );
588 m_indiP_req_home.setRule( pcf::IndiProperty::AtMostOne );
589
591
592 REG_INDI_NEWPROP( m_indiP_req_halt, "req_halt", pcf::IndiProperty::Switch );
593 m_indiP_req_halt.setRule( pcf::IndiProperty::AtMostOne );
594
595 REG_INDI_NEWPROP( m_indiP_req_ehalt, "req_ehalt", pcf::IndiProperty::Switch );
596 m_indiP_req_ehalt.setRule( pcf::IndiProperty::AtMostOne );
597
598 REG_INDI_NEWPROP( m_indiP_knob_enable, "knob_enable", pcf::IndiProperty::Switch );
599 m_indiP_knob_enable.setPerm( pcf::IndiProperty::ReadWrite );
600 m_indiP_knob_enable.setState( pcf::IndiProperty::Idle );
601 m_indiP_knob_enable.setRule( pcf::IndiProperty::AtMostOne );
602
603 REG_INDI_NEWPROP( m_indiP_led_enable, "led_enable", pcf::IndiProperty::Switch );
604 m_indiP_led_enable.setPerm( pcf::IndiProperty::ReadWrite );
605 m_indiP_led_enable.setState( pcf::IndiProperty::Idle );
606 m_indiP_led_enable.setRule( pcf::IndiProperty::AtMostOne );
607
608 for( size_t n = 0; n < m_stages.size(); ++n )
609 {
610 m_indiP_curr_state.add( pcf::IndiElement( m_stages[n].name() ) );
611
612 m_indiP_max_pos.add( pcf::IndiElement( m_stages[n].name() ) );
613 m_indiP_max_pos[m_stages[n].name()] = -1;
614
615 m_indiP_parked.add( pcf::IndiElement( m_stages[n].name() ) );
616
617 m_indiP_lastHomed.add( pcf::IndiElement( m_stages[n].name() ) );
618
619 m_indiP_curr_pos.add( pcf::IndiElement( m_stages[n].name() ) );
620
621 m_indiP_temp.add( pcf::IndiElement( m_stages[n].name() ) );
622
623 m_indiP_warn.add( pcf::IndiElement( m_stages[n].name() ) );
624 m_indiP_warn[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
625
626 m_indiP_tgt_pos.add( pcf::IndiElement( m_stages[n].name() ) );
627
628 m_indiP_req_home.add( pcf::IndiElement( m_stages[n].name() ) );
629 m_indiP_req_home[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
630
631 m_indiP_req_halt.add( pcf::IndiElement( m_stages[n].name() ) );
632 m_indiP_req_halt[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
633
634 m_indiP_req_ehalt.add( pcf::IndiElement( m_stages[n].name() ) );
635 m_indiP_req_ehalt[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
636
637 m_indiP_knob_enable.add( pcf::IndiElement( m_stages[n].name() ) );
638 m_indiP_knob_enable[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
639
640 m_indiP_led_enable.add( pcf::IndiElement( m_stages[n].name() ) );
641 m_indiP_led_enable[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
642
643 // Now load last state from disk
644 std::ifstream posIn;
645 posIn.open( std::format( "{}/{}/{}", m_sysPath, m_configName, m_stages[n].name() ) );
646
647 if( !posIn )
648 {
649 continue;
650 }
651
652 if( m_stages[n].readStateFile( posIn ) < 0 )
653 {
654 return log<software_critical, -1>( std::format( "error reading state file for {}", m_stages[n].name() ) );
655 }
656
657 m_indiP_curr_pos[m_stages[n].name()].set( m_stages[n].rawPos() );
658 m_indiP_tgt_pos[m_stages[n].name()].set( m_stages[n].tgtPos() );
659 m_indiP_parked[m_stages[n].name()].set( m_stages[n].parked() );
660 m_indiP_lastHomed[m_stages[n].name()].set( m_stages[n].lastHomed() );
661 m_indiP_max_pos[m_stages[n].name()].set( m_stages[n].maxPos() );
662 m_indiP_knob_enable[m_stages[n].name()].set( m_stages[n].knobEnabled() ? pcf::IndiElement::On
663 : pcf::IndiElement::Off );
664 m_indiP_led_enable[m_stages[n].name()].set( m_stages[n].ledEnabled() ? pcf::IndiElement::On
665 : pcf::IndiElement::Off );
666 }
667
668 return 0;
669}
670
672{
674 {
675 log<text_log>( "In appLogic but in state INITIALIZED.", logPrio::LOG_CRITICAL );
676 return -1;
677 }
678
679 if( state() == stateCodes::POWERON )
680 {
681 for( size_t i = 0; i < m_stages.size(); ++i )
682 {
683 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "POWERON" ) );
684 }
685
687
688 return 0; // go around once to give POWERON time to propagate
689 }
690
691 if( state() == stateCodes::NODEVICE )
692 {
693 for( size_t i = 0; i < m_stages.size(); ++i )
694 {
695 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
696 }
697
699
700 if( rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES )
701 {
702 if( powerState() != 1 || powerStateTarget() != 1 )
703 {
704 return 0; // means we're powering off
705 }
706
707 if( !stateLogged() )
708 {
710 }
711
712 return 0;
713 }
714
716 {
717 if( !stateLogged() )
718 {
720 std::format( "USB Device {}:{}:{} not found in udev", m_idVendor, m_idProduct, m_serial ) );
721 }
722
723 return 0;
724 }
725 else
726 {
727 std::stringstream logs;
728 log<text_log>( std::format(
729 "USB Device {}:{}:{} found in udev as {}", m_idVendor, m_idProduct, m_serial, m_deviceName ) );
730
732
733 for( size_t i = 0; i < m_stages.size(); ++i )
734 {
735 if( m_stages[i].deviceAddress() < 1 )
736 {
737 continue;
738 }
739
740 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTCONNECTED" ) );
741 }
742
743 return 0; // we return to give the stage time to initialize the connection if this is a USB-FTDI power
744 // on/plug-in event.
745 }
746 }
747
749 {
750 std::lock_guard<std::mutex> guard( m_indiMutex );
751
752 int rv = connect();
753
754 if( rv == ZC_CONNECTED )
755 {
757
758 for( size_t i = 0; i < m_stages.size(); ++i )
759 {
760 if( m_stages[i].deviceAddress() < 1 )
761 {
762 continue;
763 }
764
765 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "CONNECTED" ) );
766 }
767
768 if( !stateLogged() )
769 {
770 log<text_log>( "Connected to stage(s) on " + m_deviceName );
771 }
772 }
773 else if( rv == ZC_NOT_CONNECTED )
774 {
775 return 0;
776 }
777 }
778
780 {
781 for( size_t i = 0; i < m_stages.size(); ++i )
782 {
783 if( m_stages[i].deviceAddress() < 1 )
784 {
785 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
786 continue; // Skip configured but not found stage
787 }
788
789 std::lock_guard<std::mutex> guard( m_indiMutex ); // Inside loop so INDI requests can steal it
790
791 m_stages[i].enableKnob( m_port, false ); // Always disable the knob on startup
792
793 m_stages[i].enableLED( m_port, false ); // Always disable the LEDs on startup
794
795 m_stages[i].getMaxPos( m_port );
796
797 updateIfChanged( m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos() );
798
799 // First unpark if possible
800 if( m_stages[i].unpark( m_port ) < 0 )
801 {
802 if( powerState() != 1 || powerStateTarget() != 1 )
803 {
804 return 0; // means we're powering off
805 }
806
809 return 0;
810 }
811
812 // Get warnings so first pass through has correct state for home/not-homed
813 if( m_stages[i].getWarnings( m_port ) < 0 )
814 {
815 if( powerState() != 1 || powerStateTarget() != 1 )
816 {
817 return 0; // means we're powering off
818 }
819
822 return 0;
823 }
824 }
825
827
828 return 0;
829 }
830
831 if( state() == stateCodes::READY )
832 {
833 { // mutex scope
834 std::lock_guard<std::mutex> guard( m_indiMutex );
835
836 bool canRefreshDiscovery = true;
837 for( size_t i = 0; i < m_stages.size(); ++i )
838 {
839 if( m_stages[i].deviceAddress() > 0 && m_stages[i].deviceStatus() == 'B' )
840 {
841 canRefreshDiscovery = false;
842 break;
843 }
844 }
845
846 int rv = ZC_CONNECTED;
848 {
850 }
851
852 if( rv == ZC_ERROR )
853 {
854 if( powerState() != 1 || powerStateTarget() != 1 )
855 {
856 return 0; // means we're powering off
857 }
858
859 return 0;
860 }
861 }
862
863 // Here we check complete stage state.
864 for( size_t i = 0; i < m_stages.size(); ++i )
865 {
866 if( m_stages[i].deviceAddress() < 1 )
867 {
868 continue; // Skip configured but not found stage
869 }
870
871 std::lock_guard<std::mutex> guard( m_indiMutex ); // Inside loop so INDI requests can steal it
872
873 if( m_stages[i].getKnob( m_port ) < 0 )
874 {
875 if( powerState() != 1 || powerStateTarget() != 1 )
876 {
877 return 0; // means we're powering off
878 }
879
882 return 0;
883 }
885 m_stages[i].name(),
886 ( m_stages[i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
887
888 if( m_stages[i].getLED( m_port ) < 0 )
889 {
890 if( powerState() != 1 || powerStateTarget() != 1 )
891 {
892 return 0; // means we're powering off
893 }
894
897 return 0;
898 }
900 m_stages[i].name(),
901 ( m_stages[i].ledEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
902
903 if( m_stages[i].getParked( m_port ) < 0 )
904 {
905 if( powerState() != 1 || powerStateTarget() != 1 )
906 {
907 return 0; // means we're powering off
908 }
909
912 return 0;
913 }
914
915 updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
916 updateIfChanged( m_indiP_lastHomed, m_stages[i].name(), m_stages[i].lastHomed() );
917
918 if( m_stages[i].updatePos( m_port ) < 0 )
919 {
920 if( powerState() != 1 || powerStateTarget() != 1 )
921 {
922 return 0; // means we're powering off
923 }
924
927 return 0;
928 }
929
930 updateIfChanged( m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos() );
931 updateIfChanged( m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos() );
932
933 if( m_stages[i].deviceStatus() == 'B' )
934 {
935 if( m_stages[i].homing() )
936 {
937 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "HOMING" ) );
938 }
939 else
940 {
941 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "OPERATING" ) );
942 }
943 }
944 else if( m_stages[i].deviceStatus() == 'I' )
945 {
946 if( m_stages[i].homing() )
947 {
948 log<software_error>( std::format( "stage {} idle but in "
949 "state homing. bug.",
950 m_stages[i].name() ) );
951 return 0;
952 }
953
954 if( m_stages[i].warnWR() )
955 {
956 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTHOMED" ) );
957 }
958 else
959 {
960 if( !m_stages[i].parked() )
961 {
962 if( m_stages[i].park( m_port ) < 0 )
963 {
964 if( powerState() != 1 || powerStateTarget() != 1 )
965 {
966 return 0; // means we're powering off
967 }
968
971 return 0;
972 }
973
974 std::ofstream posOut;
975
976 { // scope for priv
977 elevatedPrivileges ep( this );
978 posOut.open( std::format( "{}/{}/{}", m_sysPath, m_configName, m_stages[i].name() ) );
979 }
980
981 if( !posOut )
982 {
983 log<software_error>( std::format( "error opening state file for {}", m_stages[i].name() ) );
984 }
985 else if( m_stages[i].writeStateFile( posOut ) < 0 )
986 {
987 log<software_error>( std::format( "error writing state file for {}", m_stages[i].name() ) );
988 }
989
990 updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
991 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "READY" ) );
992 }
993 }
994 }
995 else
996 {
997 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
998 }
999
1000 if( m_stages[i].warn() )
1001 {
1002 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::On );
1003 }
1004 else
1005 {
1006 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off );
1007 }
1008
1009 if( m_stages[i].updateTemp( m_port ) < 0 )
1010 {
1011 if( powerState() != 1 || powerStateTarget() != 1 )
1012 {
1013 return 0; // means we're powering off
1014 }
1015
1018 return 0;
1019 }
1020 updateIfChanged( m_indiP_temp, m_stages[i].name(), m_stages[i].temp() );
1021
1022 if( m_stages[i].getWarnings( m_port ) < 0 )
1023 {
1024 if( powerState() != 1 || powerStateTarget() != 1 )
1025 {
1026 return 0; // means we're powering off
1027 }
1030 return 0;
1031 }
1032 }
1033 }
1034
1035 if( state() == stateCodes::ERROR )
1036 {
1038 if( rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES )
1039 {
1040 if( powerState() != 1 || powerStateTarget() != 1 )
1041 {
1042 return 0; // means we're powering off
1043 }
1044
1045 if( !stateLogged() )
1046 {
1048 }
1049
1050 return recoverFromError( false );
1051 }
1052
1054 {
1055 if( powerState() != 1 || powerStateTarget() != 1 )
1056 {
1057 return 0; // means we're powering off
1058 }
1059
1060 if( !stateLogged() )
1061 {
1063 std::format( "USB Device {}:{}:{} not found in udev", m_idVendor, m_idProduct, m_serial ) );
1064 }
1065
1066 return recoverFromError( false );
1067 }
1068
1069 if( powerState() != 1 || powerStateTarget() != 1 )
1070 {
1071 return 0; // means we're powering off
1072 }
1073
1074 if( !stateLogged() )
1075 {
1076 log<text_log>( "Recovering from stage communication error by resetting the connection.",
1078 }
1079
1080 return recoverFromError( true );
1081 }
1082
1083 if( powerState() != 1 || powerStateTarget() != 1 )
1084 {
1085 return 0; // means we're powering off
1086 }
1087
1088 if( state() == stateCodes::FAILURE )
1089 {
1090 return -1;
1091 }
1092
1093 return 0;
1094}
1095
1097{
1099
1100 std::lock_guard<std::mutex> lock( m_indiMutex );
1101
1102 for( size_t i = 0; i < m_stages.size(); ++i )
1103 {
1104 m_stages[i].onPowerOff();
1105
1106 // Publish the retained stage snapshot before advertising POWEROFF so
1107 // subscribers can consume the last known parked/position state first.
1108 updateIfChanged( m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos() );
1109 updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
1110 updateIfChanged( m_indiP_lastHomed, m_stages[i].name(), m_stages[i].lastHomed() );
1111 updateIfChanged( m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos() );
1112 updateIfChanged( m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos() );
1114 m_stages[i].name(),
1115 ( m_stages[i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
1117 m_stages[i].name(),
1118 ( m_stages[i].ledEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
1119 updateIfChanged( m_indiP_temp, m_stages[i].name(), std::string( "" ) );
1120 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "POWEROFF" ) );
1121 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off );
1122 }
1123
1124 return 0;
1125}
1126
1128{
1129 return 0;
1130}
1131
1133{
1134 for( size_t i = 0; i < m_stages.size(); ++i )
1135 {
1136 if( m_stages[i].deviceAddress() < 1 )
1137 {
1138 continue;
1139 }
1140
1141 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
1142 }
1143
1144 return 0;
1145}
1146
1147INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_tgt_pos )( const pcf::IndiProperty &ipRecv )
1148{
1149 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tgt_pos, ipRecv );
1150
1151 for( size_t n = 0; n < m_stages.size(); ++n )
1152 {
1153 if( ipRecv.find( m_stages[n].name() ) )
1154 {
1155 long tgt = ipRecv[m_stages[n].name()].get<long>();
1156 if( tgt >= 0 )
1157 {
1158 if( m_stages[n].deviceAddress() < 1 )
1159 {
1160 return log<software_error, -1>( std::format(
1161 "stage {} with with s/n {} not found in system.", m_stages[n].name(), m_stages[n].serial() ) );
1162 }
1163
1164 std::lock_guard<std::mutex> guard( m_indiMutex );
1165
1166 if( m_stages[n].moveAbs( m_port, tgt ) < 0 )
1167 {
1168 return log<software_error, -1>( { "error from moveAbs for " + m_stages[n].name() } );
1169 }
1170
1171 updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), m_stages[n].tgtPos() );
1172 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
1173 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string( "OPERATING" ) );
1174 }
1175 }
1176 }
1177
1178 return 0;
1179}
1180
1181INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_home )( const pcf::IndiProperty &ipRecv )
1182{
1183 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_home, ipRecv );
1184
1185 // Make sure only one request is sent to avoid racing
1186 size_t stageno = std::numeric_limits<size_t>::max();
1187
1188 bool found = false;
1189
1190 for( size_t n = 0; n < m_stages.size(); ++n )
1191 {
1192 if( ipRecv.find( m_stages[n].name() ) )
1193 {
1194 if( found )
1195 {
1196 log<software_error>( { "more than one stage specified in req_home, rejecting request" } );
1197 return -1;
1198 }
1199
1200 if( m_stages[n].deviceAddress() < 1 )
1201 {
1202 return log<software_error, -1>( std::format( "stage {} with with "
1203 "s/n {} not found",
1204 m_stages[n].name(),
1205 m_stages[n].serial() ) );
1206 }
1207
1208 stageno = n;
1209 found = true;
1210 }
1211 }
1212
1213 if( !found || stageno >= m_stages.size() )
1214 {
1215 log<software_error>( "no valid stage specified in req_home, rejecting request" );
1216 return -1;
1217 }
1218
1219 if( ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
1220 {
1221 return log<software_warning, 0>( std::format( "request off for stage {} "
1222 "in req_home",
1223 m_stages[stageno].name() ) );
1224 }
1225
1226 std::lock_guard<std::mutex> guard( m_indiMutex );
1227
1228 if( m_stages[stageno].homing() )
1229 {
1230 return log<software_warning, 0>( std::format( "stage {} is already "
1231 "homing in req_home",
1232 m_stages[stageno].name() ) );
1233 }
1234
1235 if( m_stages[stageno].home( m_port ) < 0 )
1236 {
1237 return log<software_error, -1>( std::format( "error from home for {}", m_stages[stageno].name() ) );
1238 }
1239
1240 updateIfChanged( m_indiP_tgt_pos, m_stages[stageno].name(), 0 );
1241 updateIfChanged( m_indiP_parked, m_stages[stageno].name(), m_stages[stageno].parked() );
1242 updateIfChanged( m_indiP_curr_state, m_stages[stageno].name(), std::string( "HOMING" ) );
1243
1244 return 0;
1245}
1246
1247INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_home_all )( const pcf::IndiProperty &ipRecv )
1248{
1249 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_home_all, ipRecv );
1250
1251 if( !ipRecv.find( "request" ) )
1252 {
1253 return 0;
1254 }
1255
1256 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1257 {
1258 for( size_t n = 0; n < m_stages.size(); ++n )
1259 {
1260 if( m_stages[n].deviceAddress() < 1 )
1261 {
1262 continue;
1263 }
1264 std::lock_guard<std::mutex> guard( m_indiMutex );
1265
1266 if( m_stages[n].homing() )
1267 {
1268 continue;
1269 }
1270
1271 if( m_stages[n].home( m_port ) < 0 )
1272 {
1273 return log<software_error, -1>( { "error from home for " + m_stages[n].name() } );
1274 }
1275
1276 updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), 0 );
1277 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
1278 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string( "HOMING" ) );
1279 }
1280 }
1281
1282 return 0;
1283}
1284
1285INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_halt )( const pcf::IndiProperty &ipRecv )
1286{
1287 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_halt, ipRecv );
1288
1289 // Make sure only one request is sent to avoid racing
1290 size_t stageno = std::numeric_limits<size_t>::max();
1291
1292 bool found = false;
1293
1294 for( size_t n = 0; n < m_stages.size(); ++n )
1295 {
1296 if( ipRecv.find( m_stages[n].name() ) )
1297 {
1298 if( found )
1299 {
1300 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
1301 }
1302
1303 if( m_stages[n].deviceAddress() < 1 )
1304 {
1305 return log<software_error, -1>( std::format( "stage {} with with "
1306 "s/n {} not present",
1307 m_stages[n].name(),
1308 m_stages[n].serial() ) );
1309 }
1310
1311 stageno = n;
1312 found = true;
1313 }
1314 }
1315
1316 if( !found || stageno == std::numeric_limits<size_t>::max() )
1317 {
1318 return log<software_error, -1>( "no valid stage specified in req_halt, rejecting request" );
1319 }
1320
1321 if( ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
1322 {
1323 return log<software_warning, 0>( std::format( "request off for stage {} "
1324 "in req_halt",
1325 m_stages[stageno].name() ) );
1326 }
1327
1328 std::lock_guard<std::mutex> guard( m_indiMutex );
1329
1330 if( m_stages[stageno].stop( m_port ) < 0 )
1331 {
1332 return log<software_error, -1>( std::format( "error from stop for {}", m_stages[stageno].name() ) );
1333 }
1334
1335 return 0;
1336}
1337
1338INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_req_ehalt )( const pcf::IndiProperty &ipRecv )
1339{
1340 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_ehalt, ipRecv );
1341
1342 // Here we accept multiple ehalts all at once just in case. It's an emergency~
1343 // and we don't stop for errors
1344 for( size_t n = 0; n < m_stages.size(); ++n )
1345 {
1346 if( ipRecv.find( m_stages[n].name() ) )
1347 {
1348 if( ipRecv[m_stages[n].name()].getSwitchState() == pcf::IndiElement::On )
1349 {
1350 if( m_stages[n].deviceAddress() < 1 )
1351 {
1352 log<software_error>( std::format( "stage {} with s/n {} "
1353 "not present",
1354 m_stages[n].name(),
1355 m_stages[n].serial() ) );
1356 continue;
1357 }
1358
1359 std::lock_guard<std::mutex> guard( m_indiMutex );
1360
1361 if( m_stages[n].estop( m_port ) < 0 )
1362 {
1363 log<software_error>( { "error from estop for " + m_stages[n].name() } );
1364 }
1365 }
1366 }
1367 }
1368
1369 return 0;
1370}
1371
1372INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_knob_enable )( const pcf::IndiProperty &ipRecv )
1373{
1374 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_knob_enable, ipRecv );
1375
1376 // Make sure only one request is sent to avoid racing
1377 size_t stageno = std::numeric_limits<size_t>::max();
1378
1379 bool found = false;
1380
1381 for( size_t n = 0; n < m_stages.size(); ++n )
1382 {
1383 if( ipRecv.find( m_stages[n].name() ) )
1384 {
1385 if( found )
1386 {
1387 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
1388 }
1389
1390 if( m_stages[n].deviceAddress() < 1 )
1391 {
1392 return log<software_error, -1>( std::format( "stage {} with with "
1393 "s/n {} not present",
1394 m_stages[n].name(),
1395 m_stages[n].serial() ) );
1396 }
1397
1398 stageno = n;
1399 found = true;
1400 }
1401 }
1402
1403 if( !found || stageno == std::numeric_limits<size_t>::max() )
1404 {
1405 return log<software_error, -1>( "no valid stage specified in req_knob, rejecting request" );
1406 }
1407
1408 bool enable_knob = ipRecv[m_stages[stageno].name()].getSwitchState() == pcf::IndiElement::On;
1409
1410 std::lock_guard<std::mutex> guard( m_indiMutex );
1411
1412 if( m_stages[stageno].enableKnob( m_port, enable_knob ) < 0 )
1413 {
1414 return log<software_error, -1>( std::format( "error from enable knob for {}", m_stages[stageno].name() ) );
1415 }
1416
1417 return 0;
1418}
1419
1420INDI_NEWCALLBACK_DEFN( zaberLowLevel, m_indiP_led_enable )( const pcf::IndiProperty &ipRecv )
1421{
1422 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_led_enable, ipRecv );
1423
1424 // Make sure only one request is sent to avoid racing
1425 size_t stageno = std::numeric_limits<size_t>::max();
1426
1427 bool found = false;
1428
1429 for( size_t n = 0; n < m_stages.size(); ++n )
1430 {
1431 if( ipRecv.find( m_stages[n].name() ) )
1432 {
1433 if( found )
1434 {
1435 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
1436 }
1437
1438 if( m_stages[n].deviceAddress() < 1 )
1439 {
1440 return log<software_error, -1>( std::format( "stage {} with with "
1441 "s/n {} not present",
1442 m_stages[n].name(),
1443 m_stages[n].serial() ) );
1444 }
1445
1446 stageno = n;
1447 found = true;
1448 }
1449 }
1450
1451 if( !found || stageno == std::numeric_limits<size_t>::max() )
1452 {
1453 return log<software_error, -1>( "no valid stage specified in req_led, rejecting request" );
1454 }
1455
1456 bool enable_led = ipRecv[m_stages[stageno].name()].getSwitchState() == pcf::IndiElement::On;
1457
1458 std::lock_guard<std::mutex> guard( m_indiMutex );
1459
1460 if( m_stages[stageno].enableLED( m_port, enable_led ) < 0 )
1461 {
1462 return log<software_error, -1>( std::format( "error from enable led for {}", m_stages[stageno].name() ) );
1463 }
1464
1465 return 0;
1466}
1467
1468} // namespace app
1469} // namespace MagAOX
1470
1471#endif // zaberLowLevel_hpp
The base-class for XWCTk applications.
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI property element value if it has changed.
std::string m_configName
The name of the configuration file (minus .conf).
stateCodes::stateCodeT state()
Get the current state code.
int powerState()
Returns the current power state.
int powerStateTarget()
Returns the target power state.
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, pcf::IndiProperty::PropertyStateType ipState=pcf::IndiProperty::Ok)
Update an INDI switch element value if it has changed.
int stateLogged()
Updates and returns the value of m_stateLogged. Will be 0 on first call after a state change,...
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
std::mutex m_indiMutex
Mutex for locking INDI communications.
std::string m_sysPath
The path to the system directory, for PID file, etc.
The low-level ASCII-protocol Zaber controller.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_led_enable)
zaberLowLevel()
Default constructor.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_ehalt)
int recoverFromError(bool devicePresent)
Recover from an ASCII-transport error without terminating the app.
pcf::IndiProperty m_indiP_req_home
Command a stage to home.
pcf::IndiProperty m_indiP_warn
Whether the stage has existing warnings.
pcf::IndiProperty m_indiP_led_enable
Enable or disable a stage's LED.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_halt)
z_port m_port
Connected ASCII protocol port.
virtual int appLogic()
Execute the main FSM for zaberLowLevel.
pcf::IndiProperty m_indiP_max_pos
Maximum raw position of the stage.
pcf::IndiProperty m_indiP_req_halt
Command a stage to safely halt.
virtual int onPowerOff()
Handle the transition into the powered-off state.
int refreshStageDiscovery()
Refresh discovery on an already-connected ASCII bus.
pcf::IndiProperty m_indiP_tgt_pos
Target raw position of the stage.
pcf::IndiProperty m_indiP_temp
Current temperature of the stage.
int connect()
Connect to the ASCII-protocol stage chain and discover configured devices.
int resetConnection()
Reset the active ASCII connection bookkeeping.
pcf::IndiProperty m_indiP_knob_enable
Enable or disable a stage's potentiometer.
std::unordered_map< std::string, size_t > m_stageSerial
Map from configured serial number to configured stage index.
std::unordered_map< int, size_t > m_stageAddress
Map from ASCII device address to configured stage index.
pcf::IndiProperty m_indiP_curr_state
Current state of the stage.
pcf::IndiProperty m_indiP_parked
Parked state of the stage.
int loadStages(std::string &serialRes)
Apply a parsed system.serial snapshot to the configured stages.
int m_numStages
Number of configured stages.
virtual int whilePowerOff()
Execute the powered-off loop.
virtual void loadConfig()
Load application configuration.
pcf::IndiProperty m_indiP_req_home_all
Command all stages to home.
pcf::IndiProperty m_indiP_curr_pos
Current raw position of the stage.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_tgt_pos)
virtual int appStartup()
Set up the INDI properties and restore retained stage state.
virtual int appShutdown()
Perform any shutdown tasks before exit.
std::vector< zaberStage< zaberLowLevel > > m_stages
Stage helpers in configuration order.
~zaberLowLevel() noexcept
Destructor, declared and defined for noexcept.
std::unordered_map< std::string, size_t > m_stageName
Map from configured stage name to configured stage index.
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_home)
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_req_home_all)
INDI_NEWCALLBACK_DECL(zaberLowLevel, m_indiP_knob_enable)
pcf::IndiProperty m_indiP_lastHomed
Time of last homing for the stage.
pcf::IndiProperty m_indiP_req_ehalt
Command a stage to safely immediately halt.
virtual void setupConfig()
Set up application configuration.
bool m_stageDiscoveryInitialized
Whether the active connection has completed an initial discovery pass.
A class to manage the details of one stage in a Zaber system.
#define INDI_NEWCALLBACK_DEFN(class, prop)
Define the callback for a new property request.
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
#define CREATE_REG_INDI_NEW_REQUESTSWITCH(prop, name)
Create and register a NEW INDI property as a standard request switch, using the standard callback nam...
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Definition ttyErrors.cpp:15
int parseSystemSerial(std::vector< int > &address, std::vector< std::string > &serial, const std::string &response)
Parse the system.serial query.
#define INDI_VALIDATE_CALLBACK_PROPS(prop1, prop2)
Standard check for matching INDI properties in a callback.
const pcf::IndiProperty & ipRecv
updateIfChanged(m_indiP_angle, "target", m_angle)
std::unique_lock< std::mutex > lock(m_indiMutex)
Definition dm.hpp:19
static constexpr logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
static constexpr logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
static constexpr logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
static constexpr logPrioT LOG_DEBUG
Used for debugging.
@ NODEVICE
No device exists for the application to control.
@ FAILURE
The application has failed, should be used when m_shutdown is set for an error.
@ ERROR
The application has encountered an error, from which it is recovering (with or without intervention)
@ READY
The device is ready for operation, but is not operating.
@ CONNECTED
The application has connected to the device or service.
@ UNINITIALIZED
The application is unitialized, the default.
@ INITIALIZED
The application has been initialized, set just before calling appStartup().
@ NOTCONNECTED
The application is not connected to the device or service.
@ POWERON
The device power is on.
Software CRITICAL log entry.
Software ERR log entry.
A USB device as a TTY device.
Definition usbDevice.hpp:33
std::string m_deviceName
The device path name, e.g. /dev/ttyUSB0.
Definition usbDevice.hpp:40
int getDeviceName()
Get the device name from udev using the vendor, product, and serial number.
std::string m_idProduct
The product id 4-digit code.
Definition usbDevice.hpp:35
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for the USB section.
Definition usbDevice.cpp:24
std::string m_serial
The serial number.
Definition usbDevice.hpp:36
int loadConfig(mx::app::appConfigurator &config)
Load the USB section from an application configurator.
Definition usbDevice.cpp:34
speed_t m_baudRate
The baud rate specification.
Definition usbDevice.hpp:38
std::string m_idVendor
The vendor id 4-digit code.
Definition usbDevice.hpp:34
#define TTY_E_NODEVNAMES
Definition ttyErrors.hpp:28
#define TTY_E_DEVNOTFOUND
Definition ttyErrors.hpp:30
int za_connect(z_port *port, const char *port_name)
Definition za_serial.c:53
int za_receive(z_port port, char *destination, int length)
Definition za_serial.c:151
int za_send(z_port port, const char *command, size_t sMaxSz)
Definition za_serial.c:112
int za_drain(z_port port)
Definition za_serial.c:235
int za_disconnect(z_port port)
Definition za_serial.c:99
Provides a set of functions for interacting with Zaber devices in the ASCII protocol.
@ Z_ERROR_SYSTEM_ERROR
Definition z_common.h:67
@ Z_SUCCESS
Definition z_common.h:65
@ Z_ERROR_TIMEOUT
Definition z_common.h:78
#define ZC_NOT_CONNECTED
#define ZC_ERROR
#define ZC_CONNECTED
MagAOX::app::MagAOXApp< true > MagAOXAppT
A class with details of a single zaber stage.
utilties for working with zaber stages
#define ZUTILS_E_BADSERIAL