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