Line data Source code
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 :
32 : namespace MagAOX
33 : {
34 : namespace app
35 : {
36 : namespace 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 : */
67 : template<class derivedT>
68 : struct outletController
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 : */
84 : struct channelSpec
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 :
119 : ~outletController();
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 : */
269 : int appStartup();
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")]]
280 : int setupINDI();
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 : */
289 : int updateINDI();
290 :
291 : ///@}
292 :
293 :
294 : private:
295 112 : derivedT & derived()
296 : {
297 112 : return *static_cast<derivedT *>(this);
298 : }
299 : };
300 :
301 : template<class derivedT>
302 31 : outletController<derivedT>::~outletController()
303 : {
304 105 : for(auto & it : m_channels)
305 : {
306 74 : it.second.m_mutex = nullptr;
307 : }
308 :
309 105 : for(size_t n = 0; n < m_channelMutexes.size(); ++n)
310 : {
311 74 : if(m_channelMutexes[n])
312 : {
313 74 : delete m_channelMutexes[n];
314 : }
315 : }
316 62 : }
317 :
318 :
319 : template<class derivedT>
320 29 : int outletController<derivedT>::setupConfig( mx::app::appConfigurator & config )
321 : {
322 : static_cast<void>(config);
323 :
324 29 : return 0;
325 : }
326 :
327 : template<class derivedT>
328 29 : int outletController<derivedT>::loadConfig( mx::app::appConfigurator & config )
329 : {
330 29 : if( m_outletStates.size() == 0) return OUTLET_E_NOOUTLETS;
331 :
332 : //Get the "unused" sections.
333 29 : std::vector<std::string> sections;
334 :
335 29 : config.unusedSections(sections);
336 :
337 29 : 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 29 : std::vector<std::string> chSections;
341 :
342 103 : for(size_t i=0;i<sections.size(); ++i)
343 : {
344 222 : if( config.isSetUnused( mx::app::iniFile::makeKey(sections[i], "outlet" ))
345 172 : || config.isSetUnused( mx::app::iniFile::makeKey(sections[i], "outlets" )) )
346 : {
347 74 : chSections.push_back(sections[i]);
348 : }
349 : }
350 :
351 29 : if( chSections.size() == 0 ) return OUTLET_E_NOVALIDCH;
352 :
353 : //Now configure the channels.
354 177 : for(size_t n = 0; n < chSections.size(); ++n)
355 : {
356 74 : m_channels.emplace( chSections[n] , channelSpec());
357 :
358 : //---- Set outlets ----
359 74 : std::vector<size_t> outlets;
360 148 : if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "outlet" )))
361 : {
362 124 : config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n], "outlet" ) );
363 : }
364 : else
365 : {
366 24 : config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n], "outlets" ) );
367 : }
368 :
369 74 : if(outlets.size() == 0)
370 : {
371 0 : 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 190 : for(size_t k=0;k<outlets.size(); ++k)
376 : {
377 : ///\todo test this error
378 116 : if( (int) outlets[k] - m_firstOne < 0 || (int) outlets[k] - m_firstOne > (int) m_outletStates.size())
379 : {
380 0 : 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 116 : outlets[k] -= m_firstOne;
385 : }
386 :
387 74 : m_channels[chSections[n]].m_outlets = outlets;
388 :
389 : //---- Set optional configs ----
390 148 : if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "onOrder" )))
391 : {
392 34 : std::vector<size_t> onOrder;
393 34 : config.configUnused( onOrder, mx::app::iniFile::makeKey(chSections[n], "onOrder" ) );
394 :
395 34 : if(onOrder.size() != m_channels[chSections[n]].m_outlets.size())
396 : {
397 0 : return derivedT::template log<software_error,-1>("onOrder must be same ""size as outlets. In Channel " + chSections[n]);
398 : }
399 :
400 34 : m_channels[chSections[n]].m_onOrder = onOrder;
401 34 : }
402 :
403 148 : if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "offOrder" )))
404 : {
405 26 : std::vector<size_t> offOrder;
406 26 : config.configUnused( offOrder, mx::app::iniFile::makeKey(chSections[n], "offOrder" ) );
407 :
408 26 : if(offOrder.size() != m_channels[chSections[n]].m_outlets.size())
409 : {
410 0 : return derivedT::template log<software_error,-1>("offOrder must be same ""size as outlets. In Channel " + chSections[n]);
411 : }
412 :
413 26 : m_channels[chSections[n]].m_offOrder = offOrder;
414 26 : }
415 :
416 148 : if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "onDelays" )))
417 : {
418 16 : std::vector<unsigned> onDelays;
419 16 : config.configUnused( onDelays, mx::app::iniFile::makeKey(chSections[n], "onDelays" ) );
420 :
421 16 : if(onDelays.size() != m_channels[chSections[n]].m_outlets.size())
422 : {
423 0 : return derivedT::template log<software_error,-1>("onDelays must be same ""size as outlets. In Channel " + chSections[n]);
424 : }
425 :
426 16 : m_channels[chSections[n]].m_onDelays = onDelays;
427 16 : }
428 :
429 148 : if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n], "offDelays" )))
430 : {
431 16 : std::vector<unsigned> offDelays;
432 16 : config.configUnused( offDelays, mx::app::iniFile::makeKey(chSections[n], "offDelays" ) );
433 :
434 16 : if(offDelays.size() != m_channels[chSections[n]].m_outlets.size())
435 : {
436 0 : return derivedT::template log<software_error,-1>("offDelays must be same ""size as outlets. In Channel " + chSections[n]);
437 : }
438 :
439 16 : m_channels[chSections[n]].m_offDelays = offDelays;
440 16 : }
441 : }
442 :
443 29 : m_channelMutexes.resize(m_channels.size(), nullptr);
444 29 : size_t n = 0;
445 103 : for(auto & it : m_channels)
446 : {
447 74 : m_channelMutexes[n] = new std::mutex;
448 :
449 74 : it.second.m_mutex = m_channelMutexes[n];
450 :
451 74 : ++n;
452 :
453 : }
454 :
455 29 : return 0;
456 29 : }
457 :
458 : template<class derivedT>
459 31 : int outletController<derivedT>::setNumberOfOutlets( int numOuts )
460 : {
461 31 : m_outletStates.resize(numOuts, -1);
462 31 : return 0;
463 : }
464 :
465 : template<class derivedT>
466 540 : int outletController<derivedT>::outletState( int outletNum )
467 : {
468 540 : return m_outletStates[outletNum];
469 : }
470 :
471 : template<class derivedT>
472 0 : int outletController<derivedT>::updateOutletStates()
473 : {
474 0 : for(size_t n=0; n<m_outletStates.size(); ++n)
475 : {
476 0 : int rv = derived().updateOutletState(n);
477 0 : if(rv < 0)
478 : {
479 0 : derivedT::template log<software_error>({0, rv, std::format("error updating outlet {}", n)});
480 0 : return rv;
481 : }
482 : }
483 :
484 0 : return 0;
485 : }
486 :
487 : template<class derivedT>
488 8 : size_t outletController<derivedT>::numChannels()
489 : {
490 8 : return m_channels.size();
491 : }
492 :
493 : template<class derivedT>
494 24 : std::vector<size_t> outletController<derivedT>::channelOutlets( const std::string & channel )
495 : {
496 24 : return m_channels[channel].m_outlets;
497 : }
498 :
499 : template<class derivedT>
500 24 : std::vector<size_t> outletController<derivedT>::channelOnOrder( const std::string & channel )
501 : {
502 24 : return m_channels[channel].m_onOrder;
503 : }
504 :
505 : template<class derivedT>
506 24 : std::vector<size_t> outletController<derivedT>::channelOffOrder( const std::string & channel )
507 : {
508 24 : return m_channels[channel].m_offOrder;
509 : }
510 :
511 : template<class derivedT>
512 24 : std::vector<unsigned> outletController<derivedT>::channelOnDelays( const std::string & channel )
513 : {
514 24 : return m_channels[channel].m_onDelays;
515 : }
516 :
517 : template<class derivedT>
518 24 : std::vector<unsigned> outletController<derivedT>::channelOffDelays( const std::string & channel )
519 : {
520 24 : return m_channels[channel].m_offDelays;
521 : }
522 :
523 : template<class derivedT>
524 256 : int outletController<derivedT>::channelState( const std::string & channel )
525 : {
526 256 : int st = outletState(m_channels[channel].m_outlets[0]);
527 :
528 404 : for( size_t n = 1; n < m_channels[channel].m_outlets.size(); ++n )
529 : {
530 148 : if( st != outletState(m_channels[channel].m_outlets[n]) ) st = 1;
531 : }
532 :
533 256 : return st;
534 : }
535 :
536 : template<class derivedT>
537 34 : int outletController<derivedT>::turnChannelOn( const std::string & channel )
538 : {
539 34 : std::unique_lock<std::mutex> channelGuard;
540 34 : if(m_channels[channel].m_mutex != nullptr)
541 : {
542 34 : channelGuard = std::unique_lock<std::mutex>(*m_channels[channel].m_mutex);
543 : }
544 : else
545 : {
546 0 : std::cerr << "mutex nullptr\n";
547 : }
548 :
549 : #ifndef OUTLET_CTRL_TEST_NOINDI
550 0 : 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 34 : if(channelState(channel) == OUTLET_STATE_ON)
555 : {
556 0 : return 0;
557 : }
558 :
559 : timespec now;
560 34 : clock_gettime(CLOCK_ISIO, &now);
561 34 : 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 0 : return 0;
564 : }
565 :
566 : //If order is specified, get first outlet number
567 34 : size_t n = 0;
568 34 : 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 34 : if( derived().turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
572 : {
573 0 : return derivedT::template log<software_error, -1>(std::format("error turning on outlet {}",n));
574 : }
575 :
576 34 : derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 2});
577 :
578 : //Now do the rest
579 56 : for(size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
580 : {
581 : //If order is specified, get next outlet number
582 22 : n=i;
583 22 : 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 22 : if( m_channels[channel].m_onDelays.size() == m_channels[channel].m_outlets.size() )
587 : {
588 4 : mx::sys::milliSleep(m_channels[channel].m_onDelays[i]);
589 : }
590 :
591 : //turn on next outlet
592 :
593 22 : if( derived().turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
594 : {
595 0 : return derivedT::template log<software_error, -1>(std::format("error turning on outlet {}", n));
596 : }
597 :
598 22 : derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne ), 2});
599 :
600 : }
601 :
602 34 : derivedT::template log<outlet_channel_state>({ channel, 2});
603 :
604 34 : if(clock_gettime(CLOCK_ISIO, &m_channels[channel].m_stateTime) < 0)
605 : {
606 0 : return derivedT::template log<software_error,-1>({errno, 0, "clock_gettime"});
607 : }
608 :
609 : #ifndef OUTLET_CTRL_TEST_NOINDI
610 0 : indi::updateIfChanged(m_indiP_stateTimes, channel, m_channels[channel].m_stateTime.tv_sec, derived().m_indiDriver, INDI_IDLE );
611 : #endif
612 :
613 34 : return 0;
614 34 : }
615 :
616 : template<class derivedT>
617 34 : int outletController<derivedT>::turnChannelOff( const std::string & channel )
618 : {
619 34 : std::unique_lock<std::mutex> channelGuard;
620 34 : if(m_channels[channel].m_mutex != nullptr)
621 : {
622 34 : channelGuard = std::unique_lock<std::mutex>(*m_channels[channel].m_mutex);
623 : }
624 : else
625 : {
626 0 : std::cerr << "mutex nullptr\n";
627 : }
628 :
629 : #ifndef OUTLET_CTRL_TEST_NOINDI
630 0 : 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 34 : if(channelState(channel) == OUTLET_STATE_OFF)
635 : {
636 0 : return 0;
637 : }
638 :
639 : timespec now;
640 34 : clock_gettime(CLOCK_ISIO, &now);
641 34 : 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 0 : return 0;
644 : }
645 :
646 : //If order is specified, get first outlet number
647 34 : size_t n = 0;
648 34 : 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 34 : if( derived().turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
652 : {
653 0 : return derivedT::template log<software_error, -1>(std::format("error turning off outlet {}", n));
654 : }
655 :
656 34 : derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
657 :
658 : //Now do the rest
659 56 : for(size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
660 : {
661 : //If order is specified, get next outlet number
662 22 : n=i;
663 22 : 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 22 : if( m_channels[channel].m_offDelays.size() == m_channels[channel].m_outlets.size() )
667 : {
668 4 : mx::sys::milliSleep(m_channels[channel].m_offDelays[i]);
669 : }
670 :
671 : //turn off next outlet
672 22 : if( derived().turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
673 : {
674 0 : return derivedT::template log<software_error, -1>(std::format("error turning off outlet {}", n));
675 : }
676 :
677 22 : derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
678 : }
679 :
680 34 : derivedT::template log<outlet_channel_state>({ channel, 0});
681 :
682 34 : if(clock_gettime(CLOCK_ISIO, &m_channels[channel].m_stateTime) < 0)
683 : {
684 0 : return derivedT::template log<software_error,-1>({errno, 0, "clock_gettime"});
685 : }
686 :
687 : #ifndef OUTLET_CTRL_TEST_NOINDI
688 0 : indi::updateIfChanged(m_indiP_stateTimes, channel, m_channels[channel].m_stateTime.tv_sec, derived().m_indiDriver, INDI_IDLE );
689 : #endif
690 :
691 34 : return 0;
692 34 : }
693 :
694 : template<class derivedT>
695 0 : int outletController<derivedT>::st_newCallBack_channels( void * app,
696 : const pcf::IndiProperty &ipRecv
697 : )
698 : {
699 0 : return static_cast<derivedT *>(app)->newCallBack_channels(ipRecv);
700 : }
701 :
702 : template<class derivedT>
703 0 : int outletController<derivedT>::newCallBack_channels( const pcf::IndiProperty &ipRecv )
704 : {
705 : //Check if we're in state READY before doing anything
706 0 : if(derived().state() != stateCodes::READY)
707 : {
708 0 : 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 0 : std::string name = ipRecv.getName();
714 :
715 0 : std::string state, target;
716 :
717 0 : if(ipRecv.find("state"))
718 : {
719 0 : state = ipRecv["state"].get<std::string>();
720 : }
721 :
722 0 : if(ipRecv.find("target"))
723 : {
724 0 : target = ipRecv["target"].get<std::string>();
725 : }
726 :
727 0 : if( target == "" ) target = state;
728 :
729 0 : target = mx::ioutils::toUpper(target);
730 :
731 :
732 0 : if( target == "ON" )
733 : {
734 0 : return turnChannelOn(name);
735 : }
736 :
737 0 : if(target == "OFF")
738 : {
739 0 : return turnChannelOff(name);
740 : }
741 :
742 0 : return 0;
743 0 : }
744 :
745 : template<class derivedT>
746 0 : int outletController<derivedT>::appStartup()
747 : {
748 0 : m_indiP_stateTimes = pcf::IndiProperty(pcf::IndiProperty::Number);
749 0 : m_indiP_stateTimes.setDevice(derived().configName());
750 0 : m_indiP_stateTimes.setName("stateTimes");
751 0 : m_indiP_stateTimes.setPerm(pcf::IndiProperty::ReadOnly);
752 0 : m_indiP_stateTimes.setState(pcf::IndiProperty::Idle);
753 :
754 0 : if(derived().registerIndiPropertyReadOnly(m_indiP_stateTimes) < 0)
755 : {
756 0 : return derivedT::template log<software_error, -1>();
757 : }
758 :
759 : //Register the static INDI properties
760 0 : m_indiP_chOutlets = pcf::IndiProperty(pcf::IndiProperty::Text);
761 0 : m_indiP_chOutlets.setDevice(derived().configName());
762 0 : m_indiP_chOutlets.setName("channelOutlets");
763 0 : m_indiP_chOutlets.setPerm(pcf::IndiProperty::ReadOnly);
764 0 : m_indiP_chOutlets.setState(pcf::IndiProperty::Idle);
765 :
766 0 : if(derived().registerIndiPropertyReadOnly(m_indiP_chOutlets) < 0)
767 : {
768 0 : return derivedT::template log<software_error, -1>();
769 : }
770 :
771 0 : m_indiP_chOnDelays = pcf::IndiProperty (pcf::IndiProperty::Number);
772 0 : m_indiP_chOnDelays.setDevice(derived().configName());
773 0 : m_indiP_chOnDelays.setName("channelOnDelays");
774 0 : m_indiP_chOnDelays.setPerm(pcf::IndiProperty::ReadOnly);
775 0 : m_indiP_chOnDelays.setState(pcf::IndiProperty::Idle);
776 :
777 0 : if(derived().registerIndiPropertyReadOnly(m_indiP_chOnDelays) < 0)
778 : {
779 0 : return derivedT::template log<software_error, -1>();
780 : }
781 :
782 0 : m_indiP_chOffDelays = pcf::IndiProperty (pcf::IndiProperty::Number);
783 0 : m_indiP_chOffDelays.setDevice(derived().configName());
784 0 : m_indiP_chOffDelays.setName("channelOffDelays");
785 0 : m_indiP_chOffDelays.setPerm(pcf::IndiProperty::ReadOnly);
786 0 : m_indiP_chOffDelays.setState(pcf::IndiProperty::Idle);
787 :
788 0 : if(derived().registerIndiPropertyReadOnly(m_indiP_chOffDelays) < 0)
789 : {
790 0 : return derivedT::template log<software_error, -1>();
791 : }
792 :
793 : //Create channel properties and register callback.
794 0 : for(auto it = m_channels.begin(); it != m_channels.end(); ++it)
795 : {
796 0 : it->second.m_indiP_prop = pcf::IndiProperty (pcf::IndiProperty::Text);
797 0 : it->second.m_indiP_prop.setDevice(derived().configName());
798 0 : it->second.m_indiP_prop.setName(it->first);
799 0 : it->second.m_indiP_prop.setPerm(pcf::IndiProperty::ReadWrite);
800 0 : it->second.m_indiP_prop.setState( pcf::IndiProperty::Idle );
801 :
802 : //add elements 'state' and 'target'
803 0 : it->second.m_indiP_prop.add (pcf::IndiElement("state"));
804 0 : it->second.m_indiP_prop.add (pcf::IndiElement("target"));
805 :
806 0 : if( derived().registerIndiPropertyNew( it->second.m_indiP_prop, st_newCallBack_channels) < 0)
807 : {
808 0 : return derivedT::template log<software_error, -1>();
809 : }
810 :
811 : //Load values into the static INDI properties
812 0 : m_indiP_stateTimes.add(pcf::IndiElement(it->first));
813 0 : m_indiP_stateTimes[it->first].set(0);
814 :
815 0 : m_indiP_chOutlets.add(pcf::IndiElement(it->first));
816 0 : std::string os = std::format("{}", it->second.m_outlets[0]);
817 0 : for(size_t i=1;i< it->second.m_outlets.size();++i) os += std::format(",{}",it->second.m_outlets[i]);
818 0 : m_indiP_chOutlets[it->first].set(os);
819 :
820 0 : m_indiP_chOnDelays.add(pcf::IndiElement(it->first));
821 0 : double sum=0;
822 0 : for(size_t i=0;i< it->second.m_onDelays.size();++i) sum += it->second.m_onDelays[i];
823 0 : m_indiP_chOnDelays[it->first].set(sum);
824 :
825 0 : m_indiP_chOffDelays.add(pcf::IndiElement(it->first));
826 0 : sum=0;
827 0 : for(size_t i=0;i< it->second.m_offDelays.size();++i) sum += it->second.m_offDelays[i];
828 0 : m_indiP_chOffDelays[it->first].set(sum);
829 :
830 : }
831 :
832 : //Register the outletStates INDI property, and add an element for each outlet.
833 0 : m_indiP_outletStates = pcf::IndiProperty (pcf::IndiProperty::Text);
834 0 : m_indiP_outletStates.setDevice(derived().configName());
835 0 : m_indiP_outletStates.setName("outlet");
836 0 : m_indiP_outletStates.setPerm(pcf::IndiProperty::ReadWrite);
837 0 : m_indiP_outletStates.setState( pcf::IndiProperty::Idle );
838 :
839 0 : if( derived().registerIndiPropertyReadOnly(m_indiP_outletStates) < 0)
840 : {
841 0 : return derivedT::template log<software_error, -1>();
842 : }
843 :
844 0 : for(size_t i=0; i< m_outletStates.size(); ++i)
845 : {
846 0 : m_indiP_outletStates.add (pcf::IndiElement(std::to_string(i+m_firstOne)));
847 : }
848 :
849 0 : return 0;
850 : }
851 :
852 : template<class derivedT>
853 0 : int outletController<derivedT>::setupINDI()
854 : {
855 0 : return appStartup();
856 : }
857 :
858 : std::string stateIntToString(int st);
859 :
860 : template<class derivedT>
861 0 : int outletController<derivedT>::updateINDI()
862 : {
863 0 : if( !derived().m_indiDriver ) return 0;
864 :
865 : //Publish outlet states (only bother if they've changed)
866 0 : for(size_t i=0; i< m_outletStates.size(); ++i)
867 : {
868 0 : 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 0 : for(auto it = m_channels.begin(); it != m_channels.end(); ++it)
873 : {
874 0 : std::string state = stateIntToString( channelState( it->first ));
875 :
876 0 : indi::updateIfChanged( it->second.m_indiP_prop, "state", state, derived().m_indiDriver );
877 : }
878 :
879 :
880 :
881 0 : return 0;
882 : }
883 :
884 : } //namespace dev
885 : } //namespace app
886 : } //namespace MagAOX
887 :
888 : #endif //app_outletController_hpp
|