#pragma once /**/ #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 config_record_valid_type_c = requires { !std::is_array_v && !std::is_void_v && !std::is_reference_v; }; // simple minimal-requirement configuration record class template struct simple_config_record_t { std::string_view key; T value; std::vector comment; }; /* 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}, {"main cycle period in millisecs"}}, /* geographic coordinates of the observation site */ // site latitude in degrees simple_config_record_t{"siteLatitude", mcc::MccAngle(43.646711_degs), {"site latitude in degrees"}}, // site longitude in degrees simple_config_record_t{"siteLongitude", mcc::MccAngle(41.440732_degs), {"site longitude in degrees"}}, // site elevation in meters simple_config_record_t{"siteElevation", 2070.0, {"site elevation in meters"}}, /* celestial coordinate transformation */ // wavelength at which refraction is calculated (in mkm) simple_config_record_t{"refractWavelength", 0.55, {"wavelength at which refraction is calculated (in mkm)"}}, // an empty filename means default precompiled string simple_config_record_t{"leapSecondFilename", std::string(), {"an empty filename means default precompiled string"}}, // an empty filename means default precompiled string simple_config_record_t{"bulletinAFilename", std::string(), {"an empty filename means default precompiled string"}}, /* pointing correction model */ // PCM default type simple_config_record_t{"pcmType", mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY, {"PCM type:", "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 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 geometrical coefficients"}}, // PCM B-spline degrees simple_config_record_t{"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! 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 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 -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 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!"}}, // 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 X-axis (HA-angle)"}}, // PCM B-spline coeffs for along Y-axis (declination or zenithal distance) simple_config_record_t{"pcmBsplineYcoeffs", std::vector{}, {"PCM B-spline coeffs for along Y-axis (declination angle)"}}, /* 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), {"timeout for telemetry updating in milliseconds"}}, // minimal allowed time in seconds to prohibited zone simple_config_record_t{"minTimeToPZone", std::chrono::seconds(10), {"minimal allowed time in seconds to prohibited zone"}}, // a time interval to update prohibited zones related quantities (millisecs) simple_config_record_t{"updatingPZoneInterval", std::chrono::milliseconds(5000), {"a time interval to update prohibited zones related quantities (millisecs)"}}, // coordinates difference in arcsecs to stop slewing simple_config_record_t{"slewToleranceRadius", 5.0, {"coordinates difference in arcsecs to stop slewing"}}, simple_config_record_t{"slewingTelemetryInterval", std::chrono::milliseconds(100), {"telemetry request interval (in millisecs) in slewing mode"}}, // target-mount coordinate difference in arcsecs to start adjusting of slewing simple_config_record_t{"adjustCoordDiff", 50.0, {"target-mount coordinate difference in arcsecs to start adjusting of slewing"}}, // minimum time in millisecs between two successive adjustments simple_config_record_t{"adjustCycleInterval", std::chrono::milliseconds(300), {"minimum time in millisecs between two successive adjustments"}}, // slew process timeout in seconds simple_config_record_t{"slewTimeout", std::chrono::seconds(3600), {"slew process timeout in seconds"}}, // 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), {"a time shift into future to compute target position in future (UT1-scale time duration, millisecs)"}}, simple_config_record_t{"trackingTelemetryInterval", std::chrono::milliseconds(100), {"telemetry request interval (in millisecs) in tracking mode"}}, // minimum time in millisecs between two successive tracking corrections simple_config_record_t{"trackingCycleInterval", std::chrono::milliseconds(300), {"minimum time in millisecs between two successive tracking corrections"}}, // 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, {"maximal valid target-to-mount distance for tracking process (arcsecs)", "if current distance is greater than assume current mount coordinate as target point"}}, /* prohibited zones */ // minimal altitude simple_config_record_t{"pzMinAltitude", mcc::MccAngle(10.0_degs), {"minimal altitude"}}, // HA-axis limit switch minimal value simple_config_record_t{"pzLimitSwitchHAMin", mcc::MccAngle(-270.0_degs), {"HA-axis limit switch minimal value"}}, // HA-axis limit switch maximal value simple_config_record_t{"pzLimitSwitchHAMax", mcc::MccAngle(270.0_degs), {"HA-axis limit switch maximal value"}}, // DEC-axis limit switch minimal value simple_config_record_t{"pzLimitSwitchDecMin", mcc::MccAngle(-90.0_degs), {"DEC-axis limit switch minimal value"}}, // DEC-axis limit switch maximal value simple_config_record_t{"pzLimitSwitchDecMax", mcc::MccAngle(90.0_degs), {"DEC-axis limit switch maximal value"}}, /* hardware-related */ // hardware mode: 1 - model mode, otherwise real mode simple_config_record_t{"RunModel", 0, {"hardware mode: 1 - model mode, otherwise real mode"}}, // mount serial device paths simple_config_record_t{"MountDevPath", std::string("/dev/ttyUSB0"), {"mount serial device paths"}}, // mount serial device speed simple_config_record_t{"MountDevSpeed", 19200, {"mount serial device speed"}}, // motor encoders serial device path simple_config_record_t{"EncoderDevPath", std::string(""), {"motor encoders serial device path"}}, // X-axis encoder serial device path simple_config_record_t{"EncoderXDevPath", std::string("/dev/encoderX0"), {"X-axis encoder serial device path"}}, // Y-axis encoder serial device path simple_config_record_t{"EncoderYDevPath", std::string("/dev/encoderY0"), {"Y-axis encoder serial device path"}}, // encoders serial device speed simple_config_record_t{"EncoderDevSpeed", 153000, {"encoders serial device speed"}}, // ==1 if encoder works as separate serial device, ==2 if there's new version with two devices simple_config_record_t{ "SepEncoder", 2, {"==1 if encoder works as separate serial device, ==2 if there's new version with two devices"}}, // mount polling interval in millisecs simple_config_record_t{"MountReqInterval", std::chrono::milliseconds(100), {"mount polling interval in millisecs"}}, // encoders polling interval in millisecs simple_config_record_t{"EncoderReqInterval", std::chrono::milliseconds(1), {"encoders polling interval in millisecs"}}, // mount axes rate calculation interval in millisecs simple_config_record_t{"EncoderSpeedInterval", std::chrono::milliseconds(100), {"mount axes rate calculation interval in millisecs"}}, simple_config_record_t{"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"}}, simple_config_record_t{"PIDRefreshDt", std::chrono::milliseconds(100), {"PID refresh interval"}}, simple_config_record_t{"PIDCycleDt", std::chrono::milliseconds(5000), {"PID I cycle time (analog of 'RC' for PID on opamps)"}}, // X-axis coordinate PID P,I,D-params simple_config_record_t{"XPIDC", std::vector{0.8, 0.1, 0.3}, {"X-axis coordinate PID P,I,D-params"}}, // X-axis rate PID P,I,D-params simple_config_record_t{"XPIDV", std::vector{1.0, 0.01, 0.2}, {"X-axis rate PID P,I,D-params"}}, // Y-axis coordinate PID P, I, D-params simple_config_record_t{"YPIDC", std::vector{0.8, 0.1, 0.3}, {"Y-axis coordinate PID P, I, D-params"}}, // Y-axis rate PID P,I,D-params simple_config_record_t{"YPIDV", std::vector{0.5, 0.2, 0.5}, {"Y-axis rate PID P,I,D-params"}}, // 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 HA-axis (Y-axis of Sidereal servo microcontroller)"}}, // 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), {"maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller)"}}, simple_config_record_t{"MaxPointingErr", mcc::MccAngle(8.0_degs), {"slewing-to-pointing mode angular limit in degrees"}}, simple_config_record_t{"MaxFinePointingErr", mcc::MccAngle(1.5_degs), {"pointing-to-guiding mode angular limit in degrees"}}, simple_config_record_t{"MaxGuidingErr", mcc::MccAngle(0.5_arcsecs), {"guiding 'good'-flag error cirle radius (mount-to-target distance) in arcsecs"}} ); 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, vd); 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, 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; }; 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; 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(); }; 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(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>("XPIDC").value_or(std::vector{}); 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(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>("YPIDC").value_or(std::vector{}); 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(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::MccAngle(8.0_degs)); hw_cfg.devConfig.MaxPointingErr = ang; ang = getValue("MaxFinePointingErr").value_or(mcc::MccAngle(1.5_degs)); hw_cfg.devConfig.MaxFinePointingErr = ang; ang = getValue("MaxGuidingErr").value_or(mcc::MccAngle(0.5_arcsecs)); hw_cfg.devConfig.MaxGuidingErr = ang; return hw_cfg; } mcc::MccSimpleMovingModelParams movingModelParams() const { static constexpr double arcsecs2rad = std::numbers::pi / 180.0 / 3600.0; // arcseconds to radians mcc::MccSimpleMovingModelParams pars; auto get_value = [&pars, 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.updatingPZoneInterval = getValue("updatingPZoneInterval") .value_or(pars.updatingPZoneInterval); pars.slewToleranceRadius = getValue("slewToleranceRadius").value_or(pars.slewToleranceRadius) * arcsecs2rad; 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) * arcsecs2rad; pars.adjustCycleInterval = getValue("adjustCycleInterval").value_or(pars.adjustCycleInterval); pars.slewTimeout = getValue("slewTimeout").value_or(pars.slewTimeout); get_value("trackingTelemetryInterval", pars.trackingTelemetryInterval); 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); 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); } } 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; } bool dumpDefaultsToFile(const std::filesystem::path& path) { std::ofstream fst(path); if (!fst.is_open()) { return false; } fst << "#\n"; fst << "# ASTROSIB FM-700 MOUNT CONFIGURATION\n" << "#\n"; fst << "# (created at " << std::format("{:%FT%T UTC}", std::chrono::system_clock::now()) << ")\n"; fst << "#\n"; auto wrec = [&fst, this]() { fst << "\n"; for (size_t i = 0; i < std::get(_keyValue).comment.size(); ++i) { fst << "# " << std::get(_keyValue).comment[i] << "\n"; } fst << std::get(_keyValue).key << " = "; auto v = std::get(_keyValue).value; using v_t = std::remove_cvref_t; if constexpr (std::is_arithmetic_v || mcc::traits::mcc_char_range) { fst << std::format("{}", v); } else if constexpr (mcc::traits::mcc_time_duration_c) { fst << std::format("{}", v.count()); } else if constexpr (mcc::mcc_angle_c) { fst << std::format("{}", mcc::MccAngle(static_cast(v)).degrees()); } else if constexpr (std::same_as) { if (v == mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY) { fst << mcc::MccDefaultPCMTypeString; } else if (v == mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE) { fst << mcc::MccDefaultPCMTypeString; } else if (v == mcc::MccDefaultPCMType::PCM_TYPE_BSPLINE) { fst << mcc::MccDefaultPCMTypeString; } } else if constexpr (std::ranges::range && std::formattable, char>) { size_t sz = std::ranges::size(v); if (!sz) { return; } --sz; auto it = v.begin(); for (size_t j = 0; j < sz; ++j, ++it) { fst << std::format("{}", *it) << base_t::VALUE_ARRAY_DELIM; } fst << std::format("{}", *it); } else if constexpr (std::formattable) { fst << std::format("{}", v); } else { static_assert(false, "INVALID TYPE!"); } fst << "\n"; }; [&wrec](std::index_sequence) { (wrec.operator()(), ...); }(std::make_index_sequence>()); fst.close(); return true; }; }; } // namespace asibfm700