API
 
Loading...
Searching...
No Matches
zaberLowLevelBinary.hpp
Go to the documentation of this file.
1/** \file zaberLowLevelBinary.hpp
2 * \brief The MagAO-X low-level binary-protocol Zaber controller.
3 * \author Jared R. Males (jaredmales@gmail.com)
4 *
5 * \ingroup zaberLowLevelBinary_files
6 */
7
8#ifndef zaberLowLevelBinary_hpp
9#define zaberLowLevelBinary_hpp
10
11#include <iostream>
12#include <thread>
13
14#include "../../libMagAOX/libMagAOX.hpp" // Note this is included on command line to trigger pch
15#include "../../magaox_git_version.h"
16
18
19#include "zaberBinaryStage.hpp"
20#include "zb_serial.h"
21
22#define ZBC_CONNECTED ( 0 )
23#define ZBC_ERROR ( -1 )
24#define ZBC_NOT_CONNECTED ( 10 )
25
26/** \defgroup zaberLowLevelBinary low-level binary zaber controller
27 * \brief The low-level binary interface to a set of chained Zaber stages
28 *
29 * \ingroup apps
30 */
31
32/** \defgroup zaberLowLevelBinary_files zaber low-level binary files
33 * \ingroup zaberLowLevelBinary
34 */
35
36namespace MagAOX
37{
38namespace app
39{
40
41/// The low-level binary-protocol Zaber controller.
42/**
43 * This app mirrors `zaberLowLevel` as closely as possible while speaking the
44 * firmware 5.xx T-series binary protocol.
45 *
46 * \ingroup zaberLowLevelBinary
47 */
49{
51
52 protected:
53 /// Number of configured stages.
54 int m_numStages{ 0 };
55
56 /// Connected binary protocol port.
58
59 /** \name Configurable Parameters
60 *
61 * @{
62 */
63 /// Whether to renumber the daisy chain during connect.
64 bool m_renumberOnConnect{ true };
65
66 /// Maximum device address to probe while matching configured serial numbers.
68
69 /// Binary command timeout in milliseconds.
70 int m_commandTimeout{ 250 };
71
72 /// Pause in milliseconds after renumbering before further commands are sent.
74
75 ///@}
76
77 /** \name Stage Mapping - Data
78 *
79 * @{
80 */
81 /// Stage helpers in configuration order.
82 std::vector<zaberBinaryStage<zaberLowLevelBinary>> m_stages;
83
84 /// Map from binary device address to configured stage index.
85 std::unordered_map<int, size_t> m_stageAddress;
86
87 /// Map from configured serial number to configured stage index.
88 std::unordered_map<std::string, size_t> m_stageSerial;
89
90 /// Map from configured stage name to configured stage index.
91 std::unordered_map<std::string, size_t> m_stageName;
92
93 /// Whether the active connection has completed an initial discovery pass.
95
96 ///@}
97
98 public:
99 /// Default constructor.
101
102 /// Destructor.
106
107 /// Set up application configuration.
108 virtual void setupConfig();
109
110 /// Load application configuration.
111 virtual void loadConfig();
112
113 /// Connect to the binary-protocol stage chain and discover configured devices.
114 int connect();
115
116 /// Discover configured stages on the binary bus.
117 int loadStages();
118
119 /// Apply a discovered address-to-serial snapshot to the configured stages.
120 int loadStages( const std::vector<int> &addresses, /**< [in] discovered device addresses */
121 const std::vector<std::string> &serials /**< [in] discovered device serial numbers */
122 );
123
124 /// Refresh discovery on an already-connected binary bus.
126
127 /// Query a device directly for discovery-time replies.
128 int queryDevice( int32_t &response, /**< [out] decoded reply data */
129 uint8_t deviceAddress, /**< [in] device address */
130 uint8_t commandNumber, /**< [in] command number */
131 int32_t data, /**< [in] command data */
132 uint8_t expectedReply /**< [in] expected reply command number */
133 );
134
135 /// Send a broadcast or address-specific command with no awaited reply.
136 int sendCommandNoReply( uint8_t deviceAddress, /**< [in] device address */
137 uint8_t commandNumber, /**< [in] command number */
138 int32_t data /**< [in] command data */
139 );
140
141 /// Reset the active binary connection bookkeeping.
142 int resetConnection();
143
144 /// Recover from a binary-transport error without terminating the app.
145 int recoverFromError( bool devicePresent /**< [in] true if the USB tty still exists in udev */ );
146
147 /// Set up the INDI properties and restore retained stage state.
148 virtual int appStartup();
149
150 /// Execute the main FSM for `zaberLowLevelBinary`.
151 virtual int appLogic();
152
153 /// Handle the transition into the powered-off state.
154 virtual int onPowerOff();
155
156 /// Execute the powered-off loop.
157 virtual int whilePowerOff();
158
159 /// Perform any shutdown tasks before exit.
160 virtual int appShutdown();
161
162 protected:
163 /** \name INDI Stage State - Data
164 *
165 * @{
166 */
167 /// Current stage state reported per configured stage.
168 pcf::IndiProperty m_indiP_curr_state;
169
170 /// Maximum raw position reported per configured stage.
171 pcf::IndiProperty m_indiP_max_pos;
172
173 /// Parked-state bookkeeping reported per configured stage.
174 pcf::IndiProperty m_indiP_parked;
175
176 /// Last-homed timestamps reported per configured stage.
177 pcf::IndiProperty m_indiP_lastHomed;
178
179 /// Current raw position reported per configured stage.
180 pcf::IndiProperty m_indiP_curr_pos;
181
182 /// Driver temperature reported per configured stage.
183 pcf::IndiProperty m_indiP_temp;
184
185 /// Warning-state switch reported per configured stage.
186 pcf::IndiProperty m_indiP_warn;
187
188 /// Requested target raw position per configured stage.
189 pcf::IndiProperty m_indiP_tgt_pos;
190
191 /// Per-stage home requests.
192 pcf::IndiProperty m_indiP_req_home;
193
194 /// Global request to home all configured stages.
195 pcf::IndiProperty m_indiP_req_home_all;
196
197 /// Per-stage halt requests.
198 pcf::IndiProperty m_indiP_req_halt;
199
200 /// Per-stage emergency-halt requests.
201 pcf::IndiProperty m_indiP_req_ehalt;
202
203 /// Enable or disable a stage's potentiometer.
204 pcf::IndiProperty m_indiP_knob_enable;
205
206 ///@}
207
208 public:
209 /** \name INDI Stage State
210 *
211 * @{
212 */
219 ///@}
220};
221
222zaberLowLevelBinary::zaberLowLevelBinary() : MagAOXApp( MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED )
223{
224 m_powerMgtEnabled = true;
225}
226
228{
230
231 config.add( "stages.renumberOnConnect",
232 "",
233 "stages.renumberOnConnect",
234 argType::Required,
235 "stages",
236 "renumberOnConnect",
237 false,
238 "bool",
239 "Whether to issue a broadcast renumber on connect. Default is true." );
240
241 config.add( "stages.maxDiscoveryAddress",
242 "",
243 "stages.maxDiscoveryAddress",
244 argType::Required,
245 "stages",
246 "maxDiscoveryAddress",
247 false,
248 "int",
249 "Maximum device address to scan when matching configured serial numbers." );
250
251 config.add( "stages.commandTimeout",
252 "",
253 "stages.commandTimeout",
254 argType::Required,
255 "stages",
256 "commandTimeout",
257 false,
258 "int",
259 "Binary command timeout in milliseconds." );
260
261 config.add( "stages.renumberPauseMs",
262 "",
263 "stages.renumberPauseMs",
264 argType::Required,
265 "stages",
266 "renumberPauseMs",
267 false,
268 "int",
269 "Pause in milliseconds after a renumber command before discovery begins." );
270}
271
273{
274 this->m_baudRate = B9600;
275
276 int rv = tty::usbDevice::loadConfig( config );
277 if( rv != 0 && rv != TTY_E_NODEVNAMES && rv != TTY_E_DEVNOTFOUND )
278 {
280 }
281
282 config( m_renumberOnConnect, "stages.renumberOnConnect" );
283 config( m_maxDiscoveryAddress, "stages.maxDiscoveryAddress" );
284 config( m_commandTimeout, "stages.commandTimeout" );
285 config( m_renumberPauseMs, "stages.renumberPauseMs" );
286 std::vector<std::string> sections;
287 config.unusedSections( sections );
288
289 if( sections.size() == 0 )
290 {
291 log<software_error>( { "No stages found" } );
292 return;
293 }
294
295 for( size_t n = 0; n < sections.size(); ++n )
296 {
297 if( config.isSetUnused( mx::app::iniFile::makeKey( sections[n], "serial" ) ) )
298 {
300
301 size_t idx = m_stages.size() - 1;
302 m_stages[idx].name( sections[n] );
303
304 std::string tmp = m_stages[idx].serial();
305 config.configUnused( tmp, mx::app::iniFile::makeKey( sections[n], "serial" ) );
306 m_stages[idx].serial( tmp );
307
308 int32_t targetSpeed = m_stages[idx].targetSpeed();
309 config.configUnused( targetSpeed, mx::app::iniFile::makeKey( sections[n], "targetSpeed" ) );
310 m_stages[idx].targetSpeed( targetSpeed );
311
312 m_stageName.insert( { m_stages[idx].name(), idx } );
313 m_stageSerial.insert( { m_stages[idx].serial(), idx } );
314 }
315 }
316}
317
319 int32_t &response, uint8_t deviceAddress, uint8_t commandNumber, int32_t data, uint8_t expectedReply )
320{
321 uint8_t command[6];
322 if( zb_encode( command, deviceAddress, commandNumber, data ) != Z_SUCCESS )
323 {
324 return log<software_error, -1>( "zb_encode failed" );
325 }
326
327 if( zb_send( m_port, command ) != 6 )
328 {
329 return log<software_error, -1>( "zb_send failed" );
330 }
331
332 uint8_t reply[6];
333 int rv = zb_receive( m_port, reply );
334 if( rv != 6 )
335 {
336 return -1;
337 }
338
339 if( reply[0] != deviceAddress )
340 {
341 return -1;
342 }
343
344 if( reply[1] == 255 )
345 {
346 return -1;
347 }
348
349 if( reply[1] != expectedReply )
350 {
351 return -1;
352 }
353
354 if( zb_decode( &response, reply ) != Z_SUCCESS )
355 {
356 return -1;
357 }
358
359 return 0;
360}
361
362int zaberLowLevelBinary::sendCommandNoReply( uint8_t deviceAddress, uint8_t commandNumber, int32_t data )
363{
364 uint8_t command[6];
365 if( zb_encode( command, deviceAddress, commandNumber, data ) != Z_SUCCESS )
366 {
367 return log<software_error, -1>( "zb_encode failed" );
368 }
369
370 if( zb_send( m_port, command ) != 6 )
371 {
372 return log<software_error, -1>( "zb_send failed" );
373 }
374
375 return 0;
376}
377
379{
380 if( m_port > 0 )
381 {
382 int rv = zb_disconnect( m_port );
383 if( rv < 0 )
384 {
385 log<text_log>( "Error disconnecting from zaber binary system.", logPrio::LOG_ERROR );
386 }
387 }
388
389 m_port = 0;
391
392 return 0;
393}
394
396{
398
399 if( devicePresent )
400 {
402 }
403 else
404 {
406 }
407
408 for( size_t i = 0; i < m_stages.size(); ++i )
409 {
410 if( devicePresent && m_stages[i].deviceAddress() > 0 )
411 {
412 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTCONNECTED" ) );
413 }
414 else
415 {
416 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
417 }
418 }
419
420 return 0;
421}
422
424{
426
427 int zrv;
428 { // mutex scope
429 elevatedPrivileges elPriv( this );
430 zrv = zb_connect( &m_port, m_deviceName.c_str() );
431 }
432
433 if( zrv != Z_SUCCESS || m_port <= 0 )
434 {
436
437 if( !stateLogged() )
438 {
439 log<software_error>( { "can not connect to zaber binary stage(s)" } );
440 }
441
442 return ZBC_NOT_CONNECTED;
443 }
444
446 {
447 log<software_error>( { "error setting binary command timeout" } );
449 return ZBC_ERROR;
450 }
451
452 if( zb_drain( m_port ) != Z_SUCCESS )
453 {
454 log<software_error>( { "error draining binary port" } );
456 return ZBC_ERROR;
457 }
458
460 {
462 {
463 log<text_log>( "Error sending renumber query to stages", logPrio::LOG_ERROR );
465 return ZBC_ERROR;
466 }
467
468 std::this_thread::sleep_for( std::chrono::milliseconds( m_renumberPauseMs ) );
469 zb_drain( m_port );
470 }
471
472 return loadStages();
473}
474
476{
477 std::vector<int> addresses;
478 std::vector<std::string> serials;
479
480 if( zb_drain( m_port ) != Z_SUCCESS )
481 {
482 log<software_error>( { "error draining binary port" } );
484 return ZBC_ERROR;
485 }
486
487 for( int address = 1; address <= m_maxDiscoveryAddress; ++address )
488 {
491 static_cast<uint8_t>( address ),
493 0,
495 {
496 continue;
497 }
498
499 addresses.push_back( address );
500 serials.push_back( std::to_string( serialNumber ) );
501 }
502
503 return loadStages( addresses, serials );
504}
505
506int zaberLowLevelBinary::loadStages( const std::vector<int> &addresses, const std::vector<std::string> &serials )
507{
508 std::vector<int> oldAddresses;
510 size_t oldPresentCount = 0;
511
512 oldAddresses.reserve( m_stages.size() );
513 for( size_t n = 0; n < m_stages.size(); ++n )
514 {
515 oldAddresses.push_back( m_stages[n].deviceAddress() );
516 if( m_stages[n].deviceAddress() > 0 )
517 {
519 }
520 }
521
522 if( addresses.size() != serials.size() )
523 {
524 return log<software_error, ZBC_ERROR>( { "discovery address/serial vector size mismatch" } );
525 }
526
528 {
529 log<text_log>( "Found " + std::to_string( addresses.size() ) + " stages." );
530 }
531
532 m_stageAddress.clear();
533
534 for( size_t n = 0; n < m_stages.size(); ++n )
535 {
536 m_stages[n].deviceAddress( -1 );
537 }
538
539 for( size_t n = 0; n < addresses.size(); ++n )
540 {
541 const int address = addresses[n];
542 const std::string serial = serials[n];
543 if( m_stageSerial.count( serial ) == 1 )
544 {
545 size_t idx = m_stageSerial[serial];
546 m_stages[idx].deviceAddress( address );
547 m_stageAddress.insert( { address, idx } );
549 {
550 log<text_log>( "stage @" + std::to_string( address ) + " with s/n " + serial + " corresponds to " +
551 m_stages[idx].name() );
552 }
553 }
554 else
555 {
556 log<text_log>( "Unknown stage @" + std::to_string( address ) + " with s/n " + serial,
558 }
559 }
560
561 for( size_t n = 0; n < m_stages.size(); ++n )
562 {
563 if( m_stages[n].deviceAddress() < 1 )
564 {
565 if( firstDiscoveryPass || n >= oldAddresses.size() || oldAddresses[n] > 0 )
566 {
567 log<text_log>( std::format( "stage {} with s/n {} not found in system.",
568 m_stages[n].name(),
569 m_stages[n].serial() ),
571 state( state(), true );
572 }
573 }
574 }
575
577
578 return ZBC_CONNECTED;
579}
580
582{
583 if( m_port <= 0 )
584 {
585 return ZBC_NOT_CONNECTED;
586 }
587
588 return loadStages();
589}
590
592{
594 {
595 log<text_log>( "In appStartup but in state UNINITIALIZED.", logPrio::LOG_CRITICAL );
596 return -1;
597 }
598
599 if( m_stages.size() == 0 )
600 {
601 log<text_log>( "No stages configured.", logPrio::LOG_CRITICAL );
602 return -1;
603 }
604
605 REG_INDI_NEWPROP_NOCB( m_indiP_curr_state, "curr_state", pcf::IndiProperty::Text );
606 REG_INDI_NEWPROP_NOCB( m_indiP_max_pos, "max_pos", pcf::IndiProperty::Text );
607 REG_INDI_NEWPROP_NOCB( m_indiP_parked, "parked", pcf::IndiProperty::Number );
608 REG_INDI_NEWPROP_NOCB( m_indiP_lastHomed, "last_homed", pcf::IndiProperty::Number );
609 REG_INDI_NEWPROP_NOCB( m_indiP_curr_pos, "curr_pos", pcf::IndiProperty::Number );
610 REG_INDI_NEWPROP_NOCB( m_indiP_temp, "temp", pcf::IndiProperty::Number );
611 REG_INDI_NEWPROP_NOCB( m_indiP_warn, "warning", pcf::IndiProperty::Switch );
612 m_indiP_warn.setRule( pcf::IndiProperty::AnyOfMany );
613
614 REG_INDI_NEWPROP( m_indiP_tgt_pos, "tgt_pos", pcf::IndiProperty::Number );
615 REG_INDI_NEWPROP( m_indiP_req_home, "req_home", pcf::IndiProperty::Switch );
616 m_indiP_req_home.setRule( pcf::IndiProperty::AtMostOne );
617
619
620 REG_INDI_NEWPROP( m_indiP_req_halt, "req_halt", pcf::IndiProperty::Switch );
621 m_indiP_req_halt.setRule( pcf::IndiProperty::AtMostOne );
622
623 REG_INDI_NEWPROP( m_indiP_req_ehalt, "req_ehalt", pcf::IndiProperty::Switch );
624 m_indiP_req_ehalt.setRule( pcf::IndiProperty::AtMostOne );
625
626 REG_INDI_NEWPROP( m_indiP_knob_enable, "knob_enable", pcf::IndiProperty::Switch );
627 m_indiP_knob_enable.setPerm( pcf::IndiProperty::ReadWrite );
628 m_indiP_knob_enable.setState( pcf::IndiProperty::Idle );
629 m_indiP_knob_enable.setRule( pcf::IndiProperty::AtMostOne );
630
631 for( size_t n = 0; n < m_stages.size(); ++n )
632 {
633 m_indiP_curr_state.add( pcf::IndiElement( m_stages[n].name() ) );
634
635 m_indiP_max_pos.add( pcf::IndiElement( m_stages[n].name() ) );
636 m_indiP_max_pos[m_stages[n].name()] = -1;
637
638 m_indiP_parked.add( pcf::IndiElement( m_stages[n].name() ) );
639 m_indiP_lastHomed.add( pcf::IndiElement( m_stages[n].name() ) );
640 m_indiP_curr_pos.add( pcf::IndiElement( m_stages[n].name() ) );
641 m_indiP_temp.add( pcf::IndiElement( m_stages[n].name() ) );
642
643 m_indiP_warn.add( pcf::IndiElement( m_stages[n].name() ) );
644 m_indiP_warn[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
645
646 m_indiP_tgt_pos.add( pcf::IndiElement( m_stages[n].name() ) );
647
648 m_indiP_req_home.add( pcf::IndiElement( m_stages[n].name() ) );
649 m_indiP_req_home[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
650
651 m_indiP_req_halt.add( pcf::IndiElement( m_stages[n].name() ) );
652 m_indiP_req_halt[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
653
654 m_indiP_req_ehalt.add( pcf::IndiElement( m_stages[n].name() ) );
655 m_indiP_req_ehalt[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
656
657 m_indiP_knob_enable.add( pcf::IndiElement( m_stages[n].name() ) );
658 m_indiP_knob_enable[m_stages[n].name()].setSwitchState( pcf::IndiElement::Off );
659
660 std::ifstream posIn;
661 posIn.open( std::format( "{}/{}/{}", m_sysPath, m_configName, m_stages[n].name() ) );
662 if( !posIn )
663 {
664 continue;
665 }
666
667 if( m_stages[n].readStateFile( posIn ) < 0 )
668 {
669 return log<software_critical, -1>( std::format( "error reading state file for {}", m_stages[n].name() ) );
670 }
671
672 m_indiP_curr_pos[m_stages[n].name()].set( m_stages[n].rawPos() );
673 m_indiP_tgt_pos[m_stages[n].name()].set( m_stages[n].tgtPos() );
674 m_indiP_parked[m_stages[n].name()].set( m_stages[n].parked() );
675 m_indiP_lastHomed[m_stages[n].name()].set( m_stages[n].lastHomed() );
676 m_indiP_max_pos[m_stages[n].name()].set( m_stages[n].maxPos() );
677 m_indiP_knob_enable[m_stages[n].name()].set( m_stages[n].knobEnabled() ? pcf::IndiElement::On
678 : pcf::IndiElement::Off );
679 }
680
681 return 0;
682}
683
685{
687 {
688 log<text_log>( "In appLogic but in state INITIALIZED.", logPrio::LOG_CRITICAL );
689 return -1;
690 }
691
692 if( state() == stateCodes::POWERON )
693 {
694 for( size_t i = 0; i < m_stages.size(); ++i )
695 {
696 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "POWERON" ) );
697 }
698
700 return 0;
701 }
702
703 if( state() == stateCodes::NODEVICE )
704 {
705 for( size_t i = 0; i < m_stages.size(); ++i )
706 {
707 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
708 }
709
711 if( rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES )
712 {
713 if( powerState() != 1 || powerStateTarget() != 1 )
714 {
715 return 0;
716 }
717
718 if( !stateLogged() )
719 {
721 }
722 return 0;
723 }
724
726 {
727 if( !stateLogged() )
728 {
730 std::format( "USB Device {}:{}:{} not found in udev", m_idVendor, m_idProduct, m_serial ) );
731 }
732 return 0;
733 }
734
736 std::format( "USB Device {}:{}:{} found in udev as {}", m_idVendor, m_idProduct, m_serial, m_deviceName ) );
737
739 for( size_t i = 0; i < m_stages.size(); ++i )
740 {
741 if( m_stages[i].deviceAddress() < 1 )
742 {
743 continue;
744 }
745 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTCONNECTED" ) );
746 }
747
748 return 0;
749 }
750
752 {
753 std::lock_guard<std::mutex> guard( m_indiMutex );
754
755 int rv = connect();
756 if( rv == ZBC_CONNECTED )
757 {
759 for( size_t i = 0; i < m_stages.size(); ++i )
760 {
761 if( m_stages[i].deviceAddress() < 1 )
762 {
763 continue;
764 }
765
766 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "CONNECTED" ) );
767 }
768
769 if( !stateLogged() )
770 {
771 log<text_log>( "Connected to binary stage(s) on " + m_deviceName );
772 }
773 }
774 else if( rv == ZBC_NOT_CONNECTED )
775 {
776 return 0;
777 }
778 }
779
781 {
782 for( size_t i = 0; i < m_stages.size(); ++i )
783 {
784 if( m_stages[i].deviceAddress() < 1 )
785 {
786 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
787 continue;
788 }
789
790 std::lock_guard<std::mutex> guard( m_indiMutex );
791
792 if( m_stages[i].enableKnob( m_port, false ) < 0 )
793 {
796 return 0;
797 }
798
799 if( m_stages[i].getMaxPos( m_port ) < 0 )
800 {
803 return 0;
804 }
805
806 if( m_stages[i].setTargetSpeed( m_port, m_stages[i].targetSpeed() ) < 0 )
807 {
810 return 0;
811 }
812
813 updateIfChanged( m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos() );
814
815 if( m_stages[i].updatePos( m_port ) < 0 )
816 {
819 return 0;
820 }
821
822 if( m_stages[i].recallParkPosition( m_port ) < 0 )
823 {
824 log<text_log>( "No stored parked position found for " + m_stages[i].name(), logPrio::LOG_INFO );
825 }
826 else if( m_stages[i].restoreParkedState( m_port ) < 0 )
827 {
830 return 0;
831 }
832
833 if( m_stages[i].getWarnings( m_port ) < 0 )
834 {
837 return 0;
838 }
839 }
840
842 return 0;
843 }
844
845 if( state() == stateCodes::READY )
846 {
847 { // mutex scope
848 std::lock_guard<std::mutex> guard( m_indiMutex );
849
850 bool canRefreshDiscovery = true;
851 for( size_t i = 0; i < m_stages.size(); ++i )
852 {
853 if( m_stages[i].deviceAddress() > 0 && m_stages[i].deviceStatus() == 'B' )
854 {
855 canRefreshDiscovery = false;
856 break;
857 }
858 }
859
860 int rv = ZBC_CONNECTED;
862 {
864 }
865
866 if( rv == ZBC_ERROR )
867 {
868 if( powerState() != 1 || powerStateTarget() != 1 )
869 {
870 return 0;
871 }
872
873 return 0;
874 }
875 }
876
877 for( size_t i = 0; i < m_stages.size(); ++i )
878 {
879 if( m_stages[i].deviceAddress() < 1 )
880 {
881 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
882 continue;
883 }
884
885 std::lock_guard<std::mutex> guard( m_indiMutex );
886
887 if( m_stages[i].updatePos( m_port ) < 0 )
888 {
891 return 0;
892 }
893
894 if( m_stages[i].getParked( m_port ) < 0 )
895 {
898 return 0;
899 }
900
901 if( m_stages[i].getKnob( m_port ) < 0 )
902 {
905 return 0;
906 }
907
908 updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
909 updateIfChanged( m_indiP_lastHomed, m_stages[i].name(), m_stages[i].lastHomed() );
910 updateIfChanged( m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos() );
911 updateIfChanged( m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos() );
912
914 m_stages[i].name(),
915 m_stages[i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off );
916
917 if( m_stages[i].deviceStatus() == 'B' )
918 {
919 if( m_stages[i].homing() )
920 {
921 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "HOMING" ) );
922 }
923 else
924 {
925 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "OPERATING" ) );
926 }
927 }
928 else if( m_stages[i].deviceStatus() == 'I' )
929 {
930 if( m_stages[i].homing() )
931 {
932 log<software_error>( std::format( "stage {} idle but in state homing. bug.", m_stages[i].name() ) );
933 return 0;
934 }
935
936 if( m_stages[i].warnWR() )
937 {
938 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NOTHOMED" ) );
939 }
940 else
941 {
942 if( !m_stages[i].parked() )
943 {
944 if( m_stages[i].park( m_port ) < 0 )
945 {
948 return 0;
949 }
950
951 std::ofstream posOut;
952 { // mutex scope
953 elevatedPrivileges ep( this );
954 posOut.open( std::format( "{}/{}/{}", m_sysPath, m_configName, m_stages[i].name() ) );
955 }
956
957 if( !posOut )
958 {
959 log<software_error>( std::format( "error opening state file for {}", m_stages[i].name() ) );
960 }
961 else if( m_stages[i].writeStateFile( posOut ) < 0 )
962 {
963 log<software_error>( std::format( "error writing state file for {}", m_stages[i].name() ) );
964 }
965
966 updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
967 }
968
969 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "READY" ) );
970 }
971 }
972 else
973 {
974 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
975 }
976
977 if( m_stages[i].warn() )
978 {
979 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::On );
980 }
981 else
982 {
983 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off );
984 }
985
986 m_stages[i].updateTemp( m_port );
987 updateIfChanged( m_indiP_temp, m_stages[i].name(), m_stages[i].temp() );
988 }
989 }
990
991 if( state() == stateCodes::ERROR )
992 {
994 if( rv < 0 && rv != TTY_E_DEVNOTFOUND && rv != TTY_E_NODEVNAMES )
995 {
996 if( powerState() != 1 || powerStateTarget() != 1 )
997 {
998 return 0;
999 }
1000
1001 if( !stateLogged() )
1002 {
1004 }
1005 return recoverFromError( false );
1006 }
1007
1009 {
1010 if( !stateLogged() )
1011 {
1013 std::format( "USB Device {}:{}:{} not found in udev", m_idVendor, m_idProduct, m_serial ) );
1014 }
1015 return recoverFromError( false );
1016 }
1017
1018 if( powerState() != 1 || powerStateTarget() != 1 )
1019 {
1020 return 0;
1021 }
1022
1023 if( !stateLogged() )
1024 {
1025 log<text_log>( "Recovering from binary stage communication error by resetting the connection.",
1027 }
1028
1029 return recoverFromError( true );
1030 }
1031
1032 if( powerState() != 1 || powerStateTarget() != 1 )
1033 {
1034 return 0;
1035 }
1036
1037 if( state() == stateCodes::FAILURE )
1038 {
1039 return -1;
1040 }
1041
1042 return 0;
1043}
1044
1046{
1048
1049 std::lock_guard<std::mutex> lock( m_indiMutex );
1050 for( size_t i = 0; i < m_stages.size(); ++i )
1051 {
1052 m_stages[i].onPowerOff();
1053
1054 // Publish the retained stage snapshot before advertising POWEROFF so
1055 // subscribers can consume the last known parked/position state first.
1056 updateIfChanged( m_indiP_max_pos, m_stages[i].name(), m_stages[i].maxPos() );
1057 updateIfChanged( m_indiP_parked, m_stages[i].name(), m_stages[i].parked() );
1058 updateIfChanged( m_indiP_lastHomed, m_stages[i].name(), m_stages[i].lastHomed() );
1059 updateIfChanged( m_indiP_curr_pos, m_stages[i].name(), m_stages[i].rawPos() );
1060 updateIfChanged( m_indiP_tgt_pos, m_stages[i].name(), m_stages[i].tgtPos() );
1062 m_stages[i].name(),
1063 ( m_stages[i].knobEnabled() ? pcf::IndiElement::On : pcf::IndiElement::Off ) );
1064 updateIfChanged( m_indiP_temp, m_stages[i].name(), std::string( "" ) );
1065 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "POWEROFF" ) );
1066 updateIfChanged( m_indiP_warn, m_stages[i].name(), pcf::IndiElement::Off );
1067 }
1068
1069 return 0;
1070}
1071
1073{
1074 return 0;
1075}
1076
1078{
1079 for( size_t i = 0; i < m_stages.size(); ++i )
1080 {
1081 if( m_stages[i].deviceAddress() < 1 )
1082 {
1083 continue;
1084 }
1085
1086 updateIfChanged( m_indiP_curr_state, m_stages[i].name(), std::string( "NODEVICE" ) );
1087 }
1088
1089 return 0;
1090}
1091
1092INDI_NEWCALLBACK_DEFN( zaberLowLevelBinary, m_indiP_tgt_pos )( const pcf::IndiProperty &ipRecv )
1093{
1094 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_tgt_pos, ipRecv );
1095
1096 for( size_t n = 0; n < m_stages.size(); ++n )
1097 {
1098 if( ipRecv.find( m_stages[n].name() ) )
1099 {
1100 long tgt = ipRecv[m_stages[n].name()].get<long>();
1101 if( tgt >= 0 )
1102 {
1103 if( m_stages[n].deviceAddress() < 1 )
1104 {
1105 return log<software_error, -1>( std::format(
1106 "stage {} with s/n {} not found in system.", m_stages[n].name(), m_stages[n].serial() ) );
1107 }
1108
1109 std::lock_guard<std::mutex> guard( m_indiMutex );
1110 if( m_stages[n].moveAbs( m_port, tgt ) < 0 )
1111 {
1112 return log<software_error, -1>( { "error from moveAbs for " + m_stages[n].name() } );
1113 }
1114
1115 updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), m_stages[n].tgtPos() );
1116 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
1117 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string( "OPERATING" ) );
1118 }
1119 }
1120 }
1121
1122 return 0;
1123}
1124
1125INDI_NEWCALLBACK_DEFN( zaberLowLevelBinary, m_indiP_req_home )( const pcf::IndiProperty &ipRecv )
1126{
1127 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_home, ipRecv );
1128
1129 size_t stageno = std::numeric_limits<size_t>::max();
1130 bool found = false;
1131
1132 for( size_t n = 0; n < m_stages.size(); ++n )
1133 {
1134 if( ipRecv.find( m_stages[n].name() ) )
1135 {
1136 if( found )
1137 {
1138 return log<software_error, -1>( "more than one stage specified in req_home, rejecting request" );
1139 }
1140
1141 if( m_stages[n].deviceAddress() < 1 )
1142 {
1143 return log<software_error, -1>(
1144 std::format( "stage {} with s/n {} not found", m_stages[n].name(), m_stages[n].serial() ) );
1145 }
1146
1147 stageno = n;
1148 found = true;
1149 }
1150 }
1151
1152 if( !found || stageno >= m_stages.size() )
1153 {
1154 return log<software_error, -1>( "no valid stage specified in req_home, rejecting request" );
1155 }
1156
1157 if( ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
1158 {
1159 return log<software_warning, 0>(
1160 std::format( "request off for stage {} in req_home", m_stages[stageno].name() ) );
1161 }
1162
1163 std::lock_guard<std::mutex> guard( m_indiMutex );
1164 if( m_stages[stageno].homing() )
1165 {
1166 return log<software_warning, 0>(
1167 std::format( "stage {} is already homing in req_home", m_stages[stageno].name() ) );
1168 }
1169
1170 if( m_stages[stageno].home( m_port ) < 0 )
1171 {
1172 return log<software_error, -1>( std::format( "error from home for {}", m_stages[stageno].name() ) );
1173 }
1174
1175 updateIfChanged( m_indiP_tgt_pos, m_stages[stageno].name(), 0 );
1176 updateIfChanged( m_indiP_parked, m_stages[stageno].name(), m_stages[stageno].parked() );
1177 updateIfChanged( m_indiP_curr_state, m_stages[stageno].name(), std::string( "HOMING" ) );
1178
1179 return 0;
1180}
1181
1182INDI_NEWCALLBACK_DEFN( zaberLowLevelBinary, m_indiP_req_home_all )( const pcf::IndiProperty &ipRecv )
1183{
1184 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_home_all, ipRecv );
1185
1186 if( !ipRecv.find( "request" ) )
1187 {
1188 return 0;
1189 }
1190
1191 if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On )
1192 {
1193 for( size_t n = 0; n < m_stages.size(); ++n )
1194 {
1195 if( m_stages[n].deviceAddress() < 1 )
1196 {
1197 continue;
1198 }
1199
1200 std::lock_guard<std::mutex> guard( m_indiMutex );
1201 if( m_stages[n].homing() )
1202 {
1203 continue;
1204 }
1205
1206 if( m_stages[n].home( m_port ) < 0 )
1207 {
1208 return log<software_error, -1>( { "error from home for " + m_stages[n].name() } );
1209 }
1210
1211 updateIfChanged( m_indiP_tgt_pos, m_stages[n].name(), 0 );
1212 updateIfChanged( m_indiP_parked, m_stages[n].name(), m_stages[n].parked() );
1213 updateIfChanged( m_indiP_curr_state, m_stages[n].name(), std::string( "HOMING" ) );
1214 }
1215 }
1216
1217 return 0;
1218}
1219
1220INDI_NEWCALLBACK_DEFN( zaberLowLevelBinary, m_indiP_req_halt )( const pcf::IndiProperty &ipRecv )
1221{
1222 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_halt, ipRecv );
1223
1224 size_t stageno = std::numeric_limits<size_t>::max();
1225 bool found = false;
1226
1227 for( size_t n = 0; n < m_stages.size(); ++n )
1228 {
1229 if( ipRecv.find( m_stages[n].name() ) )
1230 {
1231 if( found )
1232 {
1233 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
1234 }
1235
1236 if( m_stages[n].deviceAddress() < 1 )
1237 {
1238 return log<software_error, -1>(
1239 std::format( "stage {} with s/n {} not present", m_stages[n].name(), m_stages[n].serial() ) );
1240 }
1241
1242 stageno = n;
1243 found = true;
1244 }
1245 }
1246
1247 if( !found || stageno == std::numeric_limits<size_t>::max() )
1248 {
1249 return log<software_error, -1>( "no valid stage specified in req_halt, rejecting request" );
1250 }
1251
1252 if( ipRecv[m_stages[stageno].name()].getSwitchState() != pcf::IndiElement::On )
1253 {
1254 return log<software_warning, 0>(
1255 std::format( "request off for stage {} in req_halt", m_stages[stageno].name() ) );
1256 }
1257
1258 std::lock_guard<std::mutex> guard( m_indiMutex );
1259 if( m_stages[stageno].stop( m_port ) < 0 )
1260 {
1261 return log<software_error, -1>( std::format( "error from stop for {}", m_stages[stageno].name() ) );
1262 }
1263
1264 return 0;
1265}
1266
1267INDI_NEWCALLBACK_DEFN( zaberLowLevelBinary, m_indiP_req_ehalt )( const pcf::IndiProperty &ipRecv )
1268{
1269 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_req_ehalt, ipRecv );
1270
1271 for( size_t n = 0; n < m_stages.size(); ++n )
1272 {
1273 if( ipRecv.find( m_stages[n].name() ) && ipRecv[m_stages[n].name()].getSwitchState() == pcf::IndiElement::On )
1274 {
1275 if( m_stages[n].deviceAddress() < 1 )
1276 {
1277 log<software_error>(
1278 std::format( "stage {} with s/n {} not present", m_stages[n].name(), m_stages[n].serial() ) );
1279 continue;
1280 }
1281
1282 std::lock_guard<std::mutex> guard( m_indiMutex );
1283 if( m_stages[n].estop( m_port ) < 0 )
1284 {
1285 log<software_error>( { "error from estop for " + m_stages[n].name() } );
1286 }
1287 }
1288 }
1289
1290 return 0;
1291}
1292
1293INDI_NEWCALLBACK_DEFN( zaberLowLevelBinary, m_indiP_knob_enable )( const pcf::IndiProperty &ipRecv )
1294{
1295 INDI_VALIDATE_CALLBACK_PROPS( m_indiP_knob_enable, ipRecv );
1296
1297 // Make sure only one request is sent to avoid racing
1298 size_t stageno = std::numeric_limits<size_t>::max();
1299
1300 bool found = false;
1301
1302 for( size_t n = 0; n < m_stages.size(); ++n )
1303 {
1304 if( ipRecv.find( m_stages[n].name() ) )
1305 {
1306 if( found )
1307 {
1308 return log<software_error, -1>( "more than one stage specified in req_halt, rejecting request" );
1309 }
1310
1311 if( m_stages[n].deviceAddress() < 1 )
1312 {
1313 return log<software_error, -1>( std::format( "stage {} with with "
1314 "s/n {} not present",
1315 m_stages[n].name(),
1316 m_stages[n].serial() ) );
1317 }
1318
1319 stageno = n;
1320 found = true;
1321 }
1322 }
1323
1324 if( !found || stageno == std::numeric_limits<size_t>::max() )
1325 {
1326 return log<software_error, -1>( "no valid stage specified in req_knob, rejecting request" );
1327 }
1328
1329 bool enable_knob = ipRecv[m_stages[stageno].name()].getSwitchState() == pcf::IndiElement::On;
1330
1331 std::lock_guard<std::mutex> guard( m_indiMutex );
1332
1333 if( m_stages[stageno].enableKnob( m_port, enable_knob ) < 0 )
1334 {
1335 return log<software_error, -1>( std::format( "error from enable knob for {}", m_stages[stageno].name() ) );
1336 }
1337
1338 return 0;
1339}
1340
1341} // namespace app
1342} // namespace MagAOX
1343
1344#endif // zaberLowLevelBinary_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.
A class to manage the details of one binary-protocol stage in a Zaber system.
The low-level binary-protocol Zaber controller.
pcf::IndiProperty m_indiP_warn
Warning-state switch reported per configured stage.
bool m_renumberOnConnect
Whether to renumber the daisy chain during connect.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_ehalt)
virtual void setupConfig()
Set up application configuration.
int sendCommandNoReply(uint8_t deviceAddress, uint8_t commandNumber, int32_t data)
Send a broadcast or address-specific command with no awaited reply.
std::unordered_map< int, size_t > m_stageAddress
Map from binary device address to configured stage index.
int recoverFromError(bool devicePresent)
Recover from a binary-transport error without terminating the app.
int m_renumberPauseMs
Pause in milliseconds after renumbering before further commands are sent.
std::unordered_map< std::string, size_t > m_stageName
Map from configured stage name to configured stage index.
int connect()
Connect to the binary-protocol stage chain and discover configured devices.
virtual int onPowerOff()
Handle the transition into the powered-off state.
z_port m_port
Connected binary protocol port.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_halt)
virtual int appLogic()
Execute the main FSM for zaberLowLevelBinary.
virtual int whilePowerOff()
Execute the powered-off loop.
int m_commandTimeout
Binary command timeout in milliseconds.
int queryDevice(int32_t &response, uint8_t deviceAddress, uint8_t commandNumber, int32_t data, uint8_t expectedReply)
Query a device directly for discovery-time replies.
virtual int appShutdown()
Perform any shutdown tasks before exit.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_home_all)
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_knob_enable)
int loadStages()
Discover configured stages on the binary bus.
pcf::IndiProperty m_indiP_knob_enable
Enable or disable a stage's potentiometer.
int m_maxDiscoveryAddress
Maximum device address to probe while matching configured serial numbers.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_req_home)
pcf::IndiProperty m_indiP_curr_pos
Current raw position reported per configured stage.
pcf::IndiProperty m_indiP_parked
Parked-state bookkeeping reported per configured stage.
pcf::IndiProperty m_indiP_req_home_all
Global request to home all configured stages.
std::unordered_map< std::string, size_t > m_stageSerial
Map from configured serial number to configured stage index.
pcf::IndiProperty m_indiP_curr_state
Current stage state reported per configured stage.
pcf::IndiProperty m_indiP_max_pos
Maximum raw position reported per configured stage.
bool m_stageDiscoveryInitialized
Whether the active connection has completed an initial discovery pass.
pcf::IndiProperty m_indiP_tgt_pos
Requested target raw position per configured stage.
std::vector< zaberBinaryStage< zaberLowLevelBinary > > m_stages
Stage helpers in configuration order.
int refreshStageDiscovery()
Refresh discovery on an already-connected binary bus.
pcf::IndiProperty m_indiP_req_home
Per-stage home requests.
pcf::IndiProperty m_indiP_req_halt
Per-stage halt requests.
pcf::IndiProperty m_indiP_req_ehalt
Per-stage emergency-halt requests.
INDI_NEWCALLBACK_DECL(zaberLowLevelBinary, m_indiP_tgt_pos)
virtual void loadConfig()
Load application configuration.
pcf::IndiProperty m_indiP_lastHomed
Last-homed timestamps reported per configured stage.
virtual int appStartup()
Set up the INDI properties and restore retained stage state.
int m_numStages
Number of configured stages.
int resetConnection()
Reset the active binary connection bookkeeping.
pcf::IndiProperty m_indiP_temp
Driver temperature reported per configured stage.
#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.
@ 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.
std::string ttyErrorString(int ec)
Get a text explanation of a TTY_E_ error code.
Definition ttyErrors.cpp:15
Provides a set of functions for interacting with Zaber devices in the binary protocol.
#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_INFO
Informational. The info log level is the lowest level recorded during normal operations.
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.
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 zb_decode(int32_t *destination, const uint8_t *reply)
Definition zb_serial.c:48
int zb_encode(uint8_t *destination, uint8_t device_number, uint8_t command_number, int32_t data)
Definition zb_serial.c:25
int zb_disconnect(z_port port)
int zb_send(z_port port, const uint8_t *command)
int zb_receive(z_port port, uint8_t *destination)
int zb_drain(z_port port)
int zb_set_timeout(z_port port, int milliseconds)
int zb_connect(z_port *port, const char *port_name)
A class with details of a single binary-protocol Zaber stage.
#define ZBC_ERROR
MagAOX::app::MagAOXApp< true > MagAOXAppT
#define ZBC_CONNECTED
#define ZBC_NOT_CONNECTED
@ Z_SUCCESS
Definition z_common.h:65