9#ifndef app_outletController_hpp
10#define app_outletController_hpp
12#include <ImageStreamIO/ImageStreamIO.h>
14#include <mx/app/application.hpp>
15#include <mx/sys/timeUtils.hpp>
17#include "../../INDI/libcommon/IndiProperty.hpp"
18#include "../../libMagAOX/libMagAOX.hpp"
19#include "../indiUtils.hpp"
22#define OUTLET_STATE_UNKNOWN (-1)
23#define OUTLET_STATE_OFF (0)
24#define OUTLET_STATE_INTERMEDIATE (1)
25#define OUTLET_STATE_ON (2)
28#define OUTLET_E_NOOUTLETS (-10)
29#define OUTLET_E_NOCHANNELS (-15)
30#define OUTLET_E_NOVALIDCH (-20)
67template<
class derivedT>
253 const pcf::IndiProperty &
ipRecv
279 [[deprecated(
"use appStartup() instead")]]
297 return *
static_cast<derivedT *
>(
this);
301template<
class derivedT>
304 for(
auto &
it : m_channels)
306 it.second.m_mutex =
nullptr;
309 for(
size_t n = 0; n < m_channelMutexes.size(); ++n)
311 if(m_channelMutexes[n])
313 delete m_channelMutexes[n];
319template<
class derivedT>
322 static_cast<void>(config);
327template<
class derivedT>
333 std::vector<std::string> sections;
335 config.unusedSections(sections);
340 std::vector<std::string> chSections;
342 for(
size_t i=0;i<sections.size(); ++i)
344 if( config.isSetUnused( mx::app::iniFile::makeKey(sections[i],
"outlet" ))
345 || config.isSetUnused( mx::app::iniFile::makeKey(sections[i],
"outlets" )) )
347 chSections.push_back(sections[i]);
354 for(
size_t n = 0; n < chSections.size(); ++n)
356 m_channels.emplace( chSections[n] ,
channelSpec());
359 std::vector<size_t> outlets;
360 if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n],
"outlet" )))
362 config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n],
"outlet" ) );
366 config.configUnused( outlets, mx::app::iniFile::makeKey(chSections[n],
"outlets" ) );
369 if(outlets.size() == 0)
371 return derivedT::template log<software_error,-1>( std::format(
"no outlets in Channel ""{} is not valid", chSections[n]));
375 for(
size_t k=0;k<outlets.size(); ++k)
378 if( (
int) outlets[k] - m_firstOne < 0 || (int) outlets[k] - m_firstOne > (
int) m_outletStates.size())
380 return derivedT::template log<software_error,-1>( std::format(
"Outlet {} in Channel ""{} is not valid", outlets[k], chSections[n]), logPrio::LOG_ERROR);
384 outlets[k] -= m_firstOne;
387 m_channels[chSections[n]].m_outlets = outlets;
390 if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n],
"onOrder" )))
392 std::vector<size_t> onOrder;
393 config.configUnused( onOrder, mx::app::iniFile::makeKey(chSections[n],
"onOrder" ) );
395 if(onOrder.size() != m_channels[chSections[n]].m_outlets.size())
397 return derivedT::template log<software_error,-1>(
"onOrder must be same ""size as outlets. In Channel " + chSections[n]);
400 m_channels[chSections[n]].m_onOrder = onOrder;
403 if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n],
"offOrder" )))
405 std::vector<size_t> offOrder;
406 config.configUnused( offOrder, mx::app::iniFile::makeKey(chSections[n],
"offOrder" ) );
408 if(offOrder.size() != m_channels[chSections[n]].m_outlets.size())
410 return derivedT::template log<software_error,-1>(
"offOrder must be same ""size as outlets. In Channel " + chSections[n]);
413 m_channels[chSections[n]].m_offOrder = offOrder;
416 if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n],
"onDelays" )))
418 std::vector<unsigned> onDelays;
419 config.configUnused( onDelays, mx::app::iniFile::makeKey(chSections[n],
"onDelays" ) );
421 if(onDelays.size() != m_channels[chSections[n]].m_outlets.size())
423 return derivedT::template log<software_error,-1>(
"onDelays must be same ""size as outlets. In Channel " + chSections[n]);
426 m_channels[chSections[n]].m_onDelays = onDelays;
429 if( config.isSetUnused( mx::app::iniFile::makeKey(chSections[n],
"offDelays" )))
431 std::vector<unsigned> offDelays;
432 config.configUnused( offDelays, mx::app::iniFile::makeKey(chSections[n],
"offDelays" ) );
434 if(offDelays.size() != m_channels[chSections[n]].m_outlets.size())
436 return derivedT::template log<software_error,-1>(
"offDelays must be same ""size as outlets. In Channel " + chSections[n]);
439 m_channels[chSections[n]].m_offDelays = offDelays;
443 m_channelMutexes.resize(m_channels.size(),
nullptr);
445 for(
auto &
it : m_channels)
447 m_channelMutexes[n] =
new std::mutex;
449 it.second.m_mutex = m_channelMutexes[n];
458template<
class derivedT>
461 m_outletStates.resize(numOuts, -1);
465template<
class derivedT>
468 return m_outletStates[outletNum];
471template<
class derivedT>
474 for(
size_t n=0; n<m_outletStates.size(); ++n)
476 int rv = derived().updateOutletState(n);
479 derivedT::template log<software_error>({0, rv, std::format(
"error updating outlet {}", n)});
487template<
class derivedT>
490 return m_channels.size();
493template<
class derivedT>
496 return m_channels[channel].m_outlets;
499template<
class derivedT>
502 return m_channels[channel].m_onOrder;
505template<
class derivedT>
508 return m_channels[channel].m_offOrder;
511template<
class derivedT>
514 return m_channels[channel].m_onDelays;
517template<
class derivedT>
520 return m_channels[channel].m_offDelays;
523template<
class derivedT>
526 int st = outletState(m_channels[channel].m_outlets[0]);
528 for(
size_t n = 1; n < m_channels[channel].m_outlets.size(); ++n )
530 if( st != outletState(m_channels[channel].m_outlets[n]) ) st = 1;
536template<
class derivedT>
539 std::unique_lock<std::mutex> channelGuard;
540 if(m_channels[channel].m_mutex !=
nullptr)
542 channelGuard = std::unique_lock<std::mutex>(*m_channels[channel].m_mutex);
546 std::cerr <<
"mutex nullptr\n";
549 #ifndef OUTLET_CTRL_TEST_NOINDI
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))
568 if( m_channels[channel].m_onOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_onOrder[0];
571 if( derived().turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
573 return derivedT::template log<software_error, -1>(std::format(
"error turning on outlet {}",n));
576 derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 2});
579 for(
size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
583 if( m_channels[channel].m_onOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_onOrder[i];
586 if( m_channels[channel].m_onDelays.size() == m_channels[channel].m_outlets.size() )
588 mx::sys::milliSleep(m_channels[channel].m_onDelays[i]);
593 if( derived().turnOutletOn(m_channels[channel].m_outlets[n]) < 0 )
595 return derivedT::template log<software_error, -1>(std::format(
"error turning on outlet {}", n));
598 derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne ), 2});
602 derivedT::template log<outlet_channel_state>({ channel, 2});
604 if(clock_gettime(CLOCK_ISIO, &m_channels[channel].m_stateTime) < 0)
606 return derivedT::template log<software_error,-1>({errno, 0,
"clock_gettime"});
609 #ifndef OUTLET_CTRL_TEST_NOINDI
616template<
class derivedT>
619 std::unique_lock<std::mutex> channelGuard;
620 if(m_channels[channel].m_mutex !=
nullptr)
622 channelGuard = std::unique_lock<std::mutex>(*m_channels[channel].m_mutex);
626 std::cerr <<
"mutex nullptr\n";
629 #ifndef OUTLET_CTRL_TEST_NOINDI
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))
648 if( m_channels[channel].m_offOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_offOrder[0];
651 if( derived().turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
653 return derivedT::template log<software_error, -1>(std::format(
"error turning off outlet {}", n));
656 derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
659 for(
size_t i = 1; i< m_channels[channel].m_outlets.size(); ++i)
663 if( m_channels[channel].m_offOrder.size() == m_channels[channel].m_outlets.size() ) n = m_channels[channel].m_offOrder[i];
666 if( m_channels[channel].m_offDelays.size() == m_channels[channel].m_outlets.size() )
668 mx::sys::milliSleep(m_channels[channel].m_offDelays[i]);
672 if( derived().turnOutletOff(m_channels[channel].m_outlets[n]) < 0 )
674 return derivedT::template log<software_error, -1>(std::format(
"error turning off outlet {}", n));
677 derivedT::template log<outlet_state>({ (uint8_t) (n + m_firstOne), 0});
680 derivedT::template log<outlet_channel_state>({ channel, 0});
682 if(clock_gettime(CLOCK_ISIO, &m_channels[channel].m_stateTime) < 0)
684 return derivedT::template log<software_error,-1>({errno, 0,
"clock_gettime"});
687 #ifndef OUTLET_CTRL_TEST_NOINDI
694template<
class derivedT>
696 const pcf::IndiProperty &
ipRecv
699 return static_cast<derivedT *
>(app)->newCallBack_channels(
ipRecv);
702template<
class derivedT>
708 return derivedT::template log<software_error, -1>(
"can't change outlet state when not READY");
713 std::string name =
ipRecv.getName();
715 std::string state,
target;
719 state =
ipRecv[
"state"].get<std::string>();
734 return turnChannelOn(name);
739 return turnChannelOff(name);
745template<
class derivedT>
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);
754 if(derived().registerIndiPropertyReadOnly(m_indiP_stateTimes) < 0)
756 return derivedT::template log<software_error, -1>();
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);
766 if(derived().registerIndiPropertyReadOnly(m_indiP_chOutlets) < 0)
768 return derivedT::template log<software_error, -1>();
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);
777 if(derived().registerIndiPropertyReadOnly(m_indiP_chOnDelays) < 0)
779 return derivedT::template log<software_error, -1>();
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);
788 if(derived().registerIndiPropertyReadOnly(m_indiP_chOffDelays) < 0)
790 return derivedT::template log<software_error, -1>();
794 for(
auto it = m_channels.begin();
it != m_channels.end(); ++
it)
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 );
803 it->second.m_indiP_prop.add (pcf::IndiElement(
"state"));
804 it->second.m_indiP_prop.add (pcf::IndiElement(
"target"));
806 if( derived().registerIndiPropertyNew(
it->second.m_indiP_prop, st_newCallBack_channels) < 0)
808 return derivedT::template log<software_error, -1>();
812 m_indiP_stateTimes.add(pcf::IndiElement(
it->first));
813 m_indiP_stateTimes[
it->first].set(0);
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);
820 m_indiP_chOnDelays.add(pcf::IndiElement(
it->first));
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);
825 m_indiP_chOffDelays.add(pcf::IndiElement(
it->first));
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);
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 );
839 if( derived().registerIndiPropertyReadOnly(m_indiP_outletStates) < 0)
841 return derivedT::template log<software_error, -1>();
844 for(
size_t i=0; i< m_outletStates.size(); ++i)
846 m_indiP_outletStates.add (pcf::IndiElement(std::to_string(i+m_firstOne)));
852template<
class derivedT>
860template<
class derivedT>
863 if( !derived().m_indiDriver )
return 0;
866 for(
size_t i=0; i< m_outletStates.size(); ++i)
872 for(
auto it = m_channels.begin();
it != m_channels.end(); ++
it)
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.
const pcf::IndiProperty & ipRecv
#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.
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_prop
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< int > m_outletStates
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.