370 lines
12 KiB
C++
370 lines
12 KiB
C++
#pragma once
|
|
|
|
|
|
|
|
/****************************************************************************************
|
|
* *
|
|
* MOUNT CONTROL COMPONENTS LIBRARY *
|
|
* *
|
|
* *
|
|
* IMPLEMENTATION OF MOUNT TELEMETRY CLASS *
|
|
* *
|
|
****************************************************************************************/
|
|
|
|
|
|
#include <future>
|
|
#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<mcc::impl::MccTelemetryErrorCode> : 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<MccTelemetryErrorCode>(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<int>(ec), MccTelemetryCategory::get());
|
|
}
|
|
|
|
|
|
|
|
template <mcc_hardware_c HARDWARE_T>
|
|
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<typename decltype(timeout)::rep>) {
|
|
if (utils::isEqual(timeout.count(), 0.0) || timeout.count() < 0.0) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (timeout.count() <= 0.0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
*_telemetryDataTimeout = std::chrono::duration_cast<std::chrono::nanoseconds>(timeout);
|
|
}
|
|
|
|
template <traits::mcc_time_duration_c DT>
|
|
DT getTelemetryDataTimeout() const
|
|
{
|
|
return std::chrono::duration_cast<DT>(_telemetryDataTimeout->load());
|
|
}
|
|
|
|
std::chrono::milliseconds getTelemetryDataTimeout() const
|
|
{
|
|
return getTelemetryDataTimeout<std::chrono::milliseconds>();
|
|
}
|
|
|
|
private:
|
|
// HARDWARE_T* _hardware{nullptr};
|
|
MccSkyPoint _enteredTargetPos{};
|
|
|
|
// std::function<error_t(telemetry_data_t*)> _updateFunc{};
|
|
telemetry_data_t* _tdataPtr;
|
|
|
|
std::unique_ptr<std::atomic_flag> _dataUpdatingRequested{new std::atomic_flag{}};
|
|
std::unique_ptr<std::atomic_flag> _dataUpdatingStart{new std::atomic_flag{}};
|
|
std::unique_ptr<std::timed_mutex> _timeoutMutex{new std::timed_mutex()};
|
|
|
|
std::stop_source _updatingStopSource{};
|
|
|
|
error_t _lastUpdateError{MccTelemetryErrorCode::ERROR_OK};
|
|
|
|
std::unique_ptr<std::atomic<std::chrono::nanoseconds>> _telemetryDataTimeout{
|
|
new std::atomic<std::chrono::nanoseconds>{_defaultTelemetryDataTimeout}};
|
|
|
|
std::unique_ptr<std::mutex> _updateMutex{new std::mutex{}};
|
|
|
|
std::future<void> _updatingFuture{};
|
|
};
|
|
|
|
|
|
} // namespace mcc::impl
|