#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ #include #include "mcc_finite_state_machine.h" #include "mcc_mount_coord.h" #include "mcc_traits.h" /* SOME LIBRARY-WIDE DECLARATIONS */ namespace mcc { // mount construction type (only the most common ones) enum class MccMountType : uint8_t { GERMAN_TYPE, FORK_TYPE, CROSSAXIS_TYPE, ALTAZ_TYPE }; template static constexpr std::string_view MccMountTypeStr = TYPE == MccMountType::GERMAN_TYPE ? "GERMAN" : TYPE == MccMountType::FORK_TYPE ? "FORK" : TYPE == MccMountType::CROSSAXIS_TYPE ? "CROSSAXIS" : TYPE == MccMountType::ALTAZ_TYPE ? "ALTAZ" : "UNKNOWN"; template static constexpr bool mcc_is_equatorial_mount = TYPE == MccMountType::GERMAN_TYPE ? true : TYPE == MccMountType::FORK_TYPE ? true : TYPE == MccMountType::CROSSAXIS_TYPE ? true : TYPE == MccMountType::ALTAZ_TYPE ? false : false; template static constexpr bool mcc_is_altaz_mount = TYPE == MccMountType::GERMAN_TYPE ? false : TYPE == MccMountType::FORK_TYPE ? false : TYPE == MccMountType::CROSSAXIS_TYPE ? false : TYPE == MccMountType::ALTAZ_TYPE ? true : false; static consteval bool mccIsEquatorialMount(const MccMountType type) { return type == MccMountType::GERMAN_TYPE ? true : type == MccMountType::FORK_TYPE ? true : type == MccMountType::CROSSAXIS_TYPE ? true : type == MccMountType::ALTAZ_TYPE ? false : false; }; static consteval bool mccIsAltAzMount(const MccMountType type) { return type == MccMountType::GERMAN_TYPE ? false : type == MccMountType::FORK_TYPE ? false : type == MccMountType::CROSSAXIS_TYPE ? false : type == MccMountType::ALTAZ_TYPE ? true : false; }; /* NULL-LOGGER CLASS */ struct MccNullLogger { typedef int loglevel_t; void setLogLevel(loglevel_t){}; loglevel_t getLogLevel() const { return 0; }; void logMessage(loglevel_t, const std::string&) {}; void logError(const std::string&) {}; void logDebug(const std::string&) {}; void logWarn(const std::string&) {}; void logInfo(const std::string&) {}; }; } // namespace mcc /* MOUNT COMPONENTS CONCEPTS */ namespace mcc::traits { /* GENERIC LOGGER */ template concept mcc_logger_c = requires(T t, const T t_const) { typename T::loglevel_t; { t.setLogLevel(std::declval()) }; { t_const.getLogLevel() } -> std::same_as; { t.logMessage(std::declval(), std::declval()) }; { t.logError(std::declval()) }; { t.logDebug(std::declval()) }; { t.logWarn(std::declval()) }; { t.logInfo(std::declval()) }; }; /* A CONCEPT FOR COORDINATE REPRESENTATION */ // it is a fundametal floating-point type or // a class that can be constructed from or converted to the double fundametal type template concept mcc_coord_t = std::floating_point || (std::convertible_to && std::constructible_from); /* A CONCEPT FOR UTC TIME POINT REPRESENTATION */ // it is a std::chrono::sys_time or // fundamental arithmetic type that represent number of seconds after the 00:00:00 of 1 January 1970 (UNIX time) template concept mcc_utc_time_point_c = mcc_systime_c || std::is_arithmetic_v; /* A CONCEPT FOR CLASS TO REPRESENT CELESTIAL POINT */ template concept mcc_celestial_point_c = requires(T t) { // a type to represent UTC time point of coordinates // it's clear that this makes sense for apparent coordinates typename T::time_point_t; // coordinates pair type (e.g. IRCS RA,DEC, Az,Alt and so on) requires std::same_as; typename T::coord_t; // co-longitude (e.g. RA or Az) requires std::same_as; // co-latitude (e.g. DEC or ZD) requires std::same_as; }; /* ASTROMETRY-RELATED COMPUTATION ENGINE */ template concept mcc_astrom_engine_c = requires(T t, const T t_const) { requires mcc_error_c; // typename T::engine_state_t; // requires std::movable; typename T::coord_t; // type for coordinates representation typename T::time_point_t; // type to represent UTC time point typename T::juldate_t; // type to represent Julian date typename T::sideral_time_t; // type to represent sideral time typename T::eo_t; // equation of origins typename T::pa_t; // type to represent parallactic angle typename T::refract_result_t; /* coordinates conversional methods */ // ICRS RA and DEC to observed place: icrs2obs(ra, dec, jd, ra_app, dec_app, ha, az, alt, eo) { t.icrs2obs(std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval()) } -> std::same_as; // observed place to ICRS RA and DEC: obs2icrs(type, x, y, jd, ra_icrs, dec_icrs) // (x, y) = (AZ, ZD) if type == MccCoordPairKind::COORDS_KIND_AZZD // (x, y) = (AZ, ALT) if type == MccCoordPairKind::COORDS_KIND_AZALT // (x, y) = (HA, DEC) if type == MccCoordPairKind::COORDS_KIND_HADEC_APP // (x, y) = (RA, DEC) if type == MccCoordPairKind::COORDS_KIND_RADEC_APP { t.obs2icrs(std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval()) } -> std::same_as; // compute hour angle and declination from azimuth and altitude: hadec2azalt(ha, dec, az, alt) { t.hadec2azalt(std::declval(), std::declval(), std::declval(), std::declval()) } -> std::same_as; // compute azimuth and altitude from hour angle and declination: azalt2hadec(az, alt, ha, dec) { t.azalt2hadec(std::declval(), std::declval(), std::declval(), std::declval()) } -> std::same_as; // compute parallactic angle: hadec2pa(ha, dec, pa) { t.hadec2pa(std::declval(), std::declval(), std::declval()) } -> std::same_as; // transform coordinates according to its pair types and time points (a high-level wrapper): // // coord2coord(coord_pair_kind_from, x_from, y_from, time_point_from, coord_pair_kind_to, x_to, y_to, time_point_to) // // (x_*, y_*) = (AZ, ZD) if coord_pair_kind_* == MccCoordPairKind::COORDS_KIND_AZZD // (x_*, y_*) = (AZ, ALT) if coord_pair_kind_* == MccCoordPairKind::COORDS_KIND_AZALT // (x_*, y_*) = (HA, DEC) if coord_pair_kind_* == MccCoordPairKind::COORDS_KIND_HADEC_APP (apparent) // (x_*, y_*) = (RA, DEC) if coord_pair_kind_* == MccCoordPairKind::COORDS_KIND_RADEC_APP (apparent) // (x_*, y_*) = (RA, DEC) if coord_pair_kind_* == MccCoordPairKind::COORDS_KIND_RADEC_ICRS (ICRS) // // if coord_pair_kind_* and time_point_* are equal then x_to = x_from, y_to = y_from { t.coord2coord(std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval(), std::declval()) } -> std::same_as; // compute equation of origins { t.eqOrigins(std::declval(), std::declval()) } -> std::same_as; /* time-related methods */ // this static method must return a current time point { T::timePointNow() } -> std::same_as; // Gregorian Calendar time point to Julian Date: greg2jul(time_point, jd) { t.greg2jul(std::declval(), std::declval()) } -> std::same_as; // apparent sideral time: apparentSiderTime(jd, st, islocal) // if islocal == false then the method must return the Greenwich apparent sideral time, otherwise - local one { t.apparentSiderTime(std::declval(), std::declval(), std::declval()) } -> std::same_as; /* atmospheric refraction-related methods */ // compute refraction-related quantities: refraction(refr_params) { t.refraction(std::declval()) } -> std::same_as; // compute refraction correction for given altitude: refractCorrection(alt, refr_params, refr_corr) { t.refractCorrection(std::declval(), std::declval(), std::declval()) } -> std::same_as; }; /* A VERY GENERIC MOUNT HARDWARE CONCEPT */ template concept mcc_mount_hardware_c = !std::copyable && std::movable && requires(T t, const T t_const) { requires mcc_error_c; typename T::time_point_t; typename T::coord_t; { t_const.id() } -> mcc_formattable; // a type that defines at least HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, HW_MOVE_TRACKING // and HW_MOVE_GUIDING compile-time constants. The main purpose of this type is a // possible tunning of hardware setPos-related commands // // e.g. an implementations can be as follows: // enum class hw_moving_type_t: int {HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, HW_MOVE_TRACKING, HW_MOVE_GUIDING} // // struct hw_moving_type_t { // uint16_t HW_MOVE_SLEWING = 111; // uint16_t HW_MOVE_ADJUSTING = 222; // uint16_t HW_MOVE_TRACKING = 333; // uint16_t HW_MOVE_GUIDING = 444; // } requires requires(typename T::hw_moving_type_t state) { []() { // hardware was asked for slewing (move to given celestial point) static constexpr auto v1 = T::hw_moving_type_t::HW_MOVE_SLEWING; // hardware was asked for adjusting after slewing ("seeking" given celestial point at the end of slewing // process) static constexpr auto v2 = T::hw_moving_type_t::HW_MOVE_ADJUSTING; // hardware was asked for tracking (track given celestial point) static constexpr auto v3 = T::hw_moving_type_t::HW_MOVE_TRACKING; // hardware was asked for guiding (small corrections to track given celestial point) static constexpr auto v4 = T::hw_moving_type_t::HW_MOVE_GUIDING; }(); }; // a class that contains at least time of measurement, coordinates for x,y axes and its moving rates requires requires(typename T::axes_pos_t pos) { requires std::same_as; // time point requires std::same_as; // co-longitude coordinate requires std::same_as; // co-latitude coordinate requires std::same_as; requires std::same_as; requires std::same_as; // a 'hint' to hardware }; // set positions (angles) of mount axes with given speeds // NOTE: exact interpretation (or even ignoring) of the given moving speeds is subject of a hardware-class // implementation. // e.g. it can be maximal speeds at slewing ramp { t.setPos(std::declval()) } -> std::same_as; // get current positions and speeds (angles) of mount axes { t.getPos(std::declval()) } -> std::same_as; { t.stop() } -> std::same_as; // stop any moving { t.init() } -> std::same_as; // initialize hardware }; /* POINTING-ERROR CORRECTION */ template concept mcc_mount_pec_c = requires(T t) { requires mcc_error_c; typename T::coord_t; // the 'T' class must contain static constexpr member of 'MccMountType' type requires requires { requires std::same_as; []() { static constexpr MccMountType val = T::mountType; return val; }(); // to ensure 'mountType' can be used in compile-time context }; // a class that contains at least .dx and .dy public fields requires requires(typename T::pec_result_t res) { requires std::same_as; requires std::same_as; }; { t.compute(std::declval(), std::declval(), std::declval()) } -> std::same_as; }; /* MOUNT STATE TELEMETRY */ // a class that contains at least celestial (equatorial and horizontal) and harware coordinates template concept mcc_mount_telemetry_data_c = std::movable && requires(T telemetry) { typename T::coord_t; typename T::time_point_t; // time point requires std::same_as; // target sky point ICRS and current coordinates requires std::same_as; // apparent RA requires std::same_as; // apparent DEC requires std::same_as; // hour angle requires std::same_as; // azimuth requires std::same_as; // altitude // mount current coordinates requires std::same_as; // apparent RA requires std::same_as; // apparent DEC requires std::same_as; // hour angle requires std::same_as; // azimuth requires std::same_as; // altitude requires std::same_as; // hardware encoder X-axis position requires std::same_as; // hardware encoder Y-axis position requires std::same_as; // hardware encoder X-axis rate requires std::same_as; // hardware encoder Y-axis rate // corrections to transform mount hardware coordinates to apparent // (pointing error corrections) requires std::same_as; requires std::same_as; }; template concept mcc_mount_telemetry_c = requires(T t, const T t_const) { requires mcc_error_c; // // a class that at least contains celestial (equatorial and horizontal) coordinates // requires requires(typename T::mount_telemetry_data_t telemetry) { // typename T::mount_telemetry_data_t::coord_t; // requires std::same_as; // // apparent RA requires std::same_as; // apparent DEC requires std::same_as; // // hour angle requires std::same_as; // // azimuth requires std::same_as; // altitude // }; requires mcc_mount_telemetry_data_c; { t.update() } -> std::same_as; { t.data(std::declval()) } -> std::same_as; }; // /* SLEW PARAMETERS */ // template // concept mcc_slew_params_c = std::movable && requires(T t) { // // input coordinates pair type (e.g. IRCS RA,DEC, Az,Alt and so on) // requires std::same_as; // typename T::coord_t; // // co-longitude (e.g. RA or Az) // requires std::same_as; // // co-latitude (e.g. DEC or ZD) // requires std::same_as; // // stop after slewing // requires std::convertible_to; // }; /* GENERIC SLEW AND GUIDING MODEL */ template concept mcc_slew_model_c = requires(T t) { requires mcc_error_c; // requires mcc_slew_params_c; requires mcc_celestial_point_c; // { t.slew(std::declval()) } -> std::same_as; { t.slew(std::declval()) } -> std::same_as; { t.stop() } -> std::same_as; }; template concept mcc_guiding_model_c = requires(T t) { requires mcc_error_c; requires mcc_celestial_point_c; // start process of guiding { t.guiding(std::declval()) } -> std::same_as; { t.stop() } -> std::same_as; }; /* MOUNT PROHIBITED ZONE */ struct MccPzoneAbstractInterface { bool inZone(this auto&& self, mcc_mount_telemetry_data_c auto const& telemetry_data) { using self_t = decltype(self); if constexpr (std::same_as, MccPzoneAbstractInterface>) { static_assert(false, "Call an empty MccPzoneAbstractInterface::inZone method"); } else { return std::forward(self).inZone(telemetry_data); } } bool inZone(this auto&& self, mcc_celestial_point_c auto const& sky_point) { using self_t = decltype(self); if constexpr (std::same_as, MccPzoneAbstractInterface>) { static_assert(false, "Call an empty MccPzoneAbstractInterface::inZone method"); } else { return std::forward(self).inZone(sky_point); } } // returns a time to reach the zone auto timeTo(this auto&& self, mcc_mount_telemetry_data_c auto const& telemetry_data) { using self_t = decltype(self); if constexpr (std::same_as, MccPzoneAbstractInterface>) { static_assert(false, "Call an empty MccPzoneAbstractInterface::timeTo method"); } else { return std::forward(self).timeTo(telemetry_data); } } auto timeTo(this auto&& self, mcc_celestial_point_c auto const& sky_point) { using self_t = decltype(self); if constexpr (std::same_as, MccPzoneAbstractInterface>) { static_assert(false, "Call an empty MccPzoneAbstractInterface::timeTo method"); } else { return std::forward(self).timeTo(sky_point); } } // returns a time to exit from the zone auto timeFrom(this auto&& self, mcc_mount_telemetry_data_c auto const& telemetry_data) { using self_t = decltype(self); if constexpr (std::same_as, MccPzoneAbstractInterface>) { static_assert(false, "Call an empty MccPzoneAbstractInterface::timeFrom method"); } else { return std::forward(self).timeFrom(telemetry_data); } } auto timeFrom(this auto&& self, mcc_celestial_point_c auto const& sky_point) { using self_t = decltype(self); if constexpr (std::same_as, MccPzoneAbstractInterface>) { static_assert(false, "Call an empty MccPzoneAbstractInterface::timeFrom method"); } else { return std::forward(self).timeFrom(sky_point); } } }; template concept mcc_prohibited_zone_c = mcc_mount_telemetry_data_c && std::movable && requires(T t, const T t_const) { typename T::coord_t; // typename T::time_point_t; requires mcc_time_duration_c; // static constexpr member to represent infinite duration requires requires { requires std::same_as; []() { constexpr auto val = T::infiniteDuration; return val; }; }; // static constexpr member to represent zero duration requires requires { requires std::same_as; []() { constexpr auto val = T::zeroDuration; return val; }; }; // return a name of the zone { t_const.name() } -> mcc_formattable; // check if given coordinates are into the zone. { t.inZone(std::declval()) } -> std::convertible_to; // a time duration to reach the zone. // special values the method must return: // 'infiniteDuration' if the given sky point never reaches the zone // 0 (zero duration) if the given sky point is already in the zone or it never exits from the zone { t.timeTo(std::declval()) } -> std::same_as; // a time duration to exit from the zone. // special values the method must return: // 0 (zero duration) if the given sky point already exited from the zone or it never reaches the zone { t.timeFrom(std::declval()) } -> std::same_as; }; // an input range of prohibited zones template concept mcc_irange_of_pzones_c = mcc_mount_telemetry_data_c && std::ranges::input_range && mcc_prohibited_zone_c, TelemetryDataT>; // // a concept for a callable with the first argument of type satisfied to 'mcc_prohibited_zone_c' // template // concept mcc_pzone_foreach_func_c = mcc_is_callable && mcc_mount_telemetry_data_c && // mcc_prohibited_zone_c, TelemetryDataT>; // There is no way to declare a concept of class with templated method so one needs to define // a generic interface of prohibited zones holder/container explicitly template struct MccPZoneAbstractContainer { virtual ~MccPZoneAbstractContainer() = default; // must return a size of the container after the addition of the given zone template ZT> size_t pzAddZone(this auto&& self, ZT zone) { using self_t = decltype(self); if constexpr (std::same_as, MccPZoneAbstractContainer>) { static_assert(false, "Call an empty MccPZoneAbstractContainer::pzAddZone method"); } else { return std::forward(self).pzAddZone(std::move(zone)); } } // clear the container auto pzClearZones(this auto&& self) { using self_t = decltype(self); if constexpr (std::same_as, MccPZoneAbstractContainer>) { static_assert(false, "Call an empty MccPZoneAbstractContainer::pzClearZones method"); } else { return std::forward(self).pzClearZones(); } } // must return the size of the container (number of zones) size_t pzSize(this auto&& self) { using self_t = decltype(self); if constexpr (std::same_as, MccPZoneAbstractContainer>) { static_assert(false, "Call an empty MccPZoneAbstractContainer::pzSize method"); } else { return std::forward(self).pzSize(); } } // must return true if the given telemetry coordinates are in any of zones in the container and // false otherwise template bool pzInZone(this auto&& self, const TelemetryDataT& tdata, RT& result) { using self_t = decltype(self); if constexpr (std::same_as, MccPZoneAbstractContainer>) { static_assert(false, "Call an empty MccPZoneAbstractContainer::pzInZone method"); } else { return std::forward(self).pzInZone(tdata, result); } } template auto pzTimeTo(this auto&& self, const TelemetryDataT& tdata, RT& result) { using self_t = decltype(self); if constexpr (std::same_as, MccPZoneAbstractContainer>) { static_assert(false, "Call an empty MccPZoneAbstractContainer::pzInZone method"); } else { return std::forward(self).pzTimeTo(tdata, result); } } template auto pzTimeFrom(this auto&& self, const TelemetryDataT& tdata, RT& result) { using self_t = decltype(self); if constexpr (std::same_as, MccPZoneAbstractContainer>) { static_assert(false, "Call an empty MccPZoneAbstractContainer::pzInZone method"); } else { return std::forward(self).pzTimeFrom(tdata, result); } } protected: MccPZoneAbstractContainer() = default; }; // a full concept for prohibited zones container template concept mcc_mount_pzones_container_c = std::derived_from> && requires { // common time duration type for zones 'timeTo' and 'timeFrom' methods requires mcc_time_duration_c; // static constexpr member to represent infinite duration requires requires { requires std::same_as; []() { constexpr auto val = T::infiniteDuration; return val; }; }; // static constexpr member to represent zero duration requires requires { requires std::same_as; []() { constexpr auto val = T::zeroDuration; return val; }; }; }; /* MOUNT GENERIC CONTROLS */ template concept mcc_mount_controls_c = requires(T t) { // concept mcc_mount_controls_c = std::move_constructible && std::movable && requires(T t) { requires mcc_astrom_engine_c; requires mcc_mount_pec_c; requires mcc_mount_hardware_c; requires mcc_mount_telemetry_c; requires mcc_slew_model_c; requires mcc_guiding_model_c; // a std::tuple of prohibited zones // []... Ts>(std::tuple) { // }(t.prohibitedZones); // requires mcc_tuple_c; requires mcc_irange_of_pzones_c; }; /* GENERIC MOUNT CONCEPTS */ template concept mcc_mount_c = requires(T t) { // the class must define typename 'mount_controls_t' and it must be its base class requires mcc_mount_controls_c; requires std::derived_from; // deduced from 'mount_controls_t' typenames requires mcc_mount_telemetry_c; requires std::same_as; requires mcc_astrom_engine_c; requires mcc_mount_pec_c; requires mcc_mount_hardware_c; requires mcc_slew_model_c; requires mcc_guiding_model_c; // public methods { t.mountTelemetryData(std::declval()) } -> std::same_as; { t.slewMount(std::declval()) } -> std::same_as; { t.guidingTarget(std::declval()) } -> std::same_as; }; // generic with public logging methods template concept mcc_log_mount_c = mcc_mount_c && mcc_logger_c; // a generic Finite State Machine mount with logging methods template concept mcc_fsm_log_mount_c = std::derived_from && mcc_log_mount_c; } // namespace mcc::traits /* CHECK LIBRARY-WIDE CLASS DECLARATIONS FOR ITS CONCEPTS SATISFACTION */ namespace mcc { static_assert(traits::mcc_logger_c, "MccNullLogger INVALID DECLARATION!"); } // namespace mcc