API
bmcCtrl.hpp
Go to the documentation of this file.
1 /** \file bmcCtrl.hpp
2  * \brief The MagAO-X BMC DM controller header file
3  *
4  * \ingroup bmcCtrl_files
5  */
6 
7 
8 
9 /*
10 Open questions:
11 * Fix actuator mapping issue (don't want a bunch of if statements)
12 
13 
14 Test:
15 * bias code
16 
17 */
18 
19 
20 // #define _GLIBCXX_USE_CXX11_ABI 0
21 
22 
23 #ifndef bmcCtrl_hpp
24 #define bmcCtrl_hpp
25 
26 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
27 #include "../../magaox_git_version.h"
28 
29 
30 /* BMC SDK C Header */
31 #include <BMCApi.h>
32 
33 
34 /** \defgroup bmcCtrl
35  * \brief The MagAO-X application to control a BMC DM
36  *
37  * <a href="..//apps_html/page_module_bmcCtrl.html">Application Documentation</a>
38  *
39  * \ingroup apps
40  *
41  */
42 
43 /** \defgroup bmcCtrl_files
44  * \ingroup bmcCtrl
45  */
46 
47 namespace MagAOX
48 {
49 namespace app
50 {
51 
52 /// The MagAO-X BMC DM Controller
53 /**
54  * \ingroup bmcCtrl
55  */
56 class bmcCtrl : public MagAOXApp<true>, public dev::dm<bmcCtrl,float>, public dev::shmimMonitor<bmcCtrl>
57 {
58 
59  //Give the test harness access.
60  friend class bmcCtrl_test;
61 
62  friend class dev::dm<bmcCtrl,float>;
63 
64  friend class dev::shmimMonitor<bmcCtrl>;
65 
66  typedef float realT; ///< This defines the datatype used to signal the DM using the ImageStreamIO library.
67 
70 
71 
72 protected:
73 
74  /** \name Configurable Parameters
75  *@{
76  */
77 
78  std::string m_serialNumber; ///< The BMC serial number used to open the correct DM profile
79 
80  long m_satThresh {100000} ;///< Threshold above which to log saturation.
81 
82  ///@}
83 
84  long m_nsat {0};
85 public:
86  /// Default c'tor.
87  bmcCtrl();
88 
89  /// D'tor.
90  ~bmcCtrl() noexcept;
91 
92  /// Setup the configuration system.
93  virtual void setupConfig();
94 
95  /// Implementation of loadConfig logic, separated for testing.
96  /** This is called by loadConfig().
97  */
98  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
99 
100  /// Load the configuration
101  virtual void loadConfig();
102 
103  /// Startup function
104  /** Sets up INDI, and starts the shmim thread.
105  *
106  */
107  virtual int appStartup();
108 
109  /// Implementation of the FSM for bmcCtrl.
110  /**
111  * \returns 0 on no critical error
112  * \returns -1 on an error requiring shutdown
113  */
114  virtual int appLogic();
115 
116  /// Shutdown the app.
117  /**
118  *
119  */
120  virtual int appShutdown();
121 
122  /// Cleanup after a power off.
123  /**
124  */
125  virtual int onPowerOff();
126 
127  /// Maintenace while powered off.
128  /**
129  */
130  virtual int whilePowerOff();
131 
132  /** \name DM Base Class Interface
133  *
134  *@{
135  */
136 
137  /// Initialize the DM and prepare for operation.
138  /** Application is in state OPERATING upon successful conclusion.
139  *
140  * \returns 0 on success
141  * \returns -1 on error
142  */
143  int initDM();
144 
145  /// Zero all commands on the DM
146  /** This does not update the shared memory buffer.
147  *
148  * \returns 0 on success
149  * \returns -1 on error
150  */
151  int zeroDM();
152 
153  /// Send a command to the DM
154  /** This is called by the shmim monitoring thread in response to a semaphore trigger.
155  *
156  * \returns 0 on success
157  * \returns -1 on error
158  */
159  int commandDM(void * curr_src);
160 
161  /// Release the DM, making it safe to turn off power.
162  /** The application will be state READY at the conclusion of this.
163  *
164  * \returns 0 on success
165  * \returns -1 on error
166  */
167  int releaseDM();
168 
169  ///@}
170 
171  /** \name BMC Interface
172  *@{
173  */
174 
175 protected:
176  double m_act_gain {0}; ///< Actuator gain (microns/volt)
177  double m_volume_factor {0}; ///< the volume factor to convert from displacement to commands
178  uint32_t m_nbAct {0}; ///< The number of actuators
179 
180  int * m_actuator_mapping {nullptr}; ///< Array containing the mapping from 2D grid position to linear index in the command vector
181 
182  double * m_dminputs {nullptr}; ///< Pre-allocated command vector, used only in commandDM
183 
184  DM m_dm = {}; ///< BMC SDK handle for the DM.
185 
186  bool m_dmopen {false}; ///< Track whether the DM connection has been opened
187 
188 public:
189 
190  /// Parse the BMC calibration file
191  /** \returns 0 on success
192  * \returns -1 on error
193  */
194  int parse_calibration_file();
195 
196  /// Read the actuator mapping from a FITS file
197  /**
198  * \todo convert this to use mxlib::fitsFile
199  *
200  * \returns 0 on success
201  * \returns -1 on error
202  */
203  int get_actuator_mapping();
204 
205  ///@}
206 
207 
208 };
209 
210 bmcCtrl::bmcCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
211 {
212  m_powerMgtEnabled = true;
213  return;
214 }
215 
217 {
219  if(m_dminputs) free(m_dminputs);
220 
221 }
222 
224 {
225  config.add("dm.serialNumber", "", "dm.serialNumber", argType::Required, "dm", "serialNumber", false, "string", "The BMC serial number used to find correct DM Profile.");
226  config.add("dm.calibRelDir", "", "dm.calibRelDir", argType::Required, "dm", "calibRelDir", false, "string", "Used to find the default config directory.");
227  config.add("dm.satThresh", "", "dm.satThresh", argType::Required, "dm", "satThresh", false, "string", "Threshold above which to log saturation.");
228 
230 
231 }
232 
233 int bmcCtrl::loadConfigImpl( mx::app::appConfigurator & _config )
234 {
235  config(m_calibRelDir, "dm.calibRelDir");
236  config(m_serialNumber, "dm.serialNumber");
237  config(m_satThresh, "dm.satThresh");
238 
239  //m_calibRelDir = "dm/bmc_2k";
240 
242 
243  return 0;
244 }
245 
247 {
248  loadConfigImpl(config);
249 
250 }
251 
253 {
254  if(parse_calibration_file() < 0)
255  {
256  log<software_critical>({__FILE__,__LINE__});
257  return -1;
258  }
259 
260  if(m_act_gain == 0 || m_volume_factor == 0)
261  {
262  log<software_critical>({__FILE__,__LINE__, "calibration not loaded properly"});
263  return -1;
264  }
265 
267 
269 
270  return 0;
271 }
272 
274 {
276 
278 
279  if(state()==stateCodes::POWEROFF) return 0;
280 
282  {
283  sleep(5);
284  std::cerr << "initing DM" << std::endl;
285  return initDM();
286  }
287 
288  if(m_nsat > m_satThresh)
289  {
290  log<text_log>("Saturated actuators in last second: " + std::to_string(m_nsat), logPrio::LOG_WARNING);
291  }
292 
293  m_nsat = 0;
294 
295  return 0;
296 }
297 
299 {
300  if(m_dmopen) releaseDM();
301 
303 
305 
306  return 0;
307 }
308 
310 {
312 }
313 
315 {
317 }
318 
320 {
321  if(m_dmopen)
322  {
323  log<text_log>("DM is already initialized. Release first.", logPrio::LOG_ERROR);
324  return -1;
325  }
326 
327  std::string ser = mx::ioutils::toUpper(m_serialNumber);
328  BMCRC ret = NO_ERR;
329  ret = BMCOpen(&m_dm, ser.c_str());
330 
331  if(ret == NO_ERR) m_dmopen = true; // remember that the DM connection has been opened
332 
333  if(ret != NO_ERR)
334  {
335  const char *err;
336  err = BMCErrorString(ret);
337  log<text_log>(std::string("DM initialization failed: ") + err, logPrio::LOG_ERROR);
338 
339  m_dm = {};
340  return -1;
341  }
342 
343  if (!m_dmopen)
344  {
345  log<text_log>("DM initialization failed. Couldn't open DM handle.", logPrio::LOG_ERROR);
346  return -1;
347  }
348 
349  log<text_log>("BMC " + m_serialNumber + " initialized", logPrio::LOG_NOTICE);
350 
351  // Get number of actuators
352  m_nbAct = m_dm.ActCount;
353 
354 
355  // Load the DM map
356  uint32_t *map_lut;
357  map_lut = (uint32_t *)malloc(sizeof(uint32_t)*MAX_DM_SIZE);
358  ret = BMCLoadMap(&m_dm, NULL, map_lut);
359 
360  if(ret != NO_ERR)
361  {
362  const char *err;
363  err = BMCErrorString(ret);
364  log<text_log>(std::string("DM initialization failed. Couldn't load map.") + err, logPrio::LOG_ERROR);
365 
366  m_dm = {};
367  return -1;
368  }
369 
370 
371  if(m_dminputs) free(m_dminputs);
372  m_dminputs = (double*) calloc( m_nbAct, sizeof( double ) );
373 
374  if(zeroDM() < 0)
375  {
376  log<text_log>("DM initialization failed. Error zeroing DM.", logPrio::LOG_ERROR);
377  return -1;
378  }
379 
380  /* get actuator mapping from 2D cacao image to 1D vector for BMC input */
382  m_actuator_mapping = (int *) malloc(m_nbAct * sizeof(int)); /* memory for actuator mapping */
383 
384  /* initialize to -1 to allow for handling addressable but ignored actuators */
385  for (uint32_t idx = 0; idx < m_nbAct; ++idx)
386  {
387  m_actuator_mapping[idx] = -1;
388  }
389 
390  if(get_actuator_mapping() < 0)
391  {
392  log<text_log>("DM initialization failed. Failed to get actuator mapping.", logPrio::LOG_ERROR);
393  return -1;
394  }
395 
396  if(m_actuator_mapping == nullptr)
397  {
398  log<text_log>("DM initialization failed. null pointer.", logPrio::LOG_ERROR);
399  return -1;
400  }
401 
403 
404  return 0;
405 }
406 
408 {
409  if(!m_dmopen)
410  {
411  log<text_log>("DM not initialized (NULL pointer)", logPrio::LOG_ERROR);
412  return -1;
413  }
414 
415  if(m_nbAct == 0)
416  {
417  log<text_log>("DM not initialized (number of actuators)", logPrio::LOG_ERROR);
418  return -1;
419  }
420 
421  double * dminputs = (double*) calloc( m_nbAct, sizeof( double ) );
422 
423  /* Send the all 0 command to the DM */
424  BMCRC ret = BMCSetArray(&m_dm, dminputs, NULL);
425 
426  /* Release memory */
427  free( dminputs );
428 
429  if(ret != NO_ERR)
430  {
431  const char *err;
432  err = BMCErrorString(ret);
433  log<text_log>(std::string("Error zeroing DM: ") + err, logPrio::LOG_ERROR);
434  return -1;
435  }
436 
437  log<text_log>("DM zeroed");
438  return 0;
439 }
440 
441 int bmcCtrl::commandDM(void * curr_src)
442 {
443  //This is based on Kyle Van Gorkoms original sendCommand function.
444 
445  /*This loop performs the following steps:
446  1) converts from float to double
447  2) convert to volume-normalized displacement
448  3) convert to squared fractional voltage clamped from 0 to 1.
449  */
450 
451  #ifdef XWC_DMTIMINGS
452  dmT::m_tact0 = mx::sys::get_curr_time();
453  #endif
454 
455  for (uint32_t idx = 0; idx < m_nbAct; ++idx)
456  {
457  int address = m_actuator_mapping[idx];
458  if(address == -1)
459  {
460  m_dminputs[idx] = 0.; // addressable but ignored actuators set to 0
461  }
462  else
463  {
464  m_dminputs[idx] = ((double) (static_cast<realT *>(curr_src)[address])) * m_volume_factor/m_act_gain;
465 
466  if (m_dminputs[idx] > 1)
467  {
468  m_dminputs[idx] = 1;
469  }
470  else if (m_dminputs[idx] < 0)
471  {
472  m_dminputs[idx] = 0;
473  }
474  else
475  {
476  m_dminputs[idx] = sqrt(m_dminputs[idx]);
477  }
478  }
479  }
480 
481  #ifdef XWC_DMTIMINGS
482  dmT::m_tact1 = mx::sys::get_curr_time();
483  #endif
484 
485  /* Send the command to the DM */
486  BMCRC ret = BMCSetArray(&m_dm, m_dminputs, NULL);
487 
488  #ifdef XWC_DMTIMINGS
489  dmT::m_tact2 = mx::sys::get_curr_time();
490  #endif
491 
492  /* Return immediately upon error, logging the error
493  message first and then return the failure code. */
494  if(ret != NO_ERR)
495  {
496  const char *err;
497  err = BMCErrorString(ret);
498  log<text_log>(std::string("DM command failed: ") + err, logPrio::LOG_ERROR);
499  return -1;
500  }
501 
502  #ifdef XWC_DMTIMINGS
503  dmT::m_tact3 = mx::sys::get_curr_time();
504  #endif
505 
506  /* Now update the instantaneous sat map */
507  for (uint32_t idx = 0; idx < m_nbAct; ++idx)
508  {
509  int address = m_actuator_mapping[idx];
510 
511  if(address == -1)
512  {
513  continue;
514  }
515  else if(m_dminputs[idx] >= 1 || m_dminputs[idx] <= 0)
516  {
517  ++m_nsat;
518  m_instSatMap.data()[address] = 1;
519  }
520  else
521  {
522  m_instSatMap.data()[address] = 0;
523  }
524  }
525 
526  #ifdef XWC_DMTIMINGS
527  dmT::m_tact4 = mx::sys::get_curr_time();
528  #endif
529 
530  return ret;
531 }
532 
534 {
535  // Safe DM shutdown on interrupt
536 
537  if(!m_dmopen)
538  {
539  return 0;
540  }
541 
543 
544  if(!shutdown())
545  {
546  pthread_kill(m_smThread.native_handle(), SIGUSR1);
547  }
548 
549  sleep(1);
550 
551  if(zeroDM() < 0)
552  {
553  log<text_log>("DM release failed. Error zeroing DM.", logPrio::LOG_ERROR);
554  return -1;
555  }
556 
557  // Zero all actuators (this is probably redundant after zeroing the DM above)
558  BMCRC ret = NO_ERR;
559  ret = BMCClearArray(&m_dm);
560 
561  if(ret != NO_ERR)
562  {
563  const char *err;
564  err = BMCErrorString(ret);
565  log<text_log>(std::string("DM reset failed: ") + err, logPrio::LOG_ERROR);
566  return -1;
567  }
568 
569  // Close BMC connection
570  ret = BMCClose(&m_dm);
571 
572  if(ret == NO_ERR) m_dmopen = false;
573 
574  if(ret != NO_ERR)
575  {
576  const char *err;
577  err = BMCErrorString(ret);
578  log<text_log>(std::string("DM release failed: ") + err, logPrio::LOG_ERROR);
579  return -1;
580  }
581 
582  m_dm = {};
583 
584  log<text_log>("BMC " + m_serialNumber + " reset and released", logPrio::LOG_NOTICE);
585 
586  return 0;
587 }
588 
589 /* Read in a configuration file with user-calibrated
590 values to determine the conversion from physical to
591 fractional stroke as well as the volume displaced by
592 the influence function. */
594 {
595  FILE * fp;
596  char * line = NULL;
597  size_t len = 0;
598  ssize_t read;
599  double * calibvals;
600 
601  std::string calibpath = m_calibPath + "/" + "bmc_2k_userconfig.txt";
602 
603  // open file
604  fp = fopen(calibpath.c_str(), "r");
605  if (fp == NULL)
606  {
607  log<text_log>("Could not read configuration file at " + calibpath, logPrio::LOG_ERROR);
608  return -1;
609  }
610 
611  calibvals = (double*) malloc(2*sizeof(double));
612  int idx = 0;
613  while ((read = getline(&line, &len, fp)) != -1)
614  {
615  // grab first value from each line
616  calibvals[idx] = strtod(line, NULL);
617  idx++;
618  }
619 
620  fclose(fp);
621 
622  // assign stroke and volume factors
623  m_act_gain = calibvals[0];
624  m_volume_factor = calibvals[1];
625 
626  free(calibvals);
627 
628  log<text_log>("BMC " + m_serialNumber + ": Using stroke and volume calibration from " + calibpath);
629  std::cerr << m_act_gain << " " << m_volume_factor << "\n";
630  return 0;
631 }
632 
633 int bmcCtrl::get_actuator_mapping() //const char * serial, int nbAct, int * actuator_mapping)
634 {
635  /* This function closely follows the CFITSIO imstat
636  example */
637 
638  fitsfile *fptr; /* FITS file pointer */
639  int status = 0; /* CFITSIO status value MUST be initialized to zero! */
640 
641 
642  // get file path to actuator map
643  std::string calibpath = m_calibPath + "/" + "bmc_2k_actuator_mapping.fits";
644 
645  if ( !fits_open_image(&fptr, calibpath.c_str(), READONLY, &status) )
646  {
647  int hdutype, naxis;
648  long naxes[2];
649 
650  if (fits_get_hdu_type(fptr, &hdutype, &status) || hdutype != IMAGE_HDU) {
651  printf("Error: this program only works on images, not tables\n");
652  return(1);
653  }
654 
655  fits_get_img_dim(fptr, &naxis, &status);
656  fits_get_img_size(fptr, 2, naxes, &status);
657 
658  if (status || naxis != 2) {
659  printf("Error: NAXIS = %d. Only 2-D images are supported.\n", naxis);
660  return(1);
661  }
662 
663  int * pix = (int *) malloc(naxes[0] * sizeof(int)); /* memory for 1 row */
664 
665  if (pix == NULL) {
666  printf("Memory allocation error\n");
667  return(1);
668  }
669 
670  long fpixel[2];
671  //totpix = naxes[0] * naxes[1];
672  fpixel[0] = 1; /* read starting with first pixel in each row */
673 
674  /* process image one row at a time; increment row # in each loop */
675  int ij = 0;/* actuator mapping index */
676  for (fpixel[1] = 1; fpixel[1] <= naxes[1]; fpixel[1]++)
677  {
678  /* give starting pixel coordinate and number of pixels to read */
679  if (fits_read_pix(fptr, TINT, fpixel, naxes[0],0, pix,0, &status))
680  break; /* jump out of loop on error */
681 
682  // get indices of active actuators in order
683  for (int ii = 0; ii < naxes[0]; ii++) {
684  if (pix[ii] > 0) {
685  m_actuator_mapping[pix[ii] - 1] = ij;
686  }
687  ij++;
688  }
689  }
690  fits_close_file(fptr, &status);
691 
692  free(pix);
693  }
694 
695  if (status) {
696  fits_report_error(stderr, status); /* print any error message */
697  }
698 
699 
700  log<text_log>("BMC " + m_serialNumber + ": Using actuator mapping from " + calibpath);
701  return 0;
702 }
703 
704 } //namespace app
705 } //namespace MagAOX
706 
707 #endif //bmcCtrl_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:73
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
int shutdown()
Get the value of the shutdown flag.
Definition: MagAOXApp.hpp:1182
The MagAO-X BMC DM Controller.
Definition: bmcCtrl.hpp:57
uint32_t m_nbAct
The number of actuators.
Definition: bmcCtrl.hpp:178
double m_act_gain
Actuator gain (microns/volt)
Definition: bmcCtrl.hpp:176
bool m_dmopen
Track whether the DM connection has been opened.
Definition: bmcCtrl.hpp:186
bmcCtrl()
Default c'tor.
Definition: bmcCtrl.hpp:210
virtual int whilePowerOff()
Maintenace while powered off.
Definition: bmcCtrl.hpp:314
DM m_dm
BMC SDK handle for the DM.
Definition: bmcCtrl.hpp:184
int commandDM(void *curr_src)
Send a command to the DM.
Definition: bmcCtrl.hpp:441
dev::shmimMonitor< bmcCtrl > shmimMonitorT
Definition: bmcCtrl.hpp:69
int initDM()
Initialize the DM and prepare for operation.
Definition: bmcCtrl.hpp:319
virtual void setupConfig()
Setup the configuration system.
Definition: bmcCtrl.hpp:223
double * m_dminputs
Pre-allocated command vector, used only in commandDM.
Definition: bmcCtrl.hpp:182
std::string m_serialNumber
The BMC serial number used to open the correct DM profile.
Definition: bmcCtrl.hpp:78
double m_volume_factor
the volume factor to convert from displacement to commands
Definition: bmcCtrl.hpp:177
virtual int appLogic()
Implementation of the FSM for bmcCtrl.
Definition: bmcCtrl.hpp:273
long m_satThresh
Threshold above which to log saturation.
Definition: bmcCtrl.hpp:80
virtual int appShutdown()
Shutdown the app.
Definition: bmcCtrl.hpp:298
virtual void loadConfig()
Load the configuration.
Definition: bmcCtrl.hpp:246
friend class bmcCtrl_test
Definition: bmcCtrl.hpp:60
int releaseDM()
Release the DM, making it safe to turn off power.
Definition: bmcCtrl.hpp:533
int zeroDM()
Zero all commands on the DM.
Definition: bmcCtrl.hpp:407
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition: bmcCtrl.hpp:233
virtual int onPowerOff()
Cleanup after a power off.
Definition: bmcCtrl.hpp:309
float realT
This defines the datatype used to signal the DM using the ImageStreamIO library.
Definition: bmcCtrl.hpp:66
int * m_actuator_mapping
Array containing the mapping from 2D grid position to linear index in the command vector.
Definition: bmcCtrl.hpp:180
int get_actuator_mapping()
Read the actuator mapping from a FITS file.
Definition: bmcCtrl.hpp:633
virtual int appStartup()
Startup function.
Definition: bmcCtrl.hpp:252
dev::dm< bmcCtrl, float > dmT
Definition: bmcCtrl.hpp:68
int parse_calibration_file()
Parse the BMC calibration file.
Definition: bmcCtrl.hpp:593
~bmcCtrl() noexcept
D'tor.
Definition: bmcCtrl.hpp:216
std::string m_calibPath
The path to this DM's calibration files.
Definition: dm.hpp:76
std::string m_calibRelDir
The directory relative to the calibPath. Set this before calling dm<derivedT,realT>::loadConfig().
Definition: dm.hpp:107
int appShutdown()
DM shutdown.
Definition: dm.hpp:880
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:313
int loadConfig(mx::app::appConfigurator &config)
load the configuration system results
Definition: dm.hpp:616
int whilePowerOff()
DM Poweroff Updates.
Definition: dm.hpp:906
int appStartup()
Startup function.
Definition: dm.hpp:689
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
Definition: dm.hpp:574
int onPowerOff()
DM Poweroff.
Definition: dm.hpp:898
int appLogic()
DM application logic.
Definition: dm.hpp:823
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:55
@ POWEROFF
The device power is off.
Definition: stateCodes.hpp:47
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
@ POWERON
The device power is on.
Definition: stateCodes.hpp:48
std::ostream & cerr()
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