API
dmMode.hpp
Go to the documentation of this file.
1 /** \file dmMode.hpp
2  * \brief The MagAO-X DM mode command header file
3  *
4  * \ingroup dmMode_files
5  */
6 
7 #ifndef dmMode_hpp
8 #define dmMode_hpp
9 
10 #include <mx/improc/eigenCube.hpp>
11 #include <mx/ioutils/fits/fitsFile.hpp>
12 #include <mx/improc/eigenImage.hpp>
13 #include <mx/ioutils/stringUtils.hpp>
14 #include <mx/sys/timeUtils.hpp>
15 
16 #include "../../libMagAOX/libMagAOX.hpp" //Note this is included on command line to trigger pch
17 #include "../../magaox_git_version.h"
18 
19 /** \defgroup dmMode
20  * \brief The DM mode command app, places modes on a DM channel
21  * \todo update md doc
22  * \todo the current_amps/target_amps thing is dumb. Should consider mode00.target, mode00.current maybe.
23  *
24  * <a href="../handbook/operating/software/apps/dmMode.html">Application Documentation</a>
25  *
26  * \ingroup apps
27  *
28  */
29 
30 /** \defgroup dmMode_files
31  * \ingroup dmMode
32  */
33 
34 
35 namespace MagAOX
36 {
37 namespace app
38 {
39 
40 /// The MagAO-X DM mode commander
41 /**
42  * \ingroup dmMode
43  */
44 class dmMode : public MagAOXApp<true>, public dev::telemeter<dmMode>
45 {
46 
47  typedef float realT;
48 
50 
51  friend class dev::telemeter<dmMode>;
52  friend class dmMode_test;
53 
54 protected:
55 
56  /** \name Configurable Parameters
57  *@{
58  */
59 
60  std::string m_modeCube;
61 
62  int m_maxModes {50};
63 
64  std::string m_dmName;
65 
66  std::string m_dmChannelName;
67 
68  ///@}
69 
70  mx::improc::eigenCube<realT> m_modes;
71 
72  std::vector<realT> m_amps;
73 
74  mx::improc::eigenImage<realT> m_shape;
75 
76  IMAGE m_imageStream;
77  uint32_t m_width {0}; ///< The width of the image
78  uint32_t m_height {0}; ///< The height of the image.
79 
80  uint8_t m_dataType{0}; ///< The ImageStreamIO type code.
81  size_t m_typeSize {0}; ///< The size of the type, in bytes.
82 
83  bool m_opened {true};
84  bool m_restart {false};
85 
86 public:
87  /// Default c'tor.
88  dmMode();
89 
90  /// D'tor, declared and defined for noexcept.
91  ~dmMode() noexcept
92  {}
93 
94  virtual void setupConfig();
95 
96  /// Implementation of loadConfig logic, separated for testing.
97  /** This is called by loadConfig().
98  */
99  int loadConfigImpl( mx::app::appConfigurator & _config /**< [in] an application configuration from which to load values*/);
100 
101  virtual void loadConfig();
102 
103  /// Startup function
104  /**
105  *
106  */
107  virtual int appStartup();
108 
109  /// Implementation of the FSM for dmMode.
110  /**
111  * \returns 0 on no critical error
112  * \returns -1 on an error requiring shutdown
113  */
114  virtual int appLogic();
115 
116  /// Shutdown the app.
117  /**
118  *
119  */
120  virtual int appShutdown();
121 
122 
123  int sendCommand();
124 
125  //INDI:
126 protected:
127  //declare our properties
128  pcf::IndiProperty m_indiP_dm;
129  pcf::IndiProperty m_indiP_currAmps;
130  pcf::IndiProperty m_indiP_tgtAmps;
131 
132  std::vector<std::string> m_elNames;
133 public:
136 
137  /** \name Telemeter Interface
138  *
139  * @{
140  */
141  int checkRecordTimes();
142 
143  int recordTelem( const telem_dmmodes * );
144 
145  int recordDmModes( bool force = false );
146  ///@}
147 
148 
149 };
150 
151 dmMode::dmMode() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
152 {
153 
154  return;
155 }
156 
158 {
159  config.add("dm.modeCube", "", "dm.modeCube", argType::Required, "dm", "modeCube", false, "string", "Full path to the FITS file containing the modes for this DM.");
160  config.add("dm.maxModes", "", "dm.maxModes", argType::Required, "dm", "maxModes", false, "int", "The maximum number of modes to use (truncates the cube). If <=0 all modes in cube are used.");
161  config.add("dm.name", "", "dm.name", argType::Required, "dm", "name", false, "string", "The descriptive name of this dm. Default is the channel name.");
162  config.add("dm.channelName", "", "dm.channelName", argType::Required, "dm", "channelName", false, "string", "The name of the DM channel to write to.");
163  config.add("dm.maxModes", "", "dm.maxModes", argType::Required, "dm", "maxModes", false, "int", "The maximum number of modes to use (truncates the cube).");
164 
165  telemeterT::setupConfig(config);
166 }
167 
168 int dmMode::loadConfigImpl( mx::app::appConfigurator & _config )
169 {
170 
171  _config(m_modeCube, "dm.modeCube");
172  _config(m_maxModes, "dm.maxModes");
173  _config(m_dmChannelName, "dm.channelName");
174 
176  _config(m_dmName, "dm.name");
177 
178  if(telemeterT::loadConfig(_config) < 0)
179  {
180  log<text_log>("Error during telemeter config", logPrio::LOG_CRITICAL);
181  m_shutdown = true;
182  }
183 
184  return 0;
185 }
186 
188 {
189  loadConfigImpl(config);
190 }
191 
193 {
194  mx::fits::fitsFile<realT> ff;
195 
196  if(ff.read(m_modes, m_modeCube) < 0)
197  {
198  return log<text_log,-1>("Could not open mode cube file", logPrio::LOG_ERROR);
199  }
200 
201  if(m_maxModes > 0 && m_maxModes < m_modes.planes())
202  {
203  mx::improc::eigenCube<realT> modes;
204  //This probably just works as a realloc in eigenCube but I haven't looked.
205  modes.resize(m_modes.rows(), m_modes.cols(), m_maxModes);
206  for(int p =0; p < modes.planes(); ++p) modes.image(p) = m_modes.image(p);
207  m_modes.resize(m_modes.rows(), m_modes.cols(), m_maxModes);
208  for(int p =0; p < modes.planes(); ++p) m_modes.image(p) = modes.image(p);
209  }
210 
211 
212 
213  m_amps.resize(m_modes.planes(), 0);
214  m_shape.resize(m_modes.rows(), m_modes.cols());
215 
216  REG_INDI_NEWPROP_NOCB(m_indiP_dm, "dm", pcf::IndiProperty::Text);
217  m_indiP_dm.add(pcf::IndiElement("name"));
218  m_indiP_dm["name"] = m_dmName;
219  m_indiP_dm.add(pcf::IndiElement("channel"));
220  m_indiP_dm["channel"] = m_dmChannelName;
221 
222  REG_INDI_NEWPROP(m_indiP_currAmps, "current_amps", pcf::IndiProperty::Number);
223  REG_INDI_NEWPROP(m_indiP_tgtAmps, "target_amps", pcf::IndiProperty::Number);
224 
225  m_elNames.resize(m_amps.size());
226 
227  for(size_t n=0; n < m_amps.size(); ++n)
228  {
229  //std::string el = std::to_string(n);
230  m_elNames[n] = mx::ioutils::convertToString<size_t, 4, '0'>(n);
231 
232  m_indiP_currAmps.add( pcf::IndiElement(m_elNames[n]) );
233  m_indiP_currAmps[m_elNames[n]].set(0);
234 
235  m_indiP_tgtAmps.add( pcf::IndiElement(m_elNames[n]) );
236  }
237 
238  if(telemeterT::appStartup() < 0)
239  {
240  return log<software_error,-1>({__FILE__,__LINE__});
241  }
242 
244 
245 
246 
247  return 0;
248 }
249 
251 {
253  {
254  m_opened = false;
255  m_restart = false; //Set this up front, since we're about to restart.
256 
257  if( ImageStreamIO_openIm(&m_imageStream, m_dmChannelName.c_str()) == 0)
258  {
259  if(m_imageStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
260  {
261  ImageStreamIO_closeIm(&m_imageStream);
262  }
263  else
264  {
265  m_opened = true;
266  }
267  }
268 
269  if(m_opened)
270  {
272  }
273  }
274 
276  {
277  m_dataType = m_imageStream.md[0].datatype;
278  m_typeSize = ImageStreamIO_typesize(m_dataType);
279  m_width = m_imageStream.md[0].size[0];
280  m_height = m_imageStream.md[0].size[1];
281 
282 
283  if(m_dataType != _DATATYPE_FLOAT )
284  {
285  return log<text_log,-1>("Data type of DM channel is not float.", logPrio::LOG_CRITICAL);
286  }
287 
288  if(m_typeSize != sizeof(realT))
289  {
290  return log<text_log,-1>("Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL);
291  }
292 
293  if(m_width != m_modes.rows())
294  {
295  return log<text_log,-1>("Size mismatch between DM and modes (rows)", logPrio::LOG_CRITICAL);
296  }
297 
298  if(m_height != m_modes.cols())
299  {
300  return log<text_log,-1>("Size mismatch between DM and modes (cols)", logPrio::LOG_CRITICAL);
301  }
302 
303  for(size_t n=0; n < m_amps.size(); ++n) m_amps[n] = 0;
304  sendCommand();
305 
307  }
308 
309  if(state() == stateCodes::READY)
310  {
311  if(telemeterT::appLogic() < 0)
312  {
313  log<software_error>({__FILE__, __LINE__});
314  return 0;
315  }
316  }
317 
318  return 0;
319 }
320 
322 {
324 
325  return 0;
326 }
327 
329 {
330  if(!m_opened)
331  {
332  log<text_log>("not connected to DM channel.", logPrio::LOG_WARNING);
333  return 0;
334  }
335 
336  m_shape = m_amps[0]*m_modes.image(0);
337 
338  for(size_t n = 1; n<m_amps.size(); ++n)
339  {
340  m_shape += m_amps[n]*m_modes.image(n);
341  }
342 
343  if(m_imageStream.md[0].write)
344  {
345  while(m_imageStream.md[0].write) mx::sys::microSleep(10);
346  }
347 
348  recordDmModes(true);
349  m_imageStream.md[0].write = 1;
350 
351  uint32_t curr_image;
352  if(m_imageStream.md[0].size[2] > 0) ///\todo change to naxis?
353  {
354  curr_image = m_imageStream.md[0].cnt1;
355  }
356  else curr_image = 0;
357 
358  char* next_dest = (char *) m_imageStream.array.raw + curr_image*m_width*m_height*m_typeSize;
359 
360  memcpy(next_dest, m_shape.data(), m_width*m_height*m_typeSize);
361 
362  m_imageStream.md[0].cnt0++;
363 
364  m_imageStream.md->write=0;
365  ImageStreamIO_sempost(&m_imageStream,-1);
366 
367  recordDmModes(true);
368 
369  for(size_t n = 0; n<m_amps.size(); ++n)
370  {
372  }
373  m_indiP_currAmps.setState (pcf::IndiProperty::Ok);
374  m_indiDriver->sendSetProperty (m_indiP_currAmps);
375 
376  return 0;
377 
378 }
379 
380 INDI_NEWCALLBACK_DEFN(dmMode, m_indiP_currAmps)(const pcf::IndiProperty &ipRecv)
381 {
382  if (ipRecv.getName() == m_indiP_currAmps.getName())
383  {
384  size_t found = 0;
385  for(size_t n=0; n < m_amps.size(); ++n)
386  {
387  if(ipRecv.find(m_elNames[n]))
388  {
389  realT amp = ipRecv[m_elNames[n]].get<realT>();
390 
391  ///\todo add bounds checks here
392 
393  m_amps[n] = amp;
394  ++found;
395  }
396  }
397 
398  if(found)
399  {
400  return sendCommand();
401  }
402 
403  return 0;
404 
405  }
406 
407  return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
408 }
409 
410 INDI_NEWCALLBACK_DEFN(dmMode, m_indiP_tgtAmps)(const pcf::IndiProperty &ipRecv)
411 {
412  if (ipRecv.getName() == m_indiP_tgtAmps.getName())
413  {
414  size_t found = 0;
415  for(size_t n=0; n < m_amps.size(); ++n)
416  {
417  if(ipRecv.find(m_elNames[n]))
418  {
419  realT amp = ipRecv[m_elNames[n]].get<realT>();
420 
421  ///\todo add bounds checks here
422 
423  m_amps[n] = amp;
424  ++found;
425  }
426  }
427 
428  if(found)
429  {
430  return sendCommand();
431  }
432 
433  return 0;
434 
435  }
436 
437  return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
438 }
439 
441 {
443 }
444 
446 {
447  return recordDmModes(true);
448 }
449 
450 int dmMode::recordDmModes( bool force )
451 {
452  static std::vector<float> lastamps(m_amps.size(), std::numeric_limits<float>::max());
453 
454  bool changed = false;
455  for(size_t p=0; p < m_amps.size(); ++p)
456  {
457  if(m_amps[p] != lastamps[p]) changed = true;
458  }
459 
460  if( changed || force )
461  {
462  for(size_t p=0; p < m_amps.size(); ++p)
463  {
464  lastamps[p] = m_amps[p];
465  }
466 
467  telem<telem_dmmodes>(telem_dmmodes::messageT(lastamps));
468  }
469 
470  return 0;
471 }
472 
473 } //namespace app
474 } //namespace MagAOX
475 
476 #endif //dmMode_hpp
The base-class for MagAO-X applications.
Definition: MagAOXApp.hpp:73
stateCodes::stateCodeT state()
Get the current state code.
Definition: MagAOXApp.hpp:2297
int m_shutdown
Flag to signal it's time to shutdown. When not 0, the main loop exits.
Definition: MagAOXApp.hpp:100
indiDriver< MagAOXApp > * m_indiDriver
The INDI driver wrapper. Constructed and initialized by execute, which starts and stops communication...
Definition: MagAOXApp.hpp:542
static int log(const typename logT::messageT &msg, logPrioT level=logPrio::LOG_DEFAULT)
Make a log entry.
Definition: MagAOXApp.hpp:1804
The MagAO-X DM mode commander.
Definition: dmMode.hpp:45
std::string m_dmChannelName
Definition: dmMode.hpp:66
uint32_t m_width
The width of the image.
Definition: dmMode.hpp:77
mx::improc::eigenCube< realT > m_modes
Definition: dmMode.hpp:70
virtual int appShutdown()
Shutdown the app.
Definition: dmMode.hpp:321
int recordDmModes(bool force=false)
Definition: dmMode.hpp:450
std::string m_modeCube
Definition: dmMode.hpp:60
std::string m_dmName
Definition: dmMode.hpp:64
INDI_NEWCALLBACK_DECL(dmMode, m_indiP_currAmps)
pcf::IndiProperty m_indiP_currAmps
Definition: dmMode.hpp:129
dev::telemeter< dmMode > telemeterT
Definition: dmMode.hpp:49
friend class dmMode_test
Definition: dmMode.hpp:52
mx::improc::eigenImage< realT > m_shape
Definition: dmMode.hpp:74
size_t m_typeSize
The size of the type, in bytes.
Definition: dmMode.hpp:81
pcf::IndiProperty m_indiP_dm
Definition: dmMode.hpp:128
int checkRecordTimes()
Definition: dmMode.hpp:440
std::vector< realT > m_amps
Definition: dmMode.hpp:72
~dmMode() noexcept
D'tor, declared and defined for noexcept.
Definition: dmMode.hpp:91
uint32_t m_height
The height of the image.
Definition: dmMode.hpp:78
INDI_NEWCALLBACK_DECL(dmMode, m_indiP_tgtAmps)
virtual int appLogic()
Implementation of the FSM for dmMode.
Definition: dmMode.hpp:250
int loadConfigImpl(mx::app::appConfigurator &_config)
Implementation of loadConfig logic, separated for testing.
Definition: dmMode.hpp:168
uint8_t m_dataType
The ImageStreamIO type code.
Definition: dmMode.hpp:80
virtual int appStartup()
Startup function.
Definition: dmMode.hpp:192
virtual void setupConfig()
Definition: dmMode.hpp:157
virtual void loadConfig()
Definition: dmMode.hpp:187
int recordTelem(const telem_dmmodes *)
Definition: dmMode.hpp:445
std::vector< std::string > m_elNames
Definition: dmMode.hpp:132
dmMode()
Default c'tor.
Definition: dmMode.hpp:151
pcf::IndiProperty m_indiP_tgtAmps
Definition: dmMode.hpp:130
#define REG_INDI_NEWPROP_NOCB(prop, propName, type)
Register a NEW INDI property with the class, with no callback.
Definition: indiMacros.hpp:248
#define REG_INDI_NEWPROP(prop, propName, type)
Register a NEW INDI property with the class, using the standard callback name.
Definition: indiMacros.hpp:230
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:56
@ CONNECTED
The application has connected to the device or service.
Definition: stateCodes.hpp:50
@ NOTCONNECTED
The application is not connected to the device or service.
Definition: stateCodes.hpp:49
const pcf::IndiProperty & ipRecv
Definition: MagAOXApp.hpp:3434
INDI_NEWCALLBACK_DEFN(acesxeCtrl, m_indiP_windspeed)(const pcf
Definition: acesxeCtrl.hpp:687
Definition: dm.hpp:24
constexpr static logPrioT LOG_CRITICAL
The process can not continue and will shut down (fatal)
Definition: logPriority.hpp:37
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40
constexpr static logPrioT LOG_WARNING
A condition has occurred which may become an error, but the process continues.
Definition: logPriority.hpp:43
A device base class which saves telemetry.
Definition: telemeter.hpp:69
int appShutdown()
Perform telemeter application shutdown.
Definition: telemeter.hpp:274
int loadConfig(appConfigurator &config)
Load the device section from an application configurator.
Definition: telemeter.hpp:223
int appLogic()
Perform telemeter application logic.
Definition: telemeter.hpp:268
int setupConfig(appConfigurator &config)
Setup an application configurator for the device section.
Definition: telemeter.hpp:211
int appStartup()
Starts the telemetry log thread.
Definition: telemeter.hpp:241
int checkRecordTimes(const telT &tel, telTs... tels)
Check the time of the last record for each telemetry type and make an entry if needed.
Definition: telemeter.hpp:281
Software ERR log entry.
The type of the input message.
Log entry recording DM Mode Amplitudes.
A simple text log, a string-type log.
Definition: text_log.hpp:24