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