From 0b7261a43157cbe7672694407867d940eccc4f3b Mon Sep 17 00:00:00 2001 From: "Timur A. Fatkhullin" Date: Tue, 30 Sep 2025 18:55:14 +0300 Subject: [PATCH] ... --- asibfm700/asibfm700_common.h | 2 +- asibfm700/asibfm700_configfile.h | 508 +++++++++++++++++++++++++------ mcc/CMakeLists.txt | 4 + mcc/tests/cfg_test.cpp | 54 ++++ 4 files changed, 481 insertions(+), 87 deletions(-) create mode 100644 mcc/tests/cfg_test.cpp diff --git a/asibfm700/asibfm700_common.h b/asibfm700/asibfm700_common.h index 8b1c94e..e05657f 100644 --- a/asibfm700/asibfm700_common.h +++ b/asibfm700/asibfm700_common.h @@ -104,7 +104,7 @@ struct Asibfm700MountConfigFileDefailts { // prohibited zones std::string pzMinAltitude{"10.0"}; // minimal altitude in degrees std::string pzLimitSwitchHAMin{""}; // HA-axis limit switch minimal value - std::string pzLimitSwitchHAMaz{""}; // HA-axis limit switch maximal value + std::string pzLimitSwitchHAMax{""}; // HA-axis limit switch maximal value // hardware std::string hwMaxRateHA{""}; // maximal moving rate along HA-axis (Y-axis of Sidereal servo microcontroller) diff --git a/asibfm700/asibfm700_configfile.h b/asibfm700/asibfm700_configfile.h index 5446646..0559dbe 100644 --- a/asibfm700/asibfm700_configfile.h +++ b/asibfm700/asibfm700_configfile.h @@ -2,120 +2,456 @@ /**/ +#include +#include +#include #include +#include +#include #include namespace asibfm700 { -// configuration record: -// keyword -// value -// conversion function (deserializer) +/* 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 - t.value; // value - // method to convert from string to value - { t.cnv_func(std::declval(), std::declval()) } -> std::convertible_to; + requires variant_valid_type_c; // value }; +// simple minimal-requirement configuration record class +template +struct simple_config_record_t { + std::string_view key; + T value; +}; -// table of records (std::tuple) -// +// description of config (a std::tuple of "config_record_c"s) template -concept config_table_c = requires(T t) { [](std::tuple) {}(t); }; +concept config_desc_c = requires(T t) { [](std::tuple) {}(t); }; -template -class ConfigTable +template +class ConfigHolder { protected: - TabT _cfgTable; + /* helper definitions */ - std::array> _keyHashes{}; + // deduce unique value types of the given config records + template + struct deduce_val_types; - bool keyExists(size_t hash) + 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) { - for (auto& h : _keyHashes) { - if (h == hash) { - return true; + [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; } - -public: - static constexpr char COMMENT_SYMBOL = '#'; - static constexpr char KEY_VALUE_DELIM = '='; - static constexpr char VALUE_VALUE_DELIM = ','; - - ConfigTable(const TabT& init_cfg) : _cfgTable(init_cfg) - { - _keyHashes = [this](std::index_sequence) { - return std::array{mcc::utils::FNV1aHash(std::get(_cfgTable).key)...}; - }(std::make_index_sequence>()); - } - - virtual ~ConfigTable() = default; - - - std::error_code parse(std::ranges::contiguous_range auto const& buffer) - requires std::same_as>, char> - { - std::string_view sv, curr_buffer{buffer.begin(), buffer.end()}; - - do { - auto r = std::ranges::find(curr_buffer, '\n'); - - sv = mcc::utils::trimSpaces(std::string_view(curr_buffer.begin(), r), mcc::utils::TrimType::TRIM_LEFT); - - if (sv.size()) { - if (sv[0] == COMMENT_SYMBOL) { // comment string - continue; - } - - auto it = std::ranges::find(sv, KEY_VALUE_DELIM); - if (it == sv.begin()) { // empty key! skip! - continue; - } - - auto key = mcc::utils::trimSpaces(std::string_view{sv.begin(), it}, mcc::utils::TrimType::TRIM_RIGHT); - if (!keyExists(mcc::utils::FNV1aHash(key))) { - } - - } // only spaces - - curr_buffer = {++r, buffer.end()}; - } while (!curr_buffer.empty()); - - return {}; - } - - - template - bool value(mcc::traits::mcc_input_char_range auto const& key, T& val) - { - const auto* kptr = &key; - const auto* vptr = &val; - - return [kptr, vptr, this](std::index_sequence) { - return ([&kptr, vptr, this]() { - if constexpr (std::convertible_to(_cfgTable).value), T>) { - if (keyExists(mcc::utils::FNV1aHash(*kptr))) { - *vptr = std::get(_cfgTable).value; - return true; - } - } - - return false; - }() || ...); - }(std::make_index_sequence>()); - } }; + +/* 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 diff --git a/mcc/CMakeLists.txt b/mcc/CMakeLists.txt index c758890..5d12810 100644 --- a/mcc/CMakeLists.txt +++ b/mcc/CMakeLists.txt @@ -92,5 +92,9 @@ if (WITH_TESTS) target_include_directories(${CTTE_TEST_APP} PRIVATE ${ERFA_INCLUDE_DIR}) target_link_libraries(${CTTE_TEST_APP} ERFA_LIB bsplines) + set(CFG_TEST_APP cfg_test) + add_executable(${CFG_TEST_APP} tests/cfg_test.cpp) + target_link_libraries(${CFG_TEST_APP} PRIVATE mcc) + enable_testing() endif() diff --git a/mcc/tests/cfg_test.cpp b/mcc/tests/cfg_test.cpp new file mode 100644 index 0000000..5f4bd8b --- /dev/null +++ b/mcc/tests/cfg_test.cpp @@ -0,0 +1,54 @@ +#include + +#include "../../asibfm700/asibfm700_configfile.h" + +template +struct rec_t { + std::string_view key; + VT value; +}; + +static std::string_view cfg_str = R"--(A = 11 + B=3.3 + # this is comment +C = WWWWWeeeWWWW + +E = 10,20, 40, 32 +)--"; + +int main() +{ + auto desc = std::make_tuple(rec_t{"A", 1}, rec_t{"B", 2.2}, rec_t{"C", std::string("EEE")}, rec_t{"D", 3.3}, + rec_t{"E", std::vector{1, 2, 3}}); + + asibfm700::ConfigHolder ch(desc); + + // auto err = ch.parse(cfg_str, [](auto s, auto &v) { + // if constexpr (std::is_arithmetic_v>) { + // v = 77; + // } else { + // v = std::string{s.begin(), s.end()}; + // } + + // return true; + // }); + + auto err = ch.parse(cfg_str); + + auto v = ch.value("A"); + + std::cout << v.value() << "\n"; + + // auto v2 = ch.value("D"); + auto v2 = ch.value("C"); + std::cout << v2.value_or("") << "\n"; + + auto v3 = ch.value>("E"); + std::cout << "["; + for (auto& el : v3.value_or({0, 0, 0})) { + std::cout << el << " "; + } + std::cout << "]\n"; + + return 0; +}