diff --git a/asibfm700/asibfm700_configfile.h b/asibfm700/asibfm700_configfile.h index 195508d..e232578 100644 --- a/asibfm700/asibfm700_configfile.h +++ b/asibfm700/asibfm700_configfile.h @@ -473,7 +473,312 @@ static auto Asibfm700MountConfigDefaults = std::make_tuple( -class Asibfm700MountConfig : protected ConfigHolder +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{}; + + 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); + } else if constexpr (std::same_as) { // assume here all angles are in degrees + double vd; + ec = base_t::defaultDeserializeFunc(str, vd); + if (!ec) { + value = mcc::MccAngle(vd, mcc::MccDegreeTag{}); + } + } else if constexpr (std::same_as) { + std::string vstr; + ec = base_t::defaultDeserializeFunc(str, vstr); + + 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; + }; + + void updateAll() + { + hardwarePollingPeriod = getValue("hardwarePollingPeriod").value_or({}); + + // CCTE + + siteLatitude = getValue("siteLatitude").value_or({}); + siteLongitude = getValue("siteLongitude").value_or({}); + siteElevation = getValue("siteElevation").value_or({}); + refractWavelength = getValue("refractWavelength").value_or({}); + + leapSecondFilename = getValue("leapSecondFilename").value_or({}); + bulletinAFilename = getValue("bulletinAFilename").value_or({}); + + // prohibited zones + + pzMinAltitude = getValue("pzMinAltitude").value_or({}); + pzLimitSwitchHAMin = getValue("pzLimitSwitchHAMin").value_or({}); + pzLimitSwitchHAMax = getValue("pzLimitSwitchHAMax").value_or({}); + + + // hardware config + + servoControllerConfig.hwConfig = {}; + + servoControllerConfig.MountDevPath = getValue("MountDevPath").value_or({}); + servoControllerConfig.EncoderDevPath = getValue("EncoderDevPath").value_or({}); + servoControllerConfig.EncoderXDevPath = getValue("EncoderXDevPath").value_or({}); + servoControllerConfig.EncoderYDevPath = getValue("EncoderYDevPath").value_or({}); + + 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 = getValue("RunModel").value_or({}); + servoControllerConfig.devConfig.MountDevSpeed = getValue("MountDevSpeed").value_or({}); + servoControllerConfig.devConfig.EncoderDevSpeed = getValue("EncoderDevSpeed").value_or({}); + servoControllerConfig.devConfig.SepEncoder = getValue("SepEncoder").value_or({}); + + std::chrono::duration secs; // seconds as floating-point + + secs = getValue("MountReqInterval").value_or({}); + servoControllerConfig.devConfig.MountReqInterval = secs.count(); + + secs = getValue("EncoderReqInterval").value_or({}); + servoControllerConfig.devConfig.EncoderReqInterval = secs.count(); + + secs = getValue("EncoderSpeedInterval").value_or({}); + servoControllerConfig.devConfig.EncoderSpeedInterval = secs.count(); + + std::vector pid = getValue>("XPIDC").value_or({}); + if (pid.size() > 2) { + servoControllerConfig.devConfig.XPIDC.P = pid[0]; + servoControllerConfig.devConfig.XPIDC.I = pid[1]; + servoControllerConfig.devConfig.XPIDC.D = pid[2]; + } + + pid = getValue>("XPIDV").value_or({}); + if (pid.size() > 2) { + servoControllerConfig.devConfig.XPIDV.P = pid[0]; + servoControllerConfig.devConfig.XPIDV.I = pid[1]; + servoControllerConfig.devConfig.XPIDV.D = pid[2]; + } + + pid = getValue>("YPIDC").value_or({}); + if (pid.size() > 2) { + servoControllerConfig.devConfig.YPIDC.P = pid[0]; + servoControllerConfig.devConfig.YPIDC.I = pid[1]; + servoControllerConfig.devConfig.YPIDC.D = pid[2]; + } + + pid = getValue>("YPIDV").value_or({}); + 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 = + getValue("telemetryTimeout").value_or({}); + + movingModelParams.minTimeToPZone = + getValue("minTimeToPZone").value_or({}); + + movingModelParams.updatingPZoneInterval = + getValue("updatingPZoneInterval").value_or({}); + + movingModelParams.slewToleranceRadius = + getValue("slewToleranceRadius").value_or({}); + + movingModelParams.adjustCoordDiff = + getValue("adjustCoordDiff").value_or({}); + + movingModelParams.adjustCycleInterval = + getValue("adjustCycleInterval").value_or({}); + + movingModelParams.slewTimeout = getValue("slewTimeout").value_or({}); + + movingModelParams.timeShiftToTargetPoint = + getValue("timeShiftToTargetPoint").value_or({}); + + movingModelParams.trackingCycleInterval = + getValue("trackingCycleInterval").value_or({}); + + + // PCM data + + pcmData.type = getValue("pcmType").value_or({}); + + pcmData.siteLatitude = getValue("siteLatitude").value_or({}); + + pid = getValue>("pcmGeomCoeffs").value_or({}); + 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 = getValue("pcmBsplineDegree").value_or({}); + if (dd.size() >= 2) { + pcmData.bspline.bsplDegreeX = dd[0] > 0 ? dd[0] : 3; + pcmData.bspline.bsplDegreeY = dd[1] > 0 ? dd[1] : 3; + } + + pid = getValue>("pcmBsplineXknots").value_or({}); + // 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 = getValue>("pcmBsplineYknots").value_or({}); + // 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 = getValue>("pcmBsplineXcoeffs").value_or({}); + + if (pid.size() >= Ncoeffs) { + pcmData.bspline.coeffsX.resize(Ncoeffs); + for (size_t i = 0; i < Ncoeffs; ++i) { + pcmData.bspline.coeffsX[i] = pid[i]; + } + } + + pid = getValue>("pcmBsplineYcoeffs").value_or({}); + + if (pid.size() >= Ncoeffs) { + pcmData.bspline.coeffsY.resize(Ncoeffs); + for (size_t i = 0; i < Ncoeffs; ++i) { + pcmData.bspline.coeffsY[i] = pid[i]; + } + } + } + +public: + 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{}; + + + 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); + 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 + std::error_code setValue(std::string_view key, const T& value) + { + auto ec = base_t::setValue(key, value); + if (!ec) { + updateAll(); + } + + return ec; + } +}; + + +class Asibfm700MountConfig2 : protected ConfigHolder { using base_t = ConfigHolder; @@ -481,12 +786,12 @@ public: using base_t::update; using base_t::value; - Asibfm700MountConfig() : base_t(Asibfm700MountConfigDefaults) + Asibfm700MountConfig2() : base_t(Asibfm700MountConfigDefaults) { updateAll(); } - ~Asibfm700MountConfig() = default; + ~Asibfm700MountConfig2() = default; std::error_code load(const std::filesystem::path& path) { diff --git a/asibfm700/tests/cfg_test.cpp b/asibfm700/tests/cfg_test.cpp index 341fb13..306c57a 100644 --- a/asibfm700/tests/cfg_test.cpp +++ b/asibfm700/tests/cfg_test.cpp @@ -56,15 +56,18 @@ int main() fst.close(); asibfm700::Asibfm700MountConfig acfg; + // asibfm700::Asibfm700MountConfig2 acfg; auto ec = acfg.load("/tmp/cfg.cfg"); std::cout << "EC (load) = " << ec.message() << "\n"; std::cout << "refr w: " << acfg.refractWavelength << "\n"; - acfg.update("refractWavelength", 0.3); + // acfg.update("refractWavelength", 0.3); + acfg.setValue("refractWavelength", 0.3); - auto e = acfg.value("refractWavelength"); + auto e = acfg.getValue("refractWavelength"); + // auto e = acfg.value("refractWavelength"); std::cout << "refr w: " << e.value_or(0.0) << "\n"; std::cout << "refr w: " << acfg.refractWavelength << "\n"; @@ -78,5 +81,19 @@ int main() std::cout << "kvh[C] = " << vs.value_or("") << "\n"; } + ec = kvh.fromCharRange(cfg_str); + if (ec) { + std::cout << "EC = " << ec.message() << "\n"; + } else { + v3 = kvh.getValue>("E"); + std::cout << "["; + for (auto& el : v3.value_or({0, 0, 0})) { + std::cout << el << " "; + } + std::cout << "]\n"; + } + + + return 0; } diff --git a/mcc/mcc_utils.h b/mcc/mcc_utils.h index 89ad893..08e746f 100644 --- a/mcc/mcc_utils.h +++ b/mcc/mcc_utils.h @@ -427,6 +427,56 @@ public: static constexpr std::string_view KEY_VALUE_DELIM{"="}; static constexpr std::string_view VALUE_ARRAY_DELIM{","}; + inline static auto defaultDeserializeFunc = [](this auto&& self, std::string_view str, VT& value) { + std::error_code ret{}; + + if constexpr (std::is_arithmetic_v) { + auto v = mcc::utils::numFromStr(trimSpaces(str)); + if (!v.has_value()) { + return std::make_error_code(std::errc::invalid_argument); + } + + value = v.value(); + } else if constexpr (mcc::traits::mcc_output_char_range) { + VT 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 std::make_error_code(std::errc::invalid_argument); + } + + VT r; + el_t elem; + + auto els = std::views::split(str, VALUE_ARRAY_DELIM); + + for (auto const& el : els) { + ret = std::forward(self)(std::string_view(el), elem); + if (!ret) { + std::back_inserter(r) = elem; + } else { + return std::make_error_code(std::errc::invalid_argument); + } + } + + value = r; + } else if constexpr (mcc::traits::mcc_time_duration_c) { + typename VT::rep vd; + + ret = std::forward(self)(trimSpaces(str), vd); + if (!ret) { + value = VT{vd}; + } + } else { + ret = std::make_error_code(std::errc::invalid_argument); + } + + return ret; + }; + KeyValueHolder(DESCR_T desc) : _keyValue(desc) { [this](std::index_sequence) { @@ -468,6 +518,12 @@ public: } + template + std::error_code fromCharRange(const R& buffer, RecDelimT rec_delim = std::string_view("\n")) + { + return fromCharRange(buffer, KeyValueHolder::defaultDeserializeFunc, std::move(rec_delim)); + } + template @@ -475,6 +531,8 @@ public: DeserFuncT&& deser_func, RecDelimT rec_delim = std::string_view("\n")) { + // static_assert(mcc::traits::mcc_callable_c>, "!!!!!!!"); + if constexpr (std::is_array_v>) { // char*, const char* if constexpr (std::is_array_v>) { return fromCharRange(std::string_view{buffer}, std::forward(deser_func), @@ -489,13 +547,19 @@ public: } } - auto parse_rec = [this, &deser_func](std::string_view rec) -> std::error_code { - std::string_view key, value; + + std::error_code ec{}; + std::string_view rec, key, value; + + + auto recs = std::views::split(buffer, std::move(rec_delim)); + for (auto const& el : recs) { + rec = mcc::utils::trimSpaces(el, TrimType::TRIM_LEFT); if (rec.size()) { auto found = std::ranges::search(rec, COMMENT_SEQ); if (found.begin() != rec.end()) { // there was the comment sequence in record - rec = rec.substr(std::distance(rec.begin(), found.begin())); + rec = std::string_view(rec.begin(), found.begin()); } if (rec.size()) { @@ -504,35 +568,17 @@ public: key = trimSpaces(std::string_view(rec.begin(), found.begin()), TrimType::TRIM_RIGHT); value = std::string_view(found.end(), rec.end()); - return forKey(key, [value, &deser_func](VT& v) { return deser_func(value, v); }); + ec = forKey(key, [value, &deser_func](VT& v) { return deser_func(value, v); }); } - } + } // just comment string starting from the beginning, just skip it (no error) + + } // empty record, just skip it (no error) + + if (ec) { + break; } - }; - - auto curr_buffer = std::string_view(buffer.begin(), buffer.end()); - bool buffer_end = false; - std::error_code ec; - std::string_view rec; - - auto delim_size = std::ranges::size(rec_delim); - if (delim_size == 0) { // just one record - return parse_rec(trimSpaces(buffer, TrimType::TRIM_LEFT)); } - do { - auto found = std::ranges::search(curr_buffer, rec_delim); - if (found.begin() == curr_buffer.end()) { - buffer_end = true; - } - - rec = mcc::utils::trimSpaces(std::string_view(curr_buffer.begin(), found.begin()), TrimType::TRIM_LEFT); - - curr_buffer = {found.end(), curr_buffer.end()}; - - ec = parse_rec(rec); - } while (!buffer_end || !ec); - return ec; }