Line data Source code
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 :
49 : typedef dev::telemeter<dmMode> telemeterT;
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 0 : ~dmMode() noexcept
92 0 : {}
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:
134 0 : INDI_NEWCALLBACK_DECL(dmMode, m_indiP_currAmps);
135 0 : INDI_NEWCALLBACK_DECL(dmMode, m_indiP_tgtAmps);
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 0 : dmMode::dmMode() : MagAOXApp(MAGAOX_CURRENT_SHA1, MAGAOX_REPO_MODIFIED)
152 : {
153 :
154 0 : return;
155 0 : }
156 :
157 0 : void dmMode::setupConfig()
158 : {
159 0 : 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 0 : 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 0 : config.add("dm.name", "", "dm.name", argType::Required, "dm", "name", false, "string", "The descriptive name of this dm. Default is the channel name.");
162 0 : config.add("dm.channelName", "", "dm.channelName", argType::Required, "dm", "channelName", false, "string", "The name of the DM channel to write to.");
163 0 : config.add("dm.maxModes", "", "dm.maxModes", argType::Required, "dm", "maxModes", false, "int", "The maximum number of modes to use (truncates the cube).");
164 :
165 0 : telemeterT::setupConfig(config);
166 0 : }
167 :
168 0 : int dmMode::loadConfigImpl( mx::app::appConfigurator & _config )
169 : {
170 :
171 0 : _config(m_modeCube, "dm.modeCube");
172 0 : _config(m_maxModes, "dm.maxModes");
173 0 : _config(m_dmChannelName, "dm.channelName");
174 :
175 0 : m_dmName = m_dmChannelName;
176 0 : _config(m_dmName, "dm.name");
177 :
178 0 : if(telemeterT::loadConfig(_config) < 0)
179 : {
180 0 : log<text_log>("Error during telemeter config", logPrio::LOG_CRITICAL);
181 0 : m_shutdown = true;
182 : }
183 :
184 0 : return 0;
185 : }
186 :
187 0 : void dmMode::loadConfig()
188 : {
189 0 : loadConfigImpl(config);
190 0 : }
191 :
192 0 : int dmMode::appStartup()
193 : {
194 0 : mx::fits::fitsFile<realT> ff;
195 :
196 0 : mx::error_t errc = ff.read(m_modes, m_modeCube);
197 0 : if(errc != mx::error_t::noerror)
198 : {
199 0 : return log<text_log,-1>(std::format("Could not open mode cube file: {} "
200 0 : "({})", mx::errorMessage(errc), mx::errorName(errc)), logPrio::LOG_ERROR);
201 : }
202 :
203 0 : if(m_maxModes > 0 && m_maxModes < m_modes.planes())
204 : {
205 0 : mx::improc::eigenCube<realT> modes;
206 : //This probably just works as a realloc in eigenCube but I haven't looked.
207 0 : modes.resize(m_modes.rows(), m_modes.cols(), m_maxModes);
208 0 : for(int p =0; p < modes.planes(); ++p) modes.image(p) = m_modes.image(p);
209 0 : m_modes.resize(m_modes.rows(), m_modes.cols(), m_maxModes);
210 0 : for(int p =0; p < modes.planes(); ++p) m_modes.image(p) = modes.image(p);
211 0 : }
212 :
213 :
214 :
215 0 : m_amps.resize(m_modes.planes(), 0);
216 0 : m_shape.resize(m_modes.rows(), m_modes.cols());
217 :
218 0 : REG_INDI_NEWPROP_NOCB(m_indiP_dm, "dm", pcf::IndiProperty::Text);
219 0 : m_indiP_dm.add(pcf::IndiElement("name"));
220 0 : m_indiP_dm["name"] = m_dmName;
221 0 : m_indiP_dm.add(pcf::IndiElement("channel"));
222 0 : m_indiP_dm["channel"] = m_dmChannelName;
223 :
224 0 : REG_INDI_NEWPROP(m_indiP_currAmps, "current_amps", pcf::IndiProperty::Number);
225 0 : REG_INDI_NEWPROP(m_indiP_tgtAmps, "target_amps", pcf::IndiProperty::Number);
226 :
227 0 : m_elNames.resize(m_amps.size());
228 :
229 0 : for(size_t n=0; n < m_amps.size(); ++n)
230 : {
231 : //std::string el = std::to_string(n);
232 0 : m_elNames[n] = mx::ioutils::convertToString<size_t, 4, '0'>(n);
233 :
234 0 : m_indiP_currAmps.add( pcf::IndiElement(m_elNames[n]) );
235 0 : m_indiP_currAmps[m_elNames[n]].set(0);
236 :
237 0 : m_indiP_tgtAmps.add( pcf::IndiElement(m_elNames[n]) );
238 : }
239 :
240 0 : if(telemeterT::appStartup() < 0)
241 : {
242 0 : return log<software_error,-1>({__FILE__,__LINE__});
243 : }
244 :
245 0 : state(stateCodes::NOTCONNECTED);
246 :
247 :
248 :
249 0 : return 0;
250 0 : }
251 :
252 0 : int dmMode::appLogic()
253 : {
254 0 : if(state() == stateCodes::NOTCONNECTED)
255 : {
256 0 : m_opened = false;
257 0 : m_restart = false; //Set this up front, since we're about to restart.
258 :
259 0 : if( ImageStreamIO_openIm(&m_imageStream, m_dmChannelName.c_str()) == 0)
260 : {
261 0 : if(m_imageStream.md[0].sem < 10) ///<\todo this is hardcoded in ImageStreamIO.c -- should be a define
262 : {
263 0 : ImageStreamIO_closeIm(&m_imageStream);
264 : }
265 : else
266 : {
267 0 : m_opened = true;
268 : }
269 : }
270 :
271 0 : if(m_opened)
272 : {
273 0 : state(stateCodes::CONNECTED);
274 : }
275 : }
276 :
277 0 : if(state() == stateCodes::CONNECTED)
278 : {
279 0 : m_dataType = m_imageStream.md[0].datatype;
280 0 : m_typeSize = ImageStreamIO_typesize(m_dataType);
281 0 : m_width = m_imageStream.md[0].size[0];
282 0 : m_height = m_imageStream.md[0].size[1];
283 :
284 :
285 0 : if(m_dataType != _DATATYPE_FLOAT )
286 : {
287 0 : return log<text_log,-1>("Data type of DM channel is not float.", logPrio::LOG_CRITICAL);
288 : }
289 :
290 0 : if(m_typeSize != sizeof(realT))
291 : {
292 0 : return log<text_log,-1>("Type-size mismatch, realT is not float.", logPrio::LOG_CRITICAL);
293 : }
294 :
295 0 : if(m_width != m_modes.rows())
296 : {
297 0 : return log<text_log,-1>("Size mismatch between DM and modes (rows)", logPrio::LOG_CRITICAL);
298 : }
299 :
300 0 : if(m_height != m_modes.cols())
301 : {
302 0 : return log<text_log,-1>("Size mismatch between DM and modes (cols)", logPrio::LOG_CRITICAL);
303 : }
304 :
305 0 : for(size_t n=0; n < m_amps.size(); ++n) m_amps[n] = 0;
306 0 : sendCommand();
307 :
308 0 : state(stateCodes::READY);
309 : }
310 :
311 0 : if(state() == stateCodes::READY)
312 : {
313 0 : if(telemeterT::appLogic() < 0)
314 : {
315 0 : log<software_error>({__FILE__, __LINE__});
316 0 : return 0;
317 : }
318 : }
319 :
320 0 : return 0;
321 : }
322 :
323 0 : int dmMode::appShutdown()
324 : {
325 0 : telemeterT::appShutdown();
326 :
327 0 : return 0;
328 : }
329 :
330 0 : int dmMode::sendCommand()
331 : {
332 0 : if(!m_opened)
333 : {
334 0 : log<text_log>("not connected to DM channel.", logPrio::LOG_WARNING);
335 0 : return 0;
336 : }
337 :
338 0 : m_shape = m_amps[0]*m_modes.image(0);
339 :
340 0 : for(size_t n = 1; n<m_amps.size(); ++n)
341 : {
342 0 : m_shape += m_amps[n]*m_modes.image(n);
343 : }
344 :
345 0 : if(m_imageStream.md[0].write)
346 : {
347 0 : while(m_imageStream.md[0].write) mx::sys::microSleep(10);
348 : }
349 :
350 0 : recordDmModes(true);
351 0 : m_imageStream.md[0].write = 1;
352 :
353 : uint32_t curr_image;
354 0 : if(m_imageStream.md[0].size[2] > 0) ///\todo change to naxis?
355 : {
356 0 : curr_image = m_imageStream.md[0].cnt1;
357 : }
358 0 : else curr_image = 0;
359 :
360 0 : char* next_dest = (char *) m_imageStream.array.raw + curr_image*m_width*m_height*m_typeSize;
361 :
362 0 : memcpy(next_dest, m_shape.data(), m_width*m_height*m_typeSize);
363 :
364 0 : m_imageStream.md[0].cnt0++;
365 :
366 0 : m_imageStream.md->write=0;
367 0 : ImageStreamIO_sempost(&m_imageStream,-1);
368 :
369 0 : recordDmModes(true);
370 :
371 0 : for(size_t n = 0; n<m_amps.size(); ++n)
372 : {
373 0 : m_indiP_currAmps[m_elNames[n]] = m_amps[n];
374 : }
375 0 : m_indiP_currAmps.setState (pcf::IndiProperty::Ok);
376 0 : m_indiDriver->sendSetProperty (m_indiP_currAmps);
377 :
378 0 : return 0;
379 :
380 : }
381 :
382 0 : INDI_NEWCALLBACK_DEFN(dmMode, m_indiP_currAmps)(const pcf::IndiProperty &ipRecv)
383 : {
384 0 : if (ipRecv.getName() == m_indiP_currAmps.getName())
385 : {
386 0 : size_t found = 0;
387 0 : for(size_t n=0; n < m_amps.size(); ++n)
388 : {
389 0 : if(ipRecv.find(m_elNames[n]))
390 : {
391 0 : realT amp = ipRecv[m_elNames[n]].get<realT>();
392 :
393 : ///\todo add bounds checks here
394 :
395 0 : m_amps[n] = amp;
396 0 : ++found;
397 : }
398 : }
399 :
400 0 : if(found)
401 : {
402 0 : return sendCommand();
403 : }
404 :
405 0 : return 0;
406 :
407 : }
408 :
409 0 : return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
410 : }
411 :
412 0 : INDI_NEWCALLBACK_DEFN(dmMode, m_indiP_tgtAmps)(const pcf::IndiProperty &ipRecv)
413 : {
414 0 : if (ipRecv.getName() == m_indiP_tgtAmps.getName())
415 : {
416 0 : size_t found = 0;
417 0 : for(size_t n=0; n < m_amps.size(); ++n)
418 : {
419 0 : if(ipRecv.find(m_elNames[n]))
420 : {
421 0 : realT amp = ipRecv[m_elNames[n]].get<realT>();
422 :
423 : ///\todo add bounds checks here
424 :
425 0 : m_amps[n] = amp;
426 0 : ++found;
427 : }
428 : }
429 :
430 0 : if(found)
431 : {
432 0 : return sendCommand();
433 : }
434 :
435 0 : return 0;
436 :
437 : }
438 :
439 0 : return log<software_error,-1>({__FILE__,__LINE__, "invalid indi property name"});
440 : }
441 :
442 0 : int dmMode::checkRecordTimes()
443 : {
444 0 : return telemeterT::checkRecordTimes(telem_dmmodes());
445 : }
446 :
447 0 : int dmMode::recordTelem( const telem_dmmodes * )
448 : {
449 0 : return recordDmModes(true);
450 : }
451 :
452 0 : int dmMode::recordDmModes( bool force )
453 : {
454 0 : static std::vector<float> lastamps(m_amps.size(), std::numeric_limits<float>::max());
455 :
456 0 : bool changed = false;
457 0 : for(size_t p=0; p < m_amps.size(); ++p)
458 : {
459 0 : if(m_amps[p] != lastamps[p]) changed = true;
460 : }
461 :
462 0 : if( changed || force )
463 : {
464 0 : for(size_t p=0; p < m_amps.size(); ++p)
465 : {
466 0 : lastamps[p] = m_amps[p];
467 : }
468 :
469 0 : telem<telem_dmmodes>(telem_dmmodes::messageT(lastamps));
470 : }
471 :
472 0 : return 0;
473 : }
474 :
475 : } //namespace app
476 : } //namespace MagAOX
477 :
478 : #endif //dmMode_hpp
|