#pragma once #include #include #include #include "mcc/mcc_pzone.h" #include "asibfm700_common.h" #include "asibfm700_servocontroller.h" namespace asibfm700 { static constexpr double SAORAS_LATITUDE = 43.646711_degs; static constexpr double SAORAS_LONGITUDE = 41.440732_degs; template struct config_record_t : mcc::impl::mcc_simple_kv_record_t { std::vector head_comment; std::string_view inline_comment; }; template static config_record_t make_config_record( std::string_view key, T value, std::vector hcomm = {}, std::string_view icomm = {}, mcc::impl::mcc_serialization_params_t const& spars = mcc::impl::mcc_serialization_params_t{}) { return config_record_t{{key, value, value, spars}, hcomm, icomm}; } static auto Asibfm700MountConfigurationDefaults = std::make_tuple( /* geographic coordinates of the observation site */ make_config_record("siteLatitude", mcc::impl::MccAngle(SAORAS_LATITUDE), {" site latitude in degrees"}), make_config_record("siteLongitude", mcc::impl::MccAngle(SAORAS_LONGITUDE), {" site longitude in degrees"}), make_config_record("siteElevation", 2070.0, {" site elevation in meters"}), /* celestial coordinate transformation */ make_config_record("refractWavelength", 0.55, {" wavelength at which refraction is calculated (in mkm)"}), make_config_record("leapSecondFilename", std::string(), {" an empty filename means default precompiled string"}), make_config_record("bulletinAFilename", std::string(), {" an empty filename means default precompiled string"}), /* pointing correction model */ // PCM type make_config_record( "pcmType", mcc::impl::MccDefaultPCMType::PCM_TYPE_GEOMETRY, {" PCM type:", " a case-sensitive string:", " GEOMETRY - 'classic' geometry-based correction coefficients", " GEOMETRY-BSPLINE - previous one and additional 2D B-spline corrections", " BSPLINE - pure 2D B-spline corrections"}), // PCM geometrical coefficients make_config_record("pcmGeomCoeffs", std::vector{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}, {" PCM geometrical coefficients"}), // make_config_record("pcmBsplineDegree", std::vector{3, 3}, {"PCM B-spline degrees"}), // 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! make_config_record( "pcmBsplineXknots", mcc::impl::mccGenerateBsplineKnots(0.0, 360.0_degs, 8), {" 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!"}), // PCM B-spline knots along Y-axis (declination or zenithal distance). // By default from -SAORAS-latitude 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! make_config_record("pcmBsplineYknots", mcc::impl::mccGenerateBsplineKnots(-SAORAS_LATITUDE, 90.0_degs, 8), {" PCM B-spline knots along Y-axis (declination or zenithal distance).", " By default from -SAORAS-latitude 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!"}), // PCM B-spline coeffs for along X-axis (HA-angle or azimuth) make_config_record("pcmBsplineXcoeffs", std::vector{}, {" PCM B-spline coeffs for along X-axis (HA-angle)"}), // PCM B-spline coeffs for along Y-axis (declination or zenithal distance) make_config_record("pcmBsplineYcoeffs", std::vector{}, {" PCM B-spline coeffs for along Y-axis (declination angle)"}), // inverse PCM B-spline coeffs for along X-axis (HA-angle or azimuth) make_config_record("pcmInverseBsplineXcoeffs", std::vector{}, {" Inverse PCM B-spline coeffs for along X-axis (HA-angle)"}), // inverse PCM B-spline coeffs for along Y-axis (declination or zenithal distance) make_config_record("pcmInverseBsplineYcoeffs", std::vector{}, {" Inverse PCM B-spline coeffs for along Y-axis (declination angle)"}), /* prohibited zones */ // minimal altitude (default: 10.0 degrees and SAO RAS latitude) make_config_record("altLimPZ", mcc::impl::MccAltLimitPZ{10.0_degs, SAORAS_LATITUDE}, {" minimal altitude prohibited zone"}), // HA-axis limit switch make_config_record( "axisLimitSwitchHA", mcc::impl::MccAxisLimitSwitchPZ{-170.0_degs, 170.0_degs, nullptr}, {" HA-axis limit switch stop angles"}), // DEC-axis limit switch make_config_record( "axisLimitSwitchDEC", mcc::impl::MccAxisLimitSwitchPZ{-90.0_degs, 90.0_degs, nullptr}, {" DEC-axis limit switch stop angles"}), /* hardware-related */ // hardware polling period in millisecs (used in hardwareSetState method; see asibfm700_servocontroller.cpp) make_config_record("hardwarePollingPeriod", std::chrono::milliseconds{300}, {"hardware driver polling period in millisecs"}), // hardware polling timeout in millisecs (used in hardwareSetState method; see asibfm700_servocontroller.cpp) make_config_record("hardwarePollingTimeout", std::chrono::milliseconds{30000}, {"hardware driver polling timeout in millisecs"}), // hardware mode: 1 - model mode, otherwise real mode make_config_record("RunModel", 0, {" hardware mode: 1 - model mode, otherwise real mode"}), // mount serial device paths make_config_record("MountDevPath", std::string("/dev/ttyUSB0"), {" mount serial device paths"}), // mount serial device speed make_config_record("MountDevSpeed", 19200, {" mount serial device speed"}), // motor encoders serial device path make_config_record("EncoderDevPath", std::string(""), {" motor encoders serial device path"}), // X-axis encoder serial device path make_config_record("EncoderXDevPath", std::string("/dev/encoder_X0"), {" X-axis encoder serial device path"}), // Y-axis encoder serial device path make_config_record("EncoderYDevPath", std::string("/dev/encoder_Y0"), {" Y-axis encoder serial device path"}), // encoders serial device speed make_config_record("EncoderDevSpeed", 153000, {" encoders serial device speed"}), // ==1 if encoder works as separate serial device, ==2 if there's new version with two devices make_config_record( "SepEncoder", 2, {" ==1 if encoder works as separate serial device, ==2 if there's new version with two devices"}), // mount polling interval in millisecs make_config_record("MountReqInterval", std::chrono::milliseconds(100), {" mount polling interval in millisecs"}), // encoders polling interval in millisecs make_config_record("EncoderReqInterval", std::chrono::milliseconds(1), {" encoders polling interval in millisecs"}), // mount axes rate calculation interval in millisecs make_config_record("EncoderSpeedInterval", std::chrono::milliseconds(50), {" mount axes rate calculation interval in millisecs"}), make_config_record("PIDMaxDt", std::chrono::milliseconds(1000), {" maximal PID refresh time interval in millisecs", " NOTE: if PID data will be refreshed with interval longer than this value (e.g. user polls " " encoder data too rarely)", " then the PID 'expired' data will be cleared and new computing loop is started"}), make_config_record("PIDRefreshDt", std::chrono::milliseconds(100), {" PID refresh interval"}), make_config_record("PIDCycleDt", std::chrono::milliseconds(5000), {" PID I cycle time (analog of 'RC' for PID on opamps)"}), // // X-axis coordinate PID P,I,D-params // make_config_record("XPIDC", std::vector{0.5, 0.1, 0.2}, {" X-axis coordinate PID P,I,D-params"}), // X-axis rate PID P,I,D-params make_config_record("XPIDV", std::vector{0.09, 0.0, 0.05}, {" X-axis rate PID P,I,D-params"}), // // Y-axis coordinate PID P, I, D-params // make_config_record("YPIDC", std::vector{0.5, 0.1, 0.2}, {" Y-axis coordinate PID P, I, D-params"}), // Y-axis rate PID P,I,D-params make_config_record("YPIDV", std::vector{0.09, 0.0, 0.05}, {" Y-axis rate PID P,I,D-params"}), // maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller) make_config_record( "hwMaxRateHA", mcc::impl::MccAngle(8.0_degs), {" maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller)"}), // maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller) make_config_record( "hwMaxRateDEC", mcc::impl::MccAngle(10.0_degs), {" maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller)"}), make_config_record("MaxPointingErr", mcc::impl::MccAngle(8.0_degs), {" slewing-to-pointing mode angular limit in degrees"}), make_config_record("MaxFinePointingErr", mcc::impl::MccAngle(1.5_degs), {" pointing-to-guiding mode angular limit in degrees"}), make_config_record("MaxGuidingErr", mcc::impl::MccAngle(0.5_arcsecs), {" guiding 'good'-flag error cirle radius (mount-to-target distance) in degrees"}), make_config_record("XEncZero", (int64_t)0, {" X-axis encoder zero-point in ticks"}), make_config_record("YEncZero", (int64_t)0, {" Y-axis encoder zero-point in ticks"}), /* movement related common parameters */ // timeout for telemetry updating in milliseconds make_config_record("telemetryTimeout", std::chrono::milliseconds(3000), {" timeout for telemetry updating in milliseconds"}), // mount stopping process timeout in seconds make_config_record("stopTimeout", std::chrono::seconds(30), {" mount stopping process timeout in seconds"}), // minimal allowed time in seconds to prohibited zone make_config_record("minTimeToPZone", std::chrono::seconds(10), {" minimal allowed time in seconds to prohibited zone"}), /* slewing and tracking parameters */ // telemetry request interval (in millisecs) in slewing mode make_config_record("slewingTelemetryInterval", std::chrono::milliseconds(100), {" telemetry request interval (in millisecs) in slewing mode"}), // slew process timeout in seconds make_config_record("slewTimeout", std::chrono::seconds(3600), {" slew process timeout in seconds"}), // coordinates difference in arcsecs to stop slewing make_config_record("slewToleranceRadius", 5.0, {"coordinates difference in arcsecs to stop slewing"}), // slewing trajectory filename (used for debugging purposes) // if it is an empty - just skip saving make_config_record("slewingPathFilename", std::string(), {" slewing trajectory filename", "if it is an empty - just skip saving"}), make_config_record("trackingTelemetryInterval", std::chrono::milliseconds(100), {" telemetry request interval (in millisecs) in tracking mode"}), // maximal valid target-to-mount distance for tracking process (arcsecs) // if current distance is greater than assume current mount coordinate as target point make_config_record("trackingMaxCoordDiff", 20.0, {" maximal valid target-to-mount distance for tracking process (arcsecs)", " if current distance is greater than assume current mount coordinate as target point"}), make_config_record("trackingPathFilename", std::string(), {" tracking trajectory filename", "if it is an empty - just skip saving"}) ); static_assert(std::is_copy_assignable_v>); static_assert(std::is_copy_assignable_v>); // static_assert(std::is_copy_assignable_v); class Asibfm700MountConfiguration : public mcc::impl::MccKeyValueHolder { using base_t = mcc::impl::MccKeyValueHolder; public: Asibfm700MountConfiguration() : base_t(Asibfm700MountConfigurationDefaults) { // fill comments auto get_comm = [this]() { auto& rec = std::get(Asibfm700MountConfigurationDefaults); for (auto const& comm : rec.head_comment) { if (comm.size()) { _headComment[_hashes[I]].emplace_back(std::string{comm.begin(), comm.end()}); } else { _headComment[_hashes[I]].emplace_back(std::nullopt); } } _inlineComment[_hashes[I]] = rec.inline_comment; }; [&](std::index_sequence) { (get_comm.template operator()(), ...); }(std::make_index_sequence{}); } ~Asibfm700MountConfiguration() = default; Asibfm700MountConfiguration(const Asibfm700MountConfiguration&) = default; Asibfm700MountConfiguration(Asibfm700MountConfiguration&&) = default; Asibfm700MountConfiguration& operator=(const Asibfm700MountConfiguration&) = default; Asibfm700MountConfiguration& operator=(Asibfm700MountConfiguration&&) = 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(); // remove possible header (see "save" method) size_t nskip = headerLines; if (!std::regex_match(buffer, headerRx)) { nskip = 0; } ec = base_t::fromCharRange(buffer, nskip); if (!ec) { // remove possible spaces in filenames std::string val = getValue("leapSecondFilename").value_or(""); auto fname = mcc::utils::trimSpaces(val); setValue("leapSecondFilename", fname); val = getValue("bulletinAFilename").value_or(""); fname = mcc::utils::trimSpaces(val); setValue("bulletinAFilename", fname); val = getValue("MountDevPath").value_or(std::string{}); fname = mcc::utils::trimSpaces(val); setValue("MountDevPath", fname); val = getValue("EncoderDevPath").value_or(std::string{}); fname = mcc::utils::trimSpaces(val); setValue("EncoderDevPath", fname); val = getValue("EncoderXDevPath").value_or(std::string{}); fname = mcc::utils::trimSpaces(val); setValue("EncoderXDevPath", fname); val = getValue("EncoderYDevPath").value_or(std::string{}); fname = mcc::utils::trimSpaces(val); setValue("EncoderYDevPath", fname); val = getValue("slewingPathFilename").value_or(std::string{}); fname = mcc::utils::trimSpaces(val); setValue("slewingPathFilename", fname); val = getValue("trackingPathFilename").value_or(std::string{}); fname = mcc::utils::trimSpaces(val); setValue("trackingPathFilename", fname); } } 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); } _lastConfigPath = path; } return ec; } std::error_code reloadCurrentConfig() { return load(_lastConfigPath); } std::error_code save(const std::filesystem::path& path) { std::error_code ec; std::string buff; ec = toCharRange(buff); if (ec) { return ec; } std::ofstream fst(path, std::ios_base::trunc); if (!fst.is_open()) { ec = std::make_error_code(std::errc::io_error); } else { try { fst << generateHeader(); fst << buff; } catch (std::ios_base::failure const& ex) { ec = ex.code(); } catch (...) { ec = std::make_error_code(std::errc::operation_canceled); } } return ec; } std::error_code save() { return save(_lastConfigPath); } std::error_code dumpDefaultsToFile(const std::filesystem::path& path) { std::error_code ec{}; std::string output_buffer; #ifdef __GNUG__ // to fix GCC compilation crash for the versions < 16 #if GCC_VERSION < 160000 auto write_rec = [&output_buffer, &ec, obj_ptr = this](this auto& self) -> void { #else auto write_rec = [&output_buffer, &ec, this](this auto& self) -> void { #endif #endif if constexpr (I < NUMBER_OF_RECORDS) { // add an empty string within records std::format_to(std::back_inserter(output_buffer), "{}", DEFAULT_RECORD_DELIMITER); #ifdef __GNUG__ // to fix GCC compilation crash for the versions < 16 #if GCC_VERSION < 160000 ec = obj_ptr->template formatRecord(output_buffer, DEFAULT_RECORD_DELIMITER, true); #else ec = formatRecord(output_buffer, DEFAULT_RECORD_DELIMITER, true); #endif #endif if (ec) { return; } self.template operator()(); } }; write_rec(); // [&output_buffer, &ec, this](std::index_sequence) { // ((std::format_to(std::back_inserter(output_buffer), "{}", DEFAULT_RECORD_DELIMITER), // ec = formatRecord(output_buffer, DEFAULT_RECORD_DELIMITER, true)), // ...); // }(std::make_index_sequence()); if (!ec) { std::ofstream fst(path, std::ios_base::trunc); if (!fst.is_open()) { ec = std::make_error_code(std::errc::io_error); } else { try { fst << generateHeader(); fst << output_buffer; } catch (std::ios_base::failure const& ex) { ec = ex.code(); } catch (...) { ec = std::make_error_code(std::errc::operation_canceled); } } } return ec; } std::filesystem::path configFilename() const { return _lastConfigPath; } /* some most often used quantities */ template T siteLatitude() const { return static_cast(getValue("siteLatitude").value_or(mcc::impl::MccAngle{})); }; mcc::impl::MccAngle siteLatitude() const { return siteLatitude(); }; template T siteLongitude() const { return static_cast(getValue("siteLongitude").value_or(mcc::impl::MccAngle{})); }; mcc::impl::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; std::string val = getValue("leapSecondFilename").value_or(""); std::ranges::copy(val, std::back_inserter(r)); return r; } std::string leapSecondFilename() const { return leapSecondFilename(); }; template R bulletinAFilename() const { R r; std::string val = getValue("bulletinAFilename").value_or(""); std::ranges::copy(val, std::back_inserter(r)); return r; } std::string bulletinAFilename() const { return bulletinAFilename(); }; AsibFM700ServoController::hardware_config_t servoControllerConfig() const { AsibFM700ServoController::hardware_config_t hw_cfg; hw_cfg.hwConfig = {}; hw_cfg.pollingInterval = getValue("hardwarePollingPeriod").value_or(std::chrono::milliseconds(300)); hw_cfg.pollingInterval = getValue("hardwarePollingTimeout").value_or(std::chrono::milliseconds(30000)); hw_cfg.MountDevPath = getValue("MountDevPath").value_or(std::string{}); hw_cfg.EncoderDevPath = getValue("EncoderDevPath").value_or(std::string{}); hw_cfg.EncoderXDevPath = getValue("EncoderXDevPath").value_or(std::string{}); hw_cfg.EncoderYDevPath = getValue("EncoderYDevPath").value_or(std::string{}); 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(int{}); hw_cfg.devConfig.MountDevSpeed = getValue("MountDevSpeed").value_or(int{}); hw_cfg.devConfig.EncoderDevSpeed = getValue("EncoderDevSpeed").value_or(int{}); hw_cfg.devConfig.SepEncoder = getValue("SepEncoder").value_or(int{}); std::chrono::duration secs; // seconds as floating-point secs = getValue("MountReqInterval").value_or(std::chrono::milliseconds{}); hw_cfg.devConfig.MountReqInterval = secs.count(); secs = getValue("EncoderReqInterval").value_or(std::chrono::milliseconds{}); hw_cfg.devConfig.EncoderReqInterval = secs.count(); secs = getValue("EncoderSpeedInterval").value_or(std::chrono::milliseconds{}); hw_cfg.devConfig.EncoderSpeedInterval = secs.count(); secs = getValue("PIDMaxDt").value_or(std::chrono::milliseconds{1000}); hw_cfg.devConfig.PIDMaxDt = secs.count(); secs = getValue("PIDRefreshDt").value_or(std::chrono::milliseconds{100}); hw_cfg.devConfig.PIDRefreshDt = secs.count(); secs = getValue("PIDCycleDt").value_or(std::chrono::milliseconds{5000}); hw_cfg.devConfig.PIDCycleDt = secs.count(); std::vector pid = getValue>("XPIDV").value_or(std::vector{}); 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>("YPIDV").value_or(std::vector{}); 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]; } double ang = getValue("MaxPointingErr").value_or(mcc::impl::MccAngle(8.0_degs)); hw_cfg.devConfig.MaxPointingErr = ang; ang = getValue("MaxFinePointingErr").value_or(mcc::impl::MccAngle(1.5_degs)); hw_cfg.devConfig.MaxFinePointingErr = ang; ang = getValue("MaxGuidingErr").value_or(mcc::impl::MccAngle(0.5_arcsecs)); hw_cfg.devConfig.MaxGuidingErr = ang; ang = getValue("XEncZero").value_or(0); hw_cfg.devConfig.XEncZero = ang; ang = getValue("YEncZero").value_or(0); hw_cfg.devConfig.YEncZero = ang; return hw_cfg; } 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], .DECaxisFlexure = vec[7], .forkFlexure = vec[8]}; } #ifdef USE_BSPLINE_PCM // 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::impl::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.clear(); std::ranges::copy_n(vec.begin(), Ncoeffs, std::back_inserter(pcm_data.bspline.coeffsX)); // 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.clear(); std::ranges::copy_n(vec.begin(), Ncoeffs, std::back_inserter(pcm_data.bspline.coeffsY)); // pcm_data.bspline.coeffsY.resize(Ncoeffs); // for (size_t i = 0; i < Ncoeffs; ++i) { // pcm_data.bspline.coeffsY[i] = vec[i]; // } } vec = getValue>("pcmInverseBsplineXcoeffs").value_or(empty_vec); if (vec.size() >= Ncoeffs) { pcm_data.bspline.inverseCoeffsX.clear(); std::ranges::copy_n(vec.begin(), Ncoeffs, std::back_inserter(pcm_data.bspline.inverseCoeffsX)); } vec = getValue>("pcmInverseBsplineYcoeffs").value_or(empty_vec); if (vec.size() >= Ncoeffs) { pcm_data.bspline.inverseCoeffsY.clear(); std::ranges::copy_n(vec.begin(), Ncoeffs, std::back_inserter(pcm_data.bspline.inverseCoeffsY)); } #endif return pcm_data; } mcc::impl::MccAltLimitPZ minAltPZone() { return getValue>("altLimPZ") .value_or(mcc::impl::MccAltLimitPZ{10.0, SAORAS_LATITUDE}); } mcc::impl::MccAxisLimitSwitchPZ axisLimitSwitchHA() { return getValue>( "axisLimitSwitchHA") .value_or(mcc::impl::MccAxisLimitSwitchPZ{ -170.0_degs, 170.0_degs, nullptr}); } mcc::impl::MccAxisLimitSwitchPZ axisLimitSwitchDEC() { return getValue>( "axisLimitSwitchDEC") .value_or(mcc::impl::MccAxisLimitSwitchPZ{ -90.0_degs, 90.0_degs, nullptr}); } details::movement_pars_t movingModelParams() const { details::movement_pars_t pars; auto get_value = [this](std::string_view name, VT& val) { val = getValue(name).value_or(val); }; pars.telemetryTimeout = getValue("telemetryTimeout").value_or(pars.telemetryTimeout); pars.minTimeToPZone = getValue("minTimeToPZone").value_or(pars.minTimeToPZone); pars.slewToleranceRadius = getValue("slewToleranceRadius").value_or(pars.slewToleranceRadius) * mcc::MCC_ARCSECS_TO_RADS; get_value("slewingTelemetryInterval", pars.slewingTelemetryInterval); pars.slewRateX = getValue("hwMaxRateHA").value_or(pars.slewRateX); pars.slewRateY = getValue("hwMaxRateDEC").value_or(pars.slewRateY); pars.adjustCoordDiff = getValue("adjustCoordDiff").value_or(pars.adjustCoordDiff) * mcc::MCC_DEGRESS_TO_RADS; pars.slewTimeout = getValue("slewTimeout").value_or(pars.slewTimeout); pars.slewingPathFilename = getValue("slewingPathFilename").value_or(std::string()); get_value("trackingTelemetryInterval", pars.trackingTelemetryInterval); pars.trackingCycleInterval = getValue("trackingCycleInterval") .value_or(pars.trackingCycleInterval); pars.trackingMaxCoordDiff = getValue("trackingMaxCoordDiff").value_or(pars.trackingMaxCoordDiff) * mcc::MCC_ARCSECS_TO_RADS; pars.trackingPathFilename = getValue("trackingPathFilename").value_or(std::string()); return pars; } private: std::filesystem::path _lastConfigPath{}; static constexpr size_t headerLines = 5; // number of lines in the header static std::string generateHeader() { std::string s{ "#\n" "# ASTROSIB FM-700 MOUNT CONFIGURATION\n" "#\n"}; std::format_to( std::back_inserter(s), "# (created at {:%FT%T} UTC)\n#\n", std::chrono::round>>(std::chrono::system_clock::now())); // auto time_stamp = // std::chrono::round>>(std::chrono::system_clock::now()); // std::string s = std::format( // "{0:}{1:}{0:} ASTROSIB FM-700 MOUNT CONFIGURATION{1:}{0:}{1:}{0:} (created at {2:%FT%T} // UTC){1:}{0:}{1:}", COMMENT_SEQ, rec_delim, time_stamp); return s; } // regex for buffer started with standard header inline static const std::regex headerRx{ "^[ \\n]*" "# *\\n" "# *ASTROSIB FM-700 MOUNT CONFIGURATION *\\n" "# *\\n" "# *\\(created at 20[0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9] UTC\\) *\\n" "# *\\n" "[\\s\\S]*"}; }; } // namespace asibfm700