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