API
outletController.hpp
Go to the documentation of this file.
1 /** \file outletController.hpp
2  * \author Jared R. Males
3  * \brief Declares and defines a power control device framework in the MagAOXApp context
4  *
5  * \ingroup
6  *
7  */
8 
9 #ifndef app_outletController_hpp
10 #define app_outletController_hpp
11 
12 #include <mx/app/application.hpp>
13 #include <mx/sys/timeUtils.hpp>
14 
15 
16 #include "../../INDI/libcommon/IndiProperty.hpp"
17 #include "../../libMagAOX/libMagAOX.hpp"
18 #include "../indiUtils.hpp"
19 
20 
21 #define OUTLET_STATE_UNKNOWN (-1)
22 #define OUTLET_STATE_OFF (0)
23 #define OUTLET_STATE_INTERMEDIATE (1)
24 #define OUTLET_STATE_ON (2)
25 
26 
27 #define OUTLET_E_NOOUTLETS (-10)
28 #define OUTLET_E_NOCHANNELS (-15)
29 #define OUTLET_E_NOVALIDCH (-20)
30 
31 namespace MagAOX
32 {
33 namespace app
34 {
35 namespace dev
36 {
37 
38 /// A generic outlet controller
39 /** Controls a set of outlets on a device, such as A/C power outlets or digital outputs.
40  * The outlets are organized into channels, which could be made up of multiple outlets.
41  *
42  * derivedT must be a MagAOXApp, and additionally it must implement the functions
43  * \code
44  int turnOutletOn( int outletNum );
45 
46  int turnOutletOff( int outletNum );
47 
48  int updateOutletState( int outletNum );
49  * \endcode
50  * and optionally
51  * \code
52  int updateOutletStates();
53  \endcode
54  *
55  * Other requirements:
56  * - call `setNumberOfOutlets` in the derived class constructor
57  *
58  *
59  * \tparam derivedT specifies a MagAOXApp parent base class which is accessed with a `static_cast` (downcast)
60  * to perform various methods.
61  *
62  * \ingroup appdev
63  *
64  * \todo finalize 0-counting vs 1-counting rules --> do we subtract one from config-ed outlet indices if m_firstOne is true?
65  *
66  */
67 template<class derivedT>
69 {
70  bool m_firstOne {false}; ///< Flag is true if the first outlet is numbered 1, otherwise assumes starting at 0.
71 
72  std::vector<int> m_outletStates; ///< The current states of each outlet. These MUST be updated by derived classes in the overridden \ref updatedOutletState.
73 
74  pcf::IndiProperty m_indiP_outletStates; ///< Indi Property to show individual outlet states.
75 
76  /// Structure containing the specification of one channel.
77  /** A channel may include more than one outlet, may specify the order in which
78  * outlets are turned on and/or off, and may specify a delay between turning outlets on
79  * and/or off.
80  */
81  struct channelSpec
82  {
83  std::vector<size_t> m_outlets; ///< The outlets in this channel
84 
85  std::vector<size_t> m_onOrder; ///< [optional] The order in which outlets are turned on. This contains the indices of m_outlets, not the outlet numbers of the device.
86  std::vector<size_t> m_offOrder; ///< [optional] The order in which outlets are turned off. This contains the indices of m_outlets, not the outlet numbers of the device.
87 
88  std::vector<unsigned> m_onDelays; ///< [optional] The delays between outlets in a multi-oultet channel. The first entry is always ignored. The second entry is the dealy between the first and second outlet, etc.
89  std::vector<unsigned> m_offDelays; ///< [optional] The delays between outlets in a multi-oultet channel. The first entry is always ignored. The second entry is the dealy between the first and second outlet, etc.
90 
91  pcf::IndiProperty m_indiP_prop;
92  };
93 
94  /// The map of channel specifications, which can be accessed by their names.
95  std::unordered_map<std::string, channelSpec> m_channels;
96 
97  /// An INDI property which publishes the outlets associated with each channel. Useful for GUIs, etc.
98  pcf::IndiProperty m_indiP_chOutlets;
99 
100  /// An INDI property which publishes the total on delay for each channel. Useful for GUIs, etc.
101  pcf::IndiProperty m_indiP_chOnDelays;
102 
103  /// An INDI property which publishes the total off delay for each channel. Useful for GUIs, etc.
104  pcf::IndiProperty m_indiP_chOffDelays;
105 
106 
107  ///Setup an application configurator for an outletController
108  /** This is currently a no-op
109  *
110  * \returns 0 on success
111  * \returns -1 on failure
112  */
113  int setupConfig( mx::app::appConfigurator & config /**< [in] an application configuration to setup */);
114 
115  /// Load the [channel] sections from an application configurator
116  /** Any "unused" section from the config parser is analyzed to determine if it is a channel specification.
117  * If it contains the `outlet` or `outlets` keyword, then it is considered a channel. `outlet` and `outlets`
118  * are equivalent, and specify the one or more device outlets included in this channel (i.e. this may be a vector
119  * value entry).
120  *
121  * This function then looks for `onOrder` and `offOrder` keywords, which specify the order outlets are turned
122  * on or off by their indices in the vector specified by the `outlet`/`outlets` keyword (i.e not the outlet numbers).
123  *
124  * Next it looks for `onDelays` and `offDelays`, which specify the delays between outlet operations in milliseconds.
125  * The first entry is always ignored, then the second entry specifies the delay between the first and second outlet
126  * operation, etc.
127  *
128  * An example config file section is:
129  \verbatim
130  [sue] #this channel will be named sue
131  outlets=4,5 #this channel uses outlets 4 and 5
132  onOrder=1,0 #outlet 5 will be turned on first
133  offOrder=0,1 #Outlet 4 will be turned off first
134  onDelays=0,150 #a 150 msec delay between outlet turn on
135  offDelays=0,345 #a 345 msec delay between outlet turn off
136  \endverbatim
137  *
138  * \returns 0 on success
139  * \returns -1 on failure
140  */
141  int loadConfig( mx::app::appConfigurator & config /**< [in] an application configuration from which to load values */);
142 
143  /// Sets the number of outlets. This should be called by the derived class constructor.
144  /**
145  * \returns 0 on success
146  * \returns -1 on failure
147  */
148  int setNumberOfOutlets( int numOuts /**< [in] the number of outlets to allocate */);
149 
150  /// Get the currently stored outlet state, without updating from device.
151  int outletState( int outletNum );
152 
153  /// Get the state of the outlet from the device.
154  /** This will be implemented in derived classes to update the outlet state.
155  * \todo this is declared pure virtual, but there is an implementation.
156  * \returns 0 on success.
157  * \returns -1 on error.
158  */
159  virtual int updateOutletState( int outletNum /**< [in] the outlet number to update */) = 0;
160 
161  /// Get the states of all outlets from the device.
162  /** The default implementation for-loops through each outlet, calling \ref updateOutletState.
163  * Can be re-implemented in derived classes to update the outlet states.
164  *
165  * \returns 0 on success.
166  * \returns -1 on error.
167  */
168  virtual int updateOutletStates();
169 
170  /// Turn an outlet on.
171  /** This will be implemented in derived classes to turn an outlet on.
172  *
173  * \returns 0 on success.
174  * \returns -1 on error.
175  */
176  virtual int turnOutletOn( int outletNum /**< [in] the outlet number to turn on */) = 0;
177 
178  /// Turn an outlet off.
179  /** This will be implemented in derived classes to turn an outlet off.
180  *
181  * \returns 0 on success.
182  * \returns -1 on error.
183  */
184  virtual int turnOutletOff( int outletNum /**< [in] the outlet number to turn off */) = 0;
185 
186  /// Get the number of channels
187  /**
188  * \returns the number of entries in m_channels.
189  */
190  size_t numChannels();
191 
192  /// Get the vector of outlet indices for a channel.
193  /** Mainly used for testing.
194  *
195  * \returns the m_outlets member of the channelSpec specified by its name.
196  */
197  std::vector<size_t> channelOutlets( const std::string & channel /**< [in] the name of the channel */);
198 
199  /// Get the vector of outlet on orders for a channel.
200  /** Mainly used for testing.
201  *
202  * \returns the m_onOrder member of the channelSpec specified by its name.
203  */
204  std::vector<size_t> channelOnOrder( const std::string & channel /**< [in] the name of the channel */);
205 
206  /// Get the vector of outlet off orders for a channel.
207  /** Mainly used for testing.
208  *
209  * \returns the m_offOrder member of the channelSpec specified by its name.
210  */
211  std::vector<size_t> channelOffOrder( const std::string & channel /**< [in] the name of the channel */);
212 
213  /// Get the vector of outlet on delays for a channel.
214  /** Mainly used for testing.
215  *
216  * \returns the m_onDelays member of the channelSpec specified by its name.
217  */
218  std::vector<unsigned> channelOnDelays( const std::string & channel /**< [in] the name of the channel */);
219 
220  /// Get the vector of outlet off delays for a channel.
221  /** Mainly used for testing.
222  *
223  * \returns the m_offDelays member of the channelSpec specified by its name.
224  */
225  std::vector<unsigned> channelOffDelays( const std::string & channel /**< [in] the name of the channel */);
226 
227  /// Get the state of a channel.
228  /**
229  * \returns OUTLET_STATE_UNKNOWN if the state is not known
230  * \returns OUTLET_STATE_OFF if the channel is off (all outlets off)
231  * \returns OUTLET_STATE_INTERMEDIATE if outlets are intermediate or not in the same state
232  * \returns OUTLET_STATE_ON if channel is on (all outlets on)
233  */
234  int channelState( const std::string & channel /**< [in] the name of the channel */);
235 
236  /// Turn a channel on.
237  /** This implements the outlet order and delay logic.
238  *
239  * \returns 0 on success.
240  * \returns -1 on error.
241  */
242  int turnChannelOn( const std::string & channel /**< [in] the name of the channel to turn on*/);
243 
244  /// Turn a channel off.
245  /** This implements the outlet order and delay logic.
246  *
247  * \returns 0 on success.
248  * \returns -1 on error.
249  */
250  int turnChannelOff( const std::string & channel /**< [in] the name of the channel to turn on*/);
251 
252 
253  /** \name INDI Setup
254  *@{
255  */
256 
257  /// The static callback function to be registered for the channel properties.
258  /**
259  * \returns 0 on success.
260  * \returns -1 on error.
261  */
262  static int st_newCallBack_channels( void * app, ///< [in] a pointer to this, will be static_cast-ed to derivedT.
263  const pcf::IndiProperty &ipRecv ///< [in] the INDI property sent with the the new property request.
264  );
265 
266  /// The callback called by the static version, to actually process the new request.
267  /**
268  * \returns 0 on success.
269  * \returns -1 on error.
270  */
271  int newCallBack_channels( const pcf::IndiProperty &ipRecv /**< [in] the INDI property sent with the the new property request.*/);
272 
273  /// Setup the INDI properties for this device controller
274  /** This should be called in the `appStartup` function of the derived MagAOXApp.
275  * \todo change this to be appStartup like other devs.
276  * \returns 0 on success.
277  * \returns -1 on error.
278  */
279  int setupINDI();
280 
281  /// Update the INDI properties for this device controller
282  /** You should call this after updating the outlet states.
283  * It is not called automatically.
284  *
285  * \returns 0 on success.
286  * \returns -1 on error.
287  */
288  int updateINDI();
289 
290  ///@}
291 
292 
293 private:
294  derivedT & derived()
295  {
296  return *static_cast<derivedT *>(this);
297  }
298 };
299 
300 template<class derivedT>
301 int outletController<derivedT>::setupConfig( mx::app::appConfigurator & config )
302 {
303  static_cast<void>(config);
304 
305  return 0;
306 }
307 
308 template<class derivedT>
309 int outletController<derivedT>::loadConfig( mx::app::appConfigurator & config )
310 {
311  if( m_outletStates.size() == 0) return OUTLET_E_NOOUTLETS;
312 
313  //Get the "unused" sections.
314  std::vector<std::string> sections;
315 
316  config.unusedSections(sections);
317 
318  if( sections.size() == 0 ) return OUTLET_E_NOCHANNELS;
319 
320  //Now see if any are channels, which means they have an outlet= or outlets= entry
321  std::vector<std::string> chSections;
322 
323  for(size_t i=0;i<sections.size(); ++i)
324  {
325  if( config.isSetUnused( mx::app::iniFile::makeKey(sections[i], "outlet" ))
326  || config.isSetUnused( mx::app::iniFile::makeKey(sections[i], "outlets" )) )
327  {
328  chSections.push_back(sections[i]);
329  }
330  }
331 
332  if( chSections.size() == 0 ) return OUTLET_E_NOVALIDCH;
333 
334  //Now configure the chanels.
335  for(size_t n = 0; n < chSections.size(); ++n)
336  {
337  m_channels.emplace( chSections[n] , channelSpec());
338 
339  //---- Set outlets ----
340  std::vector<size_t> outlets;
341  if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "outlet" )))
342  {
343  config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n], "outlet" ) );
344  }
345  else
346  {
347  config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n], "outlets" ) );
348  }
349 
350  //Subtract one if the device numbers from 1.
351  for(size_t k=0;k<outlets.size(); ++k)
352  {
353  ///\todo test this error
354  if( (int) outlets[k] - m_firstOne < 0 || (int) outlets[k] - m_firstOne > (int) m_outletStates.size())
355  {
356  #ifndef OUTLET_CTRL_TEST_NOLOG
357  return derivedT::template log<text_log,-1>("Outlet " + std::to_string(outlets[k]) + " in Channel " + chSections[n] + " is invalid", logPrio::LOG_ERROR);
358  #else
359  return -1;
360  #endif
361  }
362 
363  outlets[k] -= m_firstOne;
364  }
365 
366  m_channels[chSections[n]].m_outlets = outlets;
367  ///\todo error checking on outlets
368 
369  //---- Set optional configs ----
370  if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "onOrder" )))
371  {
372  std::vector<size_t> onOrder;
373  config.configUnused( onOrder, mx::app::iniFile::makeKey(chSections[n], "onOrder" ) );
374 
375  ///\todo test this error
376  if(onOrder.size() != m_channels[chSections[n]].m_outlets.size())
377  {
378  #ifndef OUTLET_CTRL_TEST_NOLOG
379  return derivedT::template log<text_log,-1>("onOrder be same size as outlets. In Channel " + chSections[n], logPrio::LOG_ERROR);
380  #else
381  return -1;
382  #endif
383  }
384 
385  m_channels[chSections[n]].m_onOrder = onOrder;
386  }
387 
388  if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "offOrder" )))
389  {
390  std::vector<size_t> offOrder;
391  config.configUnused( offOrder, mx::app::iniFile::makeKey(chSections[n], "offOrder" ) );
392  m_channels[chSections[n]].m_offOrder = offOrder;
393  ///\todo error checking on offOrder, should complain if not same length
394  }
395 
396  if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "onDelays" )))
397  {
398  std::vector<unsigned> onDelays;
399  config.configUnused( onDelays, mx::app::iniFile::makeKey(chSections[n], "onDelays" ) );
400  m_channels[chSections[n]].m_onDelays = onDelays;
401  ///\todo error checking on onDelays, should complain if not same length
402  }
403 
404  if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "offDelays" )))
405  {
406  std::vector<unsigned> offDelays;
407  config.configUnused( offDelays, mx::app::iniFile::makeKey(chSections[n], "offDelays" ) );
408  m_channels[chSections[n]].m_offDelays = offDelays;
409  ///\todo error checking on offDelays, should complain if not same length
410  }
411  }
412 
413  return 0;
414 }
415 
416 template<class derivedT>
418 {
419  m_outletStates.resize(numOuts, -1);
420  return 0;
421 }
422 
423 template<class derivedT>
425 {
426  return m_outletStates[outletNum];
427 }
428 
429 template<class derivedT>
431 {
432  for(size_t n=0; n<m_outletStates.size(); ++n)
433  {
434  int rv = updateOutletState(n);
435  if(rv < 0) return rv;
436  }
437 
438  return 0;
439 }
440 
441 template<class derivedT>
443 {
444  return m_channels.size();
445 }
446 
447 template<class derivedT>
448 std::vector<size_t> outletController<derivedT>::channelOutlets( const std::string & channel )
449 {
450  return m_channels[channel].m_outlets;
451 }
452 
453 template<class derivedT>
454 std::vector<size_t> outletController<derivedT>::channelOnOrder( const std::string & channel )
455 {
456  return m_channels[channel].m_onOrder;
457 }
458 
459 template<class derivedT>
460 std::vector<size_t> outletController<derivedT>::channelOffOrder( const std::string & channel )
461 {
462  return m_channels[channel].m_offOrder;
463 }
464 
465 template<class derivedT>
466 std::vector<unsigned> outletController<derivedT>::channelOnDelays( const std::string & channel )
467 {
468  return m_channels[channel].m_onDelays;
469 }
470 
471 template<class derivedT>
472 std::vector<unsigned> outletController<derivedT>::channelOffDelays( const std::string & channel )
473 {
474  return m_channels[channel].m_offDelays;
475 }
476 
477 template<class derivedT>
478 int outletController<derivedT>::channelState( const std::string & channel )
479 {
480  int st = outletState(m_channels[channel].m_outlets[0]);
481 
482  for( size_t n = 1; n < m_channels[channel].m_outlets.size(); ++n )
483  {
484  if( st != outletState(m_channels[channel].m_outlets[n]) ) st = 1;
485  }
486 
487  return st;
488 }
489 
490 template<class derivedT>
491 int outletController<derivedT>::turnChannelOn( const std::string & channel )
492 {
493 
494  #ifndef OUTLET_CTRL_TEST_NOLOG
495  derivedT::template log<software_debug>({__FILE__, __LINE__, "turning on channel " + channel});
496  #endif
497 
498  #ifndef OUTLET_CTRL_TEST_NOINDI
499  //m_channels[channel].m_indiP_prop["target"].setValue("On");
500  indi::updateIfChanged(m_channels[channel].m_indiP_prop, "target", std::string("On"), derived().m_indiDriver, INDI_BUSY );
501  #endif
502 
503  //If order is specified, get first outlet number
504  size_t n = 0;
505  if( m_channels[channel].m_onOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_onOrder[0];
506 
507  //turn on first outlet.
508  if( turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
509  {
510 
511  #ifndef OUTLET_CTRL_TEST_NOLOG
512  derivedT::template log<software_error>({__FILE__, __LINE__, "error turning on outlet " + std::to_string(n)});
513  #else
514  std::cerr << "Failed to turn on outlet " << n << "\n";
515  #endif
516 
517  return -1;
518  }
519 
520  #ifndef OUTLET_CTRL_TEST_NOLOG
521  derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 2});
522  #endif
523 
524  //Now do the rest
525  for(size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
526  {
527  //If order is specified, get next outlet number
528  n=i;
529  if( m_channels[channel].m_onOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_onOrder[i];
530 
531  //Delay if specified
532  if( m_channels[channel].m_onDelays.size() == m_channels[channel].m_outlets.size() )
533  {
534  mx::sys::milliSleep(m_channels[channel].m_onDelays[i]);
535  }
536 
537  //turn on next outlet
538 
539  if( turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
540  {
541 
542  #ifndef OUTLET_CTRL_TEST_NOLOG
543  derivedT::template log<software_error>({__FILE__, __LINE__, "error turning on outlet " + std::to_string(n)});
544  #else
545  std::cerr << "Failed to turn on outlet " << n << "\n";
546  #endif
547 
548  return -1;
549  }
550 
551  #ifndef OUTLET_CTRL_TEST_NOLOG
552  derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne ), 2});
553  #endif
554  }
555 
556  #ifndef OUTLET_CTRL_TEST_NOLOG
557  derivedT::template log<outlet_channel_state>({ channel, 2});
558  #endif
559 
560  return 0;
561 }
562 
563 template<class derivedT>
564 int outletController<derivedT>::turnChannelOff( const std::string & channel )
565 {
566 
567  #ifndef OUTLET_CTRL_TEST_NOLOG
568  derivedT::template log<software_debug>({__FILE__, __LINE__, "turning off channel " + channel});
569  #endif
570 
571  #ifndef OUTLET_CTRL_TEST_NOINDI
572  //m_channels[channel].m_indiP_prop["target"].setValue("Off");
573  indi::updateIfChanged(m_channels[channel].m_indiP_prop, "target", std::string("Off"), derived().m_indiDriver, INDI_BUSY );
574  #endif
575 
576  //If order is specified, get first outlet number
577  size_t n = 0;
578  if( m_channels[channel].m_offOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_offOrder[0];
579 
580  //turn off first outlet.
581  if( turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
582  {
583  #ifndef OUTLET_CTRL_TEST_NOLOG
584  derivedT::template log<software_error>({__FILE__, __LINE__, "error turning off outlet " + std::to_string(n)});
585  #else
586  std::cerr << "Failed to turn off outlet " << n << "\n";
587  #endif
588 
589  return -1;
590  }
591 
592  #ifndef OUTLET_CTRL_TEST_NOLOG
593  derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
594  #endif
595 
596  //Now do the rest
597  for(size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
598  {
599  //If order is specified, get next outlet number
600  n=i;
601  if( m_channels[channel].m_offOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_offOrder[i];
602 
603  //Delay if specified
604  if( m_channels[channel].m_offDelays.size() == m_channels[channel].m_outlets.size() )
605  {
606  mx::sys::milliSleep(m_channels[channel].m_offDelays[i]);
607  }
608 
609  //turn off next outlet
610  if( turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
611  {
612  #ifndef OUTLET_CTRL_TEST_NOLOG
613  derivedT::template log<software_error>({__FILE__, __LINE__, "error turning off outlet " + std::to_string(n)});
614  #else
615  std::cerr << "Failed to turn off outlet " << n << "\n";
616  #endif
617 
618  return -1;
619  }
620 
621  #ifndef OUTLET_CTRL_TEST_NOLOG
622  derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
623  #endif
624  }
625 
626  #ifndef OUTLET_CTRL_TEST_NOLOG
627  derivedT::template log<outlet_channel_state>({ channel, 0});
628  #endif
629 
630  return 0;
631 }
632 
633 template<class derivedT>
635  const pcf::IndiProperty &ipRecv
636  )
637 {
638  return static_cast<derivedT *>(app)->newCallBack_channels(ipRecv);
639 }
640 
641 template<class derivedT>
643 {
644  //Check if we're in state READY before doing anything
645  if(derived().state() != stateCodes::READY)
646  {
647  #ifndef OUTLET_CTRL_TEST_NOLOG
648  derivedT::template log<text_log>("can't change outlet state when not READY", logPrio::LOG_ERROR);
649  #endif
650 
651  return -1;
652  }
653 
654  //Interogate ipRecv to figure out which channel it is.
655  //And then call turn on or turn off based on requested state.
656  std::string name = ipRecv.getName();
657 
658  std::string state, target;
659 
660  if(ipRecv.find("state"))
661  {
662  state = ipRecv["state"].get<std::string>();
663  }
664 
665  if(ipRecv.find("target"))
666  {
667  target = ipRecv["target"].get<std::string>();
668  }
669 
670  if( target == "" ) target = state;
671  target = mx::ioutils::toUpper(target);
672 
673  if( target == "ON" )
674  {
675  return turnChannelOn(name);
676  }
677 
678  if(target == "OFF")
679  {
680  return turnChannelOff(name);
681  }
682 
683  return 0;
684 }
685 
686 template<class derivedT>
688 {
689  //Register the static INDI properties
690  m_indiP_chOutlets = pcf::IndiProperty(pcf::IndiProperty::Text);
691  m_indiP_chOutlets.setDevice(derived().configName());
692  m_indiP_chOutlets.setName("channelOutlets");
693  m_indiP_chOutlets.setPerm(pcf::IndiProperty::ReadOnly);
694  m_indiP_chOutlets.setState(pcf::IndiProperty::Idle);
695 
696  if(derived().registerIndiPropertyReadOnly(m_indiP_chOutlets) < 0)
697  {
698  #ifndef OUTLET_CTRL_TEST_NOLOG
699  derivedT::template log<software_error>({__FILE__,__LINE__});
700  #endif
701  return -1;
702  }
703 
704  m_indiP_chOnDelays = pcf::IndiProperty (pcf::IndiProperty::Number);
705  m_indiP_chOnDelays.setDevice(derived().configName());
706  m_indiP_chOnDelays.setName("channelOnDelays");
707  m_indiP_chOnDelays.setPerm(pcf::IndiProperty::ReadOnly);
708  m_indiP_chOnDelays.setState(pcf::IndiProperty::Idle);
709 
710  if(derived().registerIndiPropertyReadOnly(m_indiP_chOnDelays) < 0)
711  {
712  #ifndef OUTLET_CTRL_TEST_NOLOG
713  derivedT::template log<software_error>({__FILE__,__LINE__});
714  #endif
715  return -1;
716  }
717 
718  m_indiP_chOffDelays = pcf::IndiProperty (pcf::IndiProperty::Number);
719  m_indiP_chOffDelays.setDevice(derived().configName());
720  m_indiP_chOffDelays.setName("channelOffDelays");
721  m_indiP_chOffDelays.setPerm(pcf::IndiProperty::ReadOnly);
722  m_indiP_chOffDelays.setState(pcf::IndiProperty::Idle);
723 
724  if(derived().registerIndiPropertyReadOnly(m_indiP_chOffDelays) < 0)
725  {
726  #ifndef OUTLET_CTRL_TEST_NOLOG
727  derivedT::template log<software_error>({__FILE__,__LINE__});
728  #endif
729  return -1;
730  }
731 
732  //Create channel properties and register callback.
733  for(auto it = m_channels.begin(); it != m_channels.end(); ++it)
734  {
735  it->second.m_indiP_prop = pcf::IndiProperty (pcf::IndiProperty::Text);
736  it->second.m_indiP_prop.setDevice(derived().configName());
737  it->second.m_indiP_prop.setName(it->first);
738  it->second.m_indiP_prop.setPerm(pcf::IndiProperty::ReadWrite);
739  it->second.m_indiP_prop.setState( pcf::IndiProperty::Idle );
740 
741  //add elements 'state' and 'target'
742  it->second.m_indiP_prop.add (pcf::IndiElement("state"));
743  it->second.m_indiP_prop.add (pcf::IndiElement("target"));
744 
745  if( derived().registerIndiPropertyNew( it->second.m_indiP_prop, st_newCallBack_channels) < 0)
746  {
747  #ifndef OUTLET_CTRL_TEST_NOLOG
748  derivedT::template log<software_error>({__FILE__,__LINE__});
749  #endif
750  return -1;
751  }
752 
753  //Load values into the static INDI properties
754  m_indiP_chOutlets.add(pcf::IndiElement(it->first));
755  std::string os = std::to_string(it->second.m_outlets[0]);
756  for(size_t i=1;i< it->second.m_outlets.size();++i) os += "," + std::to_string(it->second.m_outlets[i]);
757  m_indiP_chOutlets[it->first].set(os);
758 
759  m_indiP_chOnDelays.add(pcf::IndiElement(it->first));
760  double sum=0;
761  for(size_t i=0;i< it->second.m_onDelays.size();++i) sum += it->second.m_onDelays[i];
762  m_indiP_chOnDelays[it->first].set(sum);
763 
764  m_indiP_chOffDelays.add(pcf::IndiElement(it->first));
765  sum=0;
766  for(size_t i=0;i< it->second.m_offDelays.size();++i) sum += it->second.m_offDelays[i];
767  m_indiP_chOffDelays[it->first].set(sum);
768 
769  }
770 
771  //Register the outletStates INDI property, and add an element for each outlet.
772  m_indiP_outletStates = pcf::IndiProperty (pcf::IndiProperty::Text);
773  m_indiP_outletStates.setDevice(derived().configName());
774  m_indiP_outletStates.setName("outlet");
775  m_indiP_outletStates.setPerm(pcf::IndiProperty::ReadWrite);
776  m_indiP_outletStates.setState( pcf::IndiProperty::Idle );
777 
778  if( derived().registerIndiPropertyReadOnly(m_indiP_outletStates) < 0)
779  {
780  #ifndef OUTLET_CTRL_TEST_NOLOG
781  derivedT::template log<software_error>({__FILE__,__LINE__});
782  #endif
783  return -1;
784  }
785 /*
786  auto result = derived().m_indiNewCallBacks.insert( { "outlet", {&m_indiP_outletStates, nullptr}});
787 
788  if(!result.second)
789  {
790  return -1;
791  }*/
792 
793  for(size_t i=0; i< m_outletStates.size(); ++i)
794  {
795  m_indiP_outletStates.add (pcf::IndiElement(std::to_string(i+m_firstOne)));
796  }
797 
798  return 0;
799 }
800 
801 std::string stateIntToString(int st);
802 
803 template<class derivedT>
805 {
806  if( !derived().m_indiDriver ) return 0;
807 
808  //Publish outlet states (only bother if they've changed)
809  for(size_t i=0; i< m_outletStates.size(); ++i)
810  {
811  indi::updateIfChanged(m_indiP_outletStates, std::to_string(i+m_firstOne), stateIntToString(m_outletStates[i]), derived().m_indiDriver);
812  }
813 
814  //Publish channel states (only bother if they've changed)
815  for(auto it = m_channels.begin(); it != m_channels.end(); ++it)
816  {
817  std::string state = stateIntToString( channelState( it->first ));
818 
819  indi::updateIfChanged( it->second.m_indiP_prop, "state", state, derived().m_indiDriver );
820  }
821 
822 
823 
824  return 0;
825 }
826 
827 } //namespace dev
828 } //namespace app
829 } //namespace MagAOX
830 
831 #endif //app_outletController_hpp
@ READY
The device is ready for operation, but is not operating.
Definition: stateCodes.hpp:51
#define INDI_BUSY
Definition: indiUtils.hpp:30
std::ostream & cerr()
std::string stateIntToString(int st)
void updateIfChanged(pcf::IndiProperty &p, const std::string &el, const T &newVal, indiDriverT *indiDriver, pcf::IndiProperty::PropertyStateType newState=pcf::IndiProperty::Ok)
Update the value of the INDI element, but only if it has changed.
Definition: indiUtils.hpp:95
const pcf::IndiProperty & ipRecv
Definition: dm.hpp:24
constexpr static logPrioT LOG_ERROR
An error has occured which the software will attempt to correct.
Definition: logPriority.hpp:40
#define OUTLET_E_NOCHANNELS
#define OUTLET_E_NOOUTLETS
#define OUTLET_E_NOVALIDCH
A generic outlet controller.
int updateINDI()
Update the INDI properties for this device controller.
int setupINDI()
Setup the INDI properties for this device controller.
int outletState(int outletNum)
Get the currently stored outlet state, without updating from device.
std::vector< size_t > m_offOrder
[optional] The order in which outlets are turned off. This contains the indices of m_outlets,...
std::vector< unsigned > m_onDelays
[optional] The delays between outlets in a multi-oultet channel. The first entry is always ignored....
std::vector< unsigned > m_offDelays
[optional] The delays between outlets in a multi-oultet channel. The first entry is always ignored....
std::vector< unsigned > channelOffDelays(const std::string &channel)
Get the vector of outlet off delays for a channel.
virtual int updateOutletState(int outletNum)=0
Get the state of the outlet from the device.
std::vector< size_t > m_onOrder
[optional] The order in which outlets are turned on. This contains the indices of m_outlets,...
int turnChannelOn(const std::string &channel)
Turn a channel on.
pcf::IndiProperty m_indiP_chOffDelays
An INDI property which publishes the total off delay for each channel. Useful for GUIs,...
int channelState(const std::string &channel)
Get the state of a channel.
pcf::IndiProperty m_indiP_chOutlets
An INDI property which publishes the outlets associated with each channel. Useful for GUIs,...
bool m_firstOne
Flag is true if the first outlet is numbered 1, otherwise assumes starting at 0.
std::unordered_map< std::string, channelSpec > m_channels
The map of channel specifications, which can be accessed by their names.
int loadConfig(mx::app::appConfigurator &config)
Load the [channel] sections from an application configurator.
size_t numChannels()
Get the number of channels.
pcf::IndiProperty m_indiP_outletStates
Indi Property to show individual outlet states.
std::vector< size_t > channelOnOrder(const std::string &channel)
Get the vector of outlet on orders for a channel.
int setupConfig(mx::app::appConfigurator &config)
Setup an application configurator for an outletController.
int turnChannelOff(const std::string &channel)
Turn a channel off.
virtual int updateOutletStates()
Get the states of all outlets from the device.
std::vector< size_t > channelOutlets(const std::string &channel)
Get the vector of outlet indices for a channel.
int setNumberOfOutlets(int numOuts)
Sets the number of outlets. This should be called by the derived class constructor.
int newCallBack_channels(const pcf::IndiProperty &ipRecv)
The callback called by the static version, to actually process the new request.
pcf::IndiProperty m_indiP_chOnDelays
An INDI property which publishes the total on delay for each channel. Useful for GUIs,...
virtual int turnOutletOff(int outletNum)=0
Turn an outlet off.
std::vector< unsigned > channelOnDelays(const std::string &channel)
Get the vector of outlet on delays for a channel.
std::vector< size_t > channelOffOrder(const std::string &channel)
Get the vector of outlet off orders for a channel.
static int st_newCallBack_channels(void *app, const pcf::IndiProperty &ipRecv)
The static callback function to be registered for the channel properties.
std::vector< int > m_outletStates
The current states of each outlet. These MUST be updated by derived classes in the overridden updated...
std::vector< size_t > m_outlets
The outlets in this channel.
virtual int turnOutletOn(int outletNum)=0
Turn an outlet on.
Structure containing the specification of one channel.