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