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