#pragma once /**/ #include #include #include #include #include #include #include #include #include "asibfm700_common.h" #include "asibfm700_servocontroller.h" namespace asibfm700 { /* A SIMPLE "KEYWORD - VALUE" HOLDER CLASS SUITABLE TO STORE SOME APPLICATION CONFIGURATION */ // to follow std::variant requirements (not references, not array, not void) template concept variant_valid_type_c = requires { !std::is_array_v && !std::is_void_v && !std::is_reference_v; }; // configuration record template concept config_record_c = requires(T t) { requires std::same_as; // keyword requires variant_valid_type_c; // value }; // simple minimal-requirement configuration record class template struct simple_config_record_t { std::string_view key; T value; }; // description of config (a std::tuple of "config_record_c"s) template concept config_desc_c = requires(T t) { [](std::tuple) {}(t); }; template class ConfigHolder { protected: /* helper definitions */ // deduce unique value types of the given config records template struct deduce_val_types; template struct deduce_val_types> { using value_type_t = std::tuple; }; template struct deduce_val_types> { using value_type_t = std::conditional_t<(std::same_as || ...), typename deduce_val_types>::value_type_t, decltype(std::tuple_cat( std::declval>(), std::declval>::value_type_t>()))>; }; template using deduce_val_types_t = typename deduce_val_types::value_type_t; // deduce std::variant type from std::tuple element types template struct variant_from_tuple; template struct variant_from_tuple> { using variant_t = std::variant; }; template using variant_from_tuple_t = typename variant_from_tuple::variant_t; public: static constexpr char COMMENT_SYMBOL = '#'; static constexpr char KEY_VALUE_DELIM = '='; static constexpr char VALUE_ARRAY_DELIM = ','; // very simple de-serializer (only numbers and strings) inline static auto defaultDeserializeFunc = [](this auto&& self, std::string_view str, auto& value) { using value_t = std::decay_t; if constexpr (std::is_arithmetic_v) { auto v = mcc::utils::numFromStr(str); if (!v.has_value()) { return false; } value = v.value(); } else if constexpr (mcc::traits::mcc_output_char_range) { value_t r; std::ranges::copy(str, std::back_inserter(r)); value = r; } else if constexpr (std::ranges::range) { using el_t = std::ranges::range_value_t; if constexpr (std::is_reference_v || std::is_const_v) { // no reference or constants allowed return false; } value_t r; el_t elem; auto els = std::views::split(str, VALUE_ARRAY_DELIM); for (auto const& el : els) { // if (std::forward(self)(std::string_view(el), elem)) { if (std::forward(self)(mcc::utils::trimSpaces(el), elem)) { std::back_inserter(r) = elem; } else { return false; } } value = r; } else if constexpr (mcc::traits::mcc_time_duration_c) { typename value_t::rep vd; bool ok = self(str, vd); if (ok) { value = value_t{vd}; } } else { return false; } return true; }; ConfigHolder(DESCR_T desc) { [desc = std::move(desc), this](std::index_sequence) { ((_configDB[std::get(desc).key] = std::get(desc).value), ...); }(std::make_index_sequence>()); } virtual ~ConfigHolder() = default; // deser_func - de-serialization function, i.e., a conversional function // from string-representation to some value // // DeserFuncT is a type of callable with signature: // bool deser_func(std::string_view str, value_type& val) // where // str - input (serialized) string-representation of configuration // item value // 'value_type' - must take into account all possible value types // in the input configuration description (see constructor) // // most suitable implementation is a generic lambda function, i.e.: // auto deser_func(std::string_view str, auto& cnv_val)->bool { // ... // }; template std::error_code parse(const R& buffer, DeserFuncT&& deser_func) requires std::same_as>, char> { if constexpr (std::is_array_v>) { // char*, const char* return parse(std::string_view{std::forward(buffer)}, std::forward(deser_func)); } auto curr_buffer = std::string_view(buffer.begin(), buffer.end()); std::string_view key, value; bool buffer_end = false; do { auto it = std::ranges::find(curr_buffer, '\n'); if (it == curr_buffer.end()) { buffer_end = true; } auto sv = mcc::utils::trimSpaces(std::string_view(curr_buffer.begin(), it), mcc::utils::TrimType::TRIM_LEFT); curr_buffer = {it + 1, curr_buffer.end()}; if (sv.size() && (sv[0] != COMMENT_SYMBOL)) { it = std::ranges::find(sv, KEY_VALUE_DELIM); if (it != sv.begin()) { // ignore an empty key key = mcc::utils::trimSpaces(std::string_view(sv.begin(), it), mcc::utils::TrimType::TRIM_RIGHT); auto rec_it = _configDB.find(key); if (rec_it != _configDB.end()) { // ignore key if it is not in description value = mcc::utils::trimSpaces(std::string_view(it + 1, sv.end()), mcc::utils::TrimType::TRIM_BOTH); bool ok = forIndex(rec_it->second, value, std::forward(deser_func), rec_it->second.index()); if (!ok) { return std::make_error_code(std::errc::invalid_argument); } } } } } while (!buffer_end); return {}; } template std::error_code parse(const R& buffer) { return parse(buffer, defaultDeserializeFunc); } template std::expected value(std::string_view key) { auto it = _configDB.find(key); if (it == _configDB.end()) { return std::unexpected(std::make_error_code(std::errc::argument_out_of_domain)); } std::expected res; std::visit( [&res](auto&& val) { using v_t = std::decay_t; if constexpr (std::convertible_to) { res = static_cast(std::forward(val)); // } else if constexpr (std::constructible_from) { // res = T{std::forward(val)}; } else { res = std::unexpected(std::make_error_code(std::errc::invalid_argument)); } }, it->second); return res; } template bool update(std::string_view key, const T& value) { auto it = _configDB.find(key); if (it == _configDB.end()) { return false; } bool ok; std::visit( [&value, &ok](auto& val) { using v_t = std::decay_t; if constexpr (std::convertible_to) { val = static_cast(value); ok = true; } else { ok = false; } }, it->second); return ok; } protected: std::unordered_map>> _configDB; template bool forIndex(std::variant& var, std::string_view s, FuncT&& func, size_t idx) { if constexpr (I < sizeof...(Ts)) { if (I == idx) { using v_t = std::tuple_element_t>; v_t val; bool ok = std::forward(func)(s, val); if (ok) { var = val; } return ok; } else { return forIndex(var, s, std::forward(func), idx); } } return false; } }; /* ASTOROSIB FM700 MOUNT CONFIGURATION CLASS */ // configuration description and its defaults static auto Asibfm700MountConfigDefaults = std::make_tuple( // main cycle period in millisecs simple_config_record_t{"hardwarePollingPeriod", std::chrono::milliseconds{100}}, /* geographic coordinates of the observation site */ // site latitude in degrees simple_config_record_t{"siteLatitude", mcc::MccAngle(43.646711_degs)}, // site longitude in degrees simple_config_record_t{"siteLongitude", mcc::MccAngle(41.440732_degs)}, // site elevation in meters simple_config_record_t{"siteElevation", 2070.0}, /* celestial coordinate transformation */ // wavelength at which refraction is calculated (in mkm) simple_config_record_t{"refractWavelength", 0.55}, // an empty filename means default precompiled string simple_config_record_t{"leapSecondFilename", std::string()}, // an empty filename means default precompiled string simple_config_record_t{"bulletinAFilename", std::string()}, /* pointing correction model */ // PCM default type simple_config_record_t{"pcmType", mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY}, // PCM geometrical coefficients simple_config_record_t{"pcmGeomCoeffs", std::vector{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}}, // PCM B-spline degrees simple_config_record_t{"pcmBsplineDegree", std::vector{3, 3}}, // PCM B-spline knots along X-axis (HA-angle or azimuth). By default from 0 to 2*PI radians // NOTE: The first and last values are interpretated as border knots!!! // Thus the array length must be equal to or greater than 2! simple_config_record_t{"pcmBsplineXknots", std::vector{0.0, 0.6981317, 1.3962634, 2.0943951, 2.7925268, 3.4906585, 4.1887902, 4.88692191, 5.58505361, 6.28318531}}, // PCM B-spline knots along Y-axis (declination or zenithal distance). By default from -PI/6 to PI/2 radians // NOTE: The first and last values are interpretated as border knots!!! // Thus the array length must be equal to or greater than 2! simple_config_record_t{"pcmBsplineYknots", std::vector{-0.52359878, -0.29088821, -0.05817764, 0.17453293, 0.40724349, 0.63995406, 0.87266463, 1.10537519, 1.33808576, 1.57079633}}, // PCM B-spline coeffs for along X-axis (HA-angle or azimuth) simple_config_record_t{"pcmBsplineXcoeffs", std::vector{}}, // PCM B-spline coeffs for along Y-axis (declination or zenithal distance) simple_config_record_t{"pcmBsplineYcoeffs", std::vector{}}, /* slewing and tracking parameters */ // // arcseconds per second // simple_config_record_t{"sideralRate", 15.0410686}, // timeout for telemetry updating in milliseconds simple_config_record_t{"telemetryTimeout", std::chrono::milliseconds(3000)}, // minimal allowed time in seconds to prohibited zone simple_config_record_t{"minTimeToPZone", std::chrono::seconds(10)}, // a time interval to update prohibited zones related quantities (millisecs) simple_config_record_t{"updatingPZoneInterval", std::chrono::milliseconds(5000)}, // coordinates difference in arcsecs to stop slewing simple_config_record_t{"slewToleranceRadius", 5.0}, // target-mount coordinate difference in arcsecs to start adjusting of slewing simple_config_record_t{"adjustCoordDiff", 50.0}, // minimum time in millisecs between two successive adjustments simple_config_record_t{"adjustCycleInterval", std::chrono::milliseconds(300)}, // slew process timeout in seconds simple_config_record_t{"slewTimeout", std::chrono::seconds(3600)}, // a time shift into future to compute target position in future (UT1-scale time duration, millisecs) simple_config_record_t{"timeShiftToTargetPoint", std::chrono::milliseconds(10000)}, // minimum time in millisecs between two successive tracking corrections simple_config_record_t{"trackingCycleInterval", std::chrono::milliseconds(300)}, // maximal valid target-to-mount distance for tracking process (arcsecs) // if current distance is greater than assume current mount coordinate as target point simple_config_record_t{"trackingMaxCoordDiff", 20.0}, /* prohibited zones */ // minimal altitude simple_config_record_t{"pzMinAltitude", mcc::MccAngle(10.0_degs)}, // HA-axis limit switch minimal value simple_config_record_t{"pzLimitSwitchHAMin", mcc::MccAngle(-170.0_degs)}, // HA-axis limit switch maximal value simple_config_record_t{"pzLimitSwitchHAMax", mcc::MccAngle(170.0_degs)}, // DEC-axis limit switch minimal value simple_config_record_t{"pzLimitSwitchDecMin", mcc::MccAngle(-90.0_degs)}, // DEC-axis limit switch maximal value simple_config_record_t{"pzLimitSwitchDecMax", mcc::MccAngle(90.0_degs)}, /* hardware-related */ // hardware mode: 1 - model mode, otherwise real mode simple_config_record_t{"RunModel", 0}, // mount serial device paths simple_config_record_t{"MountDevPath", std::string("/dev/ttyUSB0")}, // mount serial device speed simple_config_record_t{"MountDevSpeed", 19200}, // motor encoders serial device path simple_config_record_t{"EncoderDevPath", std::string("")}, // X-axis encoder serial device path simple_config_record_t{"EncoderXDevPath", std::string("/dev/encoderX0")}, // Y-axis encoder serial device path simple_config_record_t{"EncoderYDevPath", std::string("/dev/encoderY0")}, // encoders serial device speed simple_config_record_t{"EncoderDevSpeed", 153000}, // ==1 if encoder works as separate serial device, ==2 if there's new version with two devices simple_config_record_t{"SepEncoder", 2}, // mount polling interval in millisecs simple_config_record_t{"MountReqInterval", std::chrono::milliseconds(100)}, // encoders polling interval in millisecs simple_config_record_t{"EncoderReqInterval", std::chrono::milliseconds(50)}, // mount axes rate calculation interval in millisecs simple_config_record_t{"EncoderSpeedInterval", std::chrono::milliseconds(100)}, // X-axis coordinate PID P,I,D-params simple_config_record_t{"XPIDC", std::vector{0.8, 0.1, 0.3}}, // X-axis rate PID P,I,D-params simple_config_record_t{"XPIDV", std::vector{1.0, 0.01, 0.2}}, // Y-axis coordinate PID P, I, D-params simple_config_record_t{"YPIDC", std::vector{0.8, 0.1, 0.3}}, // Y-axis rate PID P,I,D-params simple_config_record_t{"YPIDV", std::vector{0.5, 0.2, 0.5}}, // maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller) simple_config_record_t{"hwMaxRateHA", mcc::MccAngle(8.0_degs)}, // maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller) simple_config_record_t{"hwMaxRateDEC", mcc::MccAngle(10.0_degs)} ); class Asibfm700MountConfig : public mcc::utils::KeyValueHolder { using base_t = mcc::utils::KeyValueHolder; protected: inline static auto deserializer = [](std::string_view str, VT& value) { std::error_code ec{}; mcc::utils::MccSimpleDeserializer deser; deser.setRangeDelim(base_t::VALUE_ARRAY_DELIM); if constexpr (std::is_arithmetic_v || mcc::traits::mcc_output_char_range || std::ranges::range || mcc::traits::mcc_time_duration_c) { // ec = base_t::defaultDeserializeFunc(str, value); ec = deser(str, value); } else if constexpr (std::same_as) { // assume here all angles are in degrees double vd; // ec = base_t::defaultDeserializeFunc(str, vd); ec = deser(str, value); if (!ec) { value = mcc::MccAngle(vd, mcc::MccDegreeTag{}); } } else if constexpr (std::same_as) { std::string vstr; // ec = base_t::defaultDeserializeFunc(str, vstr); ec = deser(str, value); if (!ec) { auto s = mcc::utils::trimSpaces(vstr); if (s == mcc::MccDefaultPCMTypeString) { value = mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY; } else if (s == mcc::MccDefaultPCMTypeString) { value = mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY; } else if (s == mcc::MccDefaultPCMTypeString) { value = mcc::MccDefaultPCMType::PCM_TYPE_BSPLINE; } else { ec = std::make_error_code(std::errc::invalid_argument); } } } else { ec = std::make_error_code(std::errc::invalid_argument); } return ec; }; public: /* the most usefull config fields */ template DT hardwarePollingPeriod() const { return std::chrono::duration_cast
( getValue("hardwarePollingPeriod").value_or(std::chrono::milliseconds{})); }; std::chrono::milliseconds hardwarePollingPeriod() const { return hardwarePollingPeriod(); }; template T siteLatitude() const { return static_cast(getValue("siteLatitude").value_or(mcc::MccAngle{})); }; mcc::MccAngle siteLatitude() const { return siteLatitude(); }; template T siteLongitude() const { return static_cast(getValue("siteLongitude").value_or(mcc::MccAngle{})); }; mcc::MccAngle siteLongitude() const { return siteLongitude(); }; template T siteElevation() const requires std::is_arithmetic_v { return getValue("siteElevation").value_or(0.0); } double siteElevation() const { return getValue("siteElevation").value_or(0.0); }; template T refractWavelength() const requires std::is_arithmetic_v { return getValue("refractWavelength").value_or(0.0); } double refractWavelength() const { return getValue("refractWavelength").value_or(0.0); }; template R leapSecondFilename() const { R r; if constexpr (std::ranges::view) { std::string const& val = getValue("leapSecondFilename").value_or(""); r = R{val.begin(), val.end()}; } else { std::string val = getValue("leapSecondFilename").value_or(""); std::ranges::copy(val, std::back_inserter(r)); } return r; } std::string_view leapSecondFilename() const { return leapSecondFilename(); }; template R bulletinAFilename() const { R r; if constexpr (std::ranges::view) { std::string const& val = getValue("bulletinAFilename").value_or(""); r = R{val.begin(), val.end()}; } else { std::string val = getValue("bulletinAFilename").value_or(""); std::ranges::copy(val, std::back_inserter(r)); } return r; } std::string_view bulletinAFilename() const { return bulletinAFilename(); }; template T pzMinAltitude() const { return static_cast(getValue("pzMinAltitude").value_or(mcc::MccAngle{})); }; mcc::MccAngle pzMinAltitude() const { return pzMinAltitude(); }; template T pzLimitSwitchHAMin() const { return static_cast(getValue("pzLimitSwitchHAMin").value_or(mcc::MccAngle{})); }; mcc::MccAngle pzLimitSwitchHAMin() const { return pzLimitSwitchHAMin(); }; template T pzLimitSwitchHAMax() const { return static_cast(getValue("pzLimitSwitchHAMax").value_or(mcc::MccAngle{})); }; mcc::MccAngle pzLimitSwitchHAMax() const { return pzLimitSwitchHAMax(); }; AsibFM700ServoController::hardware_config_t servoControllerConfig() const { AsibFM700ServoController::hardware_config_t hw_cfg; hw_cfg.hwConfig = {}; hw_cfg.MountDevPath = getValue("MountDevPath").value_or({}); hw_cfg.EncoderDevPath = getValue("EncoderDevPath").value_or({}); hw_cfg.EncoderXDevPath = getValue("EncoderXDevPath").value_or({}); hw_cfg.EncoderYDevPath = getValue("EncoderYDevPath").value_or({}); hw_cfg.devConfig.MountDevPath = hw_cfg.MountDevPath.data(); hw_cfg.devConfig.EncoderDevPath = hw_cfg.EncoderDevPath.data(); hw_cfg.devConfig.EncoderXDevPath = hw_cfg.EncoderXDevPath.data(); hw_cfg.devConfig.EncoderYDevPath = hw_cfg.EncoderYDevPath.data(); hw_cfg.devConfig.RunModel = getValue("RunModel").value_or({}); hw_cfg.devConfig.MountDevSpeed = getValue("MountDevSpeed").value_or({}); hw_cfg.devConfig.EncoderDevSpeed = getValue("EncoderDevSpeed").value_or({}); hw_cfg.devConfig.SepEncoder = getValue("SepEncoder").value_or({}); std::chrono::duration secs; // seconds as floating-point secs = getValue("MountReqInterval").value_or({}); hw_cfg.devConfig.MountReqInterval = secs.count(); secs = getValue("EncoderReqInterval").value_or({}); hw_cfg.devConfig.EncoderReqInterval = secs.count(); secs = getValue("EncoderSpeedInterval").value_or({}); hw_cfg.devConfig.EncoderSpeedInterval = secs.count(); std::vector pid = getValue>("XPIDC").value_or({}); if (pid.size() > 2) { hw_cfg.devConfig.XPIDC.P = pid[0]; hw_cfg.devConfig.XPIDC.I = pid[1]; hw_cfg.devConfig.XPIDC.D = pid[2]; } pid = getValue>("XPIDV").value_or({}); if (pid.size() > 2) { hw_cfg.devConfig.XPIDV.P = pid[0]; hw_cfg.devConfig.XPIDV.I = pid[1]; hw_cfg.devConfig.XPIDV.D = pid[2]; } pid = getValue>("YPIDC").value_or({}); if (pid.size() > 2) { hw_cfg.devConfig.YPIDC.P = pid[0]; hw_cfg.devConfig.YPIDC.I = pid[1]; hw_cfg.devConfig.YPIDC.D = pid[2]; } pid = getValue>("YPIDV").value_or({}); if (pid.size() > 2) { hw_cfg.devConfig.YPIDV.P = pid[0]; hw_cfg.devConfig.YPIDV.I = pid[1]; hw_cfg.devConfig.YPIDV.D = pid[2]; } return hw_cfg; } mcc::MccSimpleMovingModelParams movingModelParams() const { static constexpr double arcsecs2rad = std::numbers::pi / 180.0 / 3600.0; // arcseconds to radians mcc::MccSimpleMovingModelParams pars; pars.telemetryTimeout = getValue("telemetryTimeout").value_or(pars.telemetryTimeout); pars.minTimeToPZone = getValue("minTimeToPZone").value_or(pars.minTimeToPZone); pars.updatingPZoneInterval = getValue("updatingPZoneInterval") .value_or(pars.updatingPZoneInterval); pars.slewToleranceRadius = getValue("slewToleranceRadius").value_or(pars.slewToleranceRadius) * arcsecs2rad; pars.adjustCoordDiff = getValue("adjustCoordDiff").value_or(pars.adjustCoordDiff) * arcsecs2rad; pars.adjustCycleInterval = getValue("adjustCycleInterval").value_or(pars.adjustCycleInterval); pars.slewTimeout = getValue("slewTimeout").value_or(pars.slewTimeout); pars.timeShiftToTargetPoint = getValue("timeShiftToTargetPoint") .value_or(pars.timeShiftToTargetPoint); pars.trackingCycleInterval = getValue("trackingCycleInterval") .value_or(pars.trackingCycleInterval); pars.trackingMaxCoordDiff = getValue("trackingMaxCoordDiff").value_or(pars.trackingMaxCoordDiff) * arcsecs2rad; return pars; } Asibfm700PCM::pcm_data_t pcmData() const { Asibfm700PCM::pcm_data_t pcm_data; std::vector empty_vec; pcm_data.type = getValue("pcmType").value_or(pcm_data.type); pcm_data.siteLatitude = getValue("siteLatitude").value_or(pcm_data.siteLatitude); std::vector vec = getValue>("pcmGeomCoeffs").value_or(empty_vec); if (vec.size() >= 9) { // must be 9 coefficients pcm_data.geomCoefficients = {.zeroPointX = vec[0], .zeroPointY = vec[1], .collimationErr = vec[2], .nonperpendErr = vec[3], .misalignErr1 = vec[4], .misalignErr2 = vec[5], .tubeFlexure = vec[6], .forkFlexure = vec[7], .DECaxisFlexure = vec[8]}; } std::vector dd = getValue("pcmBsplineDegree").value_or(dd); if (dd.size() >= 2) { pcm_data.bspline.bsplDegreeX = dd[0] > 0 ? dd[0] : 3; pcm_data.bspline.bsplDegreeY = dd[1] > 0 ? dd[1] : 3; } vec = getValue>("pcmBsplineXknots").value_or(empty_vec); // pid must contains interior and border (single point for each border) knots so minimal length must be 2 if (vec.size() >= 2) { // generate full knots array (with border knots) size_t Nknots = vec.size() + pcm_data.bspline.bsplDegreeX * 2 - 2; pcm_data.bspline.knotsX.resize(Nknots); for (size_t i = 0; i <= pcm_data.bspline.bsplDegreeX; ++i) { // border knots pcm_data.bspline.knotsX[i] = vec[0]; pcm_data.bspline.knotsX[Nknots - i - 1] = vec.back(); } for (size_t i = 0; i < (vec.size() - 2); ++i) { // interior knots pcm_data.bspline.knotsX[i + pcm_data.bspline.bsplDegreeX] = vec[1 + i]; } } vec = getValue>("pcmBsplineYknots").value_or(empty_vec); // pid must contains interior and border (single point for each border) knots so minimal length must be 2 if (vec.size() >= 2) { // generate full knots array (with border knots) size_t Nknots = vec.size() + pcm_data.bspline.bsplDegreeY * 2 - 2; pcm_data.bspline.knotsY.resize(Nknots); for (size_t i = 0; i <= pcm_data.bspline.bsplDegreeY; ++i) { // border knots pcm_data.bspline.knotsY[i] = vec[0]; pcm_data.bspline.knotsY[Nknots - i - 1] = vec.back(); } for (size_t i = 0; i < (vec.size() - 2); ++i) { // interior knots pcm_data.bspline.knotsY[i + pcm_data.bspline.bsplDegreeY] = vec[1 + i]; } } // minimal allowed number of B-spline coefficients size_t Ncoeffs = pcm_data.type == mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY ? 0 : (pcm_data.bspline.knotsX.size() - pcm_data.bspline.bsplDegreeX - 1) * (pcm_data.bspline.knotsY.size() - pcm_data.bspline.bsplDegreeY - 1); vec = getValue>("pcmBsplineXcoeffs").value_or(empty_vec); if (vec.size() >= Ncoeffs) { pcm_data.bspline.coeffsX.resize(Ncoeffs); for (size_t i = 0; i < Ncoeffs; ++i) { pcm_data.bspline.coeffsX[i] = vec[i]; } } vec = getValue>("pcmBsplineYcoeffs").value_or(empty_vec); if (vec.size() >= Ncoeffs) { pcm_data.bspline.coeffsY.resize(Ncoeffs); for (size_t i = 0; i < Ncoeffs; ++i) { pcm_data.bspline.coeffsY[i] = vec[i]; } } return pcm_data; } Asibfm700MountConfig() : base_t(Asibfm700MountConfigDefaults) {} ~Asibfm700MountConfig() = default; std::error_code load(const std::filesystem::path& path) { std::string buffer; std::error_code ec; auto sz = std::filesystem::file_size(path, ec); if (!ec && sz) { std::ifstream fst(path); try { buffer.resize(sz); fst.read(buffer.data(), sz); fst.close(); ec = base_t::fromCharRange(buffer, deserializer); } catch (std::ios_base::failure const& ex) { ec = ex.code(); } catch (std::length_error const& ex) { ec = std::make_error_code(std::errc::no_buffer_space); } catch (std::bad_alloc const& ex) { ec = std::make_error_code(std::errc::not_enough_memory); } catch (...) { ec = std::make_error_code(std::errc::operation_canceled); } } return ec; } }; class Asibfm700MountConfig2 : protected ConfigHolder { using base_t = ConfigHolder; public: using base_t::update; using base_t::value; Asibfm700MountConfig2() : base_t(Asibfm700MountConfigDefaults) { updateAll(); } ~Asibfm700MountConfig2() = default; std::error_code load(const std::filesystem::path& path) { std::string buffer; std::error_code ec; auto sz = std::filesystem::file_size(path, ec); if (!ec && sz) { std::ifstream fst(path); try { buffer.resize(sz); fst.read(buffer.data(), sz); fst.close(); ec = base_t::parse(buffer, deserializer); if (!ec) { updateAll(); } } catch (std::ios_base::failure const& ex) { ec = ex.code(); } catch (std::length_error const& ex) { ec = std::make_error_code(std::errc::no_buffer_space); } catch (std::bad_alloc const& ex) { ec = std::make_error_code(std::errc::not_enough_memory); } catch (...) { ec = std::make_error_code(std::errc::operation_canceled); } } return ec; } template bool update(std::string_view key, const T& value) { bool ok = base_t::update(key, value); if (ok) { updateAll(); } return ok; } std::chrono::milliseconds hardwarePollingPeriod{}; mcc::MccAngle siteLatitude{}; mcc::MccAngle siteLongitude{}; double siteElevation{}; double refractWavelength{}; std::string leapSecondFilename{}; std::string bulletinAFilename{}; mcc::MccAngle pzMinAltitude{}; mcc::MccAngle pzLimitSwitchHAMin{}; mcc::MccAngle pzLimitSwitchHAMax{}; AsibFM700ServoController::hardware_config_t servoControllerConfig{}; mcc::MccSimpleMovingModelParams movingModelParams{}; Asibfm700PCM::pcm_data_t pcmData{}; protected: void updateAll() { hardwarePollingPeriod = std::get(this->_configDB["hardwarePollingPeriod"]); // CCTE siteLatitude = std::get(this->_configDB["siteLatitude"]); siteLongitude = std::get(this->_configDB["siteLongitude"]); siteElevation = std::get(this->_configDB["siteElevation"]); refractWavelength = std::get(this->_configDB["refractWavelength"]); leapSecondFilename = std::get(this->_configDB["leapSecondFilename"]); bulletinAFilename = std::get(this->_configDB["bulletinAFilename"]); // prohibited zones pzMinAltitude = std::get(this->_configDB["pzMinAltitude"]); pzLimitSwitchHAMin = std::get(this->_configDB["pzLimitSwitchHAMin"]); pzLimitSwitchHAMax = std::get(this->_configDB["pzLimitSwitchHAMax"]); // hardware config servoControllerConfig.hwConfig = {}; servoControllerConfig.MountDevPath = std::get(this->_configDB["MountDevPath"]); servoControllerConfig.EncoderDevPath = std::get(this->_configDB["EncoderDevPath"]); servoControllerConfig.EncoderXDevPath = std::get(this->_configDB["EncoderXDevPath"]); servoControllerConfig.EncoderYDevPath = std::get(this->_configDB["EncoderYDevPath"]); servoControllerConfig.devConfig.MountDevPath = servoControllerConfig.MountDevPath.data(); servoControllerConfig.devConfig.EncoderDevPath = servoControllerConfig.EncoderDevPath.data(); servoControllerConfig.devConfig.EncoderXDevPath = servoControllerConfig.EncoderXDevPath.data(); servoControllerConfig.devConfig.EncoderYDevPath = servoControllerConfig.EncoderYDevPath.data(); servoControllerConfig.devConfig.RunModel = std::get(this->_configDB["RunModel"]); servoControllerConfig.devConfig.MountDevSpeed = std::get(this->_configDB["MountDevSpeed"]); servoControllerConfig.devConfig.EncoderDevSpeed = std::get(this->_configDB["EncoderDevSpeed"]); servoControllerConfig.devConfig.SepEncoder = std::get(this->_configDB["SepEncoder"]); std::chrono::duration secs; // seconds as floating-point secs = std::get(this->_configDB["MountReqInterval"]); servoControllerConfig.devConfig.MountReqInterval = secs.count(); secs = std::get(this->_configDB["EncoderReqInterval"]); servoControllerConfig.devConfig.EncoderReqInterval = secs.count(); secs = std::get(this->_configDB["EncoderSpeedInterval"]); servoControllerConfig.devConfig.EncoderSpeedInterval = secs.count(); std::vector pid = std::get>(this->_configDB["XPIDC"]); if (pid.size() > 2) { servoControllerConfig.devConfig.XPIDC.P = pid[0]; servoControllerConfig.devConfig.XPIDC.I = pid[1]; servoControllerConfig.devConfig.XPIDC.D = pid[2]; } pid = std::get>(this->_configDB["XPIDV"]); if (pid.size() > 2) { servoControllerConfig.devConfig.XPIDV.P = pid[0]; servoControllerConfig.devConfig.XPIDV.I = pid[1]; servoControllerConfig.devConfig.XPIDV.D = pid[2]; } pid = std::get>(this->_configDB["YPIDC"]); if (pid.size() > 2) { servoControllerConfig.devConfig.YPIDC.P = pid[0]; servoControllerConfig.devConfig.YPIDC.I = pid[1]; servoControllerConfig.devConfig.YPIDC.D = pid[2]; } pid = std::get>(this->_configDB["YPIDV"]); if (pid.size() > 2) { servoControllerConfig.devConfig.YPIDV.P = pid[0]; servoControllerConfig.devConfig.YPIDV.I = pid[1]; servoControllerConfig.devConfig.YPIDV.D = pid[2]; } // slew and track parameters movingModelParams.telemetryTimeout = std::get(this->_configDB["telemetryTimeout"]); movingModelParams.minTimeToPZone = std::get(this->_configDB["minTimeToPZone"]); movingModelParams.updatingPZoneInterval = std::get(this->_configDB["updatingPZoneInterval"]); movingModelParams.slewToleranceRadius = std::get(this->_configDB["slewToleranceRadius"]); movingModelParams.adjustCoordDiff = std::get(this->_configDB["adjustCoordDiff"]); movingModelParams.adjustCycleInterval = std::get(this->_configDB["adjustCycleInterval"]); movingModelParams.slewTimeout = std::get(this->_configDB["slewTimeout"]); movingModelParams.timeShiftToTargetPoint = std::get(this->_configDB["timeShiftToTargetPoint"]); movingModelParams.trackingCycleInterval = std::get(this->_configDB["trackingCycleInterval"]); // PCM data pcmData.type = std::get(this->_configDB["pcmType"]); pcmData.siteLatitude = std::get(this->_configDB["siteLatitude"]); pid = std::get>(this->_configDB["pcmGeomCoeffs"]); if (pid.size() >= 9) { // must be 9 coefficients pcmData.geomCoefficients = {.zeroPointX = pid[0], .zeroPointY = pid[1], .collimationErr = pid[2], .nonperpendErr = pid[3], .misalignErr1 = pid[4], .misalignErr2 = pid[5], .tubeFlexure = pid[6], .forkFlexure = pid[7], .DECaxisFlexure = pid[8]}; } std::vector dd = std::get(this->_configDB["pcmBsplineDegree"]); if (dd.size() >= 2) { pcmData.bspline.bsplDegreeX = dd[0] > 0 ? dd[0] : 3; pcmData.bspline.bsplDegreeY = dd[1] > 0 ? dd[1] : 3; } pid = std::get>(this->_configDB["pcmBsplineXknots"]); // pid must contains interior and border (single point for each border) knots so minimal length must be 2 if (pid.size() >= 2) { // generate full knots array (with border knots) size_t Nknots = pid.size() + pcmData.bspline.bsplDegreeX * 2 - 2; pcmData.bspline.knotsX.resize(Nknots); for (size_t i = 0; i <= pcmData.bspline.bsplDegreeX; ++i) { // border knots pcmData.bspline.knotsX[i] = pid[0]; pcmData.bspline.knotsX[Nknots - i - 1] = pid.back(); } for (size_t i = 0; i < (pid.size() - 2); ++i) { // interior knots pcmData.bspline.knotsX[i + pcmData.bspline.bsplDegreeX] = pid[1 + i]; } } pid = std::get>(this->_configDB["pcmBsplineYknots"]); // pid must contains interior and border (single point for each border) knots so minimal length must be 2 if (pid.size() >= 2) { // generate full knots array (with border knots) size_t Nknots = pid.size() + pcmData.bspline.bsplDegreeY * 2 - 2; pcmData.bspline.knotsY.resize(Nknots); for (size_t i = 0; i <= pcmData.bspline.bsplDegreeY; ++i) { // border knots pcmData.bspline.knotsY[i] = pid[0]; pcmData.bspline.knotsY[Nknots - i - 1] = pid.back(); } for (size_t i = 0; i < (pid.size() - 2); ++i) { // interior knots pcmData.bspline.knotsY[i + pcmData.bspline.bsplDegreeY] = pid[1 + i]; } } // minimal allowed number of B-spline coefficients size_t Ncoeffs = pcmData.type == mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY ? 0 : (pcmData.bspline.knotsX.size() - pcmData.bspline.bsplDegreeX - 1) * (pcmData.bspline.knotsY.size() - pcmData.bspline.bsplDegreeY - 1); pid = std::get>(this->_configDB["pcmBsplineXcoeffs"]); if (pid.size() >= Ncoeffs) { pcmData.bspline.coeffsX.resize(Ncoeffs); for (size_t i = 0; i < Ncoeffs; ++i) { pcmData.bspline.coeffsX[i] = pid[i]; } } pid = std::get>(this->_configDB["pcmBsplineYcoeffs"]); if (pid.size() >= Ncoeffs) { pcmData.bspline.coeffsY.resize(Ncoeffs); for (size_t i = 0; i < Ncoeffs; ++i) { pcmData.bspline.coeffsY[i] = pid[i]; } } } inline static auto deserializer = [](std::string_view str, auto& value) { using value_t = std::decay_t; bool ok = true; if constexpr (std::is_arithmetic_v || mcc::traits::mcc_output_char_range || std::ranges::range || mcc::traits::mcc_time_duration_c) { return base_t::defaultDeserializeFunc(str, value); } else if constexpr (std::same_as) { // assume here all angles are in degrees double vd; ok = base_t::defaultDeserializeFunc(str, vd); if (ok) { value = mcc::MccAngle(vd, mcc::MccDegreeTag{}); } } else if constexpr (std::same_as) { std::string vstr; ok = base_t::defaultDeserializeFunc(str, vstr); auto s = mcc::utils::trimSpaces(vstr); if (ok) { if (s == mcc::MccDefaultPCMTypeString) { value = mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY; } else if (s == mcc::MccDefaultPCMTypeString) { value = mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY; } else if (s == mcc::MccDefaultPCMTypeString) { value = mcc::MccDefaultPCMType::PCM_TYPE_BSPLINE; } else { return false; } } } else { return false; } return ok; }; }; static constexpr std::string_view Asibfm700MountConfigString = R"--( # # ASTROSIB FM-700 MOUNT DEFAULT CONFIGURATION # # (created 2025-10-01T03:00:00.0) # # main cycle period hardwarePollingPeriod = 100 # geographic coordinates of the observation site # site latitude in degrees siteLatitude = 43.646711 # site longitude in degrees siteLongitude = 41.440732 # site elevation in meters siteElevation = 2070.0 # celestial coordinate transformation # wavelength at which refraction is calculated (in mkm) refractWavelength = 0.5 # an empty filename means default precompiled string leapSecondFilename = # an empty filename means default precompiled string bulletinAFilename = # pointing correction model # PCM default type pcmType = GEOMETRY # PCM geometrical coefficients pcmGeomCoeffs = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 # PCM B-spline degrees pcmBsplineDegree = 3, 3 # PCM B-spline knots along X-axis (HA-angle or azimuth). By default from 0 to 2*PI radians pcmBsplineXknots = 0.0, 0.6981317, 1.3962634, 2.0943951, 2.7925268, 3.4906585, 4.1887902, 4.88692191, 5.58505361, 6.28318531 # PCM B-spline knots along Y-axis (declination or zenithal distance). By default from -PI/6 to PI/2 radians pcmBsplineYknots = -0.52359878, -0.29088821, -0.05817764, 0.17453293, 0.40724349, 0.63995406, 0.87266463, 1.10537519, 1.33808576, 1.57079633 # PCM B-spline coeffs for along X-axis (HA-angle or azimuth) pcmBsplineXcoeffs = # PCM B-spline coeffs for along Y-axis (declination or zenithal distance) pcmBsplineYcoeffs = # slewing and tracking parameters # arcseconds per second #sideralRate = 15.0410686 # timeout for telemetry updating in milliseconds telemetryTimeout = 3000 # minimal allowed time in seconds to prohibited zone minTimeToPZone = 10 # a time interval to update prohibited zones related quantities (millisecs) updatingPZoneInterval = 5000 # coordinates difference in arcsecs to stop slewing slewToleranceRadius = 5.0 # target-mount coordinate difference in arcsecs to start adjusting of slewing adjustCoordDiff = 50.0 # minimum time in millisecs between two successive adjustments adjustCycleInterval = 300 # slew process timeout in seconds slewTimeout = 3600 # a time shift into future to compute target position in future (UT1-scale time duration, millisecs) timeShiftToTargetPoint = 10000 # minimum time in millisecs between two successive tracking corrections trackingCycleInterval = 300 # prohibited zones # minimal altitude in degrees pzMinAltitude = 10.0 # HA-axis limit switch minimal value in degrees pzLimitSwitchHAMin = -170.0 # HA-axis limit switch maximal value in degrees pzLimitSwitchHAMax = 170.0 # DEC-axis limit switch minimal value in degrees pzLimitSwitchDecMin = -90.0 # DEC-axis limit switch maximal value in degrees pzLimitSwitchDecMax = 90.0 # hardware-related # hardware mode: 1 - model mode, otherwise real mode RunModel = 0 # mount serial device paths MountDevPath = /dev/ttyUSB0 # mount serial device speed MountDevSpeed = 19200 # motor encoders serial device path EncoderDevPath = # X-axis encoder serial device path EncoderXDevPath = /dev/encoderX0 # Y-axis encoder serial device path EncoderYDevPath = /dev/encoderY0 # encoders serial device speed EncoderDevSpeed = 153000 # ==1 if encoder works as separate serial device, ==2 if there's new version with two devices SepEncoder = 2 # mount polling interval in millisecs MountReqInterval = 100 # encoders polling interval in millisecs EncoderReqInterval = 50 # mount axes rate calculation interval in millisecs EncoderSpeedInterval = 100 # X-axis coordinate PID P,I,D-params XPIDC = 0.8, 0.1, 0.3 # X-axis rate PID P,I,D-params XPIDV = 1.0, 0.01, 0.2 # Y-axis coordinate PID P,I,D-params YPIDC = 0.8, 0.1, 0.3 # Y-axis rate PID P,I,D-params YPIDV = 0.5, 0.2, 0.5 # maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller) hwMaxRateHA = 8.0 # maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller) hwMaxRateDEC = 10.0 )--"; } // namespace asibfm700