#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ /* SOME LIBRARY-WIDE DECLARATIONS */ #include #include // #include "mcc_traits.h" #include "mcc_angle.h" #include "mcc_finite_state_machine.h" 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; }; // enum MccCoordPairKind : size_t { // COORDS_KIND_GENERIC, // COORDS_KIND_RADEC_ICRS, // COORDS_KIND_RADEC_APP, // COORDS_KIND_HADEC_APP, // COORDS_KIND_AZZD, // COORDS_KIND_AZALT, // COORDS_KIND_XY, // COORDS_KIND_LATLON // }; /* GENERIC LOGGER CLASS CONCEPT */ template concept mcc_logger_c = requires(T t, const T t_const) { { t.logError(std::declval()) }; { t.logDebug(std::declval()) }; { t.logWarn(std::declval()) }; { t.logInfo(std::declval()) }; }; struct MccNullLogger { void logError(const std::string&) {} void logDebug(const std::string&) {} void logWarn(const std::string&) {} void logInfo(const std::string&) {} }; /* FLOATING-POINT LIKE CLASS CONCEPT */ template concept mcc_fp_type_like_c = std::floating_point || (std::convertible_to && std::constructible_from && std::default_initializable); /* ANGLE REPRESENTATION CLASS CONCEPT */ template concept mcc_angle_c = mcc_fp_type_like_c && requires(T v, double vd) { // mandatory arithmetic operations { v + v } -> std::same_as; { v - v } -> std::same_as; { v += v } -> std::same_as; { v -= v } -> std::same_as; { vd + v } -> std::same_as; { vd - v } -> std::same_as; { v + vd } -> std::same_as; { v - vd } -> std::same_as; { v += vd } -> std::same_as; { v -= vd } -> std::same_as; { v * vd } -> std::same_as; { v / vd } -> std::same_as; }; /* TIME POINT CLASS CONCEPT */ /* * USE OF STL std::chrono::time_point */ template concept mcc_time_point_c = traits::mcc_systime_c; // concept mcc_time_point_c = requires(T t) { [](std::chrono::time_point) {}(t); }; /* JULIAN DAY CLASS CONCEPT */ template concept mcc_julday_c = mcc_fp_type_like_c && requires(const T v) { // modified Julian Day { v.MJD() } -> std::convertible_to; // comparison operators v <=> v; }; /* ERROR CLASS CONCEPT */ template concept mcc_error_c = std::convertible_to || requires(const T t) { { t.operator bool() }; }; template static constexpr ErrT mcc_deduce_error(const DErrT& err, const ErrT& default_err) { if constexpr (std::same_as) { return err; } else { return default_err; } } /* ATMOSPHERIC REFRACTION MODEL CLASS CONCEPT */ template concept mcc_refract_model_c = requires(const T t_const) { { t_const.name() } -> std::formattable; }; /* CELESTIAL POINT WITH A PAIR OF COORDINATES CLASS CONCEPT */ template concept mcc_celestial_point_c = requires(T t) { requires std::same_as; // type of given coordinate pair requires mcc_time_point_c; // time point for given coordinates requires mcc_angle_c; // co-longitude (X-axis) requires mcc_angle_c; // co-latitude (Y-axis) }; static constexpr void mcc_copy_celestial_point(mcc_celestial_point_c auto const& from_pt, mcc_celestial_point_c auto* to_pt) { if (to_pt == nullptr) { return; } using from_pt_t = std::remove_cvref_t; using to_pt_t = std::remove_cvref_t; if constexpr (std::derived_from && std::copyable) { *to_pt = from_pt; return; } to_pt->pair_kind = from_pt.pair_kind; to_pt->time_point = std::chrono::time_point_casttime_point)::duration>(from_pt.time_point); to_pt->X = (double)from_pt.X; to_pt->Y = (double)from_pt.Y; } /* CELESTIAL POINT WITH APPARENT EQUATORIAL AND HORIZONTAL CLASS CONCEPT */ template concept mcc_eqt_hrz_coord_c = mcc_celestial_point_c && requires(T t) { requires mcc_angle_c; // right ascension requires mcc_angle_c; // declination requires mcc_angle_c; // hour angle requires mcc_angle_c; // azimuth (NOTE: ASSUMING THE AZINUTH IS COUNTED FROM THE SOUTH THROUGH THE // WEST!!!) requires mcc_angle_c; // zenithal distance requires mcc_angle_c; // altitude }; static constexpr void mcc_copy_eqt_hrz_coord(mcc_eqt_hrz_coord_c auto const& from_pt, mcc_eqt_hrz_coord_c auto* to_pt) { if (to_pt == nullptr) { return; } using from_pt_t = std::remove_cvref_t; using to_pt_t = std::remove_cvref_t; if constexpr (std::derived_from && std::copyable) { *to_pt = from_pt; return; } to_pt->pair_kind = from_pt.pair_kind; to_pt->time_point = std::chrono::time_point_casttime_point)::duration>(from_pt.time_point); to_pt->X = (double)from_pt.X; to_pt->Y = (double)from_pt.Y; to_pt->RA_APP = (double)from_pt.RA_APP; to_pt->DEC_APP = (double)from_pt.DEC_APP; to_pt->HA = (double)from_pt.HA; to_pt->AZ = (double)from_pt.AZ; to_pt->ZD = (double)from_pt.ZD; to_pt->ALT = (double)from_pt.ALT; } /* CELESTIAL COORDINATES TRANSFORMATION ENGINE */ template struct mcc_CCTE_interface_t { virtual ~mcc_CCTE_interface_t() = default; template SelfT> RetT timepointToJulday(this SelfT&& self, mcc_time_point_c auto tp, mcc_julday_c auto* julday) { return std::forward(self).timepointToJulday(std::move(tp), julday); } // APPARENT SIDERAL TIME template SelfT> RetT timepointToAppSideral(this SelfT&& self, mcc_time_point_c auto tp, mcc_angle_c auto* st, bool islocal = false) { return std::forward(self).timepointToAppSideral(std::move(tp), st, islocal); } template SelfT> RetT juldayToAppSideral(this SelfT&& self, mcc_julday_c auto jd, mcc_angle_c auto* st, bool islocal = false) { return std::forward(self).timepointToAppSideral(std::move(jd), st, islocal); } // NOTE: ASSUMING THE AZINUTH IS COUNTED FROM THE SOUTH THROUGH THE WEST!!! template SelfT> RetT transformCoordinates(this SelfT&& self, mcc_celestial_point_c auto from_pt, mcc_celestial_point_c auto* to_pt) { return std::forward(self).transformCoordinates(std::move(from_pt), to_pt); } // NOTE: ASSUMING THE AZIMUTH IS COUNTED FROM THE SOUTH THROUGH THE WEST!!! template SelfT> RetT transformCoordinates(this SelfT&& self, mcc_celestial_point_c auto from_pt, mcc_eqt_hrz_coord_c auto* to_pt) { return std::forward(self).transformCoordinates(std::move(from_pt), to_pt); } template SelfT> RetT parallacticAngle(this SelfT&& self, mcc_celestial_point_c auto pt, mcc_angle_c auto* pa) { return std::forward(self).parallacticAngle(std::move(pt), pa); } template SelfT> RetT refractionCorrection(this SelfT&& self, mcc_celestial_point_c auto pt, mcc_angle_c auto* dZ) { return std::forward(self).refractionCoeff(std::move(pt), dZ); } protected: mcc_CCTE_interface_t() = default; }; template concept mcc_ccte_c = std::derived_from> && requires(const T t_const, T t) { { t_const.nameCCTE() } -> std::formattable; requires mcc_refract_model_c; { t.refractionModel(std::declval()) } -> std::same_as; }; /* MOUNT TELEMETRY DATA CLASS CONCEPT */ template concept mcc_pointing_target_coord_c = mcc_eqt_hrz_coord_c && requires(T t) { requires mcc_angle_c; // ICRS right ascention requires mcc_angle_c; // ICRS declination }; static constexpr void mcc_copy_pointing_target_coord(mcc_pointing_target_coord_c auto const& from_pt, mcc_pointing_target_coord_c auto* to_pt) { if (to_pt == nullptr) { return; } using from_pt_t = std::remove_cvref_t; using to_pt_t = std::remove_cvref_t; if constexpr (std::derived_from && std::copyable) { *to_pt = from_pt; return; } to_pt->pair_kind = from_pt.pair_kind; to_pt->time_point = std::chrono::time_point_casttime_point)::duration>(from_pt.time_point); to_pt->X = (double)from_pt.X; to_pt->Y = (double)from_pt.Y; to_pt->RA_ICRS = (double)from_pt.RA_ICRS; to_pt->DEC_ICRS = (double)from_pt.DEC_ICRS; to_pt->RA_APP = (double)from_pt.RA_APP; to_pt->DEC_APP = (double)from_pt.DEC_APP; to_pt->HA = (double)from_pt.HA; to_pt->AZ = (double)from_pt.AZ; to_pt->ZD = (double)from_pt.ZD; to_pt->ALT = (double)from_pt.ALT; } template concept mcc_telemetry_data_c = mcc_eqt_hrz_coord_c && std::default_initializable && requires(T t) { // target target coordinates requires mcc_pointing_target_coord_c; // t.X and t.Y (from mcc_celestial_point_c) are encoder coordinates // t.* from mcc_eqt_hrz_coord_c are apparent mount pointing coordinates requires mcc_angle_c; // speed along X from hardware encoder requires mcc_angle_c; // speed along Y from hardware encoder // corrections to transform hardware encoder coordinates to apparent celestial ones requires mcc_angle_c; // PCM correction along X-axis requires mcc_angle_c; // PCM correction along Y-axis // atmospheric refraction correction for current zenithal distance requires mcc_angle_c; // for current .ZD }; static constexpr void mcc_copy_telemetry_data(mcc_telemetry_data_c auto const& from_pt, mcc_telemetry_data_c auto* to_pt) { if (to_pt == nullptr) { return; } using from_pt_t = std::remove_cvref_t; using to_pt_t = std::remove_cvref_t; if constexpr (std::derived_from && std::copyable) { *to_pt = from_pt; return; } to_pt->pair_kind = from_pt.pair_kind; to_pt->time_point = std::chrono::time_point_casttime_point)::duration>(from_pt.time_point); to_pt->X = (double)from_pt.X; to_pt->Y = (double)from_pt.Y; to_pt->speedX = (double)from_pt.speedX; to_pt->speedY = (double)from_pt.speedY; to_pt->RA_APP = (double)from_pt.RA_APP; to_pt->DEC_APP = (double)from_pt.DEC_APP; to_pt->HA = (double)from_pt.HA; to_pt->AZ = (double)from_pt.AZ; to_pt->ZD = (double)from_pt.ZD; to_pt->ALT = (double)from_pt.ALT; to_pt->pcmX = (double)from_pt.pcmX; to_pt->pcmY = (double)from_pt.pcmY; to_pt->refCorr = (double)from_pt.refCorr; mcc_copy_pointing_target_coord(from_pt.target, &to_pt->target); } /* MOUNT TELEMETRY MANAGER CLASS CONCEPT */ template struct mcc_telemetry_interface_t { virtual ~mcc_telemetry_interface_t() = default; // update telemetry data right now // an implementation is expected to update the data within the given 'timeout' time interval // if not the method must return an appropriate timeout-error template SelfT> RetT updateTelemetryData(this SelfT&& self, traits::mcc_time_duration_c auto const& timeout) { return std::forward(self).updateTelemetryData(timeout); } // get current data // an implementation is expected to return current, possibly already expired, telemetry data // one should call consistently both 'updateTelemetryData' + 'telemetryData' methods // to ensure that the data is up-to-date template SelfT> RetT telemetryData(this SelfT&& self, mcc_telemetry_data_c auto* data) { return std::forward(self).telemetryData(data); } // waiting for updated data // an implementation is expected to block the current thread waiting for // telemetry data to be updated (internal synchronization) // if a timeout occured the method must return an appropriate timeout-error template SelfT> RetT waitForTelemetryData(this SelfT&& self, mcc_telemetry_data_c auto* data, traits::mcc_time_duration_c auto const& timeout) { return std::forward(self).waitForTelemetryData(data, timeout); } // set target coordinates template SelfT> RetT setPointingTarget(this SelfT&& self, mcc_celestial_point_c auto pt) { return std::forward(self).telemetryData(std::move(pt)); } // compute difference in coordinates: // dx = targetX - mountX // dy = targetY - mountY // where X and Y is in according to 'pair_kind' input parameter template SelfT> RetT targetToMountDiff(this SelfT&& self, MccCoordPairKind pair_kind, mcc_angle_c auto* dx, mcc_angle_c auto* dy) { std::forward(self).targetToMountDiff(pair_kind, dx, dy); } protected: mcc_telemetry_interface_t() = default; }; template concept mcc_telemetry_c = std::derived_from>; /* POINTING CORRECTION MODEL CLASS CONCEPT */ template concept mcc_PCM_result_c = requires(T t) { requires mcc_angle_c; requires mcc_angle_c; }; template struct mcc_PCM_interface_t { virtual ~mcc_PCM_interface_t() = default; template SelfT> RetT computePCM(this SelfT&& self, mcc_celestial_point_c auto pt, ResT* result) { return std::forward(self).computePCM(std::move(pt), result); } protected: mcc_PCM_interface_t() = default; }; template concept mcc_PCM_c = std::derived_from> && requires { // the 'T' class must contain static constexpr member of 'MccMountType' type requires std::same_as; []() { static constexpr MccMountType val = T::mountType; return val; }(); // to ensure 'mountType' can be used in compile-time context }; /* MOUNT HARDWARE ABSTRACTION CLASS CONCEPT */ template concept mcc_hardware_c = requires(T t, const T t_const) { requires mcc_error_c; { t_const.hardwareName() } -> std::formattable; // a type that defines at least HW_MOVE_STOPPED, 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 hardwareSetState-related commands and detect stop-state // // e.g. an implementations can be as follows: // enum class hardware_moving_state_t: int {HW_MOVE_STOPPED, HW_MOVE_SLEWING, HW_MOVE_ADJUSTING, // HW_MOVE_TRACKING, HW_MOVE_GUIDING} // // struct hardware_moving_state_t { // uint16_t HW_MOVE_STOPPED = 0; // 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::hardware_moving_state_t type) { []() { // mount axes were stopped static constexpr auto v0 = T::hardware_moving_state_t::HW_MOVE_STOPPED; // hardware was asked for slewing (move to given celestial point) static constexpr auto v1 = T::hardware_moving_state_t::HW_MOVE_SLEWING; // hardware was asked for adjusting after slewing // (adjusting actual mount position to align with target celestial point at the end of slewing process) static constexpr auto v2 = T::hardware_moving_state_t::HW_MOVE_ADJUSTING; // hardware was asked for tracking (track target celestial point) static constexpr auto v3 = T::hardware_moving_state_t::HW_MOVE_TRACKING; // hardware was asked for guiding // (small corrections to align actual mount position with target celestial point) static constexpr auto v4 = T::hardware_moving_state_t::HW_MOVE_GUIDING; }(); }; // a class that contains at least time point of measurement, coordinates for x,y axes, its moving rates and moving // type requires requires(typename T::hardware_state_t state) { requires mcc_time_point_c; // time point requires mcc_angle_c; // target or current co-longitude coordinate requires mcc_angle_c; // target or current co-latitude coordinate requires mcc_angle_c; // moving speed along co-longitude coordinate requires mcc_angle_c; // moving speed along co-latitude coordinate std::same_as; }; // set hardware state: // i.g. 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.hardwareSetState(std::declval()) } -> std::same_as; // get current state { t.hardwareGetState(std::declval()) } -> std::same_as; { t.hardwareStop() } -> std::same_as; // stop any moving { t.hardwareInit() } -> std::same_as; // initialize hardware }; /* PROHIBITED ZONE CLASS CONCEPT */ template struct mcc_pzone_interface_t { virtual ~mcc_pzone_interface_t() = default; template SelfT, typename InputT> RetT inPZone(this SelfT&& self, InputT coords, bool* result) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) && requires { self.inPZone(coords, result); } { return std::forward(self).InPZone(std::move(coords), result); } template SelfT, typename InputT> RetT timeToPZone(this SelfT&& self, InputT coords, traits::mcc_time_duration_c auto* res_time) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) && requires { self.timeToPZone(coords, res_time); } { return std::forward(self).timeToPZone(std::move(coords), res_time); } template SelfT, typename InputT> RetT timeFromPZone(this SelfT&& self, InputT coords, traits::mcc_time_duration_c auto* res_time) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) && requires { self.timeFromPZone(coords, res_time); } { return std::forward(self).timeFromPZone(std::move(coords), res_time); } // // NOTE: the method must return: // point = mcc_celestial_point_c{.pair_kind = MccCoordPairKind::COORDS_KIND_GENERIC, .X = NaN, .Y = NaN} // if there is no intersection with the zone for given coordinates! template SelfT, typename InputT> RetT intersectPZone(this SelfT&& self, InputT coords, mcc_celestial_point_c auto* point) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) && requires { self.intersectPZone(coords, point); } { return std::forward(self).intersectPZone(std::move(coords), point); } protected: mcc_pzone_interface_t() = default; }; template concept mcc_prohibited_zone_c = std::derived_from> && requires(const T t_const) { { t_const.name() } -> std::formattable; }; /* PROHIBITED ZONES CONTAINER CLASS CONCEPT */ template struct mcc_pzone_container_interface_t { virtual ~mcc_pzone_container_interface_t() = default; template SelfT> size_t addPZone(this SelfT&& self, mcc_prohibited_zone_c auto zone) { return std::forward(self).addPZone(std::move(zone)); } template SelfT> void clearPZones(this SelfT&& self) { return std::forward(self).clearPZones(); } template SelfT> size_t sizePZones(this SelfT&& self) { return std::forward(self).sizePZones(); } template SelfT, typename InputT> RetT inPZone(this SelfT&& self, InputT coords, bool* common_result, std::ranges::output_range auto* result) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { return std::forward(self).InPZone(std::move(coords), common_result, result); } template SelfT, typename InputT, traits::mcc_time_duration_c DT> RetT timeToPZone(this SelfT&& self, InputT coords, std::ranges::output_range
auto* res_time) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { return std::forward(self).timeToPZone(std::move(coords), res_time); } template SelfT, typename InputT, traits::mcc_time_duration_c DT> RetT timeFromPZone(this SelfT&& self, InputT coords, std::ranges::output_range
auto* res_time) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { return std::forward(self).timeFromPZone(std::move(coords), res_time); } template SelfT, typename InputT, mcc_celestial_point_c CPT> RetT intersectPZone(this SelfT&& self, InputT coords, std::ranges::output_range auto* result) requires(mcc_eqt_hrz_coord_c || mcc_celestial_point_c) { return std::forward(self).intersectPZone(std::move(coords), result); } protected: mcc_pzone_container_interface_t() = default; }; template concept mcc_pzone_container_c = std::derived_from>; /* MOUNT MOVING MODEL CONCEPT */ template concept mcc_slewing_model_c = requires(T t) { requires mcc_error_c; // a class of slewing process parameters requires requires(typename T::slewing_params_t pars) { // slew mount to target and stop std::convertible_to; }; { t.slewToTarget() } -> std::same_as; { t.stopSlewing() } -> std::same_as; { t.setSlewingParams(std::declval()) } -> std::same_as; { t.getSlewingParams() } -> std::same_as; }; template concept mcc_tracking_model_c = requires(T t) { requires mcc_error_c; // a class of tracking process parameters requires requires(typename T::tracking_params_t pars) { requires mcc_angle_c; requires mcc_angle_c; }; { t.trackTarget() } -> std::same_as; { t.stopTracking() } -> std::same_as; { t.setTrackingParams(std::declval()) } -> std::same_as; { t.getTrackingParams() } -> std::same_as; }; template concept mcc_guiding_model_c = requires(T t) { requires mcc_error_c; // a class of guiding process parameters requires requires(typename T::guiding_params_t pars) { // guide along both mount axis std::convertible_to; }; { t.startGuidingTarget() } -> std::same_as; { t.stopGuidingTarget() } -> std::same_as; { t.setGuidingParams(std::declval()) } -> std::same_as; { t.getGuidingParams() } -> std::same_as; }; /* GENERIC MOUNT CLASS CONCEPT */ // template // struct mcc_generic_mount_interface_t { // virtual ~mcc_generic_mount_interface_t() = default; // // slew mount to target (target coordinates were defined in telemetry data) // template SelfT> // RetT slewToTarget(this SelfT&& self, mcc_slewing_model_c auto model) // { // return std::forward(self).slewToTarget(std::move(model)); // } // // track target, i.e., the mount moves with celestial speed // template SelfT> // RetT trackTarget(this SelfT&& self, mcc_tracking_model_c auto model) // { // return std::forward(self).trackTarget(std::move(model)); // } // template SelfT> // RetT startGuidingTarget(this SelfT&& self, mcc_guiding_model_c auto model) // { // return std::forward(self).startGuidingTarget(std::move(model)); // } // template SelfT> // RetT stopGuidingTarget(this SelfT&& self) // { // return std::forward(self).stopGuidingTarget(); // } // template SelfT> // RetT stopMount(this SelfT&& self, StopReasonT reason) // { // return std::forward(self).stopMount(std::move(reason)); // } // protected: // mcc_generic_mount_interface_t() = default; // }; // template // concept mcc_generic_mount_c // = std::derived_from> // && mcc_telemetry_c && mcc_pzone_container_c; template concept mcc_generic_mount_c = mcc_telemetry_c && mcc_pzone_container_c && requires(T t) { requires mcc_error_c; // slew mount to target (target coordinates were defined in telemetry data) { t.slewToTarget() } -> std::same_as; // track target, i.e., the mount moves with celestial speed { t.trackTarget() } -> std::same_as; { t.startGuidingTarget() } -> std::same_as; { t.stopGuidingTarget() } -> std::same_as; // stop any movement { t.stopMount() } -> std::same_as; // init mount { t.initMount() }; }; // with logging methods template concept mcc_generic_log_mount_c = mcc_generic_mount_c && mcc_logger_c; // Finite-state-machine template concept mcc_generic_fsm_mount_c = mcc_generic_mount_c && std::derived_from; template concept mcc_generic_fsm_log_mount_c = mcc_generic_mount_c && mcc_logger_c && std::derived_from; } // namespace mcc