mountcontrol/asibfm700/asibfm700_configfile.h
Timur A. Fatkhullin 4d7e830798 ...
2025-10-01 06:35:44 +03:00

916 lines
33 KiB
C++

#pragma once
/**/
#include <expected>
#include <filesystem>
#include <fstream>
#include <unordered_map>
#include <mcc_angle.h>
#include <mcc_moving_model_common.h>
#include <mcc_pcm.h>
#include <mcc_utils.h>
#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 <typename T>
concept variant_valid_type_c = requires { !std::is_array_v<T> && !std::is_void_v<T> && !std::is_reference_v<T>; };
// configuration record
template <typename T>
concept config_record_c = requires(T t) {
requires std::same_as<decltype(t.key), std::string_view>; // keyword
requires variant_valid_type_c<decltype(t.value)>; // value
};
// simple minimal-requirement configuration record class
template <variant_valid_type_c T>
struct simple_config_record_t {
std::string_view key;
T value;
};
// description of config (a std::tuple of "config_record_c"s)
template <typename T>
concept config_desc_c = requires(T t) { []<config_record_c... Ts>(std::tuple<Ts...>) {}(t); };
template <config_desc_c DESCR_T>
class ConfigHolder
{
protected:
/* helper definitions */
// deduce unique value types of the given config records
template <config_desc_c TplT>
struct deduce_val_types;
template <config_record_c RT>
struct deduce_val_types<std::tuple<RT>> {
using value_type_t = std::tuple<decltype(RT::value)>;
};
template <config_record_c RT, config_record_c... RTs>
struct deduce_val_types<std::tuple<RT, RTs...>> {
using value_type_t =
std::conditional_t<(std::same_as<RT, RTs> || ...),
typename deduce_val_types<std::tuple<RTs...>>::value_type_t,
decltype(std::tuple_cat(
std::declval<std::tuple<decltype(RT::value)>>(),
std::declval<typename deduce_val_types<std::tuple<RTs...>>::value_type_t>()))>;
};
template <config_desc_c TplT>
using deduce_val_types_t = typename deduce_val_types<TplT>::value_type_t;
// deduce std::variant type from std::tuple element types
template <mcc::traits::mcc_tuple_c TplT>
struct variant_from_tuple;
template <typename T, typename... Ts>
struct variant_from_tuple<std::tuple<T, Ts...>> {
using variant_t = std::variant<T, Ts...>;
};
template <mcc::traits::mcc_tuple_c TplT>
using variant_from_tuple_t = typename variant_from_tuple<TplT>::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<decltype(value)>;
if constexpr (std::is_arithmetic_v<value_t>) {
auto v = mcc::utils::numFromStr<value_t>(str);
if (!v.has_value()) {
return false;
}
value = v.value();
} else if constexpr (mcc::traits::mcc_output_char_range<value_t>) {
value_t r;
std::ranges::copy(str, std::back_inserter(r));
value = r;
} else if constexpr (std::ranges::range<value_t>) {
using el_t = std::ranges::range_value_t<value_t>;
if constexpr (std::is_reference_v<el_t> || std::is_const_v<el_t>) { // 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<decltype(self)>(self)(std::string_view(el), elem)) {
if (std::forward<decltype(self)>(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]<size_t... Is>(std::index_sequence<Is...>) {
((_configDB[std::get<Is>(desc).key] = std::get<Is>(desc).value), ...);
}(std::make_index_sequence<std::tuple_size_v<DESCR_T>>());
}
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::ranges::contiguous_range R, typename DeserFuncT>
std::error_code parse(const R& buffer, DeserFuncT&& deser_func)
requires std::same_as<std::remove_cvref_t<std::ranges::range_value_t<R>>, char>
{
if constexpr (std::is_array_v<std::decay_t<R>>) { // char*, const char*
return parse(std::string_view{std::forward<R>(buffer)}, std::forward<DeserFuncT>(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<DeserFuncT>(deser_func),
rec_it->second.index());
if (!ok) {
return std::make_error_code(std::errc::invalid_argument);
}
}
}
}
} while (!buffer_end);
return {};
}
template <std::ranges::contiguous_range R>
std::error_code parse(const R& buffer)
{
return parse(buffer, defaultDeserializeFunc);
}
template <typename T>
std::expected<T, std::error_code> 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<T, std::error_code> res;
std::visit(
[&res](auto&& val) {
using v_t = std::decay_t<decltype(val)>;
if constexpr (std::convertible_to<v_t, T>) {
res = static_cast<T>(std::forward<decltype(val)>(val));
} else if constexpr (std::constructible_from<T, v_t>) {
res = T{std::forward<decltype(val)>(val)};
} else {
res = std::unexpected(std::make_error_code(std::errc::invalid_argument));
}
},
it->second);
return res;
}
protected:
std::unordered_map<std::string_view, variant_from_tuple_t<deduce_val_types_t<DESCR_T>>> _configDB;
template <size_t I = 0, typename FuncT, typename... Ts>
bool forIndex(std::variant<Ts...>& 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<I, std::tuple<Ts...>>;
v_t val;
bool ok = std::forward<FuncT>(func)(s, val);
if (ok) {
var = val;
}
return ok;
} else {
return forIndex<I + 1>(var, s, std::forward<FuncT>(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<double>{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<size_t>{3, 3}},
// 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<double>{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
// 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<double>{-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<double>{}},
// PCM B-spline coeffs for along Y-axis (declination or zenithal distance)
simple_config_record_t{"pcmBsplineYcoeffs", std::vector<double>{}},
/* 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 */
// hardware mode: 1 - model mode, otherwise real mode
simple_config_record_t{"RunModel", 0},
// mount serial device paths
simple_config_record_t{"MountDevPath", std::string("/dev/ttyUSB0")},
// mount serial device speed
simple_config_record_t{"MountDevSpeed", 19200},
// motor encoders serial device path
simple_config_record_t{"EncoderDevPath", std::string("")},
// X-axis encoder serial device path
simple_config_record_t{"EncoderXDevPath", std::string("/dev/encoderX0")},
// Y-axis encoder serial device path
simple_config_record_t{"EncoderYDevPath", std::string("/dev/encoderY0")},
// encoders serial device speed
simple_config_record_t{"EncoderDevSpeed", 153000},
// ==1 if encoder works as separate serial device, ==2 if there's new version with two devices
simple_config_record_t{"SepEncoder", 2},
// mount polling interval in millisecs
simple_config_record_t{"MountReqInterval", std::chrono::milliseconds(100)},
// encoders polling interval in millisecs
simple_config_record_t{"EncoderReqInterval", std::chrono::milliseconds(50)},
// mount axes rate calculation interval in millisecs
simple_config_record_t{"EncoderSpeedInterval", std::chrono::milliseconds(100)},
// X-axis coordinate PID P,I,D-params
simple_config_record_t{"XPIDC", std::vector<double>{0.8, 0.1, 0.3}},
// X-axis rate PID P,I,D-params
simple_config_record_t{"XPIDV", std::vector<double>{1.0, 0.01, 0.2}},
// Y-axis coordinate PID P, I, D-params
simple_config_record_t{"YPIDC", std::vector<double>{0.8, 0.1, 0.3}},
// Y-axis rate PID P,I,D-params
simple_config_record_t{"YPIDV", std::vector<double>{0.5, 0.2, 0.5}},
// 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 DEC-axis (X-axis of Sidereal servo microcontroller)
simple_config_record_t{"hwMaxRateDEC", mcc::MccAngle(10.0_degs)}
);
class Asibfm700MountConfig : protected ConfigHolder<decltype(Asibfm700MountConfigDefaults)>
{
using base_t = ConfigHolder<decltype(Asibfm700MountConfigDefaults)>;
public:
using base_t::value;
Asibfm700MountConfig() : base_t(Asibfm700MountConfigDefaults)
{
update();
}
~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::parse(buffer, deserializer);
if (!ec) {
update();
}
} 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;
}
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{};
protected:
void update()
{
hardwarePollingPeriod = std::get<decltype(hardwarePollingPeriod)>(this->_configDB["hardwarePollingPeriod"]);
// CCTE
siteLatitude = std::get<mcc::MccAngle>(this->_configDB["siteLatitude"]);
siteLongitude = std::get<mcc::MccAngle>(this->_configDB["siteLongitude"]);
siteElevation = std::get<double>(this->_configDB["siteElevation"]);
refractWavelength = std::get<double>(this->_configDB["refractWavelength"]);
leapSecondFilename = std::get<std::string>(this->_configDB["leapSecondFilename"]);
bulletinAFilename = std::get<std::string>(this->_configDB["bulletinAFilename"]);
// prohibited zones
pzMinAltitude = std::get<mcc::MccAngle>(this->_configDB["pzMinAltitude"]);
pzLimitSwitchHAMin = std::get<mcc::MccAngle>(this->_configDB["pzLimitSwitchHAMin"]);
pzLimitSwitchHAMax = std::get<mcc::MccAngle>(this->_configDB["pzLimitSwitchHAMax"]);
// hardware config
servoControllerConfig.hwConfig = {};
servoControllerConfig.MountDevPath = std::get<std::string>(this->_configDB["MountDevPath"]);
servoControllerConfig.EncoderDevPath = std::get<std::string>(this->_configDB["EncoderDevPath"]);
servoControllerConfig.EncoderXDevPath = std::get<std::string>(this->_configDB["EncoderXDevPath"]);
servoControllerConfig.EncoderYDevPath = std::get<std::string>(this->_configDB["EncoderYDevPath"]);
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 = std::get<int>(this->_configDB["RunModel"]);
servoControllerConfig.devConfig.MountDevSpeed = std::get<int>(this->_configDB["MountDevSpeed"]);
servoControllerConfig.devConfig.EncoderDevSpeed = std::get<int>(this->_configDB["EncoderDevSpeed"]);
servoControllerConfig.devConfig.SepEncoder = std::get<int>(this->_configDB["SepEncoder"]);
std::chrono::duration<double> secs; // seconds as floating-point
secs = std::get<std::chrono::milliseconds>(this->_configDB["MountReqInterval"]);
servoControllerConfig.devConfig.MountReqInterval = secs.count();
secs = std::get<std::chrono::milliseconds>(this->_configDB["EncoderReqInterval"]);
servoControllerConfig.devConfig.EncoderReqInterval = secs.count();
secs = std::get<std::chrono::milliseconds>(this->_configDB["EncoderSpeedInterval"]);
servoControllerConfig.devConfig.EncoderSpeedInterval = secs.count();
std::vector<double> pid = std::get<std::vector<double>>(this->_configDB["XPIDC"]);
if (pid.size() > 2) {
servoControllerConfig.devConfig.XPIDC.P = pid[0];
servoControllerConfig.devConfig.XPIDC.I = pid[1];
servoControllerConfig.devConfig.XPIDC.D = pid[2];
}
pid = std::get<std::vector<double>>(this->_configDB["XPIDV"]);
if (pid.size() > 2) {
servoControllerConfig.devConfig.XPIDV.P = pid[0];
servoControllerConfig.devConfig.XPIDV.I = pid[1];
servoControllerConfig.devConfig.XPIDV.D = pid[2];
}
pid = std::get<std::vector<double>>(this->_configDB["YPIDC"]);
if (pid.size() > 2) {
servoControllerConfig.devConfig.YPIDC.P = pid[0];
servoControllerConfig.devConfig.YPIDC.I = pid[1];
servoControllerConfig.devConfig.YPIDC.D = pid[2];
}
pid = std::get<std::vector<double>>(this->_configDB["YPIDV"]);
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 =
std::get<decltype(movingModelParams.telemetryTimeout)>(this->_configDB["telemetryTimeout"]);
movingModelParams.minTimeToPZone =
std::get<decltype(movingModelParams.minTimeToPZone)>(this->_configDB["minTimeToPZone"]);
movingModelParams.updatingPZoneInterval =
std::get<decltype(movingModelParams.updatingPZoneInterval)>(this->_configDB["updatingPZoneInterval"]);
movingModelParams.slewToleranceRadius =
std::get<decltype(movingModelParams.slewToleranceRadius)>(this->_configDB["slewToleranceRadius"]);
movingModelParams.adjustCoordDiff =
std::get<decltype(movingModelParams.adjustCoordDiff)>(this->_configDB["adjustCoordDiff"]);
movingModelParams.adjustCycleInterval =
std::get<decltype(movingModelParams.adjustCycleInterval)>(this->_configDB["adjustCycleInterval"]);
movingModelParams.slewTimeout =
std::get<decltype(movingModelParams.slewTimeout)>(this->_configDB["slewTimeout"]);
movingModelParams.timeShiftToTargetPoint =
std::get<decltype(movingModelParams.timeShiftToTargetPoint)>(this->_configDB["timeShiftToTargetPoint"]);
movingModelParams.trackingCycleInterval =
std::get<decltype(movingModelParams.trackingCycleInterval)>(this->_configDB["trackingCycleInterval"]);
// PCM data
pcmData.type = std::get<decltype(pcmData.type)>(this->_configDB["pcmType"]);
pcmData.siteLatitude = std::get<mcc::MccAngle>(this->_configDB["siteLatitude"]);
pid = std::get<std::vector<double>>(this->_configDB["pcmGeomCoeffs"]);
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<size_t> dd = std::get<decltype(dd)>(this->_configDB["pcmBsplineDegree"]);
if (dd.size() >= 2) {
pcmData.bspline.bsplDegreeX = dd[0] > 0 ? dd[0] : 3;
pcmData.bspline.bsplDegreeY = dd[1] > 0 ? dd[1] : 3;
}
pid = std::get<std::vector<double>>(this->_configDB["pcmBsplineXknots"]);
// 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 = std::get<std::vector<double>>(this->_configDB["pcmBsplineYknots"]);
// 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 = std::get<std::vector<double>>(this->_configDB["pcmBsplineXcoeffs"]);
if (pid.size() >= Ncoeffs) {
pcmData.bspline.coeffsX.resize(Ncoeffs);
for (size_t i = 0; i < Ncoeffs; ++i) {
pcmData.bspline.coeffsX[i] = pid[i];
}
}
pid = std::get<std::vector<double>>(this->_configDB["pcmBsplineYcoeffs"]);
if (pid.size() >= Ncoeffs) {
pcmData.bspline.coeffsY.resize(Ncoeffs);
for (size_t i = 0; i < Ncoeffs; ++i) {
pcmData.bspline.coeffsY[i] = pid[i];
}
}
}
inline static auto deserializer = [](std::string_view str, auto& value) {
using value_t = std::decay_t<decltype(value)>;
bool ok = true;
if constexpr (std::is_arithmetic_v<value_t> || std::ranges::output_range<value_t, char> ||
std::ranges::range<value_t>) {
return base_t::defaultDeserializeFunc(str, value);
} else if constexpr (mcc::traits::mcc_time_duration_c<value_t>) {
typename value_t::rep vd;
ok = base_t::defaultDeserializeFunc(str, vd);
if (ok) {
value = value_t{vd};
}
} else if constexpr (std::same_as<value_t, mcc::MccAngle>) { // assume here all angles are in degrees
double vd;
ok = base_t::defaultDeserializeFunc(str, vd);
if (ok) {
value = mcc::MccAngle(vd, mcc::MccDegreeTag{});
}
} else if constexpr (std::same_as<value_t, mcc::MccDefaultPCMType>) {
std::string vstr;
ok = base_t::defaultDeserializeFunc(str, vstr);
auto s = mcc::utils::trimSpaces(vstr);
if (ok) {
if (s == mcc::MccDefaultPCMTypeString<mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY>) {
value = mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY;
} else if (s == mcc::MccDefaultPCMTypeString<mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY_BSPLINE>) {
value = mcc::MccDefaultPCMType::PCM_TYPE_GEOMETRY;
} else if (s == mcc::MccDefaultPCMTypeString<mcc::MccDefaultPCMType::PCM_TYPE_BSPLINE>) {
value = mcc::MccDefaultPCMType::PCM_TYPE_BSPLINE;
} else {
return false;
}
}
} else {
return false;
}
return ok;
};
};
static constexpr std::string_view Asibfm700MountConfigString =
R"--(
#
# ASTROSIB FM-700 MOUNT DEFAULT CONFIGURATION
#
# (created 2025-10-01T03:00:00.0)
#
# main cycle period
hardwarePollingPeriod = 100
# geographic coordinates of the observation site
# site latitude in degrees
siteLatitude = 43.646711
# site longitude in degrees
siteLongitude = 41.440732
# site elevation in meters
siteElevation = 2070.0
# celestial coordinate transformation
# wavelength at which refraction is calculated (in mkm)
refractWavelength = 0.5
# an empty filename means default precompiled string
leapSecondFilename =
# an empty filename means default precompiled string
bulletinAFilename =
# pointing correction model
# PCM default type
pcmType = GEOMETRY
# PCM geometrical coefficients
pcmGeomCoeffs = 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
# PCM B-spline degrees
pcmBsplineDegree = 3, 3
# PCM B-spline knots along X-axis (HA-angle or azimuth). By default from 0 to 2*PI radians
pcmBsplineXknots = 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
pcmBsplineYknots = -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)
pcmBsplineXcoeffs =
# PCM B-spline coeffs for along Y-axis (declination or zenithal distance)
pcmBsplineYcoeffs =
# slewing and tracking parameters
# arcseconds per second
#sideralRate = 15.0410686
# timeout for telemetry updating in milliseconds
telemetryTimeout = 3000
# minimal allowed time in seconds to prohibited zone
minTimeToPZone = 10
# a time interval to update prohibited zones related quantities (millisecs)
updatingPZoneInterval = 5000
# coordinates difference in arcsecs to stop slewing
slewToleranceRadius = 5.0
# target-mount coordinate difference in arcsecs to start adjusting of slewing
adjustCoordDiff = 50.0
# minimum time in millisecs between two successive adjustments
adjustCycleInterval = 300
# slew process timeout in seconds
slewTimeout = 3600
# a time shift into future to compute target position in future (UT1-scale time duration, millisecs)
timeShiftToTargetPoint = 10000
# minimum time in millisecs between two successive tracking corrections
trackingCycleInterval = 300
# prohibited zones
# minimal altitude in degrees
pzMinAltitude = 10.0
# HA-axis limit switch minimal value in degrees
pzLimitSwitchHAMin = -170.0
# HA-axis limit switch maximal value in degrees
pzLimitSwitchHAMax = 170.0
# DEC-axis limit switch minimal value in degrees
pzLimitSwitchDecMin = -90.0
# DEC-axis limit switch maximal value in degrees
pzLimitSwitchDecMax = 90.0
# hardware-related
# hardware mode: 1 - model mode, otherwise real mode
RunModel = 0
# mount serial device paths
MountDevPath = /dev/ttyUSB0
# mount serial device speed
MountDevSpeed = 19200
# motor encoders serial device path
EncoderDevPath =
# X-axis encoder serial device path
EncoderXDevPath = /dev/encoderX0
# Y-axis encoder serial device path
EncoderYDevPath = /dev/encoderY0
# encoders serial device speed
EncoderDevSpeed = 153000
# ==1 if encoder works as separate serial device, ==2 if there's new version with two devices
SepEncoder = 2
# mount polling interval in millisecs
MountReqInterval = 100
# encoders polling interval in millisecs
EncoderReqInterval = 50
# mount axes rate calculation interval in millisecs
EncoderSpeedInterval = 100
# X-axis coordinate PID P,I,D-params
XPIDC = 0.8, 0.1, 0.3
# X-axis rate PID P,I,D-params
XPIDV = 1.0, 0.01, 0.2
# Y-axis coordinate PID P,I,D-params
YPIDC = 0.8, 0.1, 0.3
# Y-axis rate PID P,I,D-params
YPIDV = 0.5, 0.2, 0.5
# maximal moving rate (degrees per second) along HA-axis (Y-axis of Sidereal servo microcontroller)
hwMaxRateHA = 8.0
# maximal moving rate (degrees per second) along DEC-axis (X-axis of Sidereal servo microcontroller)
hwMaxRateDEC = 10.0
)--";
} // namespace asibfm700