#pragma once /**************************************************************************************** * * * MOUNT CONTROL COMPONENTS LIBRARY * * * * * * IMPLEMENTATION OF MOUNT TELEMETRY CLASS * * * ****************************************************************************************/ #include #include "mcc_concepts.h" #include "mcc_coordinate.h" namespace mcc::impl { enum class MccTelemetryErrorCode : int { ERROR_OK, ERROR_NULLPTR, ERROR_COORD_TRANSFORM, ERROR_PCM_COMP, ERROR_HARDWARE_GETSTATE, ERROR_UPDATE_STOPPED, ERROR_DATA_TIMEOUT, ERROR_UNSUPPORTED_COORD_PAIR, ERROR_UPDATE_LOOP_WAIT }; } // namespace mcc::impl namespace std { template <> class is_error_code_enum : public true_type { }; } // namespace std namespace mcc::impl { // error category struct MccTelemetryCategory : public std::error_category { MccTelemetryCategory() : std::error_category() {} const char* name() const noexcept { return "MCC-TELEMETRY"; } std::string message(int ec) const { MccTelemetryErrorCode err = static_cast(ec); switch (err) { case MccTelemetryErrorCode::ERROR_OK: return "OK"; case MccTelemetryErrorCode::ERROR_NULLPTR: return "nullptr input argument"; case MccTelemetryErrorCode::ERROR_COORD_TRANSFORM: return "coordinate transformation error"; case MccTelemetryErrorCode::ERROR_PCM_COMP: return "PCM computation error"; case MccTelemetryErrorCode::ERROR_HARDWARE_GETSTATE: return "cannot get hardware state"; case MccTelemetryErrorCode::ERROR_UPDATE_STOPPED: return "telemetry update was stopped"; case MccTelemetryErrorCode::ERROR_DATA_TIMEOUT: return "a timeout occured while waiting for new data"; case MccTelemetryErrorCode::ERROR_UNSUPPORTED_COORD_PAIR: return "unsupported coordinate pair"; case MccTelemetryErrorCode::ERROR_UPDATE_LOOP_WAIT: return "a timeout occured while waiting to exit the update loop"; default: return "UNKNOWN"; } } static const MccTelemetryCategory& get() { static const MccTelemetryCategory constInst; return constInst; } }; inline std::error_code make_error_code(MccTelemetryErrorCode ec) { return std::error_code(static_cast(ec), MccTelemetryCategory::get()); } template class MccTelemetry { public: typedef HARDWARE_T hardware_t; typedef std::error_code error_t; struct telemetry_data_t { MccSkyPoint targetPos{}; MccSkyPoint mountPos{}; typename HARDWARE_T::hardware_state_t hwState{}; struct { double pcmX{}, pcmY{}; } pcmCorrection{}; }; static constexpr std::chrono::milliseconds _defaultTelemetryDataTimeout{500}; /* MccTelemetry(HARDWARE_T* hardware, mcc_pcm_c auto* pcm) : _hardware(hardware) { _updateFunc = [pcm, this](telemetry_data_t* tdata) { if (tdata == nullptr) { return MccTelemetryErrorCode::ERROR_NULLPTR; } // get hardware state (coordinates, speeds and so on) auto hw_err = _hardware->hardwareGetState(&(tdata->hwState)); if (hw_err) { return mcc_deduced_err(hw_err, MccTelemetryErrorCode::ERROR_HARDWARE_GETSTATE); } // compute PCM corrections and observed (corrected for PCM) mount coordinates auto pcm_err = pcm->computePCM(tdata->hwState.XY, &(tdata->pcmCorrection), &(tdata->mountPos)); if (pcm_err) { return mcc_deduced_err(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); } return MccTelemetryErrorCode::ERROR_OK; }; _updatingStopSource = std::stop_source{}; _dataUpdatingRequested->clear(); _dataUpdatingStart->clear(); // start thread for data updating _updatingFuture = std::async( std::launch::async, [this](std::stop_token stoken, telemetry_data_t* tdata) { while (!stoken.stop_requested()) { _dataUpdatingRequested->wait(false); if (!stoken.stop_requested()) { std::lock_guard lock{*_timeoutMutex}; _dataUpdatingStart->test_and_set(); _dataUpdatingStart->notify_all(); _lastUpdateError = _updateFunc(tdata); _dataUpdatingStart->clear(); _dataUpdatingRequested->clear(); } } }, _updatingStopSource.get_token(), _tdataPtr); } */ MccTelemetry(HARDWARE_T* hardware, mcc_pcm_c auto* pcm) { _updatingStopSource = std::stop_source{}; _dataUpdatingRequested->clear(); _dataUpdatingStart->clear(); // start thread for data updating _updatingFuture = std::async( std::launch::async, [this](std::stop_token stoken, HARDWARE_T* hw, auto* pcm_ptr) { while (!stoken.stop_requested()) { _dataUpdatingRequested->wait(false); if (!stoken.stop_requested()) { std::lock_guard lock{*_timeoutMutex}; _dataUpdatingStart->test_and_set(); _dataUpdatingStart->notify_all(); if (_tdataPtr != nullptr && hw && pcm_ptr) { _lastUpdateError = MccTelemetryErrorCode::ERROR_OK; // get hardware state (coordinates, speeds and so on) auto hw_err = hw->hardwareGetState(&_tdataPtr->hwState); if (hw_err) { _lastUpdateError = mcc_deduced_err(hw_err, MccTelemetryErrorCode::ERROR_HARDWARE_GETSTATE); } else { // compute PCM corrections and observed (corrected for PCM) mount coordinates auto pcm_err = pcm_ptr->computePCM(_tdataPtr->hwState.XY, &_tdataPtr->pcmCorrection, &_tdataPtr->mountPos); if (!pcm_err) { // set target coordinates if (_enteredTargetPos.pairKind() == MccCoordPairKind::COORDS_KIND_XY) { // hardware coordinates MccGenXY xy; auto ccte_err = _enteredTargetPos.toAtSameEpoch(xy); if (!ccte_err) { decltype(_tdataPtr->pcmCorrection) pcm_corr; pcm_err = pcm_ptr->computePCM(xy, &pcm_corr, &_tdataPtr->targetPos); if (pcm_err) { _lastUpdateError = mcc_deduced_err(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); } } else { _lastUpdateError = mcc_deduced_err(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); } } else { // observed, apparent or ICRS _tdataPtr->targetPos = _enteredTargetPos; } } else { _lastUpdateError = mcc_deduced_err(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); } } } else { _lastUpdateError = MccTelemetryErrorCode::ERROR_NULLPTR; } _dataUpdatingStart->clear(); _dataUpdatingRequested->clear(); } } }, _updatingStopSource.get_token(), hardware, pcm); } MccTelemetry(MccTelemetry&&) = default; MccTelemetry& operator=(MccTelemetry&&) = default; MccTelemetry(const MccTelemetry&) = delete; MccTelemetry& operator=(const MccTelemetry&) = delete; ~MccTelemetry() { _tdataPtr = nullptr; _updatingStopSource.request_stop(); // trigger updating and exit from the inner loop of the hardware polling thread (see constructor) _dataUpdatingRequested->test_and_set(); // _dataUpdatingRequested->notify_one(); _dataUpdatingRequested->notify_all(); if (_updatingFuture.valid()) { // try to exit correctly _updatingFuture.wait_for(std::chrono::seconds(1)); } } error_t setTarget(mcc_skypoint_c auto const& sp) { _enteredTargetPos = sp; return MccTelemetryErrorCode::ERROR_OK; } // // blocks the current thread until telemetry data is received. // the maximum blocking time is equal to the set timeout (see setTelemetryDataTimeout method) // error_t telemetryData(telemetry_data_t* tdata) { std::lock_guard lock{*_updateMutex}; _lastUpdateError = MccTelemetryErrorCode::ERROR_OK; _tdataPtr = tdata; // trigger updating _dataUpdatingRequested->test_and_set(); _dataUpdatingRequested->notify_one(); // wait for updating start _dataUpdatingStart->wait(false); if (_timeoutMutex->try_lock_for(_telemetryDataTimeout->load())) { _timeoutMutex->unlock(); } else { _lastUpdateError = MccTelemetryErrorCode::ERROR_DATA_TIMEOUT; } return _lastUpdateError; } // // Set a timeout for the telemetry receiving process // void setTelemetryDataTimeout(traits::mcc_time_duration_c auto const& timeout) { if constexpr (std::floating_point) { if (utils::isEqual(timeout.count(), 0.0) || timeout.count() < 0.0) { return; } } else { if (timeout.count() <= 0.0) { return; } } *_telemetryDataTimeout = std::chrono::duration_cast(timeout); } template DT getTelemetryDataTimeout() const { return std::chrono::duration_cast
(_telemetryDataTimeout->load()); } std::chrono::milliseconds getTelemetryDataTimeout() const { return getTelemetryDataTimeout(); } private: // HARDWARE_T* _hardware{nullptr}; MccSkyPoint _enteredTargetPos{}; // std::function _updateFunc{}; telemetry_data_t* _tdataPtr; std::unique_ptr _dataUpdatingRequested{new std::atomic_flag{}}; std::unique_ptr _dataUpdatingStart{new std::atomic_flag{}}; std::unique_ptr _timeoutMutex{new std::timed_mutex()}; std::stop_source _updatingStopSource{}; error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK}; std::unique_ptr> _telemetryDataTimeout{ new std::atomic{_defaultTelemetryDataTimeout}}; std::unique_ptr _updateMutex{new std::mutex{}}; std::future _updatingFuture{}; }; } // namespace mcc::impl