#pragma once /* MOUNT CONTROL COMPONENTS LIBRARY */ /* BASIC NETWORK PROTOCOL DEFINITIONS */ #include #include #include "mcc_angle.h" #include "mcc_defaults.h" #include "mcc_generics.h" #include "mcc_utils.h" namespace mcc::network { /* * The network protocol is the ASCII-based, case-sensitive textual protocol. * The "client-server" communication is performed through messages. * The message is a minimal unit of this communication. * The model of network communication is a simple "client-server" one, i.e., * client asks - server responds. * * network communication message format: * [[][][]...] * * where * - mandatory message keyword (one or more ASCII symbols) * * * e.g. * "TARGET 12:23:45.56 00:32:21.978\n" */ /* low-level network message format definitions */ static constexpr std::string_view MCC_COMMPROTO_STOP_SEQ = "\n"; static constexpr std::string_view MCC_COMMPROTO_KEYPARAM_DELIM_SEQ = " "; static constexpr std::string_view MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ = ";"; static constexpr std::string_view MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ = ","; /* server special keywords */ static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR = "ACK"; // ACK static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR = "ERROR"; // mount operational error // pre-defined errors static constexpr std::string_view MCC_COMMPROTO_SERVER_ERROR_INVKEY_STR = "INVKEY"; // invalid keyword static constexpr std::string_view MCC_COMMPROTO_SERVER_ERROR_INVPAR_STR = "INVPAR"; // invalid parameter /* server control keywords */ static constexpr std::string_view MCC_COMMPROTO_KEYWORD_RESTART_SERVER_STR = "RESTART"; // restart server /* BELOW IS ONE OF THE PROTOCOL OPTIONS CORRESPONDING MCC_GENERIC_MOUNT_C CONCEPT */ /* predefined parameters */ static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_RADEC_ICRS = "RADEC_ICRS"; // ICRS RA and DEC static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_RADEC = "RADEC"; // apparent RA and DEC static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_HADEC = "HADEC"; // apparent HA and DEC static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_AZZD = "AZZD"; // azimuth and zenithal distance static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_AZALT = "AZALT"; // azimuth and altitude static constexpr std::string_view MCC_COMMPROTO_COORD_KIND_XY = "XY"; // hardware (encoder) coordinates // static constexpr MccCoordPairKind mcc_str2pairkind(std::string_view spair) // { // return spair == MCC_COMMPROTO_COORD_KIND_RADEC_ICRS ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS // : spair == MCC_COMMPROTO_COORD_KIND_RADEC ? MccCoordPairKind::COORDS_KIND_RADEC_APP // : spair == MCC_COMMPROTO_COORD_KIND_HADEC ? MccCoordPairKind::COORDS_KIND_HADEC_APP // : spair == MCC_COMMPROTO_COORD_KIND_AZZD ? MccCoordPairKind::COORDS_KIND_AZZD // : spair == MCC_COMMPROTO_COORD_KIND_AZALT ? MccCoordPairKind::COORDS_KIND_AZALT // : spair == MCC_COMMPROTO_COORD_KIND_XY ? MccCoordPairKind::COORDS_KIND_XY // : MccCoordPairKind::COORDS_KIND_GENERIC; // } template static constexpr MccCoordPairKind mcc_str2pairkind(R&& spair) { if constexpr (std::is_pointer_v>) { return mcc_str2pairkind(std::string_view{spair}); } const auto hash = mcc::utils::FNV1aHash(std::forward(spair)); return hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_RADEC_ICRS) ? MccCoordPairKind::COORDS_KIND_RADEC_ICRS : hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_RADEC) ? MccCoordPairKind::COORDS_KIND_RADEC_APP : hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_HADEC) ? MccCoordPairKind::COORDS_KIND_HADEC_APP : hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_AZZD) ? MccCoordPairKind::COORDS_KIND_AZZD : hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_AZALT) ? MccCoordPairKind::COORDS_KIND_AZALT : hash == mcc::utils::FNV1aHash(MCC_COMMPROTO_COORD_KIND_XY) ? MccCoordPairKind::COORDS_KIND_XY : MccCoordPairKind::COORDS_KIND_GENERIC; } static constexpr std::string_view mcc_pairkind2str(MccCoordPairKind kind) { return kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS ? MCC_COMMPROTO_COORD_KIND_RADEC_ICRS : kind == MccCoordPairKind::COORDS_KIND_RADEC_APP ? MCC_COMMPROTO_COORD_KIND_RADEC : kind == MccCoordPairKind::COORDS_KIND_HADEC_APP ? MCC_COMMPROTO_COORD_KIND_HADEC : kind == MccCoordPairKind::COORDS_KIND_AZZD ? MCC_COMMPROTO_COORD_KIND_AZZD : kind == MccCoordPairKind::COORDS_KIND_AZALT ? MCC_COMMPROTO_COORD_KIND_AZALT : kind == MccCoordPairKind::COORDS_KIND_XY ? MCC_COMMPROTO_COORD_KIND_XY : "UNKNOWN"; } /* keywords */ // NOTE: THE COORDINATES AND TIME-RELATED QUANTITIES CAN BE EXPRESSED IN THE TWO FORMATS: // 1) fixed-point real number, e.g. 123.43987537359 or -0.09775 // 2) sexagesimal number, e.g. 10:43:43.12 or -123:54:12.435 // // IN THE FIRST CASE ALL NUMBERS MUST BE INTERPRETATED AS DEGREES, // IN THE SECOND CASE NUMBERS MUST BE INTERPRETATED ACCORDING TO ITS TYPE: // ALL TIME-RELATED QUANTITIES AND RA/HA COORDINATES MUST BE EXPRESSED // IN FORMAT 'HOURS:MINUTES:SECONDS', WHILE DEC/ALT/AZ/ZD COORDINATES MUST // BE EXPRESSED AS '+/-DEGREES:ARCMINUTES:ARCSECONDS' // // USER-ENTERED (FROM NETWORK CLIENTS) COORDINATE PAIR CAN BE PROVIDED IN A MIXED FORM, I.E., // 12.34436658678 10:32:11.432 or // 10:32:11.432 12.34436658678 // // SERVER-RESPONDED COORDINATES ARE ALWAYS IN THE SAME FORMAT, SEXAGESIMAL OR FIXED-POINT // // format of output coordinates: // "COORDFMT FMT-type\n" // e.g.: // "COORDFMT SGM\n" // "COORDFMT\n" // // server must return "ACK" or "ERROR INVPAR" in the case of 'set'-operation and // "ACK COORDFMT FMT-type" in the case of 'get'-operation // e.g.: // "COORDFMT FIX\n" -> "ACK\n" // "COORDFMT SXT\n" -> "ERROR INVPAR\n" (invalid parameter of format type) // "COORDFMT\n" -> "ACK COORDFMT FIX\n" // static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_STR = "COORDFMT"; static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_SEXGM_STR = "SGM"; // sexagesimal static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDFMT_FIXED_STR = "FIX"; // fixed point // precision (number of decimal places) of returned coordinates: // "COORDPREC seconds-prec arcseconds-prec\n" // seconds-prec - precision of hour-based coordinates (RA and HA) or time-related quantities // arcseconds-prec - precision of degree-based coordinates (DEC, AZ, ZD, ALT) // precision must be given as non-negative integer number // e.g. // "COORDPREC 2,1\n" (output sexagesimal RA=12:34:56.67, DEC=32:54:21.9) // static constexpr std::string_view MCC_COMMPROTO_KEYWORD_COORDPREC_STR = "COORDPREC"; // set/get target coordinates // "TARGET X-coord Y-coord XY-kind\n", if 'XY-kind' is omitted then one should assume RADEC_ICRS // e.g.: // "TARGET 12.7683487 10:23:09.75 AZZD\n" // "TARGET HADEC\n" // "TARGET\n" // // server must return "ACK" or "ERROR INVPAR" in the case of 'set'-operation and // "ACK TARGET X-coord Y-coord XY-kind" in the case of 'get'-operation // e.g.: // "TARGET 12.7683487 10:23:09.75 AZZD\n" -> "ACK\n" // "TARGET 12.7683487 10:23:09.75 AZZE\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind) // // "TARGET HADEC\n" -> "ACK TARGET 20:21:56.32 -01:32:34.2 HADEC\n" // "TARGET\n" -> "ACK TARGET 20:21:56.32 -01:32:34.2 RADEC_ICRS\n" // static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TARGET_STR = "TARGET"; // get mount coordinates: // "MOUNT coord-kind", if 'coord-kind' is omitted then coordinates are according to mount type, // i.e., HADEC for equathorial-type mount and AZZD for alt-azimuthal one // e.g.: // "MOUNT RADEC\n" (get current apparent RA and DEC mount coordinates) // // server must return "ACK MOUNT X-coord Y-coord XY-kind" or "ERROR INVPAR" // e.g. // "MOUNT AZALT\n" -> "ACK MOUNT 1.2332325 54.23321312 AZALT\n" // "MOUNT AZAL\n" -> "ERROR INVPAR\n" (invalid parameter of coordinates pair kind) // "MOUNT\n" -> "ACK MOUNT 1.2332325 54.23321312 AZZD\n" for alt-azimuthal mount // "MOUNT\n" -> "ACK MOUNT 1.2332325 54.23321312 HADEC\n" for equathorial mount static constexpr std::string_view MCC_COMMPROTO_KEYWORD_MOUNT_STR = "MOUNT"; static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TELEMETRY_STR = "TELEMETRY"; // init mount // "INIT\n" static constexpr std::string_view MCC_COMMPROTO_KEYWORD_INIT_STR = "INIT"; // stop any movements // "STOP\n" static constexpr std::string_view MCC_COMMPROTO_KEYWORD_STOP_STR = "STOP"; // slew mount and track target: // "SLEW\n" static constexpr std::string_view MCC_COMMPROTO_KEYWORD_SLEW_STR = "SLEW"; // slew mount and stop: // "MOVE\n" static constexpr std::string_view MCC_COMMPROTO_KEYWORD_MOVE_STR = "MOVE"; // track target // "TRACK\n" static constexpr std::string_view MCC_COMMPROTO_KEYWORD_TRACK_STR = "TRACK"; // get mount status // "STATUS\n" static constexpr std::string_view MCC_COMMPROTO_KEYWORD_STATUS_STR = "STATUS"; // valid keywords static constexpr std::array MCC_COMMPROTO_VALID_KEYS = { MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR, MCC_COMMPROTO_KEYWORD_COORDFMT_STR, MCC_COMMPROTO_KEYWORD_COORDPREC_STR, MCC_COMMPROTO_KEYWORD_TARGET_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR, MCC_COMMPROTO_KEYWORD_TELEMETRY_STR, MCC_COMMPROTO_KEYWORD_INIT_STR, MCC_COMMPROTO_KEYWORD_STOP_STR, MCC_COMMPROTO_KEYWORD_SLEW_STR, MCC_COMMPROTO_KEYWORD_MOVE_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR, MCC_COMMPROTO_KEYWORD_STATUS_STR}; // hashes of valid keywords static constexpr std::array MCC_COMMPROTO_VALID_KEYS_HASH = [](std::index_sequence) { return std::array{mcc::utils::FNV1aHash(MCC_COMMPROTO_VALID_KEYS[Is])...}; }(std::make_index_sequence()); static constexpr size_t MCC_COMMPROTO_KEYWORD_SERVER_ACK_HASH = mcc::utils::FNV1aHash(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR); static constexpr size_t MCC_COMMPROTO_KEYWORD_SERVER_ERROR_HASH = mcc::utils::FNV1aHash(MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR); static constexpr size_t MCC_COMMPROTO_KEYWORD_TARGET_HASH = mcc::utils::FNV1aHash(MCC_COMMPROTO_KEYWORD_TARGET_STR); static constexpr size_t MCC_COMMPROTO_KEYWORD_MOUNT_HASH = mcc::utils::FNV1aHash(MCC_COMMPROTO_KEYWORD_MOUNT_STR); template RT = std::vector> struct mcc_netmsg_parse_result_t { size_t keyword_hash; T keyword; RT params; }; // network message parsing result class concept template concept mcc_netmsg_parse_result_c = requires(T t) { requires std::same_as; // hash of keyword requires traits::mcc_char_range; // keyword char-range representation // a range of parameters char-range representations requires std::ranges::output_range; }; // the function returns hash of message keyword // if 'from_server' is true then given network message is considered as a server response, i.e., // valid keywords are "ACK" or "ERROR" // // the funtions returns false in the case of invalid message format and true otherwise // template bool mcc_parse_netmsg(const IR& netmsg, ResT& parse_res, bool from_server = false) { if (std::ranges::size(netmsg) == 0) { return false; }; auto found = std::ranges::search(netmsg, MCC_COMMPROTO_KEYPARAM_DELIM_SEQ); if (std::distance(netmsg.begin(), found.begin()) == 0) { return false; } const size_t hash = mcc::utils::FNV1aHash(netmsg.begin(), found.begin()); auto it = std::ranges::find(MCC_COMMPROTO_VALID_KEYS_HASH, hash); if (it == MCC_COMMPROTO_VALID_KEYS_HASH.end()) { return false; } if (from_server) { // only ACK or ERROR auto ok = hash == MCC_COMMPROTO_VALID_KEYS_HASH[0] || hash == MCC_COMMPROTO_VALID_KEYS_HASH[1]; if (!ok) { return false; } } parse_res.keyword_hash = hash; parse_res.keyword = {netmsg.begin(), found.begin()}; auto pars = netmsg | std::views::drop(std::distance(netmsg.begin(), found.end())) | std::views::split(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ); decltype(parse_res.params) res; for (auto const& el : pars) { // parameters std::back_inserter(res) = {el.begin(), el.end()}; } parse_res.params = std::move(res); return true; } // construct network message // the function returns false if input keyword is not valid one (see MCC_COMMPROTO_VALID_KEYS)! template bool mcc_netmsg_construct(traits::mcc_output_char_range auto& msg, traits::mcc_input_char_range auto const& keyword, PTs... params) { const size_t hash = mcc::utils::FNV1aHash(keyword); if (!std::ranges::contains(MCC_COMMPROTO_VALID_KEYS_HASH, hash)) { return false; } msg = {keyword.begin(), keyword.end()}; if constexpr (sizeof...(PTs)) { std::ranges::copy(MCC_COMMPROTO_KEYPARAM_DELIM_SEQ, std::back_inserter(msg)); [&msg](this auto&& self, const T& par, const Ts&... pars) { if constexpr (std::is_arithmetic_v) { std::ranges::copy(std::to_string(par), std::back_inserter(msg)); } else if constexpr (std::convertible_to) { std::ranges::copy(static_cast(par), std::back_inserter(msg)); } else if constexpr (std::constructible_from) { std::ranges::copy(std::string(par), std::back_inserter(msg)); } else if constexpr (traits::mcc_char_range) { std::ranges::copy(std::string(par.begin(), par.end()), std::back_inserter(msg)); } else if constexpr (std::same_as) { std::ranges::copy(mcc_pairkind2str(par), std::back_inserter(msg)); } else { static_assert(false, "UNSUPPORTED TYPE!!!"); } if constexpr (sizeof...(Ts)) { std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(msg)); std::forward(self)(pars...); } }(params...); } return true; } // the function convert given network message parsing result class to // celestial point coordinates according to parsed message parameters. // // It is assumed that the coordinates and their type are contained in the consecutive elements of the input array // starting from the element 'from_idx' (zero-based): // // parse_res.params[..., X-COORD, Y-COORD, XY-KIND, ...] // // th last parameter 'XY-KIND' can be omitted and, in this case, 'default_kind' is assumed // // NOTE: IT IS ASSUMED THAT THE COORDINATES ARE REPRESENTED AS DEGREES EXPRESSED BY THE NUMBER WITH A FLOATING POINT // OR IN SEXAGESIMAL FORM. IN THE CASE OF SEXAGESIMAL FORM THE TYPE (DEGREES OR HOURS) OF THE COORDINATE // REPRESENTATION IS DETERMINED BY 'XY-KIND', E.G.: // parse_res.params[..., "12:34:52.123", "23:43:56.12", "HADEC", ...] // 'HADEC' STRING FOR 'XY-KIND' DETERMINES THE FIRST COORDINATE (HOUR ANGLE) // AS AN ANGLE IN HOUR FORM WHILE THE SECOND ONE (DECLINATION) IN DEGREES // // // The function returns false if it can not convert coordinate string to number or the 'XY-KIND' string is invalid // bool mcc_netmsg_get_cpoint(mcc_netmsg_parse_result_c auto const& parse_res, size_t from_idx, mcc_celestial_point_c auto& cpoint, MccCoordPairKind default_kind) requires std::ranges::contiguous_range { if (std::ranges::size(parse_res.params) < (from_idx + 2)) { return false; } MccCoordPairKind kind = default_kind; if (std::ranges::size(parse_res.params) > (from_idx + 2)) { kind = mcc_str2pairkind(parse_res.params[from_idx + 2]); if (kind == MccCoordPairKind::COORDS_KIND_GENERIC) { return false; } } std::optional ang1, ang2; switch (kind) { case mcc::MccCoordPairKind::COORDS_KIND_RADEC_ICRS: case mcc::MccCoordPairKind::COORDS_KIND_RADEC_APP: case mcc::MccCoordPairKind::COORDS_KIND_HADEC_APP: ang1 = mcc::utils::parsAngleString(parse_res.params[from_idx], true); break; default: ang1 = mcc::utils::parsAngleString(parse_res.params[from_idx]); } if (!ang1) { return false; } ang2 = mcc::utils::parsAngleString(parse_res.params[from_idx + 1]); if (!ang2) { return false; } if (kind != mcc::MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { mcc_tp2tp(std::chrono::system_clock::now(), cpoint.time_point); } else { // J2000.0: 11:58:55.816 1 January 2000 UTC auto tp = std::chrono::sys_days(std::chrono::year_month_day(std::chrono::January / std::chrono::day(1) / std::chrono::year(2000))) + std::chrono::hours(11) + std::chrono::minutes(58) + std::chrono::milliseconds(55816); mcc_tp2tp(tp, cpoint.time_point); } cpoint.pair_kind = kind; cpoint.X = MccAngle(ang1.value(), mcc::MccDegreeTag{}); // to radians cpoint.Y = MccAngle(ang2.value(), mcc::MccDegreeTag{}); // to radians return true; } template concept mcc_netmsg_valid_keys_c = requires(T t) { // std::array of valid message keywords [](std::array) { // to ensure T::NETMSG_VALID_KEYWORDS can be used as compile-time constant static constexpr auto v0 = T::NETMSG_VALID_KEYWORDS[0]; return v0; }(T::NETMSG_VALID_KEYWORDS); // std::array of valid message keywords hashes [](std::array) { // to ensure T::NETMSG_VALID_KEYWORD_HASHES can be used as compile-time constant static constexpr auto v0 = T::NETMSG_VALID_KEYWORD_HASHES[0]; return v0; }(T::NETMSG_VALID_KEYWORD_HASHES); requires T::NETMSG_VALID_KEYWORDS.size() == T::NETMSG_VALID_KEYWORD_HASHES.size(); }; struct MccNetMessageValidKeywords { static constexpr std::array NETMSG_VALID_KEYWORDS = { MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR, MCC_COMMPROTO_KEYWORD_COORDFMT_STR, MCC_COMMPROTO_KEYWORD_COORDPREC_STR, MCC_COMMPROTO_KEYWORD_TARGET_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR, MCC_COMMPROTO_KEYWORD_TELEMETRY_STR, MCC_COMMPROTO_KEYWORD_INIT_STR, MCC_COMMPROTO_KEYWORD_STOP_STR, MCC_COMMPROTO_KEYWORD_SLEW_STR, MCC_COMMPROTO_KEYWORD_MOVE_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR, MCC_COMMPROTO_KEYWORD_STATUS_STR}; // hashes of valid keywords static constexpr std::array NETMSG_VALID_KEYWORD_HASHES = [](std::index_sequence) { return std::array{mcc::utils::FNV1aHash(NETMSG_VALID_KEYWORDS[Is])...}; }(std::make_index_sequence()); constexpr static const size_t* isKeywordValid(std::string_view key) { const auto hash = mcc::utils::FNV1aHash(key); for (auto const& h : NETMSG_VALID_KEYWORD_HASHES) { if (h == hash) { return &h; } } return nullptr; } }; static_assert(mcc_netmsg_valid_keys_c, ""); template concept mcc_netmessage_c = requires(T t) { T(); }; /* helper types to format serialized celestial coordinates */ /* it can be used as inputs in "construct" method or corresponded constructor */ // format of output (serialized) coordinates enum class MccNetMessageCoordFormat { CFMT_DEGREES, CFMT_SGM }; // precision of sexagesimal coordinates (number of decimal places in seconds/arcseconds) struct MccNetMessageCoordPrec { uint8_t hour_prec = 2; uint8_t deg_prec = 1; }; template class MccNetMessage { public: protected: class DefaultDeserializer : protected mcc::utils::MccSimpleDeserializer { protected: using base_t = mcc::utils::MccSimpleDeserializer; public: DefaultDeserializer() : base_t(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ) {} template std::error_code operator()(IR&& bytes, VT& value) { if constexpr (mcc_celestial_point_c) { std::vector vs; auto ec = base_t::operator()(std::forward(bytes), vs); if (ec) { return ec; } if (vs.size() < 2) { // al least a pair of coordinates must be given return std::make_error_code(std::errc::invalid_argument); } MccCelestialPoint pt{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS}; if (vs.size() > 2) { // pair of coordinates and the pair kind pt.pair_kind = MccCoordStrToPairKind(vs[2]); if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_GENERIC) { return std::make_error_code(std::errc::invalid_argument); } } if (pt.pair_kind == MccCoordPairKind::COORDS_KIND_RADEC_ICRS) { // J2000.0: 11:58:55.816 1 January 2000 UTC auto tp = std::chrono::sys_days(std::chrono::year_month_day( std::chrono::January / std::chrono::day(1) / std::chrono::year(2000))) + std::chrono::hours(11) + std::chrono::minutes(58) + std::chrono::milliseconds(55816); mcc_tp2tp(tp, pt.time_point); } else { if (vs.size() > 3) { // coordinates epoch is given // std::chrono::sys_time tp; // std::istringstream iss(std::string{utils::trimSpaces(vs[3])}); // std::chrono::from_stream(iss, "%FT%T", tp); // if (iss.fail()) { // return std::make_error_code(std::errc::invalid_argument); // } // mcc_tp2tp(tp, pt.time_point); MccCelestialCoordEpoch cep; // bool ok = cep.fromCharRange(utils::trimSpaces(vs[3])); bool ok = cep.fromCharRange(vs[3]); if (!ok) { return std::make_error_code(std::errc::invalid_argument); } mcc_tp2tp(cep.UTC(), pt.time_point); } else { // no time point - use NOW mcc_tp2tp(std::chrono::system_clock::now(), pt.time_point); } } std::optional ang1, ang2; switch (pt.pair_kind) { // if sexagesimal then hours:minutes:seconds form case mcc::MccCoordPairKind::COORDS_KIND_RADEC_ICRS: case mcc::MccCoordPairKind::COORDS_KIND_RADEC_APP: case mcc::MccCoordPairKind::COORDS_KIND_HADEC_APP: ang1 = mcc::utils::parsAngleString(vs[0], true); break; default: // if sexagesimal then degrees:arcminutes:arcseconds form ang1 = mcc::utils::parsAngleString(vs[0]); } if (!ang1) { return std::make_error_code(std::errc::invalid_argument); } ang2 = mcc::utils::parsAngleString(vs[1]); if (!ang2) { return std::make_error_code(std::errc::invalid_argument); } pt.X = MccAngle(ang1.value(), mcc::MccDegreeTag{}); pt.Y = MccAngle(ang2.value(), mcc::MccDegreeTag{}); mcc_copy_celestial_point(pt, &value); } else if constexpr (std::same_as) { value = MccCoordStrToPairKind(bytes); if (value == MccCoordPairKind::COORDS_KIND_UNKNOWN) { return std::make_error_code(std::errc::invalid_argument); } } else if constexpr (std::same_as) { std::string v; auto ec = (*this)(std::forward(bytes), v); if (ec) { return ec; } if (v.compare(MCC_COMMPROTO_KEYWORD_COORDFMT_SEXGM_STR) == 0) { value = MccNetMessageCoordFormat::CFMT_SGM; } else if (v.compare(MCC_COMMPROTO_KEYWORD_COORDFMT_FIXED_STR) == 0) { value = MccNetMessageCoordFormat::CFMT_DEGREES; } else { return std::make_error_code(std::errc::invalid_argument); } } else if constexpr (std::same_as) { std::vector v; auto ec = (*this)(std::forward(bytes), v); if (ec) { return ec; } auto hprec = v[0]; value.hour_prec = hprec > 0 ? (hprec < std::numeric_limits::max() ? hprec : std::numeric_limits::max()) : 2; if (v.size() == 1) { value.deg_prec = 1; } else { auto dprec = v[1]; value.deg_prec = dprec > 0 ? dprec < std::numeric_limits::max() ? dprec : std::numeric_limits::max() : 1; } } else { return base_t::operator()(std::forward(bytes), value); } return {}; } }; class DefaultSerializer { MccNetMessageCoordFormat _currentCoordFormat = MccNetMessageCoordFormat::CFMT_SGM; MccNetMessageCoordPrec _currentCoordPrec{2, 1}; public: void setCoordFormat(MccNetMessageCoordFormat fmt) { _currentCoordFormat = fmt; } void setCoordPrec(MccNetMessageCoordPrec prec) { _currentCoordPrec = prec; } template void operator()(const T& value, OR& bytes) { if constexpr (std::is_arithmetic_v) { std::format_to(std::back_inserter(bytes), "{}", value); } else if constexpr (std::convertible_to) { std::ranges::copy(static_cast(value), std::back_inserter(bytes)); } else if constexpr (std::constructible_from) { std::ranges::copy(std::string(value), std::back_inserter(bytes)); } else if constexpr (traits::mcc_char_range) { std::ranges::copy(std::string(value.begin(), value.end()), std::back_inserter(bytes)); // } else if constexpr (std::same_as) { // std::ranges::copy(mcc_pairkind2str(value), std::back_inserter(bytes)); } else if constexpr (traits::mcc_time_duration_c) { (*this)(value.count(), bytes); } else if constexpr (mcc_celestial_point_c) { if (_currentCoordFormat == MccNetMessageCoordFormat::CFMT_DEGREES) { std::format_to(std::back_inserter(bytes), "{}{}{}", MccAngle(value.X).degrees(), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, MccAngle(value.Y).degrees()); } else { switch (value.pair_kind) { case MccCoordPairKind::COORDS_KIND_RADEC_ICRS: case MccCoordPairKind::COORDS_KIND_RADEC_APP: case MccCoordPairKind::COORDS_KIND_HADEC_APP: std::format_to(std::back_inserter(bytes), "{}{}", MccAngle(value.X).sexagesimal(true, _currentCoordPrec.hour_prec), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ); break; default: std::format_to(std::back_inserter(bytes), "{}{}", MccAngle(value.X).sexagesimal(false, _currentCoordPrec.deg_prec), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ); } std::format_to(std::back_inserter(bytes), "{}", MccAngle(value.Y).sexagesimal(false, _currentCoordPrec.deg_prec)); } std::format_to(std::back_inserter(bytes), "{0:}{1:}{2:}{3:%F}T{3:%T}", MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, MccCoordPairKindToStr(value.pair_kind), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, value.time_point); } else if constexpr (std::ranges::range) { auto sz = std::ranges::size(value); if (sz == 0) { return; } (*this)(*value.begin(), bytes); // the first element if (sz > 1) { for (auto const& el : value | std::views::drop(1)) { std::ranges::copy(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, std::back_inserter(bytes)); (*this)(el, bytes); } } } else if constexpr (std::same_as) { std::format_to(std::back_inserter(bytes), "{}{}{}{}{}", value.value(), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, value.message(), MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ, value.category().name()); } else if constexpr (std::formattable) { std::format_to(std::back_inserter(bytes), "{}", value); } else { static_assert(false, "UNSUPPORTED TYPE!!!"); } } }; public: typedef BASE_T valid_keys_t; typedef BYTEREPR_T byte_repr_t; enum MccNetMessageError { ERROR_OK, ERROR_EMPTY_MESSAGE, ERROR_INVALID_KEYWORD, ERROR_EMPTY_KEYWORD }; MccNetMessage() = default; template MccNetMessage(KT&& key, PTs&&... params) requires traits::mcc_output_char_range { construct(std::forward(key), std::forward(params)...); } template constexpr MccNetMessage(const R& msg) requires traits::mcc_input_char_range { fromCharRange(msg); } // constexpr MccNetMessage(const BYTEREPR_T& msg) // requires traits::mcc_input_char_range // { // fromCharRange(msg); // } template constexpr bool withKey(const KT& key) { if constexpr (std::is_pointer_v>) { return withKey(std::string_view{key}); } return mcc::utils::FNV1aHash(key) == _keywordHash; } template R keyword() const { if constexpr (traits::mcc_char_view) { return R{_keyword.begin(), _keyword.end()}; } else { R r; std::ranges::copy(_keyword, std::back_inserter(r)); return r; } } std::string_view keyword() const { return _keyword; } size_t paramSize() const { return _params.size(); } template R params(size_t start_idx = 0, size_t Nelemes = std::numeric_limits::max()) requires(traits::mcc_view_or_output_char_range || traits::mcc_range_of_char_range) { if (start_idx >= _params.size()) { return R{}; } auto stop_idx = start_idx + Nelemes - 1; if (stop_idx >= _params.size()) { stop_idx = _params.size() - 1; } if constexpr (traits::mcc_range_of_char_range) { // returm parameters as array using el_t = std::ranges::range_value_t; R r; if constexpr (traits::mcc_char_view || traits::mcc_output_char_range) { for (size_t i = start_idx; i <= stop_idx; ++i) { auto& el = _params[i]; std::back_inserter(r) = el_t{el.begin(), el.end()}; } } else { static_assert(false, "UNSUPPORTED RANGE TYPE!!!"); } return r; } else { if constexpr (traits::mcc_char_view) { // return joined parameters as a single char-range return R{_params[start_idx].begin(), _params[stop_idx].end()}; } else { R r; std::ranges::copy(std::string_view{_params[start_idx].begin(), _params[stop_idx].end()}, std::back_inserter(r)); return r; } } } std::string_view params(size_t start_idx = 0, size_t Nelemes = std::numeric_limits::max()) { return params(start_idx, Nelemes); } template R param(size_t idx) { if (idx >= _params.size()) { return {}; } if constexpr (traits::mcc_char_view) { return R{_params[idx].begin(), _params[idx].end()}; } else { R r; std::ranges::copy(_params[idx], std::back_inserter(r)); return r; } } std::string_view param(size_t idx) { if (idx >= _params.size()) { return {}; } return _params[idx]; } template std::expected paramValue(size_t idx, DeserFuncT&& deser_func) { if (idx >= _params.size()) { return std::unexpected{std::make_error_code(std::errc::argument_out_of_domain)}; } T val; auto ec = std::forward(deser_func)(_params[idx], val); if (ec) { return std::unexpected(ec); } else { return val; } } template std::expected paramValue(size_t idx) { return paramValue(idx, _defaultDeserilizer); } template R byteRepr() const { if constexpr (traits::mcc_char_view) { return R{_msgBuffer.begin(), _msgBuffer.end()}; } else { R r; std::ranges::copy(_msgBuffer, std::back_inserter(r)); return r; } } std::string_view byteRepr() const { return byteRepr(); } template std::error_code construct(KT&& key, PTs&&... params) requires traits::mcc_output_char_range { if constexpr (std::is_pointer_v>) { return construct(std::string_view(key), std::forward(params)...); } if (!std::ranges::size(key)) { return std::make_error_code(std::errc::invalid_argument); } auto r = valid_keys_t::isKeywordValid(key); if (!r) { return std::make_error_code(std::errc::argument_out_of_domain); } _keywordHash = *r; _msgBuffer = BYTEREPR_T{}; std::ranges::copy(std::forward(key), std::back_inserter(_msgBuffer)); // _keyword = {_msgBuffer.begin(), _msgBuffer.end()}; size_t key_idx = std::distance(_msgBuffer.begin(), _msgBuffer.end()); std::vector par_idx; _params.clear(); if constexpr (sizeof...(PTs)) { std::ranges::copy(MCC_COMMPROTO_KEYPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer)); convertFunc(par_idx, std::forward(params)...); for (size_t i = 0; i < par_idx.size(); i += 2) { _params.emplace_back(_msgBuffer.begin() + par_idx[i], _msgBuffer.begin() + par_idx[i + 1]); } } _keyword = std::string_view{_msgBuffer.begin(), _msgBuffer.begin() + key_idx}; return {}; } template constexpr MccNetMessageError fromCharRange(const R& r) { if constexpr (std::is_pointer_v>) { return fromCharRange(std::string_view(r)); } if (std::ranges::size(r) == 0) { return ERROR_EMPTY_MESSAGE; } std::string_view key; // auto prev_msg_buff = _msgBuffer; if constexpr (traits::mcc_output_char_range) { _msgBuffer = BYTEREPR_T{}; std::ranges::copy(r, std::back_inserter(_msgBuffer)); } else { _msgBuffer = {std::begin(r), std::end(r)}; } auto found = std::ranges::search(_msgBuffer, MCC_COMMPROTO_KEYPARAM_DELIM_SEQ); if (found.empty()) { // only keyword key = mcc::utils::trimSpaces(std::string_view{_msgBuffer.begin(), _msgBuffer.end()}); } else { key = mcc::utils::trimSpaces(std::string_view{_msgBuffer.begin(), found.begin()}); } auto kv = valid_keys_t::isKeywordValid(key); if (!kv) { // _msgBuffer = prev_msg_buff; // restore previous netmessage state return ERROR_INVALID_KEYWORD; } _keywordHash = *kv; _keyword = key; if (!found.empty()) { // params ... _params.clear(); auto pr = std::views::split(std::string_view{found.end(), _msgBuffer.end()}, MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ); for (auto const& p : pr) { _params.emplace_back(p.begin(), p.end()); } } return ERROR_OK; } protected: size_t _keywordHash{}; std::string_view _keyword{}; std::vector _params{}; BYTEREPR_T _msgBuffer{}; inline static DefaultDeserializer _defaultDeserilizer{}; DefaultSerializer _defaultSerializer{}; template void convertFunc(std::vector& idx, const T& par, const Ts&... pars) { if constexpr (std::same_as) { _defaultSerializer.setCoordFormat(par); if constexpr (sizeof...(Ts)) { convertFunc(idx, pars...); } } else if constexpr (std::same_as) { _defaultSerializer.setCoordPrec(par); if constexpr (sizeof...(Ts)) { convertFunc(idx, pars...); } } else { idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end())); _defaultSerializer(par, _msgBuffer); idx.emplace_back(std::distance(_msgBuffer.begin(), _msgBuffer.end())); if constexpr (sizeof...(Ts)) { std::ranges::copy(MCC_COMMPROTO_PARAMPARAM_DELIM_SEQ, std::back_inserter(_msgBuffer)); convertFunc(idx, pars...); } } }; }; static_assert(MccNetMessage{"ACK"}.withKey("ACK")); static_assert(MccNetMessage{"ACK"}.withKey("ACK")); } // namespace mcc::network