#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ /* IMPLEMENTATION OF TELEMETRY CLASS */ #include #include #include #include #include "mcc_defaults.h" namespace mcc { static constexpr double mcc_sideral_to_UT1_ratio = 1.002737909350795; // sideral/UT1 enum MccTelemetryErrorCode : int { ERROR_OK, ERROR_NULLPTR, ERROR_COORD_TRANSFORM, ERROR_PCM_COMP, ERROR_HARDWARE_GETPOS, ERROR_UPDATE_STOPPED, ERROR_DATA_TIMEOUT }; } // namespace mcc namespace std { template <> class is_error_code_enum : public true_type { }; } // namespace std namespace mcc { /* error category definition */ // error category struct MccTelemetryCategory : public std::error_category { MccTelemetryCategory() : std::error_category() {} const char* name() const noexcept { return "ALTITUDE-LIMIT-PZ"; } 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_GETPOS: return "cannot get hardware position"; case MccTelemetryErrorCode::ERROR_UPDATE_STOPPED: return "telemetry update was stopped"; case MccTelemetryErrorCode::ERROR_DATA_TIMEOUT: return "a timeout occured while waiting for new data"; 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()); } /* TELEMETRY UPDATE POLICY */ enum class MccTelemetryUpdatePolicy : int { TEMETRY_UPDATE_INNER, TEMETRY_UPDATE_EXTERNAL }; template class MccTelemetry : public mcc_telemetry_interface_t { public: static constexpr MccTelemetryUpdatePolicy updatePolicy = UPDATE_POLICY; typedef std::error_code error_t; MccTelemetry(mcc_ccte_c auto* ccte, mcc_PCM_c auto* pcm, mcc_hardware_c auto* hardware) : _updated(false), _data(), _updateMutex(new std::mutex), _updateCondVar(new std::condition_variable) { _data.target.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS; using ccte_t = std::remove_cvref_t; using pcm_t = std::remove_cvref_t; using hardware_t = std::remove_cvref_t; _updateTargetFunc = [ccte, pcm, this](bool only_hw, std::stop_token stop_token) -> error_t { if (!only_hw) { // // compute apparent coordinates // ICRS coordinates of the taget must be already set // _data.target.time_point = std::chrono::time_point_cast( _data.time_point); _data.target.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS; _data.target.X = _data.target.RA_ICRS; _data.target.Y = _data.target.DEC_ICRS; // update apparent cordinates auto ccte_err = ccte->transformCoordinates(_data.target, &_data.target); if (ccte_err) { return mcc_deduce_error(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); } } if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } // compute hardware coordinates // WARNING: It is assumed here that PCM corrections have small (arcseconds-arcminutes) values // since ususaly there is no reverse transformation for "hardware-to-apparent" relation! typename pcm_t::error_t pcm_err; struct { double dx, dy; } pcm_res; MccCelestialPoint pt; pt.time_point = std::chrono::time_point_cast(_data.target.time_point); if constexpr (mccIsEquatorialMount(pcm_t::mountType)) { pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; pt.X = _data.target.HA; pt.Y = _data.target.DEC_APP; pcm_err = pcm->compute(std::move(pt), &pcm_res); if (!pcm_err) { _data.target.X = _data.target.HA - pcm_res.dx; _data.target.Y = _data.target.DEC_APP - pcm_res.dy; } } else if constexpr (mccIsAltAzMount(pcm_t::mountType)) { pt.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT; pt.X = _data.target.AZ; pt.Y = _data.target.ALT; pcm_err = pcm->compute(std::move(pt), &pcm_res); if (!pcm_err) { _data.target.X = _data.target.AZ - pcm_res.dx; _data.target.Y = _data.target.ALT - pcm_res.dy; } } else { static_assert(false, "UNKNOWN MOUNT TYPE!"); } if (pcm_err) { return mcc_deduce_error(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); } return MccTelemetryErrorCode::ERROR_OK; }; _updateFunc = [ccte, pcm, hardware, this](std::stop_token stop_token) { // first, update mount quantities typename hardware_t::axes_pos_t hw_pos; auto hw_err = hardware->getPos(&hw_pos); if (hw_err) { return mcc_deduce_error(hw_err, MccTelemetryErrorCode::ERROR_HARDWARE_GETPOS); } if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } double eo; _data.time_point = std::chrono::time_point_cast(hw_pos.time_point); auto ccte_err = ccte->timepointToJulday(_data.time_point, &_data.JD); if (!ccte_err) { if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } ccte_err = ccte->juldayToAppSideral(_data.JD, &_data.LST, true); if (!ccte_err) { if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } ccte_err = ccte->equationOrigins(_data.JD, &eo); } } if (ccte_err) { return mcc_deduce_error(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); } if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } _data.speedX = (double)hw_pos.speedX; _data.speedY = (double)hw_pos.speedY; struct { double dx, dy; } pcm_res; auto pcm_err = pcm->computePCM(_data, &pcm_res); if (pcm_err) { return mcc_deduce_error(pcm_err, MccTelemetryErrorCode::ERROR_PCM_COMP); } _data.pcmX = pcm_res.dx; _data.pcmY = pcm_res.dy; if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT, .time_point = _data.time_point}; if constexpr (mccIsEquatorialMount(pcm_t::mountType)) { _data.HA = (double)hw_pos.X + pcm_res.dx; _data.DEC_APP = (double)hw_pos.Y + pcm_res.dy; _data.RA_APP = (double)_data.LST - (double)_data.HA + eo; _data.X = _data.HA; _data.Y = _data.DEC_APP; _data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; ccte_err = ccte->transformCoordinates(_data, &pt); if (!ccte_err) { _data.AZ = pt.X; _data.ALT = pt.Y; _data.ZD = std::numbers::pi / 2.0 - _data.ALT; } } else if constexpr (mccIsAltAzMount(pcm_t::mountType)) { _data.AZ = (double)hw_pos.X + pcm_res.dx; _data.ALT = (double)hw_pos.Y + pcm_res.dy; _data.ZD = std::numbers::pi / 2.0 - _data.ALT; _data.X = _data.AZ; _data.Y = _data.ALT; _data.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT; pt.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; ccte_err = ccte->transformCoordinates(_data, &pt); if (!ccte) { _data.HA = pt.X; _data.DEC_APP = pt.Y; _data.RA_APP = (double)_data.LST - (double)_data.HA + eo; } } else { static_assert(false, "UNKNOWN MOUNT TYPE!"); } if (!ccte_err) { if (stop_token.stop_requested()) { return MccTelemetryErrorCode::ERROR_UPDATE_STOPPED; } _data.pair_kind = MccCoordPairKind::COORDS_KIND_AZZD; _data.X = _data.AZ; _data.Y = _data.ZD; ccte_err = ccte->refractionCorrection(_data, &_data.refCorr); if (!ccte_err) { // restore hardware encoders coordinates _data.X = (double)hw_pos.X; _data.Y = (double)hw_pos.Y; // update target (assuming target ICRS coordinates are already set) auto ret = _updateTargetFunc(false, stop_token); if (ret) { return ret; } } } if (ccte_err) { return mcc_deduce_error(ccte_err, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); } if constexpr (mccIsEquatorialMount(pcm_t::mountType)) { _data.pair_kind = MccCoordPairKind::COORDS_KIND_HADEC_APP; } else if constexpr (mccIsAltAzMount(pcm_t::mountType)) { _data.pair_kind = MccCoordPairKind::COORDS_KIND_AZALT; } else { static_assert(false, "UNKNOWN MOUNT TYPE!"); } _updated = true; return MccTelemetryErrorCode::ERROR_OK; }; _setTargetFunc = [ccte, this](MccCelestialPoint const& pt) { // in the case of apparent input coordinates // one must ensure the same time points _data.target.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS; _data.target.time_point = std::chrono::time_point_cast(pt.time_point); auto ret = ccte->transformCoordinates(pt, &_data.target); if (!ret) { if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { _data.target.RA_ICRS = _data.target.X; _data.target.DEC_ICRS = _data.target.Y; // update apparent coordinates ret = _updateTargetFunc(false, {}); } else { // apparent coordinates were computed above // compute ICRS coordinates MccCelestialPoint cpt{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS}; ret = ccte->transformCoordinates(pt, &cpt); _data.target.RA_ICRS = cpt.X; _data.target.DEC_ICRS = cpt.Y; // compute only hardware coordinates ret = _updateTargetFunc(true, {}); } } return mcc_deduce_error(ret, MccTelemetryErrorCode::ERROR_COORD_TRANSFORM); }; } virtual ~MccTelemetry() = default; template DT telemetryDataUpdateInterval() const { return std::chrono::duration_cast
(_currentUpdateInterval); } std::chrono::milliseconds telemetryDataUpdateInterval() const { return telemetryDataUpdateInterval(); } error_t updateTelemetryData(traits::mcc_time_duration_c auto const& period) { // using d_t = typename std::remove_cvref_t::rep; // bool is_zero; // if (std::floating_point) { // is_zero = utils::isEqual(period.count(), d_t::zero()); // } else { // is_zero = period.count() == d_t::zero(); // } // if (is_zero) { // just update once // return _updateFunc(); // } else { // // try to update once // auto ret = _updateFunc(); // if (ret) { // return ret; // } // _lastUpdateError = MccTelemetryErrorCode::ERROR_OK; // _timerThread = [period, this](std::stop_token st) { // while (!st.stop_requested()) { // { // std::lock_guard thread_lock{*_updateMutex}; // _lastUpdateError = _updateFunc(); // _updateCondVar->notify_all(); // if (_lastUpdateError) { // return; // } // } // std::this_thread::sleep_for(period); // } // }; // _timerThread.detach(); // return MccTelemetryErrorCode::ERROR_OK; // } std::lock_guard thread_lock{*_updateMutex}; std::stop_source stop_source; std::future update_ft = std::async(std::launch::async, _updateFunc, stop_source.get_token()); auto status = update_ft.wait_for(period); if (status != std::future_status::ready) { auto ok = stop_source.stop_requested(); return MccTelemetryErrorCode::ERROR_DATA_TIMEOUT; } _updateCondVar->notify_all(); return update_ft.get(); } // block the thread and wait for data to be ready (external synchronization) error_t waitForTelemetryData(mcc_telemetry_data_c auto* tdata, traits::mcc_time_duration_c auto const& timeout) { if (tdata == nullptr) { return MccTelemetryErrorCode::ERROR_NULLPTR; } std::unique_lock ulock(*_updateMutex); auto res = _updateCondVar->wait_for(ulock, timeout, [this]() { return _updated; }); if (res == std::cv_status::timeout) { return MccTelemetryErrorCode::ERROR_DATA_TIMEOUT; } std::lock_guard thread_lock{*_updateMutex}; if (!_lastUpdateError) { mcc_copy_telemetry_data(_data, tdata); } return _lastUpdateError; } // update and get data as soon as possible error_t telemetryData(mcc_telemetry_data_c auto* tdata) { if (tdata == nullptr) { return MccTelemetryErrorCode::ERROR_NULLPTR; } std::lock_guard thread_lock{*_updateMutex}; // error_t ret = _updateFunc(); // if (!ret) { // mcc_copy_telemetry_data(_data, tdata); // } // return ret; mcc_copy_telemetry_data(_data, tdata); return MccTelemetryErrorCode::ERROR_OK; } error_t setPointingTarget(mcc_celestial_point_c auto pt) { std::lock_guard lock{*_updateMutex}; return _setTargetFunc(pt); } protected: std::atomic_bool _updated; MccTelemetryData _data; std::chrono::nanoseconds _currentUpdateInterval{std::chrono::milliseconds(100)}; std ::function _updateTargetFunc{}; std::function _updateFunc{}; std::function _setTargetFunc{}; std::unique_ptr _updateMutex; std::unique_ptr _updateCondVar; error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK}; std::jthread _timerThread; }; } // namespace mcc