Line data Source code
1 : /** \file indiUtils.hpp
2 : * \brief MagAO-X INDI Utilities
3 : * \author Jared R. Males (jaredmales@gmail.com)
4 : *
5 : * History:
6 : * - 2019-01-03 created by JRM
7 : *
8 : * \ingroup app_files
9 : */
10 :
11 : #ifndef app_indiUtils_hpp
12 : #define app_indiUtils_hpp
13 :
14 : #include <iostream>
15 : #include <limits>
16 :
17 : #include "../../INDI/libcommon/IndiProperty.hpp"
18 : #include "../../INDI/libcommon/IndiElement.hpp"
19 :
20 : namespace MagAOX
21 : {
22 : namespace app
23 : {
24 : namespace indi
25 : {
26 :
27 : #define INDI_IDLE ( pcf::IndiProperty::Idle )
28 : #define INDI_OK ( pcf::IndiProperty::Ok )
29 : #define INDI_BUSY ( pcf::IndiProperty::Busy )
30 : #define INDI_ALERT ( pcf::IndiProperty::Alert )
31 :
32 : /// Add a standard INDI Text element
33 : /**
34 : * \returns 0 on success
35 : * \returns -1 on error
36 : */
37 0 : inline int addTextElement( pcf::IndiProperty &prop, ///< [out] the property to which to add the elemtn
38 : const std::string &name, ///< [in] the name of the element
39 : const std::string &label = "" ///< [in] [optional] the GUI label suggestion for this property
40 : )
41 : {
42 0 : prop.add( pcf::IndiElement( name, "" ) );
43 :
44 : // Don't set "" just in case libcommon does something with defaults
45 0 : if( label != "" )
46 : {
47 0 : prop[name].setLabel( label );
48 : }
49 :
50 0 : return 0;
51 : }
52 :
53 : /// Add a standard INDI Number element
54 : /**
55 : * \returns 0 on success
56 : * \returns -1 on error
57 : */
58 : template <typename T>
59 0 : int addNumberElement( pcf::IndiProperty &prop, ///< [out] the property to which to add the elemtn
60 : const std::string &name, ///< [in] the name of the element
61 : const T &min, ///< [in] the minimum value for the element
62 : const T &max, ///< [in] the minimum value for the element
63 : const T &step, ///< [in] the step size of the lement
64 : const std::string &format, ///< [in] the _ value for the elements, applied to both target and
65 : ///< current. Set to "" to use the MagAO-X standard for type.
66 : const std::string &label = "" ///< [in] [optional] the GUI label suggestion for this property
67 : )
68 : {
69 0 : prop.add( pcf::IndiElement( name, 0 ) );
70 0 : prop[name].setMin( min );
71 0 : prop[name].setMax( max );
72 0 : prop[name].setStep( step );
73 0 : prop[name].setFormat( format );
74 :
75 : // Don't set "" just in case libcommon does something with defaults
76 0 : if( label != "" )
77 : {
78 0 : prop[name].setLabel( label );
79 : }
80 :
81 0 : return 0;
82 : }
83 :
84 : /// Update the value of the INDI element, but only if it has changed.
85 : /** Only sends the set property message if the new value is different.
86 : * For properties with more than one element that may have changed, you should use the vector version below.
87 : *
88 : * \todo this needs a const char specialization to std::string
89 : *
90 : */
91 : template <typename T, class indiDriverT>
92 16 : void updateIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
93 : const std::string &el, ///< [in] The element name
94 : const T &newVal, ///< [in] the new value
95 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
96 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
97 : {
98 16 : if( !indiDriver )
99 0 : return;
100 :
101 : try
102 : {
103 : // This is same code in IndiElement
104 16 : std::stringstream ssValue;
105 16 : ssValue.precision( 15 );
106 16 : ssValue << std::boolalpha << newVal;
107 :
108 16 : pcf::IndiProperty::PropertyStateType oldState = p.getState();
109 :
110 : // Do comparison in string space, not raw value
111 16 : if( p[el].getValue() != ssValue.str() || oldState != newState )
112 : {
113 9 : p[el].set( newVal );
114 9 : p.setTimeStamp( pcf::TimeStamp() );
115 9 : p.setState( newState );
116 9 : indiDriver->sendSetProperty( p );
117 : }
118 16 : }
119 0 : catch( std::exception &e )
120 : {
121 0 : std::cerr << "Exception caught at " << __FILE__ << " " << __LINE__ << " ";
122 0 : std::cerr << "from " << p.getName() << "." << el << ": ";
123 0 : std::cerr << e.what() << "\n";
124 : }
125 0 : catch( ... )
126 : {
127 0 : std::cerr << "Exception caught at " << __FILE__ << " " << __LINE__ << " ";
128 0 : std::cerr << "from " << p.getName() << "." << el << "\n";
129 : }
130 : }
131 :
132 : /// Update the elements of an INDI propery, but only if there has been a change in at least one.
133 : /** Only sends the set property message if at least one of the new values is different, or if the state has changed.
134 : *
135 : *
136 : */
137 : template <typename T, class indiDriverT>
138 0 : void updateIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
139 : const std::vector<std::string> &els, ///< [in] The element names
140 : const std::vector<T> &newVals, ///< [in] the new values
141 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
142 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
143 : {
144 0 : if( !indiDriver )
145 0 : return;
146 :
147 0 : size_t n = 0; // loop index outside so we can use it for error reporting.
148 :
149 : try
150 : {
151 : // First we look for any changes
152 0 : bool changed = false;
153 0 : pcf::IndiProperty::PropertyStateType oldState = p.getState();
154 :
155 0 : if( oldState != newState )
156 0 : changed = true;
157 :
158 0 : for( n = 0; n < els.size() && changed != true; ++n )
159 : {
160 : // This is same code in IndiElement
161 0 : std::stringstream ssValue;
162 0 : ssValue.precision( 15 );
163 0 : ssValue << std::boolalpha << newVals[n];
164 :
165 : // compare in string space
166 0 : if( p[els[n]].getValue() != ssValue.str() )
167 0 : changed = true;
168 : }
169 :
170 : // and if there are changes, we send an update
171 0 : if( changed )
172 : {
173 0 : for( n = 0; n < els.size(); ++n )
174 : {
175 0 : p[els[n]].set( newVals[n] );
176 : }
177 0 : p.setTimeStamp( pcf::TimeStamp() );
178 0 : p.setState( newState );
179 0 : indiDriver->sendSetProperty( p );
180 : }
181 : }
182 0 : catch( std::exception &e )
183 : {
184 0 : std::cerr << "Exception caught at " << __FILE__ << " " << __LINE__ << " ";
185 0 : if( n < els.size() )
186 : {
187 0 : std::cerr << "from " << p.getName() << "." << els[n] << ": ";
188 : }
189 0 : std::cerr << e.what() << "\n";
190 : }
191 0 : catch( ... )
192 : {
193 0 : std::cerr << "Exception caught at " << __FILE__ << " " << __LINE__ << " ";
194 0 : if( n < els.size() )
195 : {
196 0 : std::cerr << "from " << p.getName() << "." << els[n] << "\n";
197 : }
198 : }
199 : }
200 :
201 : /// Update the elements of an INDI propery, but only if there has been a change in at least one.
202 : /** Only sends the set property message if at least one of the new values is different, or if the state has changed.
203 : *
204 : * \tparam T the type of the values
205 : * \tparam elVecT the type of the element names, should be either std::string or const char *
206 : * \tparam indiDriverT the type of the INDI driver class
207 : */
208 : template <typename T, typename elVecT, class indiDriverT>
209 0 : void updatesIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
210 : const elVecT &els, ///< [in] The element names
211 : const std::vector<T> &newVals, ///< [in] the new values
212 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
213 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
214 : {
215 0 : if( !indiDriver )
216 0 : return;
217 :
218 0 : size_t n = 0; // loop index outside so we can use it for error reporting.
219 :
220 : try
221 : {
222 : // First we look for any changes
223 0 : bool changed = false;
224 0 : pcf::IndiProperty::PropertyStateType oldState = p.getState();
225 :
226 0 : if( oldState != newState )
227 0 : changed = true;
228 :
229 0 : for( n = 0; n < els.size() && changed != true; ++n )
230 : {
231 : // This is same code in IndiElement
232 0 : std::stringstream ssValue;
233 0 : ssValue.precision( 15 );
234 0 : ssValue << std::boolalpha << newVals[n];
235 :
236 : // compare in string space
237 0 : if( p[els[n]].getValue() != ssValue.str() )
238 0 : changed = true;
239 : }
240 :
241 : // and if there are changes, we send an update
242 0 : if( changed )
243 : {
244 0 : for( n = 0; n < els.size(); ++n )
245 : {
246 0 : p[els[n]].set( newVals[n] );
247 : }
248 0 : p.setTimeStamp( pcf::TimeStamp() );
249 0 : p.setState( newState );
250 0 : indiDriver->sendSetProperty( p );
251 : }
252 : }
253 0 : catch( std::exception &e )
254 : {
255 0 : std::cerr << "Exception caught at " << __FILE__ << " " << __LINE__ << " ";
256 0 : if( n < els.size() )
257 : {
258 0 : std::cerr << "from " << p.getName() << "." << els[n] << ": ";
259 : }
260 0 : std::cerr << e.what() << "\n";
261 : }
262 0 : catch( ... )
263 : {
264 0 : std::cerr << "Exception caught at " << __FILE__ << " " << __LINE__ << " ";
265 0 : if( n < els.size() )
266 : {
267 0 : std::cerr << "from " << p.getName() << "." << els[n] << "\n";
268 : }
269 : }
270 : }
271 :
272 : /// Update the elements of an INDI propery, but only if there has been a change in at least one.
273 : /** Specialization for const char * element names.
274 : *
275 : * Only sends the set property message if at least one of the new values is different, or if the state has changed.
276 : *
277 : * \tparam T the type of the values
278 : * \tparam indiDriverT the type of the INDI driver class
279 : *
280 : */
281 : template <typename T, class indiDriverT>
282 0 : void updatesIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
283 : const std::vector<const char *> &els, ///< [in] The element names
284 : const std::vector<T> &newVals, ///< [in] the new values
285 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
286 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
287 : {
288 0 : updatesIfChanged<T, std::vector<const char *>, indiDriverT>( p, els, newVals, indiDriver, newState );
289 0 : }
290 :
291 : /// Update the elements of an INDI propery, but only if there has been a change in at least one.
292 : /** Specialization for std::string element names.
293 : *
294 : * Only sends the set property message if at least one of the new values is different, or if the state has changed.
295 : *
296 : * \tparam T the type of the values
297 : * \tparam indiDriverT the type of the INDI driver class
298 : *
299 : */
300 : template <typename T, class indiDriverT>
301 : void updatesIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
302 : const std::vector<std::string> &els, ///< [in] The element names
303 : const std::vector<T> &newVals, ///< [in] the new values
304 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
305 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
306 : {
307 : updatesIfChanged<T, std::vector<std::string>, indiDriverT>( p, els, newVals, indiDriver, newState );
308 : }
309 :
310 : /// Update the value of the INDI element, but only if it has changed.
311 : /** Only sends the set property message if the new value is different.
312 : *
313 : * \todo investigate how this handles floating point values and string conversions.
314 : * \todo this needs a const char specialization to std::string
315 : *
316 : */
317 : template <class indiDriverT>
318 0 : void updateSwitchIfChanged( pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
319 : const std::string &el, ///< [in] The element name
320 : const pcf::IndiElement::SwitchStateType &newVal, ///< [in] the new value
321 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
322 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
323 : {
324 0 : if( !indiDriver )
325 : {
326 0 : return;
327 : }
328 :
329 : try
330 : {
331 0 : pcf::IndiElement::SwitchStateType oldVal = p[el].getSwitchState();
332 :
333 0 : pcf::IndiProperty::PropertyStateType oldState = p.getState();
334 :
335 0 : if( oldVal != newVal || oldState != newState )
336 : {
337 0 : p[el].setSwitchState( newVal );
338 0 : p.setTimeStamp( pcf::TimeStamp() );
339 0 : p.setState( newState );
340 0 : indiDriver->sendSetProperty( p );
341 : }
342 : }
343 0 : catch( ... )
344 : {
345 0 : std::cerr << "INDI Exception at " << __FILE__ << " " << __LINE__ << "\n";
346 0 : std::cerr << "from " << p.getName() << "." << el << "\n";
347 : }
348 : }
349 :
350 : /// Update the values of a one-of-many INDI switch vector, but only if it has changed.
351 : /** Only sends the set property message if the new settings are different.
352 : *
353 : *
354 : */
355 : template <class indiDriverT>
356 : void updateSelectionSwitchIfChanged(
357 : pcf::IndiProperty &p, ///< [in/out] The property containing the element to possibly update
358 : const std::string &el, ///< [in] The element name which is now on
359 : indiDriverT *indiDriver, ///< [in] the MagAOX INDI driver to use
360 : pcf::IndiProperty::PropertyStateType newState = pcf::IndiProperty::Ok )
361 : {
362 : if( !indiDriver )
363 : return;
364 :
365 : if( !p.find( el ) )
366 : {
367 : std::cerr << "INDI error at " << __FILE__ << " " << __LINE__ << "\n";
368 : std::cerr << p.getName() << " does not have " << el << "\n";
369 : return;
370 : }
371 :
372 : try
373 : {
374 :
375 : bool changed = false;
376 : for( auto elit = p.getElements().begin(); elit != p.getElements().end(); ++elit )
377 : {
378 : if( elit->first == el )
379 : {
380 : if( elit->second.getSwitchState() != pcf::IndiElement::On )
381 : {
382 : p[elit->first].setSwitchState( pcf::IndiElement::On );
383 : changed = true;
384 : }
385 : }
386 : else
387 : {
388 : if( elit->second.getSwitchState() != pcf::IndiElement::Off )
389 : {
390 : p[elit->first].setSwitchState( pcf::IndiElement::Off );
391 : changed = true;
392 : }
393 : }
394 : }
395 :
396 : pcf::IndiProperty::PropertyStateType oldState = p.getState();
397 :
398 : if( changed || oldState != newState )
399 : {
400 : p.setState( newState );
401 : p.setTimeStamp( pcf::TimeStamp() );
402 : indiDriver->sendSetProperty( p );
403 : }
404 : }
405 : catch( ... )
406 : {
407 : std::cerr << "INDI Exception at " << __FILE__ << " " << __LINE__ << "\n";
408 : std::cerr << "from " << p.getName() << "." << el << "\n";
409 : }
410 : }
411 :
412 : /// Parse an INDI key into the device and property names
413 : /** We often represent an INDI property as a unique key in the form
414 : * `deviceName.propName`. This function parses such a key into its
415 : * parts.
416 : *
417 : * \returns 0 on success
418 : * \returns -1 if the provided key is not at least 3 characters long
419 : * \returns -2 if no '.' is found
420 : * \returns -3 if '.' is the first character
421 : * \returns -4 if '.' is the last character
422 : */
423 6 : inline int parseIndiKey( std::string &devName, ///< [out] the device name
424 : std::string &propName, ///< [out] the property name
425 : const std::string &key ///< [in] the key to parse
426 : )
427 : {
428 6 : if( key.size() < 3 )
429 : {
430 2 : return -1;
431 : }
432 :
433 4 : size_t p = key.find( '.' );
434 :
435 4 : if( p == std::string::npos )
436 : {
437 1 : devName = "";
438 1 : propName = "";
439 1 : return -2;
440 : }
441 :
442 3 : if( p == 0 )
443 : {
444 1 : devName = "";
445 1 : propName = "";
446 1 : return -3;
447 : }
448 :
449 2 : if( p == key.size() - 1 )
450 : {
451 1 : devName = "";
452 1 : propName = "";
453 1 : return -4;
454 : }
455 :
456 1 : devName = key.substr( 0, p );
457 1 : propName = key.substr( p + 1 );
458 :
459 1 : return 0;
460 : }
461 :
462 : } // namespace indi
463 : } // namespace app
464 : } // namespace MagAOX
465 :
466 : #endif // app_magAOXIndiDriver_hpp
|