API
dm.hpp
Go to the documentation of this file.
1 /** \file dm.hpp
2  * \brief The MagAO-X generic deformable mirror controller.
3  *
4  * \author Jared R. Males (jaredmales@gmail.com)
5  *
6  * \ingroup app_files
7  */
8 
9 #ifndef dm_hpp
10 #define dm_hpp
11 
12 /** Tests
13  \todo test that restarting fpsCtrl doesn't scram this
14  */
15 
16 #include <mx/improc/eigenImage.hpp>
17 #include <mx/ioutils/fits/fitsFile.hpp>
18 
19 #include <boost/filesystem/operations.hpp>
20 
21 #include "../../ImageStreamIO/ImageStruct.hpp"
22 
23 namespace MagAOX
24 {
25 namespace app
26 {
27 namespace dev
28 {
29 
30 template<typename typeT>
31 constexpr uint8_t ImageStreamTypeCode()
32 {
33  return 0;
34 }
35 
36 template<>
37 constexpr uint8_t ImageStreamTypeCode<float>()
38 {
39  return _DATATYPE_FLOAT;
40 }
41 
42 template<>
43 constexpr uint8_t ImageStreamTypeCode<double>()
44 {
45  return _DATATYPE_DOUBLE;
46 }
47 
48 /** MagAO-X generic deformable mirror controller
49  *
50  *
51  * The derived class `derivedT` must expose the following interface
52  \code
53 
54  \endcode
55  * Each of the above functions should return 0 on success, and -1 on an error.
56  *
57  * This class should be declared a friend in the derived class, like so:
58  \code
59  friend class dev::dm<derivedT,realT>;
60  \endcode
61  *
62  * Calls to this class's `setupConfig`, `loadConfig`, `appStartup`, `appLogic`, `appShutdown`, and `udpdateINDI`
63  * functions must be placed in the derived class's functions of the same name.
64  *
65  * \ingroup appdev
66  */
67 template<class derivedT, typename realT>
68 class dm
69 {
70 
71 protected:
72 
73  /** \name Configurable Parameters
74  * @{
75  */
76 
77  std::string m_calibPath; ///< The path to this DM's calibration files.
78  std::string m_flatPath; ///< The path to this DM's flat files (usually the same as calibPath)
79  std::string m_testPath; ///< The path to this DM's test files (default is calibPath/tests;
80 
81  std::string m_flatDefault; ///< The file name of the this DM's default flat command. Path and extension will be ignored and can be omitted.
82  std::string m_testDefault; ///< The file name of the this DM's default test command. Path and extension will be ignored and can be omitted.
83 
84  std::string m_shmimFlat; ///< The name of the shmim stream to write the flat to.
85  std::string m_shmimTest; ///< The name of the shmim stream to write the test to.
86  std::string m_shmimSat; ///< The name of the shmim stream to write the saturation map to.
87  std::string m_shmimSatPerc; ///< The name of the shmim stream to write the saturation percentage map to.
88 
89  int m_satAvgInt {100}; ///< The time in milliseconds to accumulate saturation over.
90 
91  ///\todo satThreadPrio configuration is not actually implemented.
92  int m_satThreadPrio {0}; ///< Priority of the saturation thread, should normally be > 0.
93 
94  uint32_t m_dmWidth {0}; ///< The width of the images in the stream
95  uint32_t m_dmHeight {0}; ///< The height of the images in the stream
96 
97  static constexpr uint8_t m_dmDataType = ImageStreamTypeCode<realT>(); ///< The ImageStreamIO type code.
98 
99  float m_percThreshold {0.98}; //percentage of frames saturated over interval
100  float m_intervalSatThreshold {0.50}; //
102 
103  std::vector<std::string> m_satTriggerDevice;
104  std::vector<std::string> m_satTriggerProperty;
105 
106  ///@}
107 
108 
109  std::string m_calibRelDir; ///< The directory relative to the calibPath. Set this before calling dm<derivedT,realT>::loadConfig().
110 
111  int m_channels {0}; ///< The number of dmcomb channels found as part of allocation.
112 
113  std::map<std::string, std::string> m_flatCommands; ///< Map of flat file name to full path
114  std::string m_flatCurrent; ///< The name of the current flat command
115 
116  mx::improc::eigenImage<realT> m_flatCommand; ///< Data storage for the flat command
117  bool m_flatLoaded {false}; ///< Flag indicating whether a flat is loaded in memory
118 
119  IMAGE m_flatImageStream; ///< The ImageStreamIO shared memory buffer for the flat.
120  bool m_flatSet {false}; ///< Flag indicating whether the flat command has been set.
121 
122 
123  std::map<std::string, std::string> m_testCommands; ///< Map of test file name to full path
124  std::string m_testCurrent;
125 
126  mx::improc::eigenImage<realT> m_testCommand; ///< Data storage for the test command
127  bool m_testLoaded {false}; ///< Flag indicating whether a test command is loaded in memory
128 
129  IMAGE m_testImageStream; ///< The ImageStreamIO shared memory buffer for the test.
130  bool m_testSet {false}; ///< Flag indicating whether the test command has been set.
131 
132  int m_overSatAct {0}; //counter
133  int m_intervalSatExceeds {0}; //counter
134  bool m_intervalSatTrip {0}; //flag to trip the loop opening
135 
136 public:
137 
138  /// Setup the configuration system
139  /**
140  * This should be called in `derivedT::setupConfig` as
141  * \code
142  dm<derivedT,realT>::setupConfig(config);
143  \endcode
144  * with appropriate error checking.
145  */
146  void setupConfig(mx::app::appConfigurator & config /**< [out] the derived classes configurator*/);
147 
148  /// load the configuration system results
149  /**
150  * This should be called in `derivedT::loadConfig` as
151  * \code
152  dm<derivedT,realT>::loadConfig(config);
153  \endcode
154  * with appropriate error checking.
155  */
156  void loadConfig(mx::app::appConfigurator & config /**< [in] the derived classes configurator*/);
157 
158  /// Startup function
159  /**
160  * This should be called in `derivedT::appStartup` as
161  * \code
162  dm<derivedT,realT>::appStartup();
163  \endcode
164  * with appropriate error checking.
165  *
166  * \returns 0 on success
167  * \returns -1 on error, which is logged.
168  */
169  int appStartup();
170 
171  /// DM application logic
172  /** This should be called in `derivedT::appLogic` as
173  * \code
174  dm<derivedT,realT>::appLogic();
175  \endcode
176  * with appropriate error checking.
177  *
178  * \returns 0 on success
179  * \returns -1 on error, which is logged.
180  */
181  int appLogic();
182 
183  /// DM shutdown
184  /** This should be called in `derivedT::appShutdown` as
185  * \code
186  dm<derivedT,realT>::appShutdown();
187  \endcode
188  * with appropriate error checking.
189  *
190  * \returns 0 on success
191  * \returns -1 on error, which is logged.
192  */
193  int appShutdown();
194 
195  /// DM Poweroff
196  /** This should be called in `derivedT::onPowerOff` as
197  * \code
198  dm<derivedT,realT>::onPowerOff();
199  \endcode
200  * with appropriate error checking.
201  *
202  * \returns 0 on success
203  * \returns -1 on error, which is logged.
204  */
205  int onPowerOff();
206 
207  /// DM Poweroff Updates
208  /** This should be called in `derivedT::whilePowerOff` as
209  * \code
210  dm<derivedT,realT>::whilePowerOff();
211  \endcode
212  * with appropriate error checking.
213  *
214  * \returns 0 on success
215  * \returns -1 on error, which is logged.
216  */
218 
219  /// Find the DM comb channels
220  /** Introspectively finds all dmXXdispYY channels, zeroes them, and raises the semapahore
221  * on the last to cause dmcomb to update.
222  */
224 
225  /// Called after shmimMonitor connects to the dmXXdisp stream. Checks for proper size.
226  /**
227  * \returns 0 on success
228  * \returns -1 if incorrect size or data type in stream.
229  */
230  int allocate( const dev::shmimT & sp);
231 
232  /// Called by shmimMonitor when a new DM command is available. This is just a pass-through to derivedT::commandDM(char*).
233  int processImage( void * curr_src,
234  const dev::shmimT & sp
235  );
236 
237  /// Calls derived()->releaseDM() and then 0s all channels and the sat map.
238  /** This is called by the relevant INDI callback
239  *
240  * \returns 0 on success
241  * \returns -1 on error
242  */
243  int releaseDM();
244 
245  /// Check the flats directory and update the list of flats if anything changes
246  /** This is called once per appLogic and whilePowerOff loops.
247  *
248  * \returns 0 on success
249  * \returns -1 on error
250  */
251  int checkFlats();
252 
253  /// Load a flat file
254  /** Uses the target argument for lookup in m_flatCommands to find the path
255  * and loads the command in the local memory. Calls setFlat if the flat
256  * is currently set.
257  *
258  * \returns 0 on success
259  * \returns -1 on error
260  */
261  int loadFlat(const std::string & target /**< [in] the name of the flat to load */);
262 
263  /// Send the current flat command to the DM
264  /** Writes the command to the designated shmim.
265  *
266  * \returns 0 on success
267  * \returns -1 on error
268  */
269  int setFlat( bool update=false /**< [in] If true, this is an update rather than a new set*/);
270 
271  /// Zero the flat command on the DM
272  /** Writes a 0 array the designated shmim.
273  *
274  * \returns 0 on success
275  * \returns -1 on error
276  */
277  int zeroFlat();
278 
279  /// Check the tests directory and update the list of tests if anything changes
280  /** This is called once per appLogic and whilePowerOff loops.
281  *
282  * \returns 0 on success
283  * \returns -1 on error
284  */
285  int checkTests();
286 
287  /// Load a test file
288  /** Uses the target argument for lookup in m_testCommands to find the path
289  * and loads the command in the local memory. Calls setTest if the test
290  * is currently set.
291  */
292  int loadTest(const std::string & target);
293 
294  /// Send the current test command to the DM
295  /** Writes the command to the designated shmim.
296  *
297  * \returns 0 on success
298  * \returns -1 on error
299  */
300  int setTest();
301 
302  /// Zero the test command on the DM
303  /** Writes a 0 array the designated shmim.
304  *
305  * \returns 0 on success
306  * \returns -1 on error
307  */
308  int zeroTest();
309 
310  /// Zero all channels
311  /**
312  * \returns 0 on sucess
313  * \returns <0 on an error
314  */
315  int zeroAll(bool nosem = false /**< [in] [optional] if true then the semaphore is not raised after zeroing all channels*/);
316 
317 protected:
318 
319  mx::improc::eigenImage<uint8_t> m_instSatMap; ///< The instantaneous saturation map, 0/1, set by the commandDM() function of the derived class.
320  mx::improc::eigenImage<uint16_t> m_accumSatMap; ///< The accumulated saturation map, which acccumulates for m_satAvgInt then is publised as a 0/1 image.
321  mx::improc::eigenImage<float> m_satPercMap; ///< Map of the percentage of time each actator was saturated during the avg. interval.
322 
323  IMAGE m_satImageStream; ///< The ImageStreamIO shared memory buffer for the sat map.
324  IMAGE m_satPercImageStream; ///< The ImageStreamIO shared memory buffer for the sat percentage map.
325 
326  /// Clear the saturation maps and zero the shared membory.
327  /**
328  * \returns 0 on success
329  * \returns -1 on error
330  */
331  int clearSat();
332 
333  /** \name Saturation Thread
334  * This thread processes the saturation maps
335  * @{
336  */
337 
338  sem_t m_satSemaphore; ///< Semaphore used to tell the saturation thread to run.
339 
340  bool m_satThreadInit {true}; ///< Synchronizer for thread startup, to allow priority setting to finish.
341 
342  pid_t m_satThreadID {0}; ///< The ID of the saturation thread.
343 
344  pcf::IndiProperty m_satThreadProp; ///< The property to hold the saturation thread details.
345 
346  std::thread m_satThread; ///< A separate thread for the actual saturation processing
347 
348  ///Thread starter, called by MagAOXApp::threadStart on thread construction. Calls satThreadExec.
349  static void satThreadStart( dm * d /**< [in] a pointer to a dm instance (normally this) */);
350 
351  /// Execute saturation processing
353 
355  {
356  if(m_satTriggerDevice.size() > 0 && m_satTriggerProperty.size() == m_satTriggerDevice.size())
357  {
358  for(size_t n=0; n < m_satTriggerDevice.size(); ++n)
359  {
360  //We just silently fail
361  try
362  {
363  pcf::IndiProperty ipFreq(pcf::IndiProperty::Switch);
364 
365  ipFreq.setDevice(m_satTriggerDevice[n]);
366  ipFreq.setName(m_satTriggerProperty[n]);
367  ipFreq.add(pcf::IndiElement("toggle"));
368  ipFreq["toggle"] = pcf::IndiElement::Off;
369  derived().sendNewProperty(ipFreq);
370 
371  derivedT::template log<text_log>("DM saturation threshold exceeded. Loop opened.", logPrio::LOG_WARNING);
372  }
373  catch(...)
374  {
375 
376  }
377  }
378  }
379  }
380 
381  ///@}
382 
383 protected:
384 
385  /** \name INDI
386  *
387  *@{
388  */
389 protected:
390  //declare our properties
391 
392  pcf::IndiProperty m_indiP_flat; ///< Property used to set and report the current flat
393 
394  pcf::IndiProperty m_indiP_init;
395  pcf::IndiProperty m_indiP_zero;
396  pcf::IndiProperty m_indiP_release;
397 
398  pcf::IndiProperty m_indiP_flats; ///< INDI Selection switch containing the flat files.
399  pcf::IndiProperty m_indiP_flatShmim; ///< Publish the shmim being used for the flat
400  pcf::IndiProperty m_indiP_setFlat; ///< INDI toggle switch to set the current flat.
401 
402 
403  pcf::IndiProperty m_indiP_tests; ///< INDI Selection switch containing the test pattern files.
404  pcf::IndiProperty m_indiP_testShmim; ///< Publish the shmim being used for the test command
405  pcf::IndiProperty m_indiP_setTest; ///< INDI toggle switch to set the current test pattern.
406 
407  pcf::IndiProperty m_indiP_zeroAll;
408 
409 public:
410 
411  /// The static callback function to be registered for initializing the DM.
412  /**
413  * \returns 0 on success.
414  * \returns -1 on error.
415  */
416  static int st_newCallBack_init( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
417  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
418  );
419 
420  /// The callback called by the static version, to actually process the new request.
421  /**
422  * \returns 0 on success.
423  * \returns -1 on error.
424  */
425  int newCallBack_init( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
426 
427 
428  /// The static callback function to be registered for initializing the DM.
429  /**
430  * \returns 0 on success.
431  * \returns -1 on error.
432  */
433  static int st_newCallBack_zero( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
434  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
435  );
436 
437  /// The callback called by the static version, to actually process the new request.
438  /**
439  * \returns 0 on success.
440  * \returns -1 on error.
441  */
442  int newCallBack_zero( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
443 
444 
445  /// The static callback function to be registered for initializing the DM.
446  /**
447  * \returns 0 on success.
448  * \returns -1 on error.
449  */
450  static int st_newCallBack_release( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
451  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
452  );
453 
454  /// The callback called by the static version, to actually process the new request.
455  /**
456  * \returns 0 on success.
457  * \returns -1 on error.
458  */
459  int newCallBack_release( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
460 
461 
462  /// The static callback function to be registered for selecting the flat file
463  /**
464  * \returns 0 on success.
465  * \returns -1 on error.
466  */
467  static int st_newCallBack_flats( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
468  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
469  );
470 
471  /// The callback called by the static version, to actually process the new request.
472  /**
473  * \returns 0 on success.
474  * \returns -1 on error.
475  */
476  int newCallBack_flats( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
477 
478  /// The static callback function to be registered for setting the flat
479  /**
480  * \returns 0 on success.
481  * \returns -1 on error.
482  */
483  static int st_newCallBack_setFlat( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
484  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
485  );
486 
487  /// The callback called by the static version, to actually process the new request.
488  /**
489  * \returns 0 on success.
490  * \returns -1 on error.
491  */
492  int newCallBack_setFlat( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
493 
494 
495 
496 
497 
498 
499  /// The static callback function to be registered for selecting the test file
500  /**
501  * \returns 0 on success.
502  * \returns -1 on error.
503  */
504  static int st_newCallBack_tests( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
505  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
506  );
507 
508  /// The callback called by the static version, to actually process the new request.
509  /**
510  * \returns 0 on success.
511  * \returns -1 on error.
512  */
513  int newCallBack_tests( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
514 
515 
516  /// The static callback function to be registered for setting the test shape
517  /**
518  * \returns 0 on success.
519  * \returns -1 on error.
520  */
521  static int st_newCallBack_setTest( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
522  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
523  );
524 
525  /// The callback called by the static version, to actually process the new request.
526  /**
527  * \returns 0 on success.
528  * \returns -1 on error.
529  */
530  int newCallBack_setTest( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
531 
532  /// The static callback function to be registered for zeroing all channels
533  /**
534  * \returns 0 on success.
535  * \returns -1 on error.
536  */
537  static int st_newCallBack_zeroAll( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
538  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
539  );
540 
541  /// The callback for the zeroAll toggle switch, called by the static version
542  /**
543  * \returns 0 on success.
544  * \returns -1 on error.
545  */
546  int newCallBack_zeroAll( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
547 
548 
549 
550  /// Update the INDI properties for this device controller
551  /** You should call this once per main loop.
552  * It is not called automatically.
553  *
554  * \returns 0 on success.
555  * \returns -1 on error.
556  */
557  int updateINDI();
558 
559  ///@}
560 
561 private:
562  derivedT & derived()
563  {
564  return *static_cast<derivedT *>(this);
565  }
566 };
567 
568 template<class derivedT, typename realT>
569 void dm<derivedT,realT>::setupConfig(mx::app::appConfigurator & config)
570 {
571  config.add("dm.calibPath", "", "dm.calibPath", argType::Required, "dm", "calibPath", false, "string", "The path to calibration files, relative to the MagAO-X calibration path.");
572 
573  config.add("dm.flatPath", "", "dm.flatPath", argType::Required, "dm", "flatPath", false, "string", "The path to flat files. Default is the calibration path.");
574  config.add("dm.flatDefault", "", "dm.flatDefault", argType::Required, "dm", "flatDefault", false, "string", "The default flat file (path and extension are not required).");
575 
576  config.add("dm.testPath", "", "dm.testPath", argType::Required, "dm", "testPath", false, "string", "The path to test files. Default is the calibration path plus /tests.");
577  config.add("dm.testDefault", "", "dm.testDefault", argType::Required, "dm", "testDefault", false, "string", "The default test file (path and extension are not required).");
578 
579  //Overriding the shmimMonitor setup so that these all go in the dm section
580  //Otherwise, would call shmimMonitor<dm<derivedT,realT>>::setupConfig();
581  ///\todo we shmimMonitor now has configSection so this isn't necessary.
582  config.add("dm.threadPrio", "", "dm.threadPrio", argType::Required, "dm", "threadPrio", false, "int", "The real-time priority of the dm control thread.");
583  config.add("dm.cpuset", "", "dm.cpuset", argType::Required, "dm", "cpuset", false, "int", "The cpuset for the dm control thread.");
584 
585  config.add("dm.shmimName", "", "dm.shmimName", argType::Required, "dm", "shmimName", false, "string", "The name of the ImageStreamIO shared memory image to monitor for DM comands. Will be used as /tmp/<shmimName>.im.shm.");
586 
587  config.add("dm.shmimFlat", "", "dm.shmimFlat", argType::Required, "dm", "shmimFlat", false, "string", "The name of the ImageStreamIO shared memory image to write the flat command to. Default is shmimName with 00 apended (i.e. dm00disp -> dm00disp00). ");
588 
589  config.add("dm.shmimTest", "", "dm.shmimTest", argType::Required, "dm", "shmimTest", false, "string", "The name of the ImageStreamIO shared memory image to write the test command to. Default is shmimName with 01 apended (i.e. dm00disp -> dm00disp01). ");
590 
591  config.add("dm.shmimSat", "", "dm.shmimSat", argType::Required, "dm", "shmimSat", false, "string", "The name of the ImageStreamIO shared memory image to write the saturation map to. Default is shmimName with SA apended (i.e. dm00disp -> dm00dispSA). This is created.");
592 
593  config.add("dm.shmimSatPerc", "", "dm.shmimSatPerc", argType::Required, "dm", "shmimSatPerc", false, "string", "The name of the ImageStreamIO shared memory image to write the saturation percentage map to. Default is shmimName with SP apended (i.e. dm00disp -> dm00dispSP). This is created.");
594 
595  config.add("dm.satAvgInt", "", "dm.satAvgInt", argType::Required, "dm", "satAvgInt", false, "int", "The interval in milliseconds over which saturation is accumulated before updating. Default is 100 ms.");
596 
597  config.add("dm.width", "", "dm.width", argType::Required, "dm", "width", false, "string", "The width of the DM in actuators.");
598  config.add("dm.height", "", "dm.height", argType::Required, "dm", "height", false, "string", "The height of the DM in actuators.");
599 
600  config.add("dm.percThreshold", "", "dm.percThreshold", argType::Required, "dm", "percThreshold", false, "float", "Threshold on percentage of frames an actuator is saturated over an interval. Default is 0.98.");
601  config.add("dm.intervalSatThreshold", "", "dm.intervalSatThreshold", argType::Required, "dm", "intervalSatThreshold", false, "float", "Threshold on percentage of actuators which exceed percThreshold in an interval. Default is 0.5.");
602  config.add("dm.intervalSatCountThreshold", "", "dm.intervalSatCountThreshold", argType::Required, "dm", "intervalSatCountThreshold", false, "float", "Threshold one number of consecutive intervals the intervalSatThreshold is exceeded. Default is 10.");
603 
604  config.add("dm.satTriggerDevice", "", "dm.satTriggerDevice", argType::Required, "dm", "satTriggerDevice", false, "vector<string>", "Device(s) with a toggle switch to toggle on saturation trigger.");
605  config.add("dm.satTriggerProperty", "", "dm.satTriggerProperty", argType::Required, "dm", "satTriggerProperty", false, "vector<string>", "Property with a toggle switch to toggle on saturation trigger, one per entry in satTriggerDevice.");
606 
607 }
608 
609 template<class derivedT, typename realT>
610 void dm<derivedT,realT>::loadConfig(mx::app::appConfigurator & config)
611 {
612 
613  m_calibPath = derived().m_calibDir + "/" + m_calibRelDir;
614  config( m_calibPath, "dm.calibPath");
615 
616  //setup flats
617  m_flatPath = m_calibPath + "/flats";
618  config( m_flatPath, "dm.flatPath");
619 
620  config(m_flatDefault, "dm.flatDefault");
621  if(m_flatDefault != "")
622  {
623  m_flatDefault = mx::ioutils::pathStem(m_flatDefault); //strip off path and extension if provided.
624  m_flatCurrent = "default";
625  }
626 
627  //setup tests
628  m_testPath = m_calibPath + "/tests";
629  config(m_testPath, "dm.testPath");
630 
631  config(m_testDefault, "dm.testDefault");
632  if(m_testDefault != "")
633  {
634  m_testDefault = mx::ioutils::pathStem(m_testDefault); //strip off path and extension if provided.
635  m_testCurrent = "default";
636  }
637 
638  //Overriding the shmimMonitor setup so that these all go in the dm section
639  //Otherwise, would call shmimMonitor<dm<derivedT,realT>>::loadConfig(config);
640  config(derived().m_smThreadPrio, "dm.threadPrio");
641  config(derived().m_smCpuset, "dm.cpuset");
642 
643  config(derived().m_shmimName, "dm.shmimName");
644 
645  if(derived().m_shmimName != "")
646  {
647  m_shmimFlat = derived().m_shmimName + "00";
648  config(m_shmimFlat, "dm.shmimFlat");
649 
650  m_shmimTest = derived().m_shmimName + "02";
651  config(m_shmimTest, "dm.shmimTest");
652 
653 
654  m_shmimSat = derived().m_shmimName + "ST";
655  config(m_shmimSat, "dm.shmimSat");
656 
657  m_shmimSatPerc = derived().m_shmimName + "SP";
658  config(m_shmimSatPerc, "dm.shmimSatPerc");
659 
660  config(m_satAvgInt, "dm.satAvgInt");
661  }
662  else
663  {
664  config.isSet("dm.shmimFlat");
665  config.isSet("dm.shmimTest");
666  config.isSet("dm.shmimSat");
667  config.isSet("dm.shmimSatPerc");
668  config.isSet("dm.satAvgInt");
669  }
670 
671  config(m_dmWidth, "dm.width");
672  config(m_dmHeight, "dm.height");
673 
674  config(m_percThreshold, "dm.percThreshold");
675  config(m_intervalSatThreshold, "dm.intervalSatThreshold");
676  config(m_intervalSatCountThreshold, "dm.intervalSatCountThreshold");
677  config(m_satTriggerDevice, "dm.satTriggerDevice");
678  config(m_satTriggerProperty, "dm.satTriggerProperty");
679 }
680 
681 
682 template<class derivedT, typename realT>
684 {
685  if( m_dmDataType == 0)
686  {
687  derivedT::template log<software_error>({__FILE__,__LINE__, "unsupported DM data type"});
688  return -1;
689  }
690 
691  //-----------------
692  //Get the flats
693  checkFlats();
694 
695  //Register the test shmim INDI property
696  m_indiP_flatShmim = pcf::IndiProperty(pcf::IndiProperty::Text);
697  m_indiP_flatShmim.setDevice(derived().configName());
698  m_indiP_flatShmim.setName("flat_shmim");
699  m_indiP_flatShmim.setPerm(pcf::IndiProperty::ReadOnly);
700  m_indiP_flatShmim.setState(pcf::IndiProperty::Idle);
701  m_indiP_flatShmim.add(pcf::IndiElement("channel"));
702  m_indiP_flatShmim["channel"] = m_shmimFlat;
703 
704  if( derived().registerIndiPropertyReadOnly( m_indiP_flatShmim) < 0)
705  {
706  #ifndef DM_TEST_NOLOG
707  derivedT::template log<software_error>({__FILE__,__LINE__});
708  #endif
709  return -1;
710  }
711 
712  //Register the setFlat INDI property
713  derived().createStandardIndiToggleSw( m_indiP_setFlat, "flat_set");
714  if( derived().registerIndiPropertyNew( m_indiP_setFlat, st_newCallBack_setFlat) < 0)
715  {
716  #ifndef DM_TEST_NOLOG
717  derivedT::template log<software_error>({__FILE__,__LINE__});
718  #endif
719  return -1;
720  }
721 
722  //-----------------
723  //Get the tests
724  checkTests();
725 
726  //Register the test shmim INDI property
727  m_indiP_testShmim = pcf::IndiProperty(pcf::IndiProperty::Text);
728  m_indiP_testShmim.setDevice(derived().configName());
729  m_indiP_testShmim.setName("test_shmim");
730  m_indiP_testShmim.setPerm(pcf::IndiProperty::ReadOnly);
731  m_indiP_testShmim.setState(pcf::IndiProperty::Idle);
732  m_indiP_testShmim.add(pcf::IndiElement("channel"));
733  m_indiP_testShmim["channel"] = m_shmimTest;
734  derived().createStandardIndiToggleSw( m_indiP_setTest, "test_shmim");
735  if( derived().registerIndiPropertyReadOnly( m_indiP_testShmim) < 0)
736  {
737  #ifndef DM_TEST_NOLOG
738  derivedT::template log<software_error>({__FILE__,__LINE__});
739  #endif
740  return -1;
741  }
742 
743  //Register the setTest INDI property
744  derived().createStandardIndiToggleSw( m_indiP_setTest, "test_set");
745  if( derived().registerIndiPropertyNew( m_indiP_setTest, st_newCallBack_setTest) < 0)
746  {
747  #ifndef DM_TEST_NOLOG
748  derivedT::template log<software_error>({__FILE__,__LINE__});
749  #endif
750  return -1;
751  }
752 
753  //Register the init INDI property
754  derived().createStandardIndiRequestSw( m_indiP_init, "initDM");
755  if( derived().registerIndiPropertyNew( m_indiP_init, st_newCallBack_init) < 0)
756  {
757  #ifndef DM_TEST_NOLOG
758  derivedT::template log<software_error>({__FILE__,__LINE__});
759  #endif
760  return -1;
761  }
762 
763  //Register the zero INDI property
764  derived().createStandardIndiRequestSw( m_indiP_zero, "zeroDM");
765  if( derived().registerIndiPropertyNew( m_indiP_zero, st_newCallBack_zero) < 0)
766  {
767  #ifndef DM_TEST_NOLOG
768  derivedT::template log<software_error>({__FILE__,__LINE__});
769  #endif
770  return -1;
771  }
772 
773  //Register the release INDI property
774  derived().createStandardIndiRequestSw( m_indiP_release, "releaseDM");
775  if( derived().registerIndiPropertyNew( m_indiP_release, st_newCallBack_release) < 0)
776  {
777  #ifndef DM_TEST_NOLOG
778  derivedT::template log<software_error>({__FILE__,__LINE__});
779  #endif
780  return -1;
781  }
782 
783  derived().createStandardIndiRequestSw( m_indiP_zeroAll, "zeroAll");
784  if( derived().registerIndiPropertyNew( m_indiP_zeroAll, st_newCallBack_zeroAll) < 0)
785  {
786  #ifndef DM_TEST_NOLOG
787  derivedT::template log<software_error>({__FILE__,__LINE__});
788  #endif
789  return -1;
790  }
791 
792  if(m_flatDefault != "")
793  {
794  loadFlat("default");
795  }
796 
797  if(m_testDefault != "")
798  {
799  loadTest("default");
800  }
801 
802  if(sem_init(&m_satSemaphore, 0,0) < 0) return derivedT::template log<software_critical, -1>({__FILE__, __LINE__, errno,0, "Initializing sat semaphore"});
803 
804  if(derived().threadStart( m_satThread, m_satThreadInit, m_satThreadID, m_satThreadProp, m_satThreadPrio, "", "saturation", this, satThreadStart) < 0)
805  {
806  derivedT::template log<software_error, -1>({__FILE__, __LINE__});
807  return -1;
808  }
809 
810  return 0;
811 
812 }
813 
814 template<class derivedT, typename realT>
816 {
817  //do a join check to see if other threads have exited.
818  if(pthread_tryjoin_np(m_satThread.native_handle(),0) == 0)
819  {
820  derivedT::template log<software_error>({__FILE__, __LINE__, "saturation thread has exited"});
821 
822  return -1;
823  }
824 
825  checkFlats();
826  checkTests();
827 
828  if(m_intervalSatTrip)
829  {
830  intervalSatTrip();
831  m_intervalSatTrip = false;
832  }
833 
834  return 0;
835 
836 }
837 
838 
839 template<class derivedT, typename realT>
841 {
842  if(m_satThread.joinable())
843  {
844  pthread_kill(m_satThread.native_handle(), SIGUSR1);
845  try
846  {
847  m_satThread.join(); //this will throw if it was already joined
848  }
849  catch(...)
850  {
851  }
852  }
853 
854  return 0;
855 }
856 
857 template<class derivedT, typename realT>
859 {
860  releaseDM();
861  return 0;
862 }
863 
864 template<class derivedT, typename realT>
866 {
867  checkFlats();
868  checkTests();
869 
870  return 0;
871 }
872 
873 template<class derivedT, typename realT>
875 {
876  std::vector<std::string> dmlist = mx::ioutils::getFileNames("/milk/shm/", derived().m_shmimName, ".im", ".shm");
877 
878  if(dmlist.size() == 0)
879  {
880  derivedT::template log<software_error>({__FILE__, __LINE__, "no dm channels found for " + derived().m_shmimName});
881  return -1;
882  }
883 
884  m_channels = -1;
885  for(size_t n =0; n < dmlist.size(); ++n)
886  {
887  char nstr[16];
888  snprintf(nstr, sizeof(nstr), "%02d.im.shm", (int) n);
889  std::string tgt = derived().m_shmimName;
890  tgt += nstr;
891 
892  for(size_t m=0; m < dmlist.size(); ++m)
893  {
894  if( dmlist[m].find(tgt) != std::string::npos)
895  {
896  if((int) n > m_channels) m_channels = n;
897  }
898  }
899  }
900 
901  ++m_channels;
902 
903 
904  derivedT::template log<text_log>({std::string("Found ") + std::to_string(m_channels) + " channels for " + derived().m_shmimName});
905 
906  return 0;
907 }
908 
909 template<class derivedT, typename realT>
911 {
912  static_cast<void>(sp); //be unused
913 
914  int err = 0;
915 
916  if(derived().m_width != m_dmWidth)
917  {
918  derivedT::template log<software_critical>({__FILE__,__LINE__, "shmim width does not match configured DM width"});
919  ++err;
920  }
921 
922  if(derived().m_height != m_dmHeight)
923  {
924  derivedT::template log<software_critical>({__FILE__,__LINE__, "shmim height does not match configured DM height"});
925  ++err;
926  }
927 
928  if(derived().m_dataType != m_dmDataType)
929  {
930  derivedT::template log<software_critical>({__FILE__,__LINE__, "shmim data type does not match configured DM data type"});
931  ++err;
932  }
933 
934  if(err) return -1;
935 
936  m_instSatMap.resize(m_dmWidth,m_dmHeight);
937  m_instSatMap.setZero();
938 
939  m_accumSatMap.resize(m_dmWidth,m_dmHeight);
940  m_accumSatMap.setZero();
941 
942  m_satPercMap.resize(m_dmWidth,m_dmHeight);
943  m_satPercMap.setZero();
944 
945  if(findDMChannels() < 0)
946  {
947  derivedT::template log<software_critical>({__FILE__,__LINE__, "error finding DM channels"});
948 
949  return -1;
950  }
951 
952  return 0;
953 }
954 
955 template<class derivedT, typename realT>
956 int dm<derivedT,realT>::processImage( void * curr_src,
957  const dev::shmimT & sp
958  )
959 {
960  static_cast<void>(sp); //be unused
961 
962  int rv = derived().commandDM( curr_src );
963 
964  if(rv < 0)
965  {
966  derivedT::template log<software_critical>({__FILE__, __LINE__, errno, rv, "Error from commandDM"});
967  return rv;
968  }
969  //Tell the sat thread to get going
970  if(sem_post(&m_satSemaphore) < 0)
971  {
972  derivedT::template log<software_critical>({__FILE__, __LINE__, errno, 0, "Error posting to semaphore"});
973  return -1;
974  }
975 
976  return rv;
977 }
978 
979 template<class derivedT, typename realT>
981 {
982  int rv;
983  if( (rv = derived().releaseDM()) < 0)
984  {
985  derivedT::template log<software_critical>({__FILE__, __LINE__, errno, rv, "Error from releaseDM"});
986  return rv;
987  }
988 
989  if( (rv = zeroAll(true)) < 0)
990  {
991  derivedT::template log<software_error>({__FILE__, __LINE__, errno, rv, "Error from zeroAll"});
992  return rv;
993  }
994 
995  return 0;
996 
997 }
998 template<class derivedT, typename realT>
1000 {
1001  std::vector<std::string> tfs = mx::ioutils::getFileNames(m_flatPath, "", "", ".fits");
1002 
1003  //First remove default, b/c we always add it and don't want to include it in timestamp selected ones
1004  for(size_t n=0; n < tfs.size(); ++n)
1005  {
1006  if(mx::ioutils::pathStem(tfs[n]) == "default")
1007  {
1008  tfs.erase(tfs.begin()+n);
1009  --n;
1010  }
1011  }
1012 
1013  unsigned m_nFlatFiles = 5;
1014 
1015  //Here we keep only the m_nFlatFiles most recent files
1016  if(tfs.size() >= m_nFlatFiles)
1017  {
1018  std::vector<std::time_t> wtimes (tfs.size());
1019 
1020  for(size_t n=0; n < wtimes.size(); ++n)
1021  {
1022  wtimes[n] = boost::filesystem::last_write_time(tfs[n]);
1023  }
1024 
1025  std::sort(wtimes.begin(), wtimes.end());
1026 
1027  std::time_t tn = wtimes[wtimes.size() - m_nFlatFiles];
1028 
1029  for(size_t n=0; n < tfs.size(); ++n)
1030  {
1031  std::time_t lmt = boost::filesystem::last_write_time(tfs[n]);
1032  if(lmt < tn)
1033  {
1034  tfs.erase(tfs.begin() + n);
1035  --n;
1036  }
1037  }
1038  }
1039 
1040 
1041  for(auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it)
1042  {
1043  it->second = "";
1044  }
1045 
1046  bool changed = false;
1047  for(size_t n=0; n<tfs.size(); ++n)
1048  {
1049  auto ir = m_flatCommands.insert(std::pair<std::string,std::string>(mx::ioutils::pathStem(tfs[n]), tfs[n]));
1050  if(ir.second == true) changed = true;
1051  else ir.first->second = tfs[n];
1052  }
1053 
1054  for(auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it)
1055  {
1056  if(it->second == "")
1057  {
1058  changed = true;
1059  //Erase the current iterator safely, even if the first one.
1060  auto itdel = it;
1061  ++it;
1062  m_flatCommands.erase(itdel);
1063  --it;
1064  };
1065  }
1066 
1067  if(changed)
1068  {
1069  if(derived().m_indiDriver)
1070  {
1071  derived().m_indiDriver->sendDelProperty(m_indiP_flats);
1072  derived().m_indiNewCallBacks.erase(m_indiP_flats.createUniqueKey());
1073  }
1074 
1075  m_indiP_flats = pcf::IndiProperty(pcf::IndiProperty::Switch);
1076  m_indiP_flats.setDevice(derived().configName());
1077  m_indiP_flats.setName("flat");
1078  m_indiP_flats.setPerm(pcf::IndiProperty::ReadWrite);
1079  m_indiP_flats.setState(pcf::IndiProperty::Idle);
1080  m_indiP_flats.setRule(pcf::IndiProperty::OneOfMany);
1081 
1082  //Add the toggle element initialized to Off
1083  for(auto it = m_flatCommands.begin(); it != m_flatCommands.end(); ++it)
1084  {
1085  if(it->first == m_flatCurrent || m_flatCurrent == "")
1086  {
1087  m_indiP_flats.add(pcf::IndiElement(it->first, pcf::IndiElement::On));
1088  m_flatCurrent = it->first; //handles the case m_flatCurrent == "" b/c it was not set in config
1089  }
1090  else
1091  {
1092  m_indiP_flats.add(pcf::IndiElement(it->first, pcf::IndiElement::Off));
1093  }
1094  }
1095 
1096  if(m_flatDefault != "")
1097  {
1098  if(m_flatCurrent == "default")
1099  {
1100  m_indiP_flats.add(pcf::IndiElement("default", pcf::IndiElement::On));
1101  }
1102  else
1103  {
1104  m_indiP_flats.add(pcf::IndiElement("default", pcf::IndiElement::Off));
1105  }
1106  }
1107 
1108 
1109  if( derived().registerIndiPropertyNew( m_indiP_flats, st_newCallBack_flats) < 0)
1110  {
1111  #ifndef DM_TEST_NOLOG
1112  derivedT::template log<software_error>({__FILE__,__LINE__});
1113  #endif
1114  return -1;
1115  }
1116 
1117  if(derived().m_indiDriver)
1118  {
1119  derived().m_indiDriver->sendDefProperty(m_indiP_flats);
1120  }
1121  }
1122 
1123  return 0;
1124 }
1125 
1126 template <class derivedT, typename realT>
1127 int dm<derivedT, realT>::loadFlat(const std::string &intarget)
1128 {
1129  std::string target = intarget;
1130 
1131  std::string targetPath;
1132 
1133  if(target == "default")
1134  {
1135  target = m_flatDefault;
1136  targetPath = m_flatPath + "/" + m_flatDefault + ".fits";
1137  }
1138  else
1139  {
1140  try
1141  {
1142  targetPath = m_flatCommands.at(target);
1143  }
1144  catch (...)
1145  {
1146  derivedT::template log<text_log>("flat file " + target + " not found", logPrio::LOG_ERROR);
1147  return -1;
1148  }
1149  }
1150 
1151  m_flatLoaded = false;
1152  // load into memory.
1153  mx::fits::fitsFile<realT> ff;
1154  if(ff.read(m_flatCommand, targetPath) < 0)
1155  {
1156  derivedT::template log<text_log>("flat file " + targetPath + " not found", logPrio::LOG_ERROR);
1157  return -1;
1158  }
1159 
1160  derivedT::template log<text_log>("loaded flat file " + targetPath);
1161  m_flatLoaded = true;
1162 
1163  m_flatCurrent = intarget;
1164 
1165  if(m_indiP_flats.find("default"))
1166  {
1167  if(m_flatCurrent == "default")
1168  {
1169  m_indiP_flats["default"] = pcf::IndiElement::On;
1170  }
1171  else
1172  {
1173  m_indiP_flats["default"] = pcf::IndiElement::Off;
1174  }
1175  }
1176 
1177  for(auto i = m_flatCommands.begin(); i != m_flatCommands.end(); ++i)
1178  {
1179  if(!m_indiP_flats.find(i->first))
1180  {
1181  continue;
1182  }
1183 
1184  if(i->first == m_flatCurrent)
1185  {
1186  m_indiP_flats[i->first] = pcf::IndiElement::On;
1187  }
1188  else
1189  {
1190  m_indiP_flats[i->first] = pcf::IndiElement::Off;
1191  }
1192  }
1193 
1194  if(derived().m_indiDriver)
1195  {
1196  derived().m_indiDriver->sendSetProperty(m_indiP_flats);
1197  }
1198 
1199  if(m_flatSet)
1200  {
1201  setFlat();
1202  }
1203 
1204  return 0;
1205 }
1206 
1207 template<class derivedT, typename realT>
1209 {
1210  if(m_shmimFlat == "") return 0;
1211 
1212  if( ImageStreamIO_openIm(&m_flatImageStream, m_shmimFlat.c_str()) != 0)
1213  {
1214  derivedT::template log<text_log>("could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING);
1215  return -1;
1216  }
1217 
1218  if( m_flatImageStream.md[0].size[0] != m_dmWidth)
1219  {
1220  ImageStreamIO_closeIm(&m_flatImageStream);
1221  derivedT::template log<text_log>("width mismatch between " + m_shmimFlat + " and configured DM", logPrio::LOG_ERROR);
1222  return -1;
1223  }
1224 
1225  if( m_flatImageStream.md[0].size[1] != m_dmHeight)
1226  {
1227  ImageStreamIO_closeIm(&m_flatImageStream);
1228  derivedT::template log<text_log>("height mismatch between " + m_shmimFlat + " and configured DM", logPrio::LOG_ERROR);
1229  return -1;
1230  }
1231 
1232  if(!m_flatLoaded)
1233  {
1234  bool flatSet = m_flatSet;
1235  m_flatSet = false; //make sure we don't loop
1236 
1237  if( loadFlat(m_flatCurrent) < 0 )
1238  {
1239  derivedT::template log<text_log>("error loading flat " + m_flatCurrent, logPrio::LOG_ERROR);
1240  }
1241  m_flatSet = flatSet;
1242  }
1243 
1244  if(!m_flatLoaded)
1245  {
1246  ImageStreamIO_closeIm(&m_flatImageStream);
1247  derivedT::template log<text_log>("no flat loaded", logPrio::LOG_ERROR);
1248  return -1;
1249  }
1250 
1251  if( m_flatCommand.rows() != m_dmWidth)
1252  {
1253  ImageStreamIO_closeIm(&m_flatImageStream);
1254  derivedT::template log<text_log>("width mismatch between flat file and configured DM", logPrio::LOG_ERROR);
1255  return -1;
1256  }
1257 
1258  if( m_flatCommand.cols() != m_dmHeight)
1259  {
1260  ImageStreamIO_closeIm(&m_flatImageStream);
1261  derivedT::template log<text_log>("height mismatch between flat file and configured DM", logPrio::LOG_ERROR);
1262  return -1;
1263  }
1264 
1265  m_flatImageStream.md->write=1;
1266 
1267  ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here anyway. With bounds checks b/c not everyone handles cnt1 properly.
1268  //Copy
1269  memcpy( m_flatImageStream.array.raw, m_flatCommand.data(), m_dmWidth*m_dmHeight*sizeof(realT));
1270 
1271  //Set the time of last write
1272  clock_gettime(CLOCK_REALTIME, &m_flatImageStream.md->writetime);
1273 
1274  //Set the image acquisition timestamp
1275  m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
1276 
1277  m_flatImageStream.md->cnt0++;
1278  m_flatImageStream.md->write=0;
1279  ImageStreamIO_sempost(&m_flatImageStream,-1);
1280 
1281  m_flatSet = true;
1282 
1283  //Post the semaphore
1284  ImageStreamIO_closeIm(&m_flatImageStream);
1285 
1286  if(!update)
1287  {
1288  derived().updateSwitchIfChanged(m_indiP_setFlat, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy);
1289 
1290  derivedT::template log<text_log>("flat set");
1291  }
1292 
1293  return 0;
1294 }
1295 
1296 template<class derivedT, typename realT>
1298 {
1299  if(m_shmimFlat == "") return 0;
1300 
1301  if( ImageStreamIO_openIm(&m_flatImageStream, m_shmimFlat.c_str()) != 0)
1302  {
1303  derivedT::template log<text_log>("could not connect to flat channel " + m_shmimFlat, logPrio::LOG_WARNING);
1304  return -1;
1305  }
1306 
1307  if( m_flatImageStream.md[0].size[0] != m_dmWidth)
1308  {
1309  ImageStreamIO_closeIm(&m_flatImageStream);
1310  derivedT::template log<text_log>("width mismatch between " + m_shmimFlat + " and configured DM", logPrio::LOG_ERROR);
1311  return -1;
1312  }
1313 
1314  if( m_flatImageStream.md[0].size[1] != m_dmHeight)
1315  {
1316  ImageStreamIO_closeIm(&m_flatImageStream);
1317  derivedT::template log<text_log>("height mismatch between " + m_shmimFlat + " and configured DM", logPrio::LOG_ERROR);
1318  return -1;
1319  }
1320 
1321  m_flatImageStream.md->write=1;
1322 
1323  ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here anyway. With bounds checks b/c not everyone handles cnt1 properly.
1324  //Zero
1325  memset( m_flatImageStream.array.raw, 0, m_dmWidth*m_dmHeight*sizeof(realT));
1326 
1327  //Set the time of last write
1328  clock_gettime(CLOCK_REALTIME, &m_flatImageStream.md->writetime);
1329 
1330  //Set the image acquisition timestamp
1331  m_flatImageStream.md->atime = m_flatImageStream.md->writetime;
1332 
1333  m_flatImageStream.md->cnt0++;
1334  m_flatImageStream.md->write=0;
1335  ImageStreamIO_sempost(&m_flatImageStream,-1);
1336 
1337  m_flatSet = false;
1338 
1339  //Post the semaphore
1340  ImageStreamIO_closeIm(&m_flatImageStream);
1341 
1342  derived().updateSwitchIfChanged(m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle);
1343 
1344  derivedT::template log<text_log>("flat zeroed");
1345 
1346  return 0;
1347 }
1348 
1349 template<class derivedT, typename realT>
1351 {
1352  std::vector<std::string> tfs = mx::ioutils::getFileNames(m_testPath, "", "", ".fits");
1353 
1354  for(auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it)
1355  {
1356  it->second = "";
1357  }
1358 
1359  bool changed = false;
1360  for(size_t n=0; n<tfs.size(); ++n)
1361  {
1362  auto ir = m_testCommands.insert(std::pair<std::string,std::string>(mx::ioutils::pathStem(tfs[n]), tfs[n]));
1363  if(ir.second == true) changed = true;
1364  else ir.first->second = tfs[n];
1365  }
1366 
1367  for(auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it)
1368  {
1369  if(it->second == "")
1370  {
1371  changed = true;
1372  //Erase the current iterator safely, even if the first one.
1373  auto itdel = it;
1374  ++it;
1375  m_testCommands.erase(itdel);
1376  --it;
1377  };
1378  }
1379 
1380  if(changed)
1381  {
1382  if(derived().m_indiDriver)
1383  {
1384  derived().m_indiDriver->sendDelProperty(m_indiP_tests);
1385  derived().m_indiNewCallBacks.erase(m_indiP_tests.createUniqueKey());
1386  }
1387 
1388  m_indiP_tests = pcf::IndiProperty(pcf::IndiProperty::Switch);
1389  m_indiP_tests.setDevice(derived().configName());
1390  m_indiP_tests.setName("test");
1391  m_indiP_tests.setPerm(pcf::IndiProperty::ReadWrite);
1392  m_indiP_tests.setState(pcf::IndiProperty::Idle);
1393  m_indiP_tests.setRule(pcf::IndiProperty::OneOfMany);
1394 
1395  //Add the toggle element initialized to Off
1396  for(auto it = m_testCommands.begin(); it != m_testCommands.end(); ++it)
1397  {
1398  if(it->first == m_testCurrent || m_testCurrent == "")
1399  {
1400  m_indiP_tests.add(pcf::IndiElement(it->first, pcf::IndiElement::On));
1401  m_testCurrent = it->first; //Handles the case when m_testCurrent=="" b/c it was not set in config
1402  }
1403  else
1404  {
1405  m_indiP_tests.add(pcf::IndiElement(it->first, pcf::IndiElement::Off));
1406  }
1407  }
1408 
1409  if(m_testDefault != "")
1410  {
1411  if(m_testCurrent == "default")
1412  {
1413  m_indiP_tests.add(pcf::IndiElement("default", pcf::IndiElement::On));
1414  }
1415  else
1416  {
1417  m_indiP_tests.add(pcf::IndiElement("default", pcf::IndiElement::Off));
1418  }
1419  }
1420 
1421 
1422  if( derived().registerIndiPropertyNew( m_indiP_tests, st_newCallBack_tests) < 0)
1423  {
1424  #ifndef DM_TEST_NOLOG
1425  derivedT::template log<software_error>({__FILE__,__LINE__});
1426  #endif
1427  return -1;
1428  }
1429 
1430  if(derived().m_indiDriver)
1431  {
1432  derived().m_indiDriver->sendDefProperty(m_indiP_tests);
1433  }
1434  }
1435 
1436  return 0;
1437 }
1438 
1439 template <class derivedT, typename realT>
1440 int dm<derivedT, realT>::loadTest(const std::string &intarget)
1441 {
1442  std::string target = intarget; // store this for later to resolve default next:
1443 
1444  if(target == "default")
1445  {
1446  target = m_testDefault;
1447  }
1448 
1449  std::string targetPath;
1450 
1451  try
1452  {
1453  targetPath = m_testCommands.at(target);
1454  }
1455  catch (...)
1456  {
1457  derivedT::template log<text_log>("test file " + target + " not found", logPrio::LOG_ERROR);
1458  return -1;
1459  }
1460 
1461  m_testLoaded = false;
1462  // load into memory.
1463  mx::fits::fitsFile<realT> ff;
1464  if(ff.read(m_testCommand, targetPath) < 0)
1465  {
1466  derivedT::template log<text_log>("test file " + targetPath + " not found", logPrio::LOG_ERROR);
1467  return -1;
1468  }
1469 
1470  derivedT::template log<text_log>("loaded test file " + targetPath);
1471  m_testLoaded = true;
1472 
1473  m_testCurrent = intarget;
1474 
1475  if(m_indiP_tests.find("default"))
1476  {
1477  if(m_testCurrent == "default")
1478  {
1479  m_indiP_tests["default"] = pcf::IndiElement::On;
1480  }
1481  else
1482  {
1483  m_indiP_tests["default"] = pcf::IndiElement::Off;
1484  }
1485  }
1486 
1487  for(auto i = m_testCommands.begin(); i != m_testCommands.end(); ++i)
1488  {
1489  if(!m_indiP_tests.find(i->first))
1490  {
1491  continue;
1492  }
1493 
1494  if(i->first == m_testCurrent)
1495  {
1496  m_indiP_tests[i->first] = pcf::IndiElement::On;
1497  }
1498  else
1499  {
1500  m_indiP_tests[i->first] = pcf::IndiElement::Off;
1501  }
1502  }
1503 
1504  if(derived().m_indiDriver) derived().m_indiDriver->sendSetProperty (m_indiP_tests);
1505 
1506  if(m_testSet) setTest();
1507 
1508  return 0;
1509 }
1510 
1511 template<class derivedT, typename realT>
1513 {
1514 
1515  if(m_shmimTest == "") return 0;
1516 
1517  if( ImageStreamIO_openIm(&m_testImageStream, m_shmimTest.c_str()) != 0)
1518  {
1519  derivedT::template log<text_log>("could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING);
1520  return -1;
1521  }
1522 
1523  if( m_testImageStream.md->size[0] != m_dmWidth)
1524  {
1525  ImageStreamIO_closeIm(&m_testImageStream);
1526  derivedT::template log<text_log>("width mismatch between " + m_shmimTest + " and configured DM", logPrio::LOG_ERROR);
1527  return -1;
1528  }
1529 
1530  if( m_testImageStream.md->size[1] != m_dmHeight)
1531  {
1532  ImageStreamIO_closeIm(&m_testImageStream);
1533  derivedT::template log<text_log>("height mismatch between " + m_shmimTest + " and configured DM", logPrio::LOG_ERROR);
1534  return -1;
1535  }
1536 
1537  if(!m_testLoaded)
1538  {
1539  bool testSet = m_testSet;
1540  m_testSet = false; //make sure we don't loop
1541 
1542  if(loadTest(m_testCurrent)<0)
1543  {
1544  derivedT::template log<text_log>("error loading test " + m_testCurrent, logPrio::LOG_ERROR);
1545  }
1546  m_testSet = testSet;
1547  }
1548 
1549  if(!m_testLoaded)
1550  {
1551  ImageStreamIO_closeIm(&m_testImageStream);
1552  derivedT::template log<text_log>("no test loaded", logPrio::LOG_ERROR);
1553  return -1;
1554  }
1555 
1556  if( m_testCommand.rows() != m_dmWidth)
1557  {
1558  ImageStreamIO_closeIm(&m_testImageStream);
1559  derivedT::template log<text_log>("width mismatch between test file and configured DM", logPrio::LOG_ERROR);
1560  return -1;
1561  }
1562 
1563  if( m_testCommand.cols() != m_dmHeight)
1564  {
1565  ImageStreamIO_closeIm(&m_testImageStream);
1566  derivedT::template log<text_log>("height mismatch between test file and configured DM", logPrio::LOG_ERROR);
1567  return -1;
1568  }
1569 
1570  m_testImageStream.md->write=1;
1571 
1572  ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here anyway. With bounds checks b/c not everyone handles cnt1 properly.
1573  //Copy
1574  memcpy( m_testImageStream.array.raw, m_testCommand.data(), m_dmWidth*m_dmHeight*sizeof(realT));
1575 
1576  //Set the time of last write
1577  clock_gettime(CLOCK_REALTIME, &m_testImageStream.md->writetime);
1578 
1579  //Set the image acquisition timestamp
1580  m_testImageStream.md->atime = m_testImageStream.md->writetime;
1581 
1582  m_testImageStream.md->cnt0++;
1583  m_testImageStream.md->write=0;
1584  ImageStreamIO_sempost(&m_testImageStream,-1);
1585 
1586  m_testSet = true;
1587 
1588  //Post the semaphore
1589  ImageStreamIO_closeIm(&m_testImageStream);
1590 
1591  derived().updateSwitchIfChanged(m_indiP_setTest, "toggle", pcf::IndiElement::On, pcf::IndiProperty::Busy);
1592 
1593  derivedT::template log<text_log>("test set");
1594 
1595  return 0;
1596 }
1597 
1598 template<class derivedT, typename realT>
1600 {
1601  if(m_shmimTest == "") return 0;
1602 
1603  if( ImageStreamIO_openIm(&m_testImageStream, m_shmimTest.c_str()) != 0)
1604  {
1605  derivedT::template log<text_log>("could not connect to test channel " + m_shmimTest, logPrio::LOG_WARNING);
1606  return -1;
1607  }
1608 
1609  if( m_testImageStream.md[0].size[0] != m_dmWidth)
1610  {
1611  ImageStreamIO_closeIm(&m_testImageStream);
1612  derivedT::template log<text_log>("width mismatch between " + m_shmimTest + " and configured DM", logPrio::LOG_ERROR);
1613  return -1;
1614  }
1615 
1616  if( m_testImageStream.md[0].size[1] != m_dmHeight)
1617  {
1618  ImageStreamIO_closeIm(&m_testImageStream);
1619  derivedT::template log<text_log>("height mismatch between " + m_shmimTest + " and configured DM", logPrio::LOG_ERROR);
1620  return -1;
1621  }
1622 
1623  m_testImageStream.md->write=1;
1624 
1625  ///\todo we are assuming that dmXXcomYY is not a cube. This might be true, but we should add cnt1 handling here anyway. With bounds checks b/c not everyone handles cnt1 properly.
1626  //Zero
1627  memset( m_testImageStream.array.raw, 0, m_dmWidth*m_dmHeight*sizeof(realT));
1628 
1629  //Set the time of last write
1630  clock_gettime(CLOCK_REALTIME, &m_testImageStream.md->writetime);
1631 
1632  //Set the image acquisition timestamp
1633  m_testImageStream.md->atime = m_testImageStream.md->writetime;
1634 
1635  m_testImageStream.md->cnt0++;
1636  m_testImageStream.md->write=0;
1637 
1638  //Post the semaphore
1639  ImageStreamIO_sempost(&m_testImageStream,-1);
1640 
1641  m_testSet = false;
1642 
1643  ImageStreamIO_closeIm(&m_testImageStream);
1644 
1645  derived().updateSwitchIfChanged(m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle);
1646 
1647  derivedT::template log<text_log>("test zeroed");
1648 
1649  return 0;
1650 }
1651 
1652 template<class derivedT, typename realT>
1654 {
1655  if(derived().m_shmimName == "") return 0;
1656 
1657  IMAGE imageStream;
1658 
1659  for(int n=0; n < m_channels; ++n)
1660  {
1661  char nstr[16];
1662  snprintf(nstr,sizeof(nstr), "%02d", n);
1663  std::string shmimN = derived().m_shmimName + nstr;
1664 
1665  if( ImageStreamIO_openIm(&imageStream, shmimN.c_str()) != 0)
1666  {
1667  derivedT::template log<text_log>("could not connect to channel " + shmimN, logPrio::LOG_WARNING);
1668  continue;
1669  }
1670 
1671  if( imageStream.md->size[0] != m_dmWidth)
1672  {
1673  ImageStreamIO_closeIm(&imageStream);
1674  derivedT::template log<text_log>("width mismatch between " + shmimN + " and configured DM", logPrio::LOG_ERROR);
1675  derived().updateSwitchIfChanged(m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE);
1676  return -1;
1677  }
1678 
1679  if( imageStream.md->size[1] != m_dmHeight)
1680  {
1681  ImageStreamIO_closeIm(&imageStream);
1682  derivedT::template log<text_log>("height mismatch between " + shmimN + " and configured DM", logPrio::LOG_ERROR);
1683  derived().updateSwitchIfChanged(m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE);
1684  return -1;
1685  }
1686 
1687  imageStream.md->write=1;
1688  memset( imageStream.array.raw, 0, m_dmWidth*m_dmHeight*sizeof(realT));
1689 
1690  clock_gettime(CLOCK_REALTIME, &imageStream.md->writetime);
1691 
1692  //Set the image acquisition timestamp
1693  imageStream.md->atime = imageStream.md->writetime;
1694 
1695  imageStream.md->cnt0++;
1696  imageStream.md->write=0;
1697 
1698  //Raise the semaphore on last one.
1699  if(n == m_channels-1 && !nosem) ImageStreamIO_sempost(&imageStream,-1);
1700 
1701  ImageStreamIO_closeIm(&imageStream);
1702  }
1703 
1704  derivedT::template log<text_log>("all channels zeroed", logPrio::LOG_NOTICE);
1705 
1706  derived().updateSwitchIfChanged(m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE);
1707 
1708  //Also cleanup flat and test
1709  m_flatSet = false;
1710  derived().updateSwitchIfChanged(m_indiP_setFlat, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle);
1711 
1712  //Also cleanup flat and test
1713  m_testSet = false;
1714  derived().updateSwitchIfChanged(m_indiP_setTest, "toggle", pcf::IndiElement::Off, pcf::IndiProperty::Idle);
1715 
1716  int rv;
1717  if( (rv = clearSat()) < 0)
1718  {
1719  derivedT::template log<software_error>({__FILE__, __LINE__, errno, rv, "Error from clearSat"});
1720  return rv;
1721  }
1722 
1723  return 0;
1724 }
1725 
1726 template<class derivedT, typename realT>
1728 {
1729  if(m_shmimSat == "") return 0;
1730 
1731  IMAGE imageStream;
1732 
1733  std::vector<std::string> sats = {m_shmimSat, m_shmimSatPerc};
1734 
1735  for(size_t n=0; n < sats.size(); ++n)
1736  {
1737  std::string shmimN = sats[n];
1738 
1739  if( ImageStreamIO_openIm(&imageStream, shmimN.c_str()) != 0)
1740  {
1741  derivedT::template log<text_log>("could not connect to sat map " + shmimN, logPrio::LOG_WARNING);
1742  return 0;
1743  }
1744 
1745  if( imageStream.md->size[0] != m_dmWidth)
1746  {
1747  ImageStreamIO_closeIm(&imageStream);
1748  derivedT::template log<text_log>("width mismatch between " + shmimN + " and configured DM", logPrio::LOG_ERROR);
1749  derived().updateSwitchIfChanged(m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE);
1750  return -1;
1751  }
1752 
1753  if( imageStream.md->size[1] != m_dmHeight)
1754  {
1755  ImageStreamIO_closeIm(&imageStream);
1756  derivedT::template log<text_log>("height mismatch between " + shmimN + " and configured DM", logPrio::LOG_ERROR);
1757  derived().updateSwitchIfChanged(m_indiP_zeroAll, "request", pcf::IndiElement::Off, INDI_IDLE);
1758  return -1;
1759  }
1760 
1761  imageStream.md->write=1;
1762  memset( imageStream.array.raw, 0, m_dmWidth*m_dmHeight*sizeof(realT));
1763 
1764  clock_gettime(CLOCK_REALTIME, &imageStream.md->writetime);
1765 
1766  //Set the image acquisition timestamp
1767  imageStream.md->atime = imageStream.md->writetime;
1768 
1769  imageStream.md->cnt0++;
1770  imageStream.md->write=0;
1771 
1772  ImageStreamIO_closeIm(&imageStream);
1773  }
1774 
1775  m_accumSatMap.setZero();
1776  m_instSatMap.setZero();
1777 
1778 
1779  return 0;
1780 }
1781 
1782 template<class derivedT, typename realT>
1784 {
1785  d->satThreadExec();
1786 }
1787 
1788 template<class derivedT, typename realT>
1790 {
1791  //Get the thread PID immediately so the caller can return.
1792  m_satThreadID = syscall(SYS_gettid);
1793 
1794  //Wait for the thread starter to finish initializing this thread.
1795  while(m_satThreadInit == true && derived().shutdown() == 0)
1796  {
1797  sleep(1);
1798  }
1799  if(derived().shutdown()) return;
1800 
1801  uint32_t imsize[3] = {0,0,0};
1802 
1803  //Check for allocation to have happened.
1804  while((m_shmimSat == "" || m_accumSatMap.rows() == 0 || m_accumSatMap.cols() == 0) && !derived().shutdown())
1805  {
1806  sleep(1);
1807  }
1808  if(derived().shutdown()) return;
1809 
1810  imsize[0] = m_dmWidth;
1811  imsize[1] = m_dmHeight;
1812  imsize[2] = 1;
1813 
1814  ImageStreamIO_createIm_gpu(&m_satImageStream, m_shmimSat.c_str(), 3, imsize, IMAGESTRUCT_UINT8, -1, 1, IMAGE_NB_SEMAPHORE, 0, CIRCULAR_BUFFER | ZAXIS_TEMPORAL, 0);
1815  ImageStreamIO_createIm_gpu(&m_satPercImageStream, m_shmimSatPerc.c_str(), 3, imsize, IMAGESTRUCT_FLOAT, -1, 1, IMAGE_NB_SEMAPHORE, 0, CIRCULAR_BUFFER | ZAXIS_TEMPORAL, 0);
1816 
1817  bool opened = true;
1818 
1819  m_satImageStream.md->cnt1 = 0;
1820  m_satPercImageStream.md->cnt1 = 0;
1821 
1822  //This is the working memory for making the 1/0 mask out of m_accumSatMap
1823  mx::improc::eigenImage<uint8_t> satmap(m_dmWidth, m_dmHeight);
1824 
1825  int naccum = 0;
1826  double t_accumst = mx::sys::get_curr_time();
1827 
1828  //This is the main image grabbing loop.
1829  while(!derived().shutdown())
1830  {
1831  //Get timespec for sem_timedwait
1832  timespec ts;
1833  if(clock_gettime(CLOCK_REALTIME, &ts) < 0)
1834  {
1835  derivedT::template log<software_critical>({__FILE__,__LINE__,errno,0,"clock_gettime"});
1836  return;
1837  }
1838  ts.tv_sec += 1;
1839 
1840  //Wait on semaphore
1841  if(sem_timedwait(&m_satSemaphore, &ts) == 0)
1842  {
1843  //not a timeout -->accumulate
1844  for(int rr=0; rr < m_instSatMap.rows(); ++rr)
1845  {
1846  for(int cc=0; cc< m_instSatMap.cols(); ++cc)
1847  {
1848  m_accumSatMap(rr,cc) += m_instSatMap(rr,cc);
1849  }
1850  }
1851  ++naccum;
1852 
1853  // If less than avg int --> go back and wait again
1854  if(mx::sys::get_curr_time(ts) - t_accumst < m_satAvgInt/1000.0) continue;
1855 
1856 
1857 
1858 
1859  // If greater than avg int --> calc stats, write to streams.
1860  m_overSatAct = 0;
1861  for(int rr=0; rr < m_instSatMap.rows(); ++rr)
1862  {
1863  for(int cc=0; cc< m_instSatMap.cols(); ++cc)
1864  {
1865  m_satPercMap(rr,cc) = m_accumSatMap(rr,cc)/naccum;
1866  if(m_satPercMap(rr,cc) >= m_percThreshold) ++m_overSatAct;
1867  satmap(rr,cc) = (m_accumSatMap(rr,cc) > 0); //it's 1/0 map
1868  }
1869  }
1870 
1871  //Check of the number of actuators saturated above the percent threshold is greater than the number threshold
1872  //if it is, increment the counter
1873  if(m_overSatAct/(m_satPercMap.rows()*m_satPercMap.cols()*0.75) > m_intervalSatThreshold ) ++m_intervalSatExceeds;
1874  else m_intervalSatExceeds = 0;
1875 
1876  //If enough consecutive intervals exceed the count threshold, we trigger
1877  if(m_intervalSatExceeds >= m_intervalSatCountThreshold) m_intervalSatTrip = true;
1878 
1879  m_satImageStream.md->write=1;
1880  m_satPercImageStream.md->write=1;
1881 
1882  memcpy( m_satImageStream.array.raw, satmap.data() , m_dmWidth*m_dmHeight*sizeof(uint8_t));
1883  memcpy( m_satPercImageStream.array.raw, m_satPercMap.data() , m_dmWidth*m_dmHeight*sizeof(float));
1884 
1885  //Set the time of last write
1886  clock_gettime(CLOCK_REALTIME, &m_satImageStream.md->writetime);
1887  m_satPercImageStream.md->writetime = m_satImageStream.md->writetime;
1888 
1889  //Set the image acquisition timestamp
1890  m_satImageStream.md->atime = m_satImageStream.md->writetime;
1891  m_satPercImageStream.md->atime = m_satPercImageStream.md->writetime;
1892 
1893  //Update cnt1
1894  m_satImageStream.md->cnt1 = 0;
1895  m_satPercImageStream.md->cnt1 = 0;
1896 
1897  //Update cnt0
1898  m_satImageStream.md->cnt0++;
1899  m_satPercImageStream.md->cnt0++;
1900 
1901  m_satImageStream.writetimearray[0] = m_satImageStream.md->writetime;
1902  m_satImageStream.atimearray[0] = m_satImageStream.md->atime;
1903  m_satImageStream.cntarray[0] = m_satImageStream.md->cnt0;
1904 
1905  m_satPercImageStream.writetimearray[0] = m_satPercImageStream.md->writetime;
1906  m_satPercImageStream.atimearray[0] = m_satPercImageStream.md->atime;
1907  m_satPercImageStream.cntarray[0] = m_satPercImageStream.md->cnt0;
1908 
1909  //And post
1910  m_satImageStream.md->write=0;
1911  ImageStreamIO_sempost(&m_satImageStream,-1);
1912 
1913  m_satPercImageStream.md->write=0;
1914  ImageStreamIO_sempost(&m_satPercImageStream,-1);
1915 
1916  m_accumSatMap.setZero();
1917  naccum = 0;
1918  t_accumst = mx::sys::get_curr_time(ts);
1919  }
1920  else
1921  {
1922  //Check for why we timed out
1923  if(errno == EINTR) break; //This indicates signal interrupted us, time to restart or shutdown, loop will exit normally if flags set.
1924 
1925  //ETIMEDOUT just means we should wait more.
1926  //Otherwise, report an error.
1927  if(errno != ETIMEDOUT)
1928  {
1929  derivedT::template log<software_error>({__FILE__, __LINE__,errno, "sem_timedwait"});
1930  break;
1931  }
1932  }
1933  }
1934 
1935  if(opened)
1936  {
1937  ImageStreamIO_destroyIm( &m_satImageStream );
1938 
1939  ImageStreamIO_destroyIm( &m_satPercImageStream );
1940  }
1941 }
1942 
1943 template<class derivedT, typename realT>
1945 {
1946  if( !derived().m_indiDriver ) return 0;
1947 
1948 
1949 
1950  return 0;
1951 }
1952 
1953 template<class derivedT, typename realT>
1955  const pcf::IndiProperty &ipRecv
1956  )
1957 {
1958  return static_cast<derivedT *>(app)->newCallBack_init(ipRecv);
1959 }
1960 
1961 template<class derivedT, typename realT>
1962 int dm<derivedT,realT>::newCallBack_init( const pcf::IndiProperty &ipRecv )
1963 {
1964  if( ipRecv.createUniqueKey() != m_indiP_init.createUniqueKey())
1965  {
1966  return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "wrong INDI-P in callback"});
1967  }
1968 
1969  if(!ipRecv.find("request")) return 0;
1970 
1971  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
1972  {
1973  return derived().initDM();
1974  }
1975  return 0;
1976 }
1977 
1978 template<class derivedT, typename realT>
1980  const pcf::IndiProperty &ipRecv
1981  )
1982 {
1983  return static_cast<derivedT *>(app)->newCallBack_zero(ipRecv);
1984 }
1985 
1986 template<class derivedT, typename realT>
1987 int dm<derivedT,realT>::newCallBack_zero( const pcf::IndiProperty &ipRecv )
1988 {
1989  if( ipRecv.createUniqueKey() != m_indiP_zero.createUniqueKey())
1990  {
1991  return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "wrong INDI-P in callback"});
1992  }
1993 
1994  if(!ipRecv.find("request")) return 0;
1995 
1996  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
1997  {
1998  return derived().zeroDM();
1999  }
2000  return 0;
2001 }
2002 
2003 template<class derivedT, typename realT>
2005  const pcf::IndiProperty &ipRecv
2006  )
2007 {
2008  return static_cast<derivedT *>(app)->newCallBack_release(ipRecv);
2009 }
2010 
2011 template<class derivedT, typename realT>
2012 int dm<derivedT,realT>::newCallBack_release( const pcf::IndiProperty &ipRecv )
2013 {
2014  if( ipRecv.createUniqueKey() != m_indiP_release.createUniqueKey())
2015  {
2016  return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "wrong INDI-P in callback"});
2017  }
2018 
2019  if(!ipRecv.find("request")) return 0;
2020 
2021  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
2022  {
2023  return releaseDM();
2024  }
2025  return 0;
2026 }
2027 
2028 template<class derivedT, typename realT>
2030  const pcf::IndiProperty &ipRecv
2031  )
2032 {
2033  return static_cast< derivedT *>(app)->newCallBack_flats(ipRecv);
2034 }
2035 
2036 template<class derivedT, typename realT>
2037 int dm<derivedT,realT>::newCallBack_flats( const pcf::IndiProperty &ipRecv )
2038 {
2039  if(ipRecv.createUniqueKey() != m_indiP_flats.createUniqueKey())
2040  {
2041  derivedT::template log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
2042  return -1;
2043  }
2044 
2045  std::string newFlat;
2046 
2047  if(ipRecv.find("default"))
2048  {
2049  if(ipRecv["default"].getSwitchState() == pcf::IndiElement::On)
2050  {
2051  newFlat = "default";
2052  }
2053  }
2054 
2055  //always do this to check for error:
2056  for(auto i=m_flatCommands.begin(); i != m_flatCommands.end(); ++i)
2057  {
2058  if(!ipRecv.find(i->first)) continue;
2059 
2060  if(ipRecv[i->first].getSwitchState() == pcf::IndiElement::On)
2061  {
2062  if(newFlat != "")
2063  {
2064  derivedT::template log<text_log>("More than one flat selected", logPrio::LOG_ERROR);
2065  return -1;
2066  }
2067 
2068  newFlat = i->first;
2069  }
2070  }
2071 
2072  if(newFlat == "") return 0;
2073 
2074  return loadFlat(newFlat);
2075 }
2076 
2077 template<class derivedT, typename realT>
2079  const pcf::IndiProperty &ipRecv
2080  )
2081 {
2082  return static_cast< derivedT *>(app)->newCallBack_setFlat(ipRecv);
2083 }
2084 
2085 template<class derivedT, typename realT>
2086 int dm<derivedT,realT>::newCallBack_setFlat( const pcf::IndiProperty &ipRecv )
2087 {
2088  if( ipRecv.createUniqueKey() != m_indiP_setFlat.createUniqueKey())
2089  {
2090  return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "wrong INDI-P in callback"});
2091  }
2092 
2093  if(!ipRecv.find("toggle")) return 0;
2094 
2095  if(ipRecv["toggle"] == pcf::IndiElement::On)
2096  {
2097  return setFlat();
2098  }
2099  else
2100  {
2101  return zeroFlat();
2102  }
2103 }
2104 
2105 template<class derivedT, typename realT>
2107  const pcf::IndiProperty &ipRecv
2108  )
2109 {
2110  return static_cast< derivedT *>(app)->newCallBack_tests(ipRecv);
2111 }
2112 
2113 template<class derivedT, typename realT>
2114 int dm<derivedT,realT>::newCallBack_tests( const pcf::IndiProperty &ipRecv )
2115 {
2116  if(ipRecv.createUniqueKey() != m_indiP_tests.createUniqueKey())
2117  {
2118  derivedT::template log<software_error>({__FILE__, __LINE__, "invalid indi property received"});
2119  return -1;
2120  }
2121 
2122  std::string newTest;
2123 
2124  if(ipRecv.find("default"))
2125  {
2126  if(ipRecv["default"].getSwitchState() == pcf::IndiElement::On)
2127  {
2128  newTest = "default";
2129  }
2130  }
2131 
2132  //always do this to check for error:
2133  for(auto i=m_testCommands.begin(); i != m_testCommands.end(); ++i)
2134  {
2135  if(!ipRecv.find(i->first)) continue;
2136 
2137  if(ipRecv[i->first].getSwitchState() == pcf::IndiElement::On)
2138  {
2139  if(newTest != "")
2140  {
2141  derivedT::template log<text_log>("More than one test selected", logPrio::LOG_ERROR);
2142  return -1;
2143  }
2144 
2145  newTest = i->first;
2146  }
2147  }
2148 
2149  if(newTest == "") return 0;
2150 
2151  return loadTest(newTest);
2152 }
2153 
2154 template<class derivedT, typename realT>
2156  const pcf::IndiProperty &ipRecv
2157  )
2158 {
2159  return static_cast< derivedT *>(app)->newCallBack_setTest(ipRecv);
2160 }
2161 
2162 template<class derivedT, typename realT>
2163 int dm<derivedT,realT>::newCallBack_setTest( const pcf::IndiProperty &ipRecv )
2164 {
2165  if( ipRecv.createUniqueKey() != m_indiP_setTest.createUniqueKey())
2166  {
2167  return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "wrong INDI-P in callback"});
2168  }
2169 
2170  if(!ipRecv.find("toggle")) return 0;
2171 
2172  if(ipRecv["toggle"] == pcf::IndiElement::On)
2173  {
2174  return setTest();
2175  }
2176  else
2177  {
2178  return zeroTest();
2179  }
2180 }
2181 
2182 
2183 template<class derivedT, typename realT>
2185  const pcf::IndiProperty &ipRecv
2186  )
2187 {
2188  return static_cast< derivedT *>(app)->newCallBack_zeroAll(ipRecv);
2189 }
2190 
2191 template<class derivedT, typename realT>
2192 int dm<derivedT,realT>::newCallBack_zeroAll( const pcf::IndiProperty &ipRecv )
2193 {
2194  if( ipRecv.createUniqueKey() != m_indiP_zeroAll.createUniqueKey())
2195  {
2196  return derivedT::template log<software_error,-1>({__FILE__, __LINE__, "wrong INDI-P in callback"});
2197  }
2198 
2199  if(!ipRecv.find("request")) return 0;
2200 
2201  if( ipRecv["request"].getSwitchState() == pcf::IndiElement::On)
2202  {
2203  indi::updateSwitchIfChanged(m_indiP_zeroAll, "request", pcf::IndiElement::On, derived().m_indiDriver, INDI_BUSY);
2204 
2205  std::lock_guard<std::mutex> guard(derived().m_indiMutex);
2206  return zeroAll();
2207  }
2208  return 0;
2209 }
2210 
2211 } //namespace dev
2212 } //namespace app
2213 } //namespace MagAOX
2214 #endif
#define IMAGESTRUCT_FLOAT
Definition: ImageStruct.hpp:22
#define IMAGESTRUCT_UINT8
Definition: ImageStruct.hpp:14
std::string m_calibPath
The path to this DM's calibration files.
Definition: dm.hpp:77
std::map< std::string, std::string > m_flatCommands
Map of flat file name to full path.
Definition: dm.hpp:113
int newCallBack_tests(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:2114
bool m_testSet
Flag indicating whether the test command has been set.
Definition: dm.hpp:130
std::string m_flatPath
The path to this DM's flat files (usually the same as calibPath)
Definition: dm.hpp:78
pcf::IndiProperty m_indiP_setFlat
INDI toggle switch to set the current flat.
Definition: dm.hpp:400
static void satThreadStart(dm *d)
Thread starter, called by MagAOXApp::threadStart on thread construction. Calls satThreadExec.
Definition: dm.hpp:1783
pcf::IndiProperty m_indiP_init
Definition: dm.hpp:394
uint32_t m_dmWidth
The width of the images in the stream.
Definition: dm.hpp:94
pcf::IndiProperty m_indiP_flats
INDI Selection switch containing the flat files.
Definition: dm.hpp:398
void setupConfig(mx::app::appConfigurator &config)
Setup the configuration system.
Definition: dm.hpp:569
int zeroFlat()
Zero the flat command on the DM.
Definition: dm.hpp:1297
static int st_newCallBack_init(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
Definition: dm.hpp:1954
int checkFlats()
Check the flats directory and update the list of flats if anything changes.
Definition: dm.hpp:999
pcf::IndiProperty m_indiP_testShmim
Publish the shmim being used for the test command.
Definition: dm.hpp:404
int checkTests()
Check the tests directory and update the list of tests if anything changes.
Definition: dm.hpp:1350
static int st_newCallBack_tests(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the test file.
Definition: dm.hpp:2106
std::string m_flatDefault
The file name of the this DM's default flat command. Path and extension will be ignored and can be om...
Definition: dm.hpp:81
int setTest()
Send the current test command to the DM.
Definition: dm.hpp:1512
int clearSat()
Clear the saturation maps and zero the shared membory.
Definition: dm.hpp:1727
static int st_newCallBack_zero(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
Definition: dm.hpp:1979
int loadTest(const std::string &target)
Load a test file.
Definition: dm.hpp:1440
derivedT & derived()
Definition: dm.hpp:562
int loadFlat(const std::string &target)
Load a flat file.
Definition: dm.hpp:1127
int m_satThreadPrio
Priority of the saturation thread, should normally be > 0.
Definition: dm.hpp:92
IMAGE m_satPercImageStream
The ImageStreamIO shared memory buffer for the sat percentage map.
Definition: dm.hpp:324
std::string m_calibRelDir
The directory relative to the calibPath. Set this before calling dm<derivedT,realT>::loadConfig().
Definition: dm.hpp:109
pcf::IndiProperty m_indiP_setTest
INDI toggle switch to set the current test pattern.
Definition: dm.hpp:405
int findDMChannels()
Find the DM comb channels.
Definition: dm.hpp:874
static constexpr uint8_t m_dmDataType
The ImageStreamIO type code.
Definition: dm.hpp:97
pcf::IndiProperty m_indiP_tests
INDI Selection switch containing the test pattern files.
Definition: dm.hpp:403
std::string m_shmimFlat
The name of the shmim stream to write the flat to.
Definition: dm.hpp:84
int processImage(void *curr_src, const dev::shmimT &sp)
Called by shmimMonitor when a new DM command is available. This is just a pass-through to derivedT::c...
Definition: dm.hpp:956
uint32_t m_dmHeight
The height of the images in the stream.
Definition: dm.hpp:95
void loadConfig(mx::app::appConfigurator &config)
load the configuration system results
Definition: dm.hpp:610
int appShutdown()
DM shutdown.
Definition: dm.hpp:840
std::string m_testPath
The path to this DM's test files (default is calibPath/tests;.
Definition: dm.hpp:79
int newCallBack_zeroAll(const pcf::IndiProperty &ipRecv)
The callback for the zeroAll toggle switch, called by the static version.
Definition: dm.hpp:2192
bool m_flatSet
Flag indicating whether the flat command has been set.
Definition: dm.hpp:120
pcf::IndiProperty m_satThreadProp
The property to hold the saturation thread details.
Definition: dm.hpp:344
std::string m_shmimSatPerc
The name of the shmim stream to write the saturation percentage map to.
Definition: dm.hpp:87
int newCallBack_setTest(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:2163
static int st_newCallBack_flats(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for selecting the flat file.
Definition: dm.hpp:2029
std::string m_testCurrent
Definition: dm.hpp:124
int m_intervalSatExceeds
Definition: dm.hpp:133
bool m_flatLoaded
Flag indicating whether a flat is loaded in memory.
Definition: dm.hpp:117
bool m_testLoaded
Flag indicating whether a test command is loaded in memory.
Definition: dm.hpp:127
mx::improc::eigenImage< float > m_satPercMap
Map of the percentage of time each actator was saturated during the avg. interval.
Definition: dm.hpp:321
int setFlat(bool update=false)
Send the current flat command to the DM.
Definition: dm.hpp:1208
static int st_newCallBack_zeroAll(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for zeroing all channels.
Definition: dm.hpp:2184
int newCallBack_init(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:1962
int zeroAll(bool nosem=false)
Zero all channels.
Definition: dm.hpp:1653
pid_t m_satThreadID
The ID of the saturation thread.
Definition: dm.hpp:342
int zeroTest()
Zero the test command on the DM.
Definition: dm.hpp:1599
int m_satAvgInt
The time in milliseconds to accumulate saturation over.
Definition: dm.hpp:89
int newCallBack_release(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:2012
std::vector< std::string > m_satTriggerProperty
Definition: dm.hpp:104
int updateINDI()
Update the INDI properties for this device controller.
Definition: dm.hpp:1944
int newCallBack_flats(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:2037
sem_t m_satSemaphore
Semaphore used to tell the saturation thread to run.
Definition: dm.hpp:338
pcf::IndiProperty m_indiP_flat
Property used to set and report the current flat.
Definition: dm.hpp:392
IMAGE m_satImageStream
The ImageStreamIO shared memory buffer for the sat map.
Definition: dm.hpp:323
int newCallBack_zero(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:1987
bool m_satThreadInit
Synchronizer for thread startup, to allow priority setting to finish.
Definition: dm.hpp:340
std::vector< std::string > m_satTriggerDevice
Definition: dm.hpp:103
mx::improc::eigenImage< realT > m_flatCommand
Data storage for the flat command.
Definition: dm.hpp:116
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
void satThreadExec()
Execute saturation processing.
Definition: dm.hpp:1789
static int st_newCallBack_release(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for initializing the DM.
Definition: dm.hpp:2004
pcf::IndiProperty m_indiP_flatShmim
Publish the shmim being used for the flat.
Definition: dm.hpp:399
float m_intervalSatThreshold
Definition: dm.hpp:100
int whilePowerOff()
DM Poweroff Updates.
Definition: dm.hpp:865
static int st_newCallBack_setFlat(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the flat.
Definition: dm.hpp:2078
static int st_newCallBack_setTest(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for setting the test shape.
Definition: dm.hpp:2155
pcf::IndiProperty m_indiP_zeroAll
Definition: dm.hpp:407
std::map< std::string, std::string > m_testCommands
Map of test file name to full path.
Definition: dm.hpp:123
IMAGE m_testImageStream
The ImageStreamIO shared memory buffer for the test.
Definition: dm.hpp:129
std::string m_testDefault
The file name of the this DM's default test command. Path and extension will be ignored and can be om...
Definition: dm.hpp:82
int m_channels
The number of dmcomb channels found as part of allocation.
Definition: dm.hpp:111
std::string m_shmimTest
The name of the shmim stream to write the test to.
Definition: dm.hpp:85
bool m_intervalSatTrip
Definition: dm.hpp:134
int m_intervalSatCountThreshold
Definition: dm.hpp:101
int appStartup()
Startup function.
Definition: dm.hpp:683
int newCallBack_setFlat(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
Definition: dm.hpp:2086
mx::improc::eigenImage< uint16_t > m_accumSatMap
The accumulated saturation map, which acccumulates for m_satAvgInt then is publised as a 0/1 image.
Definition: dm.hpp:320
int onPowerOff()
DM Poweroff.
Definition: dm.hpp:858
float m_percThreshold
Definition: dm.hpp:99
std::string m_shmimSat
The name of the shmim stream to write the saturation map to.
Definition: dm.hpp:86
mx::improc::eigenImage< realT > m_testCommand
Data storage for the test command.
Definition: dm.hpp:126
IMAGE m_flatImageStream
The ImageStreamIO shared memory buffer for the flat.
Definition: dm.hpp:119
std::thread m_satThread
A separate thread for the actual saturation processing.
Definition: dm.hpp:346
pcf::IndiProperty m_indiP_release
Definition: dm.hpp:396
pcf::IndiProperty m_indiP_zero
Definition: dm.hpp:395
int releaseDM()
Calls derived()->releaseDM() and then 0s all channels and the sat map.
Definition: dm.hpp:980
int allocate(const dev::shmimT &sp)
Called after shmimMonitor connects to the dmXXdisp stream. Checks for proper size.
Definition: dm.hpp:910
std::string m_flatCurrent
The name of the current flat command.
Definition: dm.hpp:114
void intervalSatTrip()
Definition: dm.hpp:354
int appLogic()
DM application logic.
Definition: dm.hpp:815
#define INDI_IDLE
Definition: indiUtils.hpp:28
#define INDI_BUSY
Definition: indiUtils.hpp:30
constexpr uint8_t ImageStreamTypeCode< float >()
Definition: dm.hpp:37
constexpr uint8_t ImageStreamTypeCode()
Definition: dm.hpp:31
constexpr uint8_t ImageStreamTypeCode< double >()
Definition: dm.hpp:43
void updateSwitchIfChanged(pcf::IndiProperty &p, const std::string &el, const pcf::IndiElement::SwitchStateType &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:206
const pcf::IndiProperty & ipRecv
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