#ifndef __SRTMINORSERVOCONTAINERS_H__ #define __SRTMINORSERVOCONTAINERS_H__ /** * SRTMinorServoContainers.h * Giuseppe Carboni (giuseppe.carboni@inaf.it) */ #include #include #include #include #include #include #include #include #include "SRTMinorServoUtils.h" namespace MinorServo { /** * Object used to store some info regarding a scan. */ struct SRTMinorServoScan { /** * Name of the servo involved in the scan. */ std::string servo_name = ""; /** * Name of the axis involved in the scan. */ std::string axis_name = ""; /** * Index of the axis involved in the scan. */ size_t axis_index = 0; /** * Range of the scan. */ double scan_range = 0; /** * Starting time of the scan. */ ACS::Time start_time = 0; /** * Closing time of the scan. */ ACS::Time close_time = 0; /** * Duration of the scan. */ ACS::TimeInterval scan_duration = 0; /** * Central position of the scan axis. */ double central_position = 0; /** * Starting elevation for the scan. */ double starting_elevation = 0; }; /** * This dictionary contains the Leonardo focal configurations DISCOS enumerations, alongside their name inside the Leonardo minor servo system. */ using LDOConfigurationNameTableType = boost::bimap; const LDOConfigurationNameTableType LDOConfigurationNameTable = boost::assign::list_of (CONFIGURATION_PRIMARY, "Primario") (CONFIGURATION_GREGORIAN1, "Gregoriano1") (CONFIGURATION_GREGORIAN2, "Gregoriano2") (CONFIGURATION_GREGORIAN3, "Gregoriano3") (CONFIGURATION_GREGORIAN4, "Gregoriano4") (CONFIGURATION_GREGORIAN5, "Gregoriano5") (CONFIGURATION_GREGORIAN6, "Gregoriano6") (CONFIGURATION_GREGORIAN7, "Gregoriano7") (CONFIGURATION_GREGORIAN8, "Gregoriano8") (CONFIGURATION_BWG1, "BWG1") (CONFIGURATION_BWG2, "BWG2") (CONFIGURATION_BWG3, "BWG3") (CONFIGURATION_BWG4, "BWG4"); /** * This dictionary containes the Leonardo focal configurations DISCOS enumerations, alongside their ID counterpart as read from the Leonardo minor servo system proxy. */ using LDOConfigurationIDTableType = boost::bimap; const LDOConfigurationIDTableType LDOConfigurationIDTable = boost::assign::list_of (CONFIGURATION_UNKNOWN, 0) (CONFIGURATION_PRIMARY, 1) (CONFIGURATION_GREGORIAN1, 11) (CONFIGURATION_GREGORIAN2, 12) (CONFIGURATION_GREGORIAN3, 13) (CONFIGURATION_GREGORIAN4, 14) (CONFIGURATION_GREGORIAN5, 15) (CONFIGURATION_GREGORIAN6, 16) (CONFIGURATION_GREGORIAN7, 17) (CONFIGURATION_GREGORIAN8, 18) (CONFIGURATION_BWG1, 21) (CONFIGURATION_BWG2, 22) (CONFIGURATION_BWG3, 23) (CONFIGURATION_BWG4, 24); using SRTMinorServoCoefficientsTable = std::map>; /** * This class implements a queue of time tagged positions. it extends a simple std::map with some specific methods. */ class SRTMinorServoPositionsQueue : private std::map> { public: /** * Default constructor. Used only for lazy initialization. */ SRTMinorServoPositionsQueue() : std::map>(), m_queue_size(0), m_vector_size(0), m_mutex() {} /** * Constructor with queue size as parameter. * @param queue_size the maximum size of the queue. Once this value is reached the oldest entry gets discarded. */ SRTMinorServoPositionsQueue(size_t queue_size) : std::map>(), m_queue_size(queue_size), m_vector_size(0), m_mutex() { std::unique_lock lock(m_mutex); queueLazyInit(queue_size); } /** * Constructor with queue size and vector size as parameters. * @param queue_size the maximum size of the queue. Once this value is reached the oldest entry gets discarded. * @param vector_size the length of the vectors that this object will store. Once set it cannot be changed. This assures we are always dealing with the same number of virtual axes. */ SRTMinorServoPositionsQueue(size_t queue_size, size_t vector_size) : std::map>(), m_queue_size(queue_size), m_vector_size(vector_size), m_mutex() { std::unique_lock lock(m_mutex); queueLazyInit(queue_size); vectorLazyInit(vector_size); } /** * Custom assignment operator. It locks both the current object and the passed one with a mutex in order for the assignment operation to be thread safe. * @param other the other SRTMinorServoPositionsQueue we are assigning its value to the current object. * @return the newly populated SRTMinorServoPositionsQueue object. */ SRTMinorServoPositionsQueue& operator=(const SRTMinorServoPositionsQueue& other) { if(this != &other) { std::unique_lock lockThis(m_mutex, std::defer_lock); std::unique_lock lockOther(other.m_mutex, std::defer_lock); std::lock(lockThis, lockOther); m_queue_size = other.m_queue_size; m_vector_size = other.m_vector_size; std::map>::operator=(other); } return *this; } /** * Put method, with initializer list argument. * @param key the time the given coordinates are related to. * @param values the given set of coordinates. */ void put(ACS::Time key, const std::initializer_list& values) { put(key, std::vector(values)); } /** * Put method, with ACS::doubleSeq argument. * @param key the time the given coordinates are related to. * @param values the given set of coordinates. */ void put(ACS::Time key, const ACS::doubleSeq& values) { put(key, std::vector(values.get_buffer(), values.get_buffer() + values.length())); } /** * Put method, with std::vector argument. * @param key the time the given coordinates are related to. * @param values the given set of coordinates. */ void put(ACS::Time key, const std::vector& values) { std::unique_lock lock(m_mutex); vectorLazyInit(values.size()); queueLazyInit(m_queue_size); if(std::map>::size() == m_queue_size) { // Remove the oldest one this->erase(this->begin()); } this->emplace(std::make_pair(key, values)); } /** * Get method. It retrieves a set of coordinates from a given ACS::Time, giving back the time as well. * @param key the time the user wants to retrieve the related coordinates. * @param exact a boolean indicating whether the user wants to interpolate (false) or not (true). * @throw std::logic_error when the queue is empty. * @throw std::out_of_range when the exact point was not found in the queue. * @return a std::pair containing the ACS::Time as first element and the set of coordinates as second element. */ std::pair> get(ACS::Time key, bool exact = false) { std::shared_lock lock(m_mutex); if(this->empty()) { throw std::logic_error("The queue is empty!"); } if(const SRTMinorServoPositionsQueue::iterator point = this->find(key); point != this->end()) { return *point; } else if(exact) { // Exact point not found, we throw an exception throw std::out_of_range("Exact point not found!"); } else { // Key not found, should check outside the boundaries or interpolate if(key <= this->begin()->first) { // Aksed for a timestamp older than the earliest point in the queue return *this->cbegin(); } else if(key >= this->rbegin()->first) { // Asked for a timestamp newer than the latest point in the queue return *this->crbegin(); } else { std::vector positions(m_vector_size, 0.0); SRTMinorServoPositionsQueue::iterator p0, p1; p1 = this->lower_bound(key); p0 = p1; p0--; // Calculate the linear fit for each position double fraction = (key - p0->first) / (p1->first - p0->first); for(size_t i = 0; i < m_vector_size; i++) { positions[i] = p0->second[i] + fraction * (p1->second[i] - p0->second[i]); } return std::make_pair(key, (const std::vector)positions); } } } /** * Size method thread safe override. * @return the number of elements in the queue. */ size_t size() const { std::shared_lock lock(m_mutex); return std::map>::size(); } /** * Clear method, thread safe. It empties the queue. */ void clear() { std::unique_lock lock(m_mutex); std::map>::clear(); } /** * This method returns the number of points having a higher tag time than the one passed as argument. * @param t the time threshold. This method counts and returns the number of points having a higher time than this value. * @throw std::logic_error when the queue is empty. * @return the number of points having a higher time than the given one. */ size_t getRemainingPoints(ACS::Time t) { std::shared_lock lock(m_mutex); if(this->empty()) { throw std::logic_error("The queue is empty!"); } return std::distance(this->lower_bound(t), this->end()); } private: /** * This method gets called by the constructors for the lazy intialization of the queue size value. * @param queue_size the desired maximum queue size. * @throw std::length_error when the desired queue size is equal to 0 or when it is greater than the maximum size that can be currently allocated. */ void queueLazyInit(size_t queue_size) { if(m_queue_size == 0) { if(queue_size == 0) { // The maximum queue size was not set yet throw std::length_error("Queue length cannot be 0."); } else if(queue_size > this->max_size()) { // The requested size is greater than the maximum possible queue size throw std::length_error("Queue length cannot exceed " + std::to_string(this->max_size()) + "."); } else { m_queue_size = queue_size; } } } /** * This method gets called by the constructors for the lazy initialization of the vector size value. * @param vector_size the desired length of the vector containing the set of points. * @throw std::length_error when the desired vector size is equal to 0 or when it is longer than 6. We don't have more than 6 axes, so we hard cap this value to 6. * It also throws this when the user tries to insert a vector with different lenght then the already defined one. This ensures all stored vecors are of equal length. */ void vectorLazyInit(size_t vector_size) { if(vector_size == 0) { throw std::length_error("Vector length cannot be 0."); } else if(vector_size > 6) { // Hard cap to 6, we don't need more throw std::length_error("Vector length cannot be longer than 6."); } else if(m_vector_size == 0) { m_vector_size = vector_size; } else if(m_vector_size != vector_size) { throw std::length_error("New vector length does not match the initial one."); } } /** * The maximum size of the queue. */ size_t m_queue_size; /** * The desired length of the vectors stored by this object. */ size_t m_vector_size; /** * The shared mutex used for access synchronization. */ mutable std::shared_mutex m_mutex; }; class SRTMinorServoAnswerMap : private std::map> { /** * This class privately extends the type std::map>. * It is therefore an std::map which can hold different types of values, such as long, double and str::string, in the same container. * It is used to store the answers received from the SRTMinorServo Leonardo system. * This design was critical since all the received values have heterogeneous keys and values. * With this object, the SRTMinorServoSocket can correctly retrieve and store all the received values without having to know the keys or types a priori. */ /* * Declare this class as friend since it will have to iterate through the inner map */ friend class PySRTMinorServoCommandLibrary; public: /** * Use the same clear method of the parent class */ using std::map>::clear; /** * Default constructor, initialize the std::map object and the synchronization mutex */ SRTMinorServoAnswerMap() : std::map>(), m_mutex() {} /** * Initialize the std::map with the content of another SRTMinorServoAnswerMap, initialize the mutex, lock both objects * @param other the SRTMinorServoAnswerMap with which the content of the current object will be initialized */ SRTMinorServoAnswerMap(const SRTMinorServoAnswerMap& other) : m_mutex() { std::unique_lock lockThis(m_mutex, std::defer_lock); std::shared_lock lockOther(other.m_mutex, std::defer_lock); std::lock(lockThis, lockOther); static_cast>&>(*this) = static_cast>&>(other); } /** * Assignment operator. It lock both the current object and the assigned one's mutexes * @param other the SRTMinorServoAnswerMap which values have to be stored in the current object */ SRTMinorServoAnswerMap& operator=(const SRTMinorServoAnswerMap& other) { if(this != &other) { std::unique_lock lockThis(m_mutex, std::defer_lock); std::shared_lock lockOther(other.m_mutex, std::defer_lock); std::lock(lockThis, lockOther); static_cast>&>(*this) = static_cast>&>(other); } return *this; } /** * Update operator. It merges the current object with the elements of another object * @param other the SRTMinorServoAnswerMap which values have to be copied inside the current object */ SRTMinorServoAnswerMap& operator+=(const SRTMinorServoAnswerMap& other) { std::unique_lock lockThis(m_mutex, std::defer_lock); std::shared_lock lockOther(other.m_mutex, std::defer_lock); std::lock(lockThis, lockOther); for(const auto& entry : other) { this->operator[](entry.first) = entry.second; } return *this; } /** * Equality operator, only check the std::map and avoid comparing the mutexes, which will obviously be different * @param other the SRTMinorServoAnswerMap object to compare the current object with */ bool operator==(const SRTMinorServoAnswerMap& other) const { return static_cast>&>(*this) == static_cast>&>(other); } /** * get method. It must be used with a template parameter, in order for the SRTMinorServoAnswerMap to be able to retrieve the correct type of object for the given key. * The method will automatically convert the retrieved long, double or std::string to the given template type. * @param T the type (i.e.: int, long, double, char*) of the object to be retrieved. It can be anything derived from integral, floating point or string values. * @param key the key assigned to the value you want to retrieve * @return the value associated to given key 'key', returned as template type 'T', if possible. Be aware that some casts (i.e.: long to int, double to float) will lose precision and/or overflow * @throw std::bad_variant_access when retrieving the stored value by asking the wrong type (i.e.: stored type is a double but T is char*) * @throw std::runtime_error when attempting to retrieve a value with a type which cannot be stored in the container (anything not integral, not floating point and not similar to std::string) */ template T get(const std::string& key) const { if constexpr(std::negation_v>) { throw std::runtime_error("Unsupported type."); } std::shared_lock lock(m_mutex); try { if constexpr(std::is_integral_v) { return (T)std::get(this->at(key)); } else if constexpr(std::is_floating_point_v) { return (T)std::get(this->at(key)); } else if constexpr(is_string_v) { return (T)std::get(this->at(key)).c_str(); } } catch(std::out_of_range& ex) { std::cout << "PLAIN_COMMAND: " << this->getPlainCommand(); std::cout << "PLAIN_ANSWER:" << this->getPlainAnswer(); throw ex; } } /** * put method. The template parameter is automatically deducted from the 'value' argument. Stores the given 'value' associated with key 'key' * @param key the key associated to the stored value 'value' * @param value the value we are storing with the given key 'key' * @throw std::runtime_error when attempting to store a value with a type which cannot be stored in the container (anything not integral, not floating point and not similar to std::string) */ template void put(const std::string& key, const T& value) { if constexpr(std::negation_v>) { throw std::runtime_error("Unsupported type."); } std::unique_lock lock(m_mutex); if constexpr(std::is_integral_v) { this->operator[](key) = long(value); } else if constexpr(std::is_floating_point_v) { this->operator[](key) = double(value); } else if constexpr(is_string_v) { this->operator[](key) = std::string(value); } } /** * This method checks whether the container holds a value for the given key 'key' * @param key the key for the value we want to check if it's present in the container * @return true if the value is present in the container, false otherwise */ bool contains(const std::string& key) const { std::shared_lock lock(m_mutex); return this->find(key) != this->end(); } /** * This methods returns the std::variant type index for the value associated to the given key 'key' * @param key the key for the value we want to retrieve the type index * @throw std::out_of_range if the key is not found in the object * @return 0 for long, 1 for double, 2 for std::string */ unsigned int index(const std::string& key) const { std::shared_lock lock(m_mutex); return this->at(key).index(); } /** * This method checks whether the contained answer to a command sent to the SRTMinorServo system was positive or not * @return true if the command was correctly accepted, false if the command was not accepted or the 'OUTPUT' key was not found (unlikely scenario) */ const bool checkOutput() const { std::shared_lock lock(m_mutex); try { if(this->get("OUTPUT") == "GOOD") { return true; } } catch(std::out_of_range& ex) { // Key not found } return false; } /** * This method retrieves the ACS::Time associated with the received answer map. It converts the value from UNIX Epoch (double) to ACS::Time * @return the ACS::Time associated with the answer map */ const ACS::Time getTimestamp() const { std::shared_lock lock(m_mutex); return IRA::CIRATools::UNIXEpoch2ACSTime(this->get("TIMESTAMP")); } /** * This method returns the plain command sent using the socket. Useful for log purposes. * @return a std::string containing the plain command sent using the socket. */ const std::string getPlainCommand() const { std::shared_lock lock(m_mutex); return this->get("PLAIN_COMMAND"); } /** * This method returns the plain answer received from the socket. Useful for log purposes. * @return a std::string containing the plain answer received from the socket. */ const std::string getPlainAnswer() const { std::shared_lock lock(m_mutex); return this->get("PLAIN_ANSWER"); } protected: /** * Shared mutex to control read and write accesses. Multiple reading access are permitted and will only block writing access. Writing access will block all accesses */ mutable std::shared_mutex m_mutex; }; /** * This class is a specialization of the SRTMinorServoAnswerMap for the general Leonardo Minor Servo System status. */ class SRTMinorServoGeneralStatus : public SRTMinorServoAnswerMap { public: /** * Retrieves the current configuration from the map. * @return the current SRTMinorServoFocalConfiguration. */ SRTMinorServoFocalConfiguration getFocalConfiguration() const { return LDOConfigurationIDTable.right.at(this->get("CURRENT_CONFIG")); } /** * Retrieves a boolean indicating whether the simulation is enabled or not. * @returns a boolean indicating whether the simulation is enabled or not. */ Management::TBoolean isSimulationEnabled() const { return this->get("SIMULATION_ENABLED") == 1 ? Management::MNG_TRUE : Management::MNG_FALSE; } /** * Returns the PLC time of the reading. * @return a double indicating the PLC time, expressed as UNIX Epoch. */ double getPLCTime() const { return this->get("PLC_TIME"); } /** * Returns the firmware version present on the PLC. * @return a string containing the firmware version. */ ACE_CString getPLCVersion() const { return this->get("PLC_VERSION").c_str(); } /** * Returns who is controlling the Leonardo Minor Servo System. * @return an enum indicating who is controlling the system. */ SRTMinorServoControlStatus getControl() const { return SRTMinorServoControlStatus(this->get("CONTROL") - 1); } /** * Returns a boolean indicating whether the system is powered on or not. * @return a boolean indicating whether the system is powered on or not. */ Management::TBoolean hasPower() const { return this->get("POWER") == 1 ? Management::MNG_TRUE : Management::MNG_FALSE; } /** * Is the emergency stop pressed somewhere? Is there an emergency situation? * @return a boolean indicating if an emergency is present or not. */ Management::TBoolean emergencyPressed() const { return this->get("EMERGENCY") == 1 ? Management::MNG_TRUE : Management::MNG_FALSE; } /** * Returns the position of the gregorian cover. * @return an enum indicating the position of the gregorian cover. */ SRTMinorServoGregorianCoverStatus getGregorianCoverPosition() const { return SRTMinorServoGregorianCoverStatus(std::min(this->get("GREGORIAN_CAP"), (unsigned int)COVER_STATUS_OPEN)); } /** * Returns the status of the gregorian air blade. * @return an enum indicating the status of the gregorian air blade. */ SRTMinorServoGregorianAirBladeStatus getGregorianAirBladeStatus() const { unsigned int status = this->get("GREGORIAN_CAP"); if(status <= COVER_STATUS_CLOSED) { status = AIR_BLADE_STATUS_OFF; } else { status -= 2; } return SRTMinorServoGregorianAirBladeStatus(status); } /** * Returns the UNIX Epoch of the last executed command. * @return a double containing the UNIX Epoch of the last executed command. */ double getLastExecutedCommand() const { return this->get("LAST_EXECUTED_COMMAND"); } }; /** * This class is a specialization of the SRTMinorServoAnswerMap for a single servo status of the Leonardo Minor Servo System. */ class SRTMinorServoStatus : public SRTMinorServoAnswerMap { public: /** * Constructor. Accepts some lists of labels in order to correctly retrieve the values from the map. * @param servo_name the servo name used as a prefix when retrieving the values. * @param physical_axes_enabled the labels used to retrieve the status of each physical axis. * @param physical_positions the labels used to retrieve the position of each physical axis. * @param virtual_positions the labels used to retrieve the position of each virtual axis. * @param virtual_offsets the labels used to retrieve the offset of each virtual axis. */ SRTMinorServoStatus( const std::string& servo_name, const std::vector& physical_axes_enabled, const std::vector& physical_positions, const std::vector& virtual_positions, const std::vector& virtual_offsets ) : SRTMinorServoAnswerMap(), m_servo_name(servo_name), m_physical_axes_enabled(physical_axes_enabled), m_physical_positions(physical_positions), m_virtual_positions(virtual_positions), m_virtual_offsets(virtual_offsets) {} /** * Returns a boolean indicating whether the servo is enabled. * @returns true if enabled, false otherwise. */ Management::TBoolean isEnabled() const { return this->get(m_servo_name + "_ENABLED") == 1 ? Management::MNG_TRUE : Management::MNG_FALSE; } /** * Returns the status of the servo drive cabinet. * @returns an enum indicating the status of the servo drive cabinet. */ SRTMinorServoCabinetStatus getDriveCabinetStatus() const { return SRTMinorServoCabinetStatus(this->get(m_servo_name + "_STATUS") - 1); } /** * Returns a boolean indicating whether the servo is blocked or not. * @return true if the servo is blocked, false otherwise. */ Management::TBoolean isBlocked() const { return this->get(m_servo_name + "_BLOCK") == 1 ? Management::MNG_TRUE : Management::MNG_FALSE; } /** * Returns the operative mode of the servo. * @return an enum indicating the operative mode of the servo. */ SRTMinorServoOperativeMode getOperativeMode() const { return SRTMinorServoOperativeMode(this->get(m_servo_name + "_OPERATIVE_MODE") / 10); } /** * Returns the status of each physical axis. * @return a boolean sequence, true if the axis is enabled, false otherwise. */ ACS::booleanSeq getPhysicalAxesEnabled() const { std::shared_lock lock(m_mutex); return getSequence(m_physical_axes_enabled); } /** * Returns the position of each physical axis. * @return a double sequence containing the position of each physical axis. */ ACS::doubleSeq getPhysicalPositions() const { std::shared_lock lock(m_mutex); return getSequence(m_physical_positions); } /** * Returns the plain position of each virtual axis. * @return a double sequence containing the plain position of each virtual axis. */ ACS::doubleSeq getPlainVirtualPositions() const { std::shared_lock lock(m_mutex); return getSequence(m_virtual_positions); } /** * Returns the actual position of each virtual axis, minus the offset. * @return a double sequence containing the position of each virtual axis, minus the offset. */ ACS::doubleSeq getVirtualPositions() const { std::shared_lock lock(m_mutex); ACS::doubleSeq virtual_positions = getPlainVirtualPositions(); ACS::doubleSeq virtual_offsets = getVirtualOffsets(); for(size_t i = 0; i < virtual_positions.length(); i++) { virtual_positions[i] -= virtual_offsets[i]; } return virtual_positions; } /** * Returns the offset of each virtual axis. * @return a double sequence containing the offset of each virtual axis. */ ACS::doubleSeq getVirtualOffsets() const { std::shared_lock lock(m_mutex); return getSequence(m_virtual_offsets); } private: /** * This method extracts a sequence, either boolean or double, from the map, and returns it. * @param labels a vector of strings containing the labels to use in order to extract the corresponding values from the map. * @return the composed sequence of booleans or doubles. */ template >> T getSequence(const std::vector& labels) const { T sequence; sequence.length(labels.size()); for(size_t i = 0; i < labels.size(); i++) { if constexpr(std::is_same_v) { sequence[i] = (bool)this->get(m_servo_name + "_" + labels[i]); } else if constexpr(std::is_same_v) { sequence[i] = this->get(m_servo_name + "_" + labels[i]); } } return sequence; } /** * The name of the servo, this is used as prefix when retrieving values from the map. */ const std::string m_servo_name; /** * The labels for the enabled value of each physical axis. */ const std::vector m_physical_axes_enabled; /** * The labels for the positions of each physical axis. */ const std::vector m_physical_positions; /** * The labels for the positions of each virtual axis. */ const std::vector m_virtual_positions; /** * The labels for the offsets of each virtual axis. */ const std::vector m_virtual_offsets; }; } #endif