API
 
Loading...
Searching...
No Matches
alpaoCtrl.hpp
Go to the documentation of this file.
1/** \file alpaoCtrl.hpp
2 * \brief The MagAO-X ALPAO DM controller header file
3 *
4 * \ingroup alpaoCtrl_files
5 */
6
7#ifndef alpaoCtrl_hpp
8#define alpaoCtrl_hpp
9
10
11#include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
12#include "../../magaox_git_version.h"
13
14
15/* Alpao SDK C Header */
16#include <asdkWrapper.h>
17
18
19/** \defgroup alpaoCtrl
20 * \brief The MagAO-X application to control an ALPAO DM
21 *
22 * <a href="../handbook/operating/software/apps/alpaoCtrl.html">Application Documentation</a>
23 *
24 * \ingroup apps
25 *
26 */
27
28/** \defgroup alpaoCtrl_files
29 * \ingroup alpaoCtrl
30 */
31
32namespace MagAOX
33{
34namespace app
35{
36
37/// The MagAO-X ALPAO DM Controller
38/**
39 * \ingroup alpaoCtrl
40 */
41class alpaoCtrl : public MagAOXApp<true>, public dev::dm<alpaoCtrl,float>, public dev::shmimMonitor<alpaoCtrl>
42{
43
44 //Give the test harness access.
45 friend class alpaoCtrl_test;
46
47 friend class dev::dm<alpaoCtrl,float>;
48
50
51 friend class dev::shmimMonitor<alpaoCtrl>;
52
54
55 typedef float realT; ///< This defines the datatype used to signal the DM using the ImageStreamIO library.
56
57protected:
58
59 /** \name Configurable Parameters
60 *@{
61 */
62
63 std::string m_serialNumber; ///< The ALPAO serial number used to find the default config directory.
64
65 long m_satThresh {100} ;///< Threshold above which to log saturation.
66
67 ///@}
68
69
70 unsigned m_nsat {0};
71
72
73public:
74 /// Default c'tor.
75 alpaoCtrl();
76
77 /// D'tor.
79
80 /// Setup the configuration system.
81 virtual void setupConfig();
82
83 /// Implementation of loadConfig logic, separated for testing.
84 /** This is called by loadConfig().
85 */
86 int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
87
88 /// Load the configuration
89 virtual void loadConfig();
90
91 /// Startup function
92 /** Sets up INDI, and starts the shmim thread.
93 *
94 */
95 virtual int appStartup();
96
97 /// Implementation of the FSM for alpaoCtrl.
98 /**
99 * \returns 0 on no critical error
100 * \returns -1 on an error requiring shutdown
101 */
102 virtual int appLogic();
103
104 /// Shutdown the app.
105 /**
106 *
107 */
108 virtual int appShutdown();
109
110 /// Cleanup after a power off.
111 /**
112 */
113 virtual int onPowerOff();
114
115 /// Maintenace while powered off.
116 /**
117 */
119
120 /** \name DM Base Class Interface
121 *
122 *@{
123 */
124
125 /// Initialize the DM and prepare for operation.
126 /** Application is in state OPERATING upon successful conclusion.
127 *
128 * \returns 0 on success
129 * \returns -1 on error
130 */
131 int initDM();
132
133 /// Zero all commands on the DM
134 /** This does not update the shared memory buffer.
135 *
136 * \returns 0 on success
137 * \returns -1 on error
138 */
139 int zeroDM();
140
141 /// Send a command to the DM
142 /** This is called by the shmim monitoring thread in response to a semaphore trigger.
143 *
144 * \returns 0 on success
145 * \returns -1 on error
146 */
147 int commandDM(void * curr_src);
148
149 /// Release the DM, making it safe to turn off power.
150 /** The application will be state READY at the conclusion of this.
151 *
152 * \returns 0 on success
153 * \returns -1 on error
154 */
155 int releaseDM();
156
157 ///@}
158
159 /** \name ALPAO Interface
160 * \todo document these members
161 *@{
162 */
163
165 Scalar m_max_stroke {0}; ///< The maximum allowable stroke
166 Scalar m_volume_factor {0}; ///< the volume factor to convert from displacement to commands
167 UInt m_nbAct {0}; ///< The number of actuators
168
169 int * m_actuator_mapping {nullptr}; ///< Array containing the mapping from 2D grid position to linear index in the command vector
170
171 Scalar * m_dminputs {nullptr}; ///< Pre-allocated command vector, used only in commandDM
172
173 asdkDM * m_dm {nullptr}; ///< ALPAO SDK handle for the DM.
174
175public:
176
177 /// Parse the ALPAO calibration file
178 /** \returns 0 on success
179 * \returns -1 on error
180 */
182
183 /// Read the actuator mapping from a FITS file
184 /**
185 * \todo convert this to use mxlib::fitsFile
186 *
187 * \returns 0 on success
188 * \returns -1 on error
189 */
191
192 ///@}
193};
194
195alpaoCtrl::alpaoCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
196{
197 m_powerMgtEnabled = true;
198 return;
199}
200
207
209{
210 config.add("dm.serialNumber", "", "dm.serialNumber", argType::Required, "dm", "serialNumber", false, "string", "The ALPAO serial number used to find the default config directory.");
211 config.add("dm.satThresh", "", "dm.satThresh", argType::Required, "dm", "satThresh", false, "string", "Threshold above which to log saturation.");
212
213 DM_SETUP_CONFIG(config);
214
215}
216
217int alpaoCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
218{
219 _config(m_serialNumber, "dm.serialNumber");
220 _config(m_satThresh, "dm.satThresh");
221
222 m_calibRelDir = "dm/alpao_";
223
224 std::string ser = mx::ioutils::toLower(m_serialNumber);
225
227
229
230 return 0;
231}
232
234{
235 if(loadConfigImpl(config))
236 {
237 log<software_error>({__FILE__,__LINE__, "error loading config"});
238 m_shutdown = 1;
239 }
240
241}
242
244{
245 if(parse_calibration_file() < 0)
246 {
248 return -1;
249 }
250
251 if(m_max_stroke == 0 || m_volume_factor == 0)
252 {
253 log<software_critical>({__FILE__,__LINE__, "calibration not loaded properly"});
254 return -1;
255 }
256
258
260
261 return 0;
262}
263
265{
267
269
270 if(state()==stateCodes::POWEROFF) return 0;
271
273 {
274 if(!powerOnWaitElapsed())
275 {
276 return 0;
277 }
278
280
281 }
282
283 if(m_nsat > m_satThresh)
284 {
285 log<text_log>("Saturated actuators in last second: " + std::to_string(m_nsat), logPrio::LOG_WARNING);
286 }
287 m_nsat = 0;
288
291
292 return 0;
293}
294
296{
297 if(m_dm) releaseDM();
298
300
302
303 return 0;
304}
305
310
315
317{
318 if(m_dm != nullptr)
319 {
320 log<text_log>("DM is already initialized. Release first.", logPrio::LOG_ERROR);
321 return 0;
322 }
323
324 std::string ser = mx::ioutils::toUpper(m_serialNumber);
325 m_dm = asdkInit(ser.c_str());
326
327 acs::UInt aerr = 0;
328 asdkGetLastError(&aerr, nullptr, 0);
329 if(aerr)
330 {
331 char err[1024];
332 asdkGetLastError(&aerr, err, sizeof(err));
333 log<software_error>({__FILE__, __LINE__, std::string("DM initialization failed: ") + err});
334 m_dm = nullptr;
335 return -1;
336 }
337
338 if (m_dm == NULL)
339 {
340 char err[1024];
341 asdkGetLastError(&aerr, err, sizeof(err));
342 return log<software_error, -1>({__FILE__, __LINE__, std::string("DM initialization failed. NULL pointer: ") + err});
343 }
344
345 log<text_log>("ALPAO " + m_serialNumber + " initialized", logPrio::LOG_NOTICE);
346
347 // Get number of actuators
348 Scalar tmp;
349 if(asdkGet( m_dm, "NbOfActuator", &tmp ) < 0)
350 {
351 char err[1024];
352 asdkGetLastError(&aerr, err, sizeof(err));
353 return log<software_error, -1>({__FILE__, __LINE__, std::string("Getting number of actuators failed: ") + err});
354 }
355 m_nbAct = tmp;
356
358 m_dminputs = (Scalar*) calloc( m_nbAct, sizeof( Scalar ) );
359
360 if(zeroDM() < 0)
361 {
362 return log<software_error, -1>({__FILE__, __LINE__, "DM initialization failed. Error zeroing DM."});
363 }
364
365 /* get actuator mapping from 2D cacao image to 1D vector for ALPAO input */
367 m_actuator_mapping = (int *) malloc(m_nbAct * sizeof(int)); /* memory for actuator mapping */
368
369 if(get_actuator_mapping() < 0)
370 {
371 return log<software_error, -1>({__FILE__, __LINE__, "DM initialization failed. Failed to get actuator mapping."});
372 }
373
374 if( m_actuator_mapping == nullptr)
375 {
376 return log<software_error, -1>({__FILE__, __LINE__, "DM initialization failed. null pointer."});
377 }
378
380
381 return 0;
382}
383
385{
386 if(m_dm == nullptr)
387 {
388 return log<software_error, -1>({__FILE__, __LINE__, "DM not initialized (NULL pointer)"});
389 }
390
391 if(m_nbAct == 0)
392 {
393 return log<software_error, -1>({__FILE__, __LINE__, "DM not initialized (number of actuators)"});
394 }
395
396 Scalar * dminputs = (Scalar*) calloc( m_nbAct, sizeof( Scalar ) );
397
398 /* Send the all 0 command to the DM */
399 int ret = asdkSend(m_dm, dminputs);
400
401 /* Release memory */
402 free( dminputs );
403
404 if(ret < 0)
405 {
406 UInt aerr = 0;
407 char err[1024];
408 asdkGetLastError(&aerr, err, sizeof(err));
409
410 return log<software_error,-1>({__FILE__, __LINE__, std::string("Error zeroing DM: ") + err});
411 }
412
413 log<text_log>("DM zeroed");
414 return 0;
415}
416
417int alpaoCtrl::commandDM(void * curr_src)
418{
420
421
422 //This is based on Kyle Van Gorkoms original sendCommand function.
423
424 #ifdef XWC_DMTIMINGS
425 dmT::m_tact0 = mx::sys::get_curr_time();
426 #endif
427
428 /*This loop performs the following steps:
429 1) converts from float to double (ALPAO Scalar)
430 2) convert to volume-normalized displacement (microns)
431 3) convert to fractional stroke (-1 to +1) that the ALPAO SDK expects
432 4) calculate the mean
433 */
434 double mean = 0;
435
437 float inv_gain_scale = 1.0/gain_scale;
438
439 for (UInt idx = 0; idx < m_nbAct; ++idx)
440 {
441 m_dminputs[idx] = reinterpret_cast<realT *>(curr_src)[m_actuator_mapping[idx]] * gain_scale;
442 mean += m_dminputs[idx];
443 }
444 mean /= m_nbAct;
445
446 /*This loop performs the following steps:
447 1) remove mean from each actuator input
448 2) clip to fractional values between -1 and 1.
449 The ALPAO SDK doesn't seem to check for this, which
450 is scary and a little odd.
451 */
452 m_outputShape.setWrite(1);
453
454 for (UInt idx = 0 ; idx < m_nbAct ; ++idx)
455 {
456 m_dminputs[idx] -= mean;
457
458 if (m_dminputs[idx] > 1)
459 {
460 ++m_nsat;
461 m_dminputs[idx] = 1;
462 }
463 else if (m_dminputs[idx] < -1)
464 {
465 ++m_nsat;
466 m_dminputs[idx] = - 1;
467 }
468
470
471 }
472
473 m_outputShape.post();
474
475 #ifdef XWC_DMTIMINGS
476 dmT::m_tact1 = mx::sys::get_curr_time();
477 #endif
478
479 /* Finally, send the command to the DM */
481
482 #ifdef XWC_DMTIMINGS
483 dmT::m_tact2 = mx::sys::get_curr_time();
484 #endif
485
486 #ifdef XWC_DMTIMINGS
487 dmT::m_tact3 = mx::sys::get_curr_time();
488 #endif
489
490 /* Now update the instantaneous sat map */
491 for (UInt idx = 0; idx < m_nbAct; ++idx)
492 {
493 if(m_dminputs[idx] >= 1 || m_dminputs[idx] <= -1)
494 {
496 }
497 else
498 {
500 }
501 }
502
503 #ifdef XWC_DMTIMINGS
504 dmT::m_tact4 = mx::sys::get_curr_time();
505 #endif
506
507 return ret;
508
509}
510
512{
513 // Safe DM shutdown on interrupt
514
515 if(m_dm == nullptr)
516 {
517 return 0;
518 }
519
520 if(!shutdown())
521 {
522 pthread_kill(m_smThread.native_handle(), SIGUSR1);
523 }
524
525 sleep(1);
526
527 if(zeroDM() < 0)
528 {
529 return log<software_error,-1>({__FILE__, __LINE__, "DM release failed. Error zeroing DM."});
530 }
531
532 // Reset and release ALPAO
534
535 acs::UInt aerr = 0;
536 asdkGetLastError(&aerr, nullptr, 0);
537 if(aerr)
538 {
539 char err[1024];
540 asdkGetLastError(&aerr, err, sizeof(err));
541 return log<software_error,-1>({__FILE__, __LINE__, std::string("DM reset failed: ") + err});
542 }
543
544 asdkRelease(m_dm); ///\todo error check
545
546 aerr = 0;
547 asdkGetLastError(&aerr, nullptr, 0);
548 if(aerr)
549 {
550 char err[1024];
551 asdkGetLastError(&aerr, err, sizeof(err));
552 return log<software_error, -1>({__FILE__, __LINE__, std::string("DM release failed: ") + err});
553 }
554
555 m_dm = nullptr;
556
557 log<text_log>("ALPAO " + m_serialNumber + " reset and released", logPrio::LOG_NOTICE);
558
559 return 0;
560}
561
562/* Read in a configuration file with user-calibrated
563values to determine the conversion from physical to
564fractional stroke as well as the volume displaced by
565the influence function. */
566int alpaoCtrl::parse_calibration_file() //const char * serial, Scalar *max_stroke, Scalar *volume_factor)
567{
568 FILE * fp;
569 char * line = NULL;
570 size_t len = 0;
571 ssize_t read;
573
574 std::string ser = mx::ioutils::toLower(m_serialNumber);
575
576 std::string calibpath = m_calibPath + "/" + ser + "_userconfig.txt";
577
578 // open file
579 fp = fopen(calibpath.c_str(), "r");
580 if (fp == NULL)
581 {
582 return log<software_error,-1>({__FILE__, __LINE__, "Could not read configuration file at " + calibpath});
583 }
584
585 calibvals = (Scalar*) malloc(2*sizeof(Scalar));
586 int idx = 0;
587 while ((read = getline(&line, &len, fp)) != -1)
588 {
589 // grab first value from each line
590 calibvals[idx] = strtod(line, NULL);
591 idx++;
592 }
593
594 fclose(fp);
595
596 // assign stroke and volume factors
599
601
602 log<text_log>("ALPAO " + m_serialNumber + ": Using stroke and volume calibration from " + calibpath);
603 std::cerr << m_max_stroke << " " << m_volume_factor << "\n";
604 return 0;
605}
606
607int alpaoCtrl::get_actuator_mapping() //const char * serial, int nbAct, int * actuator_mapping)
608{
609 /* This function closely follows the CFITSIO imstat
610 example */
611
612 fitsfile *fptr; /* FITS file pointer */
613 int status = 0; /* CFITSIO status value MUST be initialized to zero! */
614
615
616
617 // get file path to actuator map
618 std::string ser = mx::ioutils::toLower(m_serialNumber);
619
620 std::string calibpath = m_calibPath + "/" + ser + "_actuator_mapping.fits";
621
622 if ( !fits_open_image(&fptr, calibpath.c_str(), READONLY, &status) )
623 {
624 int hdutype, naxis;
625 long naxes[2];
626
627 if (fits_get_hdu_type(fptr, &hdutype, &status) || hdutype != IMAGE_HDU) {
628 printf("Error: this program only works on images, not tables\n");
629 return(1);
630 }
631
632 fits_get_img_dim(fptr, &naxis, &status);
633 fits_get_img_size(fptr, 2, naxes, &status);
634
635 if (status || naxis != 2) {
636 printf("Error: NAXIS = %d. Only 2-D images are supported.\n", naxis);
637 return(1);
638 }
639
640 int * pix = (int *) malloc(naxes[0] * sizeof(int)); /* memory for 1 row */
641
642 if (pix == NULL) {
643 printf("Memory allocation error\n");
644 return(1);
645 }
646
647 long fpixel[2];
648 //totpix = naxes[0] * naxes[1];
649 fpixel[0] = 1; /* read starting with first pixel in each row */
650
651 /* process image one row at a time; increment row # in each loop */
652 int ij = 0;/* actuator mapping index */
653 for (fpixel[1] = naxes[1]; fpixel[1] >= 1; fpixel[1]--)
654 {
655 /* give starting pixel coordinate and number of pixels to read */
656 if (fits_read_pix(fptr, TINT, fpixel, naxes[0],0, pix,0, &status))
657 break; /* jump out of loop on error */
658
659 // get indices of active actuators in order
660 for (int ii = 0; ii < naxes[0]; ii++) {
661 if (pix[ii] > 0) {
662 m_actuator_mapping[ij] = (fpixel[1]-1) * naxes[0] + ii;
663 ij++;
664 }
665 }
666 }
667 fits_close_file(fptr, &status);
668
669 free(pix);
670 }
671
672 if (status) {
673 fits_report_error(stderr, status); /* print any error message */
674 }
675
676
677
678 log<text_log>("ALPAO " + m_serialNumber + ": Using actuator mapping from " + calibpath);
679 return 0;
680}
681
682} //namespace app
683} //namespace MagAOX
684
685#endif //alpaoCtrl_hpp
The base-class for XWCTk applications.
stateCodes::stateCodeT state()
Get the current state code.
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
int shutdown()
Get the value of the shutdown flag.
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
bool powerOnWaitElapsed()
This method tests whether the power on wait time has elapsed.
The MagAO-X ALPAO DM Controller.
Definition alpaoCtrl.hpp:42
Scalar m_max_stroke
The maximum allowable stroke.
~alpaoCtrl() noexcept
D'tor.
virtual void setupConfig()
Setup the configuration system.
std::string m_serialNumber
The ALPAO serial number used to find the default config directory.
Definition alpaoCtrl.hpp:63
Scalar * m_dminputs
Pre-allocated command vector, used only in commandDM.
asdkDM * m_dm
ALPAO SDK handle for the DM.
Scalar m_volume_factor
the volume factor to convert from displacement to commands
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
virtual int appShutdown()
Shutdown the app.
int get_actuator_mapping()
Read the actuator mapping from a FITS file.
int parse_calibration_file()
Parse the ALPAO calibration file.
int initDM()
Initialize the DM and prepare for operation.
virtual int appLogic()
Implementation of the FSM for alpaoCtrl.
float realT
This defines the datatype used to signal the DM using the ImageStreamIO library.
Definition alpaoCtrl.hpp:55
int * m_actuator_mapping
Array containing the mapping from 2D grid position to linear index in the command vector.
virtual int appStartup()
Startup function.
int releaseDM()
Release the DM, making it safe to turn off power.
int commandDM(void *curr_src)
Send a command to the DM.
dev::dm< alpaoCtrl, float > dmT
Definition alpaoCtrl.hpp:49
virtual int onPowerOff()
Cleanup after a power off.
alpaoCtrl()
Default c'tor.
int zeroDM()
Zero all commands on the DM.
UInt m_nbAct
The number of actuators.
friend class alpaoCtrl_test
Definition alpaoCtrl.hpp:45
virtual int whilePowerOff()
Maintenace while powered off.
dev::shmimMonitor< alpaoCtrl > shmimMonitorT
Definition alpaoCtrl.hpp:53
long m_satThresh
Threshold above which to log saturation.
Definition alpaoCtrl.hpp:65
virtual void loadConfig()
Load the configuration.
std::string m_calibPath
The path to this DM's calibration files.
Definition dm.hpp:99
mx::improc::eigenImage< uint8_t > m_instSatMap
Definition dm.hpp:170
mx::improc::milkImage< float > m_outputShape
The true output shape after saturation.
Definition dm.hpp:186
std::thread m_smThread
A separate thread for the actual monitoring.
#define DM_SETUP_CONFIG(cfig)
Call dmT::setupConfig with error checking for dm.
Definition dm.hpp:3397
#define DM_APP_SHUTDOWN
Call dmT::appShutdown with error checking for dm.
Definition dm.hpp:3437
#define DM_LOAD_CONFIG(cfig)
Call dmT::loadConfig with error checking for dm.
Definition dm.hpp:3409
#define DM_APP_STARTUP
Call shmimMonitorT::appStartup with error checking for dm.
Definition dm.hpp:3416
#define DM_APP_LOGIC
Call dmT::appLogic with error checking for dm.
Definition dm.hpp:3423
#define DM_UPDATE_INDI
Call dmT::updateINDI with error checking for dm.
Definition dm.hpp:3430
@ POWEROFF
The device power is off.
@ NOTHOMED
The device has not been homed.
@ READY
The device is ready for operation, but is not operating.
@ POWERON
The device power is on.
Definition dm.hpp:19
static constexpr logPrioT LOG_NOTICE
A normal but significant condition.
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.
#define SHMIMMONITOR_APP_SHUTDOWN
Call shmimMonitorT::appShutdown with error checking for shmimMonitor.
#define SHMIMMONITOR_APP_LOGIC
Call shmimMonitorT::appLogic with error checking for shmimMonitor.
#define SHMIMMONITOR_APP_STARTUP
Call shmimMonitorT::appStartup with error checking for shmimMonitor.
#define SHMIMMONITOR_UPDATE_INDI
Call shmimMonitorT::updateINDI with error checking for shmimMonitor.
Software ERR log entry.