This commit is contained in:
Timur A. Fatkhullin
2026-02-16 01:33:06 +03:00
parent 66e5f37c74
commit 82f1477edd
3 changed files with 575 additions and 9 deletions

View File

@@ -803,7 +803,7 @@ struct mcc_telemetry_interface_t {
// get entered target position // get entered target position
template <std::derived_from<mcc_telemetry_interface_t> SelfT> template <std::derived_from<mcc_telemetry_interface_t> SelfT>
RetT setTarget(this SelfT&& self, mcc_skypoint_c auto* pt) RetT getTarget(this SelfT&& self, mcc_skypoint_c auto* pt)
{ {
return std::forward<SelfT>(self).getTarget(pt); return std::forward<SelfT>(self).getTarget(pt);
} }
@@ -927,9 +927,14 @@ struct mcc_pzone_container_interface_t {
template <typename T> template <typename T>
concept mcc_pzone_container_c = std::derived_from<T, mcc_pzone_container_interface_t<typename T::error_t>> && requires { concept mcc_pzone_container_c =
std::derived_from<T, mcc_pzone_container_interface_t<typename T::error_t>> && requires(const T t_const) {
// error type // error type
requires mcc_error_c<typename T::error_t>; requires mcc_error_c<typename T::error_t>;
[]<std::ranges::range R>(R const&) {
return requires { requires std::formattable<std::ranges::range_value_t<R>, char>; };
}(t_const.pzoneNames());
}; };

View File

@@ -0,0 +1,550 @@
#pragma once
/****************************************************************************************
* *
* MOUNT CONTROL COMPONENTS LIBRARY *
* *
* *
* IMPLEMENTATION OF VERY SIMPLE MOUNT MOVEMENT CONTROLS CLASS *
* *
****************************************************************************************/
#include <atomic>
#include <fstream>
#include "mcc/mcc_coordinate.h"
#include "mcc_concepts.h"
#include "mcc_error.h"
namespace mcc::impl
{
namespace details
{
// just auxiliary class instance
static MccNullLogger NullLogger{};
} // namespace details
enum class MccSimpleMovementControlsErrorCode : int {
ERROR_OK,
ERROR_HW_GETSTATE,
ERROR_HW_SETSTATE,
ERROR_PCM_COMP,
ERROR_CCTE_COMP,
ERROR_GET_TELEMETRY,
ERROR_DIST_TELEMETRY,
ERROR_PZONE_CONTAINER_COMP,
ERROR_TARGET_IN_PZONE,
ERROR_NEAR_PZONE,
ERROR_TIMEOUT,
ERROR_ALREADY_SLEW,
ERROR_ALREADY_STOPPED,
ERROR_STOPPED
};
} // namespace mcc::impl
namespace std
{
template <>
class is_error_code_enum<mcc::impl::MccSimpleMovementControlsErrorCode> : public true_type
{
};
} // namespace std
namespace mcc::impl
{
// error category
struct MccSimpleMovementControlsCategory : public std::error_category {
MccSimpleMovementControlsCategory() : std::error_category() {}
const char* name() const noexcept
{
return "SIMPLE-SLEWING-MODEL";
}
std::string message(int ec) const
{
MccSimpleMovementControlsErrorCode err = static_cast<MccSimpleMovementControlsErrorCode>(ec);
switch (err) {
case MccSimpleMovementControlsErrorCode::ERROR_OK:
return "OK";
case MccSimpleMovementControlsErrorCode::ERROR_HW_GETSTATE:
return "cannot get hardware state";
case MccSimpleMovementControlsErrorCode::ERROR_HW_SETSTATE:
return "cannot set hardware state";
case MccSimpleMovementControlsErrorCode::ERROR_PCM_COMP:
return "PCM computation error";
case MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP:
return "celestial coordinate transformation error";
case MccSimpleMovementControlsErrorCode::ERROR_GET_TELEMETRY:
return "cannot get telemetry";
case MccSimpleMovementControlsErrorCode::ERROR_DIST_TELEMETRY:
return "cannot get target-to-mount-position distance";
case MccSimpleMovementControlsErrorCode::ERROR_PZONE_CONTAINER_COMP:
return "pzone container computation error";
case MccSimpleMovementControlsErrorCode::ERROR_TARGET_IN_PZONE:
return "target is in prohibited zone";
case MccSimpleMovementControlsErrorCode::ERROR_NEAR_PZONE:
return "near prohibited zone";
case MccSimpleMovementControlsErrorCode::ERROR_TIMEOUT:
return "a timeout occured while slewing";
case MccSimpleMovementControlsErrorCode::ERROR_ALREADY_SLEW:
return "already slewing";
case MccSimpleMovementControlsErrorCode::ERROR_ALREADY_STOPPED:
return "slewing is already stopped";
case MccSimpleMovementControlsErrorCode::ERROR_STOPPED:
return "slewing was stopped";
default:
return "UNKNOWN";
}
}
static const MccSimpleMovementControlsCategory& get()
{
static const MccSimpleMovementControlsCategory constInst;
return constInst;
}
};
inline std::error_code make_error_code(MccSimpleMovementControlsErrorCode ec)
{
return std::error_code(static_cast<int>(ec), MccSimpleMovementControlsCategory::get());
}
struct MccSimpleMovementControlsParameters {
// ******* common for all modes *******
// mean celestial rate
static constexpr double sideralRate = 15.0410686_arcsecs; // in radians per second
// timeout to telemetry updating
std::chrono::milliseconds telemetryTimeout{3000};
// minimal time to prohibited zone (at current speed in slewing mode). if it is lesser then exit with error
std::chrono::seconds minTimeToPZone{10};
// time interval to update prohibited zones related quantities (e.g. intersection points)
std::chrono::milliseconds updatingPZoneInterval{5000};
// ******* slewing mode *******
bool slewAndStop{false}; // slew to target and stop mount
// coordinates difference to stop slewing (in radians)
double slewToleranceRadius{5.0_arcsecs};
// telemetry request interval
std::chrono::milliseconds slewingTelemetryInterval{100};
// target-mount coordinate difference to start adjusting of slewing (in radians)
double adjustCoordDiff{slewToleranceRadius * 10.0};
// slew process timeout
std::chrono::seconds slewTimeout{3600};
double slewRateX{0.0}; // maximal slewing rate (0 means move with maximal allowed rate????!!!!!)
double slewRateY{0.0}; // maximal slewing rate (0 means move with maximal allowed rate????!!!!!)
std::chrono::milliseconds adjustCycleInterval{500}; // minimum time between two successive adjustments
double adjustRateX{5.0_arcmins}; // maximal adjusting rate (a rate at the final slewing stage)
double adjustRateY{5.0_arcmins}; // maximal adjusting rate (a rate at the final slewing stage)
// braking acceleration after execution of mount stopping command (in rads/s^2)
// it must be given as non-negative value!!!
double brakingAccelX{0.0};
double brakingAccelY{0.0};
// slewing trajectory file. if empty - just skip saving
std::string slewingPathFilename{};
// ******* tracking mode *******
// telemetry request interval
std::chrono::milliseconds trackingTelemetryInterval{100};
double trackSpeedX{};
double trackSpeedY{};
std::chrono::milliseconds trackingCycleInterval{500}; // minimum time between two successive tracking corrections
bool dualAxisTracking{true}; // mount must be of an equatorial type: false means guiding along only HA-axis
// time shift into future to compute target position in future (UT1-scale time duration)
std::chrono::milliseconds timeShiftToTargetPoint{10000};
// maximal target-to-mount difference for tracking process (in arcsecs)
// it it is greater then the current mount coordinates are used as target one
double trackingMaxCoordDiff{20.0};
// tracking trajectory file. if empty - just skip saving
std::string trackingPathFilename{};
};
class MccSimpleMovementControls
{
protected:
// a simple class for movement path saving
class PathFile
{
public:
PathFile(const std::string& filename = "") : _filename(filename), _st() {}
void setFilename(const std::string& filename)
{
_filename = filename;
}
std::string getFilename() const
{
return _filename;
}
~PathFile()
{
save();
}
friend PathFile& operator<<(PathFile& pf, auto&& v)
{
pf._st << std::forward<decltype(v)>(v);
return pf;
}
bool save()
{
std::fstream fst;
if (_filename.empty()) {
return false;
}
if (_st.str().empty()) { // nothing to save
return true;
}
fst.open(_filename);
if (!fst.is_open()) {
return false;
}
fst << _st.str();
_st.str("");
_filename.clear();
return true;
}
private:
std::string _filename;
std::istringstream _st;
};
public:
typedef MccError error_t;
typedef MccSimpleMovementControlsParameters movement_params_t;
template <mcc_hardware_c HARDWARE_T,
mcc_telemetry_c TELEMETRY_T,
mcc_pzone_container_c PZONE_CONT_T,
mcc_mount_status_c STATUS_T,
std::invocable<STATUS_T const&> CallbackFuncT = decltype([](STATUS_T const&) {}),
mcc_logger_c LOGGER_T = MccNullLogger>
MccSimpleMovementControls(
HARDWARE_T* hardware,
TELEMETRY_T* telemtry,
PZONE_CONT_T* pzone_cont,
CallbackFuncT&& mode_switch_callback = [](STATUS_T const&) {},
LOGGER_T& logger = details::NullLogger)
{
auto log_ptr = &logger;
auto send_to_hardware = [hardware, log_ptr](typename HARDWARE_T::hardware_state_t const& hw_state) -> error_t {
if constexpr (std::derived_from<decltype(hw_state.XY), MccCoordPair<MccAngleX, MccAngleY>>) {
log_ptr->logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs", hw_state.XY.x().degrees(),
hw_state.XY.y().degrees()));
} else { // user defined mcc_coord_pair_c
log_ptr->logDebug(std::format("Send to hardware: X = {} degs, Y = {} degs",
MccAngle((double)hw_state.XY.x()).degrees(),
MccAngle((double)hw_state.XY.y()).degrees()));
}
auto hw_err = hardware->hardwareSetState(hw_state);
if (hw_err) {
mcc_deduced_err(hw_err, MccSimpleMovementControlsErrorCode::ERROR_HW_SETSTATE);
}
log_ptr->logDebug(" the 'hardwareSetState' method performed successfully!");
return MccSimpleMovementControlsErrorCode::ERROR_OK;
};
auto check_pzones = [pzone_cont, log_ptr](typename TELEMETRY_T::telemetry_data_t const& tdata,
double min_time_to_pzone_in_secs, double braking_accelX,
double braking_accelY) {
bool in_zone;
std::vector<bool> in_zone_vec;
// calculate the distances along the X and Y axes that the mount will travel at the current speed in a given
// time, taking into account the braking acceleration
double speedX = tdata.hwState.speedXY.x();
double speedY = tdata.hwState.speedXY.y();
// time to stop mount with given current speed and constant braking acceleration
double tx_stop = std::abs(speedX) / braking_accelX;
double ty_stop = std::abs(speedY) / braking_accelY;
double tx = min_time_to_pzone_in_secs;
double ty = min_time_to_pzone_in_secs;
if (std::isfinite(tx_stop) && (min_time_to_pzone_in_secs > tx_stop)) {
tx = tx_stop;
}
if (std::isfinite(ty_stop) && (min_time_to_pzone_in_secs > ty_stop)) {
ty = ty_stop;
}
// the distance:
// here, one must take into account the sign of the speed!!!
double dx = speedX * tx - std::copysign(braking_accelX, speedX) * tx * tx / 2.0;
double dy = speedY * ty - std::copysign(braking_accelY, speedY) * ty * ty / 2.0;
log_ptr->logTrace(
std::format(" the distance that will be covered in the next {} seconds: X-axis: {}, Y-axis: {}",
min_time_to_pzone_in_secs, MccAngleFancyString(dx), MccAngleFancyString(dy)));
// calculate coordinates at current speed '_currentParams.minTimeToPZone' seconds ahead
// and check them for getting into the prohibited zones
std::conditional_t<mccIsEquatorialMount(HARDWARE_T::mountType), MccSkyHADEC_OBS,
std::conditional_t<mccIsAltAzMount(HARDWARE_T::mountType), MccSkyAZZD, std::nullptr_t>>
mount_pos;
static_assert(!std::is_null_pointer_v<decltype(mount_pos)>, "UNKNOWn MOUNT TYPE");
mount_pos.setX(tdata.hwState.XY.x() + tdata.pcmCorrection.x + dx);
mount_pos.setY(tdata.hwState.XY.y() + tdata.pcmCorrection.y + dy);
mount_pos.setEpoch(tdata.hwState.XY.epoch());
log_ptr->logTrace(std::format(" mount: speedX = {}/s, speedY = {}/s", MccAngleFancyString(speedX),
MccAngleFancyString(speedY)));
auto pz_err = pzone_cont->inPZone(mount_pos, &in_zone, &in_zone_vec);
if (pz_err) {
return mcc_deduced_err(pz_err, MccSimpleMovementControlsErrorCode::ERROR_PZONE_CONTAINER_COMP);
}
if (in_zone) {
size_t i = 0;
for (; i < in_zone_vec.size(); ++i) {
if (in_zone_vec[i]) {
break;
}
}
auto names = pzone_cont->pzoneNames();
auto it = names.begin();
std::ranges::advance(it, i);
log_ptr->logError(
"target point is near prohibited zone (zone index: {}, zone name: {})! Current mount position:", i,
*it);
MccSkyHADEC_OBS hadec;
MccSkyRADEC_OBS radec;
MccSkyAZZD azzd;
MccSkyAZALT azalt;
MccAngle lst;
auto ccte_err = tdata.mountPos.appSideralTime(&lst, true);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP);
}
ccte_err = tdata.mountPos.toAtSameEpoch(radec, hadec, azzd, azalt);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP);
}
log_ptr->logError(std::format(" RA-APP, DEC-APP, HA, LST: {}, {}, {}, {}",
MccAngle{radec.x()}.sexagesimal(true), MccAngle{radec.y()}.sexagesimal(),
MccAngle{hadec.x()}.sexagesimal(true), lst.sexagesimal(true)));
log_ptr->logError(std::format(" AZ, ZD, ALT: {}, {}, {}", MccAngle{azzd.x()}.sexagesimal(),
MccAngle{azzd.y()}.sexagesimal(), MccAngle{azalt.y()}.sexagesimal()));
log_ptr->logError(std::format(" hardware X, Y: {}, {}", MccAngle{tdata.hwState.XY.x()}.sexagesimal(),
MccAngle{tdata.hwState.XY.y()}.sexagesimal()));
return MccSimpleMovementControlsErrorCode::ERROR_NEAR_PZONE;
}
return MccSimpleMovementControlsErrorCode::ERROR_OK;
};
auto log_pos = [log_ptr, this](typename TELEMETRY_T::telemetry_data_t const& tdata) {
double x_mnt, y_mnt, x_tag, y_tag;
if constexpr (mccIsEquatorialMount(HARDWARE_T::mountType)) {
MccSkyHADEC_OBS hadec;
auto ccte_err = tdata.targetPos.toAtSameEpoch(hadec);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP);
}
x_tag = hadec.x();
y_tag = hadec.y();
log_ptr->logTrace(std::format(" current target: HA = {}, DEC = {}", hadec.x().sexagesimal(true),
hadec.y().sexagesimal()));
ccte_err = tdata.mountPos.toAtSameEpoch(hadec);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP);
}
x_mnt = hadec.x();
y_mnt = hadec.y();
log_ptr->logTrace(std::format(" current mount: HA = {}, DEC = {}", hadec.x().sexagesimal(true),
hadec.y().sexagesimal()));
} else if constexpr (mccIsAltAzMount(HARDWARE_T::mountType)) {
MccSkyAZZD azzd;
auto ccte_err = tdata.targetPos.toAtSameEpoch(azzd);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP);
}
x_tag = azzd.x();
y_tag = azzd.y();
log_ptr->logTrace(
std::format(" target: AZ = {}, ZD = {}", azzd.x().sexagesimal(), azzd.y().sexagesimal()));
ccte_err = tdata.mountPos.toAtSameEpoch(azzd);
if (ccte_err) {
return mcc_deduced_err(ccte_err, MccSimpleMovementControlsErrorCode::ERROR_CCTE_COMP);
}
x_mnt = azzd.x();
y_mnt = azzd.y();
log_ptr->logTrace(
std::format(" mount: AZ = {}, ZD = {}", azzd.x().sexagesimal(), azzd.y().sexagesimal()));
}
_pathFile << tdata.mountPos.epoch().UTC.time_since_epoch().count() << " " << x_tag << " " << y_tag << " "
<< x_mnt << " " << y_mnt << " " << (x_tag - x_mnt) << " " << (y_tag - y_mnt) << " "
<< (int)tdata.hwState.movementState << "\n";
};
auto cb_sptr =
std::make_shared<std::function<void(STATUS_T const&)>>(std::forward<CallbackFuncT>(mode_switch_callback));
/* stop moving function */
_stopMovingFunc = [hardware, cb_sptr, this]() {
typename HARDWARE_T::hardware_state_t hw_state;
hw_state.movementState == HARDWARE_T::hardware_movement_state_t::HW_MOVE_STOPPING;
*_stopMoving = true;
*_lastError = send_to_hardware(hw_state);
if (_lastError->load()) {
(*cb_sptr)(STATUS_T::MOUNT_STATUS_ERROR);
}
};
}
error_t slewToTarget(bool slew_and_stop = false)
{
_slewingFunc(slew_and_stop);
return *_lastError;
}
error_t trackTarget()
{
_trackingFunc();
return *_lastError;
}
error_t stopMount()
{
if (*_stopMoving) {
*_lastError = MccSimpleMovementControlsErrorCode::ERROR_ALREADY_STOPPED;
} else {
_stopMovingFunc();
}
return *_lastError;
}
error_t setMovementParams(movement_params_t const& params)
{
std::lock_guard lock{*_currentParamsMutex};
_currentParams = params;
return MccSimpleMovementControlsErrorCode::ERROR_OK;
}
movement_params_t getMovementParams() const
{
std::lock_guard lock{*_currentParamsMutex};
return _currentParams;
}
error_t mountMovementLastError() const
{
return _lastError->load();
}
protected:
std::function<void(bool)> _slewingFunc{};
std::function<void()> _trackingFunc{};
std::function<void()> _stopMovingFunc{};
std::unique_ptr<std::atomic_bool> _stopMoving{new std::atomic_bool{false}};
std::unique_ptr<std::mutex> _currentParamsMutex{new std::mutex{}};
movement_params_t _currentParams{};
std::unique_ptr<std::atomic<error_t>> _lastError{
new std::atomic<error_t>{MccSimpleMovementControlsErrorCode::ERROR_OK}};
PathFile _pathFile{};
};
} // namespace mcc::impl

View File

@@ -144,6 +144,8 @@ public:
return MccPZoneContainerErrorCode::ERROR_OK; return MccPZoneContainerErrorCode::ERROR_OK;
}); });
_names.push_back(std::format("{}", zone.pzoneName));
return _inZoneFunc.size(); return _inZoneFunc.size();
} }
@@ -152,9 +154,16 @@ public:
_inZoneFunc.clear(); _inZoneFunc.clear();
_timeToZoneFunc.clear(); _timeToZoneFunc.clear();
_timeFromZoneFunc.clear(); _timeFromZoneFunc.clear();
_names.clear();
} }
std::vector<std::string> pzoneNames() const
{
return _names;
}
error_t inPZone(mcc_skypoint_c auto const& coords, bool* at_least_one, std::ranges::output_range<bool> auto* result) error_t inPZone(mcc_skypoint_c auto const& coords, bool* at_least_one, std::ranges::output_range<bool> auto* result)
{ {
auto err = forEach(_inZoneFunc, coords, result); auto err = forEach(_inZoneFunc, coords, result);
@@ -188,9 +197,11 @@ public:
protected: protected:
typedef std::chrono::nanoseconds duration_t; typedef std::chrono::nanoseconds duration_t;
std::vector<std::function<error_t(MccSkyPoint const& pt, bool*)>> _inZoneFunc; std::vector<std::function<error_t(MccSkyPoint const& pt, bool*)>> _inZoneFunc{};
std::vector<std::function<error_t(MccSkyPoint const& pt, duration_t*)>> _timeToZoneFunc; std::vector<std::function<error_t(MccSkyPoint const& pt, duration_t*)>> _timeToZoneFunc{};
std::vector<std::function<error_t(MccSkyPoint const& pt, duration_t*)>> _timeFromZoneFunc; std::vector<std::function<error_t(MccSkyPoint const& pt, duration_t*)>> _timeFromZoneFunc{};
std::vector<std::string> _names{};
error_t forEach(auto& func_cont, MccSkyPoint const& pt, auto* result) error_t forEach(auto& func_cont, MccSkyPoint const& pt, auto* result)
{ {
@@ -205,7 +216,7 @@ protected:
res_elem_t res_elem; res_elem_t res_elem;
size_t res_sz = std::ranges::size(*result); // size_t res_sz = std::ranges::size(*result);
auto it = result->begin(); auto it = result->begin();
for (auto& func : func_cont) { for (auto& func : func_cont) {