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