1167 lines
44 KiB
C++
1167 lines
44 KiB
C++
#pragma once
|
|
|
|
/* MOUNT CONTROL COMPONENTS LIBRARY */
|
|
|
|
|
|
/* A VERY SIMPLE NETWORK SERVER IMPLEMENTATION */
|
|
|
|
|
|
#include <filesystem>
|
|
#include <set>
|
|
|
|
#include <asio/awaitable.hpp>
|
|
#include <asio/co_spawn.hpp>
|
|
#include <asio/deferred.hpp>
|
|
#include <asio/detached.hpp>
|
|
#include <asio/experimental/awaitable_operators.hpp>
|
|
#include <asio/ip/tcp.hpp>
|
|
#include <asio/local/seq_packet_protocol.hpp>
|
|
#include <asio/local/stream_protocol.hpp>
|
|
#include <asio/read.hpp>
|
|
#include <asio/redirect_error.hpp>
|
|
#include <asio/serial_port.hpp>
|
|
#include <asio/signal_set.hpp>
|
|
#include <asio/steady_timer.hpp>
|
|
#include <asio/streambuf.hpp>
|
|
#include <asio/write.hpp>
|
|
|
|
#include <spdlog/sinks/null_sink.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#if __has_include(<unistd.h>) // POSIX
|
|
#define FORK_EXISTS 1
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
|
|
#include "mcc_generics.h"
|
|
#include "mcc_netserver_endpoint.h"
|
|
#include "mcc_netserver_proto.h"
|
|
#include "mcc_traits.h"
|
|
|
|
|
|
namespace mcc::network
|
|
{
|
|
|
|
enum class MccGenericMountNetworkServerErrorCode : int {
|
|
ERROR_OK,
|
|
ERROR_MOUNT_INIT,
|
|
ERROR_MOUNT_STOP,
|
|
ERROR_MOUNT_SET_TARGET,
|
|
ERROR_MOUNT_GET_TELEMETRY,
|
|
ERROR_MOUNT_SLEW,
|
|
ERROR_MOUNT_MOVE,
|
|
ERROR_MOUNT_TRACK
|
|
};
|
|
|
|
struct MccGenericMountNetworkServerErrorCategory : std::error_category {
|
|
const char* name() const noexcept
|
|
{
|
|
return "MCC-GENERIC-MOUNT-NETWORK-SERVER";
|
|
}
|
|
|
|
std::string message(int ec) const
|
|
{
|
|
MccGenericMountNetworkServerErrorCode err = static_cast<MccGenericMountNetworkServerErrorCode>(ec);
|
|
|
|
switch (err) {
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_OK:
|
|
return "OK";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_INIT:
|
|
return "mount init error";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_STOP:
|
|
return "mount stop error";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_SET_TARGET:
|
|
return "mount set target error";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY:
|
|
return "mount get telemetry error";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_SLEW:
|
|
return "mount slewing error";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_MOVE:
|
|
return "mount moving error";
|
|
case MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_TRACK:
|
|
return "mount tracking error";
|
|
defaut:
|
|
return "unknown";
|
|
};
|
|
}
|
|
|
|
static const MccGenericMountNetworkServerErrorCategory& get()
|
|
{
|
|
static const MccGenericMountNetworkServerErrorCategory constInst;
|
|
return constInst;
|
|
}
|
|
};
|
|
|
|
inline std::error_code make_error_code(MccGenericMountNetworkServerErrorCode ec)
|
|
{
|
|
return std::error_code(static_cast<int>(ec), MccGenericMountNetworkServerErrorCategory::get());
|
|
}
|
|
|
|
} // namespace mcc::network
|
|
|
|
|
|
namespace std
|
|
{
|
|
template <>
|
|
class is_error_code_enum<mcc::network::MccGenericMountNetworkServerErrorCode> : public true_type
|
|
{
|
|
};
|
|
} // namespace std
|
|
|
|
|
|
namespace mcc::network
|
|
{
|
|
|
|
|
|
namespace traits
|
|
{
|
|
|
|
template <typename T>
|
|
concept mcc_endpoint_c = std::derived_from<T, asio::serial_port> || std::derived_from<T, asio::ip::tcp::endpoint> ||
|
|
std::derived_from<T, asio::local::stream_protocol::endpoint> ||
|
|
std::derived_from<T, asio::local::seq_packet_protocol::endpoint>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_serial_proto = std::derived_from<T, asio::serial_port>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_tcp_proto =
|
|
std::derived_from<T, asio::ip::tcp::endpoint> || std::derived_from<T, asio::ip::tcp::socket>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_local_stream_proto = std::derived_from<T, asio::local::stream_protocol::endpoint> ||
|
|
std::derived_from<T, asio::local::stream_protocol::socket>;
|
|
|
|
template <typename T>
|
|
static constexpr bool is_local_seqpack_proto = std::derived_from<T, asio::local::seq_packet_protocol::endpoint> ||
|
|
std::derived_from<T, asio::local::seq_packet_protocol::socket>;
|
|
|
|
|
|
} // namespace traits
|
|
|
|
|
|
|
|
/* A GENERIC NETWORK SERVER IMPLEMENTATION */
|
|
|
|
template <mcc_logger_c LoggerT = MccNullLogger>
|
|
class MccGenericNetworkServer : public LoggerT
|
|
{
|
|
public:
|
|
using LoggerT::logDebug;
|
|
using LoggerT::logError;
|
|
using LoggerT::logInfo;
|
|
using LoggerT::logTrace;
|
|
using LoggerT::logWarn;
|
|
|
|
static constexpr std::chrono::duration DEFAULT_RCV_TIMEOUT = std::chrono::hours(12);
|
|
static constexpr std::chrono::duration DEFAULT_SND_TIMEOUT = std::chrono::milliseconds(2000);
|
|
|
|
typedef std::vector<char> handle_message_func_result_t;
|
|
// handle received message user function
|
|
typedef std::function<handle_message_func_result_t(std::string_view)> handle_message_func_t;
|
|
|
|
|
|
MccGenericNetworkServer(asio::io_context& ctx, const handle_message_func_t& func)
|
|
requires std::is_default_constructible_v<LoggerT>
|
|
: _asioContext(ctx), _handleMessageFunc(func), _stopSignal(ctx), _restartSignal(ctx)
|
|
{
|
|
std::stringstream st;
|
|
st << std::this_thread::get_id();
|
|
|
|
logInfo(std::format("Create mount server instance (thread ID = {})", st.str()));
|
|
}
|
|
|
|
template <typename... LoggerCtorArgsTs>
|
|
MccGenericNetworkServer(asio::io_context& ctx, const handle_message_func_t& func, LoggerCtorArgsTs&&... log_args)
|
|
requires(not std::is_default_constructible_v<LoggerT>)
|
|
: LoggerT(std::forward<LoggerCtorArgsTs>(log_args)...),
|
|
_asioContext(ctx),
|
|
_handleMessageFunc(func),
|
|
_stopSignal(ctx),
|
|
_restartSignal(ctx)
|
|
{
|
|
std::stringstream st;
|
|
st << std::this_thread::get_id();
|
|
|
|
logInfo(std::format("Create mount server instance (thread ID = {})", st.str()));
|
|
}
|
|
|
|
// MccNetworkServer(asio::io_context& ctx, const handle_message_func_t& func, LoggerT logger = MccNullLogger{})
|
|
// : _asioContext(ctx), _handleMessageFunc(func), _stopSignal(ctx), _restartSignal(ctx)
|
|
// {
|
|
// std::stringstream st;
|
|
// st << std::this_thread::get_id();
|
|
|
|
// logInfo(std::format("Create mount server instance (thread ID = {})", st.str()));
|
|
// }
|
|
|
|
virtual ~MccGenericNetworkServer()
|
|
{
|
|
std::stringstream st;
|
|
st << std::this_thread::get_id();
|
|
|
|
logInfo(std::format("Delete mount server instance (thread ID = {}) ...", st.str()));
|
|
|
|
stopListening();
|
|
disconnectClients();
|
|
}
|
|
|
|
|
|
template <typename... CtorArgTs>
|
|
asio::awaitable<void> listen(std::derived_from<MccNetServerEndpoint> auto endpoint, CtorArgTs&&... ctor_args)
|
|
{
|
|
if (!endpoint.isValid()) {
|
|
logError(std::format("Cannot start listening! Invalid endpoint string representation ('{}')!",
|
|
endpoint.endpoint()));
|
|
co_return;
|
|
}
|
|
|
|
// add root path to endpoint one
|
|
std::filesystem::path pt("/");
|
|
|
|
|
|
if (endpoint.isLocalSerial()) {
|
|
pt += endpoint.path();
|
|
|
|
asio::serial_port s_port(_asioContext);
|
|
|
|
std::error_code ec;
|
|
|
|
if constexpr (sizeof...(CtorArgTs)) { // options
|
|
setSerialOpts(s_port, std::forward<CtorArgTs>(ctor_args)...);
|
|
}
|
|
|
|
s_port.open(pt.string(), ec);
|
|
if (ec) {
|
|
logError(std::format("Cannot open serial device '{}' (Error = '{}')!", pt.string(), ec.message()));
|
|
co_return;
|
|
}
|
|
|
|
// asio::co_spawn(_asioContext, listen(std::move(s_port)), asio::detached);
|
|
co_await listen(std::move(s_port));
|
|
} else if (endpoint.isLocal()) {
|
|
// create abstract namespace socket endpoint if its path starts from '@' symbol
|
|
endpoint.makeAbstract('@');
|
|
|
|
// if (endpoint.path()[0] == '\0') { // abstract namespace
|
|
// std::string p;
|
|
// std::ranges::copy(endpoint.path(), std::back_inserter(p));
|
|
// p.insert(p.begin() + 1, '/'); // insert after '\0' symbol
|
|
// pt = p;
|
|
// } else {
|
|
// pt += endpoint.path();
|
|
// }
|
|
|
|
if (endpoint.isLocalStream()) {
|
|
co_await listen(asio::local::stream_protocol::endpoint(endpoint.path(pt.string())));
|
|
} else if (endpoint.isLocalSeqpacket()) {
|
|
co_await listen(asio::local::seq_packet_protocol::endpoint(endpoint.path(pt.string())));
|
|
} else {
|
|
co_return; // it must not be!!!!
|
|
}
|
|
} else if (endpoint.isTCP()) {
|
|
// resolve hostname
|
|
try {
|
|
asio::ip::tcp::resolver res(_asioContext);
|
|
|
|
auto r_result = co_await res.async_resolve(endpoint.host(), endpoint.portView(), asio::use_awaitable);
|
|
|
|
logInfo(std::format("Resolve hostname <{}> to {} IP-addresses", endpoint.host(), r_result.size()));
|
|
|
|
|
|
bool exit_flag = false;
|
|
asio::ip::tcp::acceptor acc(_asioContext);
|
|
|
|
for (auto const& epn : r_result) {
|
|
try {
|
|
// std::stringstream st;
|
|
|
|
// logDebug("Create connection acceptor for endpoint <{}> ...",
|
|
// epn.address().to_string());
|
|
acc = asio::ip::tcp::acceptor(_asioContext, epn);
|
|
// st << acc.local_endpoint();
|
|
exit_flag = true;
|
|
break;
|
|
} catch (const std::system_error& err) {
|
|
logError(
|
|
std::format("An error occuring while creating connection acceptor (ec = {})", err.what()));
|
|
continue;
|
|
}
|
|
}
|
|
if (!exit_flag) {
|
|
logError("Cannot start listening on any resolved endpoints!");
|
|
co_return;
|
|
}
|
|
|
|
_tcpAcceptors.emplace_back(&acc);
|
|
|
|
logInfo(
|
|
std::format("Start listening at <{}> endpoint ...", acc.local_endpoint().address().to_string()));
|
|
|
|
// start accepting connections
|
|
for (;;) {
|
|
auto sock = co_await acc.async_accept(asio::use_awaitable);
|
|
// start new client session
|
|
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
|
|
}
|
|
|
|
} catch (const std::system_error& err) {
|
|
logError(
|
|
std::format("An error occured while trying to start accepting connections! ec = '{}'", err.what()));
|
|
}
|
|
}
|
|
}
|
|
|
|
template <traits::mcc_endpoint_c EpnT>
|
|
asio::awaitable<void> listen(EpnT endpoint)
|
|
{
|
|
using epn_t = std::decay_t<decltype(endpoint)>;
|
|
|
|
std::error_code ec;
|
|
|
|
if constexpr (traits::is_serial_proto<epn_t>) {
|
|
// first, check if port is open
|
|
if (!endpoint.is_open()) {
|
|
if (ec) {
|
|
// ??????????
|
|
logError("Serial port was not open! Do not start waiting for commands!");
|
|
}
|
|
} else {
|
|
asio::co_spawn(_asioContext, startSession(std::move(endpoint)), asio::detached);
|
|
}
|
|
|
|
} else if constexpr (traits::is_tcp_proto<epn_t> || traits::is_local_stream_proto<epn_t> ||
|
|
traits::is_local_seqpack_proto<epn_t>) {
|
|
try {
|
|
std::stringstream st;
|
|
|
|
st << endpoint;
|
|
logDebug(std::format("Create connection acceptor for endpoint <{}> ...", st.str()));
|
|
auto acc = typename epn_t::protocol_type::acceptor(_asioContext, endpoint);
|
|
|
|
st.str("");
|
|
st << acc.local_endpoint();
|
|
logInfo(std::format("Start listening at <{}> endpoint ...", st.str()));
|
|
|
|
|
|
if constexpr (traits::is_tcp_proto<epn_t>) {
|
|
_tcpAcceptors.emplace_back(&acc);
|
|
} else if constexpr (traits::is_local_stream_proto<epn_t>) {
|
|
_localStreamAcceptors.emplace_back(&acc);
|
|
} else if constexpr (traits::is_local_seqpack_proto<epn_t>) {
|
|
_localSeqpackAcceptors.emplace_back(&acc);
|
|
} else {
|
|
static_assert(false, "INVALID ENDPOINT!!!");
|
|
}
|
|
|
|
// start accepting connections
|
|
for (;;) {
|
|
auto sock = co_await acc.async_accept(asio::use_awaitable);
|
|
// start new client session
|
|
asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
|
|
}
|
|
|
|
|
|
} catch (const std::system_error& err) {
|
|
logError(
|
|
std::format("An error occured while trying to start accepting connections! ec = '{}'", err.what()));
|
|
}
|
|
} else {
|
|
static_assert(false, "INVALID ENDPOINT!!!");
|
|
}
|
|
|
|
co_return;
|
|
}
|
|
|
|
|
|
// close listening on all endpoints
|
|
void stopListening()
|
|
{
|
|
std::error_code ec;
|
|
|
|
logInfo("Close all listening endpoints ...");
|
|
|
|
auto num =
|
|
_serialPorts.size() + _tcpAcceptors.size() + _localStreamAcceptors.size() + _localSeqpackAcceptors.size();
|
|
if (!num) {
|
|
logInfo("There are no listening ports/sockets!");
|
|
return;
|
|
}
|
|
|
|
auto close_func = [this](auto& acc_ptrs, std::string_view desc) {
|
|
size_t N = 0, M = 0;
|
|
std::error_code ec;
|
|
|
|
if (acc_ptrs.size()) {
|
|
logInfo(std::format("Close {} acceptors ...", desc));
|
|
|
|
for (auto& acc : acc_ptrs) {
|
|
acc->close(ec);
|
|
if (ec) {
|
|
logError(std::format("Cannot close {} acceptor! ec = '{}'", desc, ec.message()));
|
|
} else {
|
|
++M;
|
|
}
|
|
++N;
|
|
}
|
|
|
|
logDebug(std::format("{} from {} {} acceptors were closed!", M, N, desc));
|
|
|
|
// pointers are invalidated here, so clear its container
|
|
acc_ptrs.clear();
|
|
}
|
|
};
|
|
|
|
close_func(_tcpAcceptors, "TCP socket");
|
|
close_func(_localStreamAcceptors, "local stream socket");
|
|
close_func(_localSeqpackAcceptors, "local seqpack socket");
|
|
|
|
|
|
logInfo("The all server listening endpoints were closed!");
|
|
}
|
|
|
|
void disconnectClients()
|
|
{
|
|
auto disconn_func = [this](std::ranges::input_range auto& ptrs) {
|
|
std::error_code ec;
|
|
for (auto& ptr : ptrs) {
|
|
// ptr->cancel(ec);
|
|
// if (ec) {
|
|
// logWarn("socket_base::cancel: an error occured (ec = {})", ec.message());
|
|
// }
|
|
|
|
ptr->shutdown(asio::socket_base::shutdown_both, ec);
|
|
if (ec) {
|
|
logWarn(std::format("socket_base::shutdown: an error occured (ec = {})", ec.message()));
|
|
}
|
|
|
|
ptr->close(ec);
|
|
if (ec) {
|
|
logWarn(std::format("socket_base::close: an error occured (ec = {})", ec.message()));
|
|
}
|
|
}
|
|
};
|
|
|
|
logInfo("Close all client connections ...");
|
|
|
|
if (_serialPorts.empty() && _localStreamSockets.empty() && _localSeqpackSockets.empty() &&
|
|
_tcpSockets.empty()) {
|
|
logInfo("There were no active client connections! Skip!");
|
|
}
|
|
|
|
if (_serialPorts.size()) {
|
|
std::lock_guard lock_g(_serialPortsMutex);
|
|
|
|
std::error_code ec;
|
|
logInfo(std::format("Close serial port clients ({} in total) ...", _serialPorts.size()));
|
|
for (auto& ptr : _serialPorts) {
|
|
ptr->cancel(ec);
|
|
if (ec) {
|
|
logWarn(std::format("serial_port::cancel: an error occured (ec = {})", ec.message()));
|
|
}
|
|
ptr->close(ec);
|
|
if (ec) {
|
|
logWarn(std::format("serial_port::close: an error occured (ec = {})", ec.message()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_localStreamSockets.size()) {
|
|
std::lock_guard lock_g(_localStreamSocketsMutex);
|
|
|
|
logInfo(
|
|
std::format("Close local stream socket-type clients ({} in total) ...", _localStreamSockets.size()));
|
|
disconn_func(_localStreamSockets);
|
|
}
|
|
|
|
if (_localSeqpackSockets.size()) {
|
|
std::lock_guard lock_g(_localSeqpackSocketsMutex);
|
|
|
|
logInfo(
|
|
std::format("Close local seqpack socket-type clients ({} in total) ...", _localSeqpackSockets.size()));
|
|
disconn_func(_localSeqpackSockets);
|
|
}
|
|
|
|
if (_tcpSockets.size()) {
|
|
std::lock_guard lock_g(_tcpSocketsMutex);
|
|
|
|
logInfo(std::format("Close TCP socket-type clients ({} in total) ...", _tcpSockets.size()));
|
|
disconn_func(_tcpSockets);
|
|
}
|
|
|
|
logInfo("Client connection were closed!");
|
|
}
|
|
|
|
void daemonize()
|
|
{
|
|
#ifdef FORK_EXISTS
|
|
logInfo("Daemonize the server ...");
|
|
|
|
_asioContext.notify_fork(asio::execution_context::fork_prepare);
|
|
|
|
auto tmp_path = std::filesystem::temp_directory_path();
|
|
if (tmp_path.empty()) {
|
|
tmp_path = std::filesystem::current_path().root_path();
|
|
}
|
|
|
|
|
|
if (pid_t pid = fork()) {
|
|
if (pid > 0) {
|
|
exit(0);
|
|
} else {
|
|
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK 1-STAGE");
|
|
logError("CANNOT FORK 1-STAGE! The server was not daemonized!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (setsid() == -1) {
|
|
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK SETSID");
|
|
logError("CANNOT FORK SETSID! The server was not daemonized!");
|
|
return;
|
|
}
|
|
|
|
logInfo(std::format("Try to set the daemon current path to '{}' ...", tmp_path.string()));
|
|
|
|
std::error_code ec{};
|
|
|
|
std::filesystem::current_path(tmp_path, ec);
|
|
if (!ec) {
|
|
logWarn(std::format("Cannot change current path to '{}'! Ignore!", tmp_path.string()));
|
|
}
|
|
|
|
umask(0);
|
|
|
|
if (pid_t pid = fork()) {
|
|
if (pid > 0) {
|
|
exit(0);
|
|
} else {
|
|
// throw std::system_error(errno, std::generic_category(), "CANNOT FORK 2-STAGE");
|
|
logError("CANNOT FORK 2-STAGE! The server was not daemonized!");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// stdin, stdout, stderr
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
|
|
_asioContext.notify_fork(asio::io_context::fork_child);
|
|
|
|
logInfo("The server was daemonized successfully!");
|
|
|
|
#else
|
|
logWarn("Host platform is not POSIX one, so cannot daemonize the server!");
|
|
#endif
|
|
}
|
|
|
|
template <std::ranges::range RST = std::vector<int>, std::ranges::range RRT = std::vector<int>>
|
|
void setupSignals(const RST& stop_sig_num = {SIGINT, SIGTERM}, const RRT& restart_sig_num = {SIGUSR1})
|
|
requires(std::convertible_to<std::ranges::range_value_t<RST>, int> &&
|
|
std::convertible_to<std::ranges::range_value_t<RRT>, int>)
|
|
{
|
|
for (const int sig : stop_sig_num) {
|
|
_stopSignal.add(sig);
|
|
}
|
|
|
|
_stopSignal.async_wait([this](std::error_code, int signo) {
|
|
logInfo(std::format("Stop signal was received (signo = {})", signo));
|
|
|
|
stopListening();
|
|
disconnectClients();
|
|
|
|
_asioContext.stop();
|
|
});
|
|
|
|
for (const int sig : restart_sig_num) {
|
|
_restartSignal.add(sig);
|
|
}
|
|
|
|
_restartSignal.async_wait([this](std::error_code, int signo) {
|
|
logInfo(std::format("Restart signal was received (signo = {})", signo));
|
|
restart();
|
|
});
|
|
}
|
|
|
|
void restart()
|
|
{
|
|
disconnectClients();
|
|
|
|
_restartSignal.async_wait([this](std::error_code, int signo) {
|
|
logInfo(std::format("Restart signal was received (signo = {})", signo));
|
|
restart();
|
|
});
|
|
}
|
|
|
|
protected:
|
|
asio::io_context& _asioContext;
|
|
|
|
handle_message_func_t _handleMessageFunc;
|
|
|
|
asio::signal_set _stopSignal, _restartSignal;
|
|
|
|
std::set<asio::serial_port*> _serialPorts;
|
|
// std::vector<asio::serial_port*> _serialPorts;
|
|
|
|
std::vector<asio::ip::tcp::acceptor*> _tcpAcceptors;
|
|
std::vector<asio::local::stream_protocol::acceptor*> _localStreamAcceptors;
|
|
std::vector<asio::local::seq_packet_protocol::acceptor*> _localSeqpackAcceptors;
|
|
|
|
std::set<asio::ip::tcp::socket*> _tcpSockets;
|
|
std::set<asio::local::stream_protocol::socket*> _localStreamSockets;
|
|
std::set<asio::local::seq_packet_protocol::socket*> _localSeqpackSockets;
|
|
// std::vector<asio::ip::tcp::socket*> _tcpSockets;
|
|
// std::vector<asio::local::stream_protocol::socket*> _localStreamSockets;
|
|
// std::vector<asio::local::seq_packet_protocol::socket*> _localSeqpackSockets;
|
|
|
|
std::mutex _serialPortsMutex, _tcpSocketsMutex, _localStreamSocketsMutex, _localSeqpackSocketsMutex;
|
|
|
|
// helpers
|
|
template <typename OptT, typename... OptTs>
|
|
void setSerialOpts(asio::serial_port& s_port, OptT&& opt, OptTs&&... opts)
|
|
{
|
|
std::error_code ec;
|
|
|
|
s_port.set_option(opt, ec);
|
|
if (ec) {
|
|
std::string_view opt_name;
|
|
|
|
if constexpr (std::same_as<OptT, asio::serial_port::baud_rate>) {
|
|
opt_name = "baud rate";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::parity>) {
|
|
opt_name = "parity";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::flow_control>) {
|
|
opt_name = "flow control";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::stop_bits>) {
|
|
opt_name = "stop bits";
|
|
} else if constexpr (std::same_as<OptT, asio::serial_port::character_size>) {
|
|
opt_name = "char size";
|
|
}
|
|
|
|
logError(std::format("Cannot set serial port '{}' option! Just skip!", opt_name));
|
|
}
|
|
|
|
if constexpr (sizeof...(OptTs)) {
|
|
setSerialOpts(s_port, std::forward<OptTs>(opts)...);
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<char> handleClientCommand(std::string_view command)
|
|
{
|
|
std::vector<char> resp{MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR.begin(),
|
|
MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR.end()};
|
|
|
|
return resp;
|
|
}
|
|
|
|
|
|
template <mcc::traits::mcc_time_duration_c RCVT = decltype(DEFAULT_RCV_TIMEOUT),
|
|
mcc::traits::mcc_time_duration_c SNDT = decltype(DEFAULT_SND_TIMEOUT)>
|
|
asio::awaitable<void> startSession(auto socket,
|
|
const RCVT& rcv_timeout = DEFAULT_RCV_TIMEOUT,
|
|
const SNDT& snd_timeout = DEFAULT_SND_TIMEOUT)
|
|
{
|
|
using namespace asio::experimental::awaitable_operators;
|
|
|
|
using sock_t = std::decay_t<decltype(socket)>;
|
|
|
|
|
|
auto look_for_whole_msg = [](auto const& bytes) {
|
|
auto found = std::ranges::search(bytes, MCC_COMMPROTO_STOP_SEQ);
|
|
return found.empty() ? std::span(bytes.begin(), bytes.begin()) : std::span(bytes.begin(), found.end());
|
|
};
|
|
|
|
|
|
asio::streambuf sbuff;
|
|
size_t nbytes;
|
|
std::stringstream st;
|
|
std::string r_epn;
|
|
|
|
st << std::this_thread::get_id();
|
|
std::string thr_id = st.str();
|
|
st.str("");
|
|
|
|
if constexpr (traits::is_serial_proto<sock_t>) {
|
|
st << "serial port: " << socket.native_handle();
|
|
} else { // network sockets
|
|
st << socket.remote_endpoint();
|
|
}
|
|
|
|
r_epn = st.str();
|
|
if (r_epn.empty()) { // UNIX domain sockets
|
|
r_epn = "local";
|
|
}
|
|
|
|
logInfo(std::format("Start client session: remote endpoint <{}> (session thread ID = {})", r_epn, thr_id));
|
|
|
|
try {
|
|
if constexpr (!traits::is_serial_proto<sock_t>) {
|
|
logTrace("Set socket option KEEP_ALIVE to TRUE");
|
|
socket.set_option(asio::socket_base::keep_alive(true));
|
|
}
|
|
|
|
if constexpr (traits::is_serial_proto<sock_t>) {
|
|
std::lock_guard lock_g(_serialPortsMutex);
|
|
_serialPorts.insert(&socket);
|
|
} else if constexpr (traits::is_tcp_proto<sock_t>) {
|
|
std::lock_guard lock_g(_tcpSocketsMutex);
|
|
// _tcpSockets.emplace_back(&socket);
|
|
_tcpSockets.insert(&socket);
|
|
} else if constexpr (traits::is_local_stream_proto<sock_t>) {
|
|
std::lock_guard lock_g(_localStreamSocketsMutex);
|
|
// _localStreamSockets.emplace_back(&socket);
|
|
_localStreamSockets.insert(&socket);
|
|
} else if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
|
std::lock_guard lock_g(_localSeqpackSocketsMutex);
|
|
// _localSeqpackSockets.emplace_back(&socket);
|
|
_localSeqpackSockets.insert(&socket);
|
|
} else {
|
|
static_assert(false, "INVALID SOCKET TTYPE!!!");
|
|
}
|
|
|
|
|
|
// send buffer sequence
|
|
// initiate the second element by "stop-sequence" symbols
|
|
std::vector<asio::const_buffer> snd_buff_seq{
|
|
{}, {MCC_COMMPROTO_STOP_SEQ.data(), MCC_COMMPROTO_STOP_SEQ.size()}};
|
|
|
|
asio::steady_timer timeout_timer(_asioContext);
|
|
std::variant<size_t, std::monostate> op_res;
|
|
|
|
std::error_code ec;
|
|
|
|
bool do_read = true;
|
|
|
|
// main "client request -- server respond" cycle
|
|
for (;;) {
|
|
// receive message
|
|
|
|
if (do_read) {
|
|
logTrace(std::format("Start socket/port reading operation with timeout {} ...", rcv_timeout));
|
|
|
|
if constexpr (traits::is_serial_proto<sock_t>) {
|
|
nbytes = 1024;
|
|
} else {
|
|
nbytes = socket.available();
|
|
}
|
|
|
|
auto buff = sbuff.prepare(nbytes ? nbytes : 1);
|
|
|
|
// timeout_timer.expires_after(std::chrono::seconds(5));
|
|
timeout_timer.expires_after(rcv_timeout);
|
|
|
|
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
|
asio::socket_base::message_flags oflags;
|
|
|
|
op_res = co_await (
|
|
socket.async_receive(buff, oflags, asio::redirect_error(asio::use_awaitable, ec)) ||
|
|
timeout_timer.async_wait(asio::use_awaitable));
|
|
|
|
} else {
|
|
op_res = co_await (asio::async_read(socket, buff, asio::transfer_at_least(1),
|
|
asio::redirect_error(asio::use_awaitable, ec)) ||
|
|
timeout_timer.async_wait(asio::use_awaitable));
|
|
}
|
|
|
|
if (ec) {
|
|
throw std::system_error(ec);
|
|
}
|
|
|
|
if (op_res.index()) {
|
|
throw std::system_error(std::make_error_code(std::errc::timed_out));
|
|
} else {
|
|
nbytes = std::get<0>(op_res);
|
|
|
|
logTrace(std::format("{} bytes were received", nbytes));
|
|
|
|
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
|
if (!nbytes) { // EOF!
|
|
throw std::system_error(std::error_code(asio::error::misc_errors::eof));
|
|
}
|
|
}
|
|
}
|
|
|
|
sbuff.commit(nbytes);
|
|
} // here, the input stream buffer still contains remaining bytes. try to handle its
|
|
|
|
auto start_ptr = static_cast<const char*>(sbuff.data().data());
|
|
|
|
auto msg = look_for_whole_msg(std::span(start_ptr, sbuff.size()));
|
|
if (msg.empty()) { // still not whole message
|
|
logTrace(std::format(
|
|
"It seems a partial command message was received, so waiting for remaining part ..."));
|
|
do_read = true;
|
|
continue;
|
|
}
|
|
|
|
|
|
// extract command without stop sequence symbols
|
|
// std::string comm;
|
|
// std::ranges::copy(msg | std::views::take(msg.size() - MCC_COMMPROTO_STOP_SEQ.size()),
|
|
// std::back_inserter(comm));
|
|
|
|
std::string_view comm{msg.begin(), msg.end() - MCC_COMMPROTO_STOP_SEQ.size()};
|
|
|
|
logDebug(std::format("A command [{}] was received from client (remote endpoint <{}>, thread ID = {})",
|
|
comm, r_epn, thr_id));
|
|
|
|
// auto resp = handleClientCommand(comm);
|
|
auto resp = _handleMessageFunc(comm);
|
|
|
|
// remove received message from the input stream buffer. NOTE: 'msg' is now invalidated!!!
|
|
sbuff.consume(msg.size());
|
|
do_read = sbuff.size() == 0;
|
|
|
|
|
|
logDebug(std::format("Send respond [{}] to client (remote endpoint <{}>, thread ID = {})",
|
|
std::string_view(resp.begin(), resp.end()), r_epn, thr_id));
|
|
|
|
// send server respond to client
|
|
snd_buff_seq[0] = {resp.data(), resp.size()};
|
|
|
|
timeout_timer.expires_after(snd_timeout);
|
|
if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
|
op_res =
|
|
co_await (socket.async_send(snd_buff_seq, 0, asio::redirect_error(asio::use_awaitable, ec)) ||
|
|
timeout_timer.async_wait(asio::use_awaitable));
|
|
} else {
|
|
// nbytes = co_await asio::async_write(socket, snd_buff_seq, asio::use_awaitable);
|
|
op_res = co_await (
|
|
asio::async_write(socket, snd_buff_seq, asio::redirect_error(asio::use_awaitable, ec)) ||
|
|
timeout_timer.async_wait(asio::use_awaitable));
|
|
}
|
|
|
|
if (ec) {
|
|
throw std::system_error(ec);
|
|
}
|
|
|
|
if (op_res.index()) {
|
|
throw std::system_error(std::make_error_code(std::errc::timed_out));
|
|
} else {
|
|
nbytes = std::get<0>(op_res);
|
|
logTrace(std::format("{} bytes were sent", nbytes));
|
|
}
|
|
|
|
if (nbytes != (resp.size() + MCC_COMMPROTO_STOP_SEQ.size())) { // !!!!!!!!!!
|
|
}
|
|
}
|
|
|
|
} catch (const std::system_error& ex) {
|
|
if (ex.code() == std::error_code(asio::error::misc_errors::eof)) {
|
|
logInfo(std::format(
|
|
"It seems client or server closed the connection (remote endpoint <{}>, thread ID = {})", r_epn,
|
|
thr_id));
|
|
} else {
|
|
logError(std::format("An error '{}' occured in client session (remote endpoint <{}>, thread ID = {})",
|
|
ex.what(), r_epn, thr_id));
|
|
}
|
|
} catch (const std::exception& ex) {
|
|
logError(
|
|
std::format("An unhandled error '{}' occured in client sesssion (remote endpoint <{}>, thread ID = {})",
|
|
ex.what(), r_epn, thr_id));
|
|
} catch (...) {
|
|
logError(std::format("An unhandled error occured in client sesssion (remote endpoint <{}>, thread ID = {})",
|
|
r_epn, thr_id));
|
|
}
|
|
|
|
// remove pointer as it is invalidated here (at the exit of the method)
|
|
if constexpr (traits::is_serial_proto<sock_t>) {
|
|
_serialPorts.erase(&socket);
|
|
} else if constexpr (traits::is_tcp_proto<sock_t>) {
|
|
_tcpSockets.erase(&socket);
|
|
} else if constexpr (traits::is_local_stream_proto<sock_t>) {
|
|
_localStreamSockets.erase(&socket);
|
|
} else if constexpr (traits::is_local_seqpack_proto<sock_t>) {
|
|
_localSeqpackSockets.erase(&socket);
|
|
} else {
|
|
static_assert(false, "INVALID SOCKET TTYPE!!!");
|
|
}
|
|
|
|
logInfo(std::format("Close client session: remote endpoint <{}> (thread ID = {})", r_epn, thr_id));
|
|
}
|
|
};
|
|
|
|
|
|
template <mcc_logger_c LoggerT = MccNullLogger>
|
|
class MccGenericMountNetworkServer : public MccGenericNetworkServer<LoggerT>
|
|
{
|
|
using base_t = MccGenericNetworkServer<LoggerT>;
|
|
|
|
public:
|
|
using typename base_t::handle_message_func_result_t;
|
|
|
|
template <mcc_generic_mount_c MountT, typename... LoggerCtorArgsTs>
|
|
MccGenericMountNetworkServer(asio::io_context& ctx, MountT& mount, LoggerCtorArgsTs&&... log_args)
|
|
: base_t(ctx, {}, std::forward<LoggerCtorArgsTs>(log_args)...)
|
|
{
|
|
// to avoid possible compiler optimization (one needs to catch 'mount' strictly by reference)
|
|
auto* mount_ptr = &mount;
|
|
|
|
base_t::_handleMessageFunc = [mount_ptr, this](std::string_view command) {
|
|
using mount_error_t = typename MountT::error_t;
|
|
mount_error_t m_err;
|
|
|
|
MccNetMessage input_msg;
|
|
using output_msg_t = MccNetMessage<handle_message_func_result_t>;
|
|
output_msg_t output_msg;
|
|
|
|
auto ec = parseMessage(command, input_msg);
|
|
if (ec) {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR, ec);
|
|
} else {
|
|
output_msg = handleMessage<output_msg_t>(input_msg, mount_ptr);
|
|
}
|
|
|
|
return output_msg.byteRepr();
|
|
};
|
|
}
|
|
|
|
virtual ~MccGenericMountNetworkServer() {}
|
|
|
|
protected:
|
|
MccCoordinateSerializer::SerializedCoordFormat _coordFormat{
|
|
MccCoordinateSerializer::SerializedCoordFormat::CFMT_SGM};
|
|
MccCoordinateSerializer::SexagesimalCoordPrec _coordPrec{2, 1};
|
|
|
|
|
|
template <typename RESULT_MSG_T, typename INPUT_MSG_T, mcc_generic_mount_c MountT>
|
|
RESULT_MSG_T handleMessage(const INPUT_MSG_T& input_msg, MountT* mount_ptr)
|
|
requires(
|
|
std::derived_from<RESULT_MSG_T,
|
|
MccNetMessage<typename RESULT_MSG_T::byte_repr_t, typename RESULT_MSG_T::valid_keys_t>> &&
|
|
std::derived_from<INPUT_MSG_T,
|
|
MccNetMessage<typename INPUT_MSG_T::byte_repr_t, typename INPUT_MSG_T::valid_keys_t>>)
|
|
{
|
|
using mount_error_t = typename MountT::error_t;
|
|
mount_error_t m_err;
|
|
|
|
RESULT_MSG_T output_msg;
|
|
|
|
std::error_code err{};
|
|
|
|
if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR)) { // strange!
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR)) { // ??!!!
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_RESTART_SERVER_STR)) {
|
|
this->restart();
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_STATUS_STR)) {
|
|
auto st = mount_ptr->status(); // according to mcc_generic_mount_c 'st' is formattable
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_STATUS_STR, st);
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_INIT_STR)) {
|
|
m_err = mount_ptr->initMount();
|
|
if (m_err) {
|
|
err = mcc_deduce_error_code(m_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_INIT);
|
|
} else {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_INIT_STR);
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_STOP_STR)) {
|
|
m_err = mount_ptr->stopMount();
|
|
if (m_err) {
|
|
err = mcc_deduce_error_code(m_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_STOP);
|
|
} else {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_STOP_STR);
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_SLEW_STR)) {
|
|
m_err = mount_ptr->slewToTarget(false);
|
|
if (m_err) {
|
|
err = mcc_deduce_error_code(m_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_SLEW);
|
|
} else {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_SLEW_STR);
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_MOVE_STR)) {
|
|
m_err = mount_ptr->slewToTarget(true);
|
|
if (m_err) {
|
|
err = mcc_deduce_error_code(m_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_MOVE);
|
|
} else {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_MOVE_STR);
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_TRACK_STR)) {
|
|
m_err = mount_ptr->trackTarget();
|
|
if (m_err) {
|
|
err = mcc_deduce_error_code(m_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_TRACK);
|
|
} else {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_TRACK_STR);
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_COORDFMT_STR)) {
|
|
auto v = input_msg.template paramValue<MccCoordinateSerializer::SerializedCoordFormat>(0);
|
|
if (v) {
|
|
_coordFormat = v.value();
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
|
|
} else {
|
|
err = v.error();
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_COORDPREC_STR)) {
|
|
auto v = input_msg.template paramValue<MccCoordinateSerializer::SexagesimalCoordPrec>(0);
|
|
if (v) {
|
|
_coordPrec = v.value();
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
|
|
} else {
|
|
err = v.error();
|
|
}
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_TARGET_STR)) {
|
|
// by default return ICRS coordinates
|
|
MccCelestialPoint cp{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
|
|
|
|
auto sz = input_msg.paramSize();
|
|
if (sz) { // set or get operation
|
|
auto vc = input_msg.template paramValue<MccCelestialPoint>(0); // is it set operation?
|
|
if (vc) { // coordinates are given - set operation
|
|
auto m_err = mount_ptr->setPointingTarget(vc.value());
|
|
if (m_err) {
|
|
if (m_err) {
|
|
err = mcc_deduce_error_code(m_err,
|
|
MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_SET_TARGET);
|
|
}
|
|
} else {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, input_msg.byteRepr());
|
|
}
|
|
} else {
|
|
auto vp = input_msg.template paramValue<MccCoordPairKind>(0);
|
|
if (vp) { // coordinate pair kind is given
|
|
cp.pair_kind = vp.value();
|
|
err = coordsFromTelemetryData(*mount_ptr, true, cp);
|
|
if (!err) {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_TARGET_STR,
|
|
cp);
|
|
}
|
|
} else { // invalid command!!!
|
|
err = vp.error();
|
|
}
|
|
}
|
|
} else { // get operation
|
|
err = coordsFromTelemetryData(*mount_ptr, true, cp);
|
|
if (!err) {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_TARGET_STR,
|
|
_coordFormat, _coordPrec, cp);
|
|
}
|
|
}
|
|
|
|
} else if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_MOUNT_STR)) {
|
|
// by default return ICRS coordinates
|
|
MccCelestialPoint cp{.pair_kind = MccCoordPairKind::COORDS_KIND_RADEC_ICRS};
|
|
|
|
if (input_msg.paramSize()) { // ccordinate pair kind is given
|
|
auto vp = input_msg.template paramValue<MccCoordPairKind>(0);
|
|
if (vp) { // coordinate pair kind is given
|
|
cp.pair_kind = vp.value();
|
|
err = coordsFromTelemetryData(*mount_ptr, false, cp);
|
|
if (!err) {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR, cp);
|
|
}
|
|
} else { // invalid command!!!
|
|
err = vp.error();
|
|
}
|
|
|
|
} else {
|
|
err = coordsFromTelemetryData(*mount_ptr, false, cp);
|
|
if (!err) {
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_MOUNT_STR,
|
|
_coordFormat, _coordPrec, cp);
|
|
}
|
|
}
|
|
} else {
|
|
err = std::make_error_code(std::errc::invalid_argument);
|
|
}
|
|
|
|
if (err) { // send error description
|
|
output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ERROR_STR, err);
|
|
}
|
|
|
|
|
|
return output_msg;
|
|
}
|
|
|
|
|
|
template <typename MSG_T>
|
|
std::error_code parseMessage(std::string_view msg_bytes, MSG_T& msg)
|
|
{
|
|
auto ec = msg.fromCharRange(msg_bytes);
|
|
|
|
return ec;
|
|
}
|
|
|
|
|
|
std::error_code coordsFromTelemetryData(mcc_generic_mount_c auto& mount,
|
|
bool target, // true - target coordinates, false - mount coordinates
|
|
mcc_celestial_point_c auto& cp)
|
|
{
|
|
MccTelemetryData tdata;
|
|
|
|
auto t_err = mount.telemetryData(&tdata);
|
|
if (t_err) {
|
|
return mcc_deduce_error_code(t_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY);
|
|
}
|
|
|
|
mcc_tp2tp(tdata.target.time_point, cp.time_point);
|
|
|
|
switch (cp.pair_kind) {
|
|
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_ICRS:
|
|
if (target) {
|
|
cp.X = tdata.target.RA_ICRS;
|
|
cp.Y = tdata.target.DEC_ICRS;
|
|
mcc_tp2tp(MccCelestialCoordEpoch::J2000_UTC, cp.time_point);
|
|
} // ??!!!
|
|
break;
|
|
case mcc::MccCoordPairKind::COORDS_KIND_RADEC_APP:
|
|
if (target) {
|
|
cp.X = tdata.target.RA_APP;
|
|
cp.Y = tdata.target.DEC_APP;
|
|
} else {
|
|
cp.X = tdata.RA_APP;
|
|
cp.Y = tdata.DEC_APP;
|
|
}
|
|
|
|
break;
|
|
case mcc::MccCoordPairKind::COORDS_KIND_HADEC_APP:
|
|
if (target) {
|
|
cp.X = tdata.target.HA;
|
|
cp.Y = tdata.target.DEC_APP;
|
|
} else {
|
|
cp.X = tdata.HA;
|
|
cp.Y = tdata.DEC_APP;
|
|
}
|
|
break;
|
|
case mcc::MccCoordPairKind::COORDS_KIND_AZZD:
|
|
if (target) {
|
|
cp.X = tdata.target.AZ;
|
|
cp.Y = tdata.target.ZD;
|
|
} else {
|
|
cp.X = tdata.AZ;
|
|
cp.Y = tdata.ZD;
|
|
}
|
|
break;
|
|
case mcc::MccCoordPairKind::COORDS_KIND_AZALT:
|
|
if (target) {
|
|
cp.X = tdata.target.AZ;
|
|
cp.Y = tdata.target.ALT;
|
|
} else {
|
|
cp.X = tdata.AZ;
|
|
cp.Y = tdata.ALT;
|
|
}
|
|
break;
|
|
case mcc::MccCoordPairKind::COORDS_KIND_XY:
|
|
if (target) {
|
|
cp.X = tdata.target.X;
|
|
cp.Y = tdata.target.Y;
|
|
} else {
|
|
cp.X = tdata.X;
|
|
cp.Y = tdata.Y;
|
|
}
|
|
break;
|
|
default:
|
|
return std::make_error_code(std::errc::invalid_argument);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
};
|
|
|
|
} // namespace mcc::network
|