#pragma once /**/ #include #include #include #include #include #include #include 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 { 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; } 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 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 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)}, /* 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 */ // maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller) simple_config_record_t{"hwMaxRateHA", mcc::MccAngle(5.0_degs)}, // maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller) simple_config_record_t{"hwMaxRateDEC", mcc::MccAngle(5.0_degs)} ); class Asibfm700MountConfig : protected ConfigHolder { using base_t = ConfigHolder; public: using base_t::value; 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); } catch (std::ios_base::failure const& ex) { return ex.code(); } catch (...) { return std::make_error_code(std::errc::not_enough_memory); } fst.close(); } else { return ec; } return base_t::parse(buffer, deserializer); } protected: inline static auto deserializer = [](std::string_view str, auto& value) { using value_t = std::decay_t; bool ok; if constexpr (std::is_arithmetic_v || std::ranges::output_range || std::ranges::range) { return base_t::defaultDeserializeFunc(str, value); } else if constexpr (mcc::traits::mcc_time_duration_c) { typename value_t::rep vd; ok = base_t::defaultDeserializeFunc(str, vd); if (ok) { value = value_t{vd}; } } 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 { return false; } return ok; }; }; } // namespace asibfm700