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