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
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
53protected:
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
69public:
70 /// Default c'tor.
71 alpaoCtrl();
72
73 /// D'tor.
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 */
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
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
171public:
172
173 /// Parse the ALPAO calibration file
174 /** \returns 0 on success
175 * \returns -1 on error
176 */
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 */
187
188 ///@}
189};
190
191alpaoCtrl::alpaoCtrl() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
192{
193 m_powerMgtEnabled = true;
194 return;
195}
196
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
213int 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
224
225 return 0;
226}
227
229{
230 loadConfigImpl(config);
231
232
233}
234
236{
237 if(parse_calibration_file() < 0)
238 {
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
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
297
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
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
405int alpaoCtrl::commandDM(void * curr_src)
406{
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 {
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 */
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 {
454 }
455 else
456 {
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
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
519values to determine the conversion from physical to
520fractional stroke as well as the volume displaced by
521the influence function. */
522int 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;
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
555
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
563int 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
584 printf("Error: this program only works on images, not tables\n");
585 return(1);
586 }
587
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 }
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:73
stateCodes::stateCodeT state()
Get the current state code.
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:59
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:51
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.
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.
long m_satThresh
Threshold above which to log saturation.
Definition alpaoCtrl.hpp:61
virtual void loadConfig()
Load the configuration.
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 appStartup()
Startup function.
Definition dm.hpp:689
int setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
Definition dm.hpp:574
int appLogic()
DM application logic.
Definition dm.hpp:823
std::thread m_smThread
A separate thread for the actual monitoring.
@ OPERATING
The device is operating, other than homing.
@ POWEROFF
The device power is off.
@ READY
The device is ready for operation, but is not operating.
@ POWERON
The device power is on.
Definition dm.hpp:24
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.
Software ERR log entry.