diff --git a/.qtcreator/CMakeLists.txt.user.25c9513 b/.qtcreator/CMakeLists.txt.user.25c9513
new file mode 100644
index 0000000..878bdfa
--- /dev/null
+++ b/.qtcreator/CMakeLists.txt.user.25c9513
@@ -0,0 +1,746 @@
+
+
+
+
+
+ EnvironmentId
+ {25c95133-c398-49ba-8aa0-e9daf4073ad0}
+
+
+ ProjectExplorer.Project.ActiveTarget
+ 0
+
+
+ ProjectExplorer.Project.EditorSettings
+
+ true
+ true
+ true
+
+ Cpp
+
+ CppGlobal
+
+
+
+ QmlJS
+
+ QmlJSGlobal
+
+
+ 2
+ UTF-8
+ false
+ 4
+ false
+ 0
+ 80
+ true
+ true
+ 1
+ 0
+ false
+ true
+ false
+ 2
+ true
+ true
+ 0
+ 8
+ true
+ false
+ 1
+ true
+ true
+ true
+ *.md, *.MD, Makefile
+ false
+ true
+ true
+
+
+
+ ProjectExplorer.Project.PluginSettings
+
+
+ true
+ false
+ true
+ true
+ true
+ true
+
+ false
+
+
+ 0
+ true
+
+ true
+ true
+ Builtin.DefaultTidyAndClazy
+ 4
+ true
+
+
+
+ true
+
+
+ true
+ true
+
+ 0
+
+
+
+ ProjectExplorer.Project.Target.0
+
+ Desktop
+ true
+ Desktop
+ Desktop
+ {0a48682c-1ff4-4142-9b6d-ae2f5a4dfc82}
+ 0
+ 0
+ 1
+
+ Debug
+ 2
+ false
+
+ -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C}
+-DCMAKE_GENERATOR:STRING=Unix Makefiles
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx}
+-DCMAKE_BUILD_TYPE:STRING=Debug
+ /home/timur/PROGRAMS/C++/mcc/build/Desktop-Debug
+
+
+
+
+ all
+
+ false
+
+ true
+ Собрать
+ CMakeProjectManager.MakeStep
+
+ 1
+ Сборка
+ Сборка
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ Собрать
+ CMakeProjectManager.MakeStep
+
+ 1
+ Очистка
+ Очистка
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Отладка
+ CMakeProjectManager.CMakeBuildConfiguration
+ 0
+ 1
+
+
+ 0
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ Собрать
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Install Application Manager package
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ false
+
+ false
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_telemetry_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_coord_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_pzone_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+ 3
+
+ 1
+
+
+ 0
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ Собрать
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Install Application Manager package
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_telemetry_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_coord_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_pzone_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+ 3
+
+
+
+ ProjectExplorer.Project.Target.1
+
+ Desktop
+ true
+ Clang
+ Clang
+ {609869bc-d303-4485-8b34-3f43a08cec9e}
+ 0
+ 0
+ 3
+
+ Debug
+ 2
+ false
+
+ -DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
+-DCMAKE_C_COMPILER:STRING=%{Compiler:Executable:C}
+-DCMAKE_GENERATOR:STRING=Ninja
+-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
+-DCMAKE_PREFIX_PATH:STRING=%{Qt:QT_INSTALL_PREFIX}
+-DQT_QMAKE_EXECUTABLE:STRING=%{Qt:qmakeExecutable}
+-DCMAKE_CXX_COMPILER:STRING=%{Compiler:Executable:Cxx}
+-DCMAKE_BUILD_TYPE:STRING=Debug
+ /home/timur/PROGRAMS/C++/mcc/build/Clang-Debug
+
+
+
+
+ all
+
+ false
+
+ true
+ Собрать
+ CMakeProjectManager.MakeStep
+
+ 1
+ Сборка
+ Сборка
+ ProjectExplorer.BuildSteps.Build
+
+
+
+
+
+ clean
+
+ false
+
+ true
+ Собрать
+ CMakeProjectManager.MakeStep
+
+ 1
+ Очистка
+ Очистка
+ ProjectExplorer.BuildSteps.Clean
+
+ 2
+ false
+
+ false
+
+ Отладка
+ CMakeProjectManager.CMakeBuildConfiguration
+ 0
+ 3
+
+
+ 0
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ Собрать
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Install Application Manager package
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ false
+
+ false
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ exe
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_telemetry_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_coord_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_pzone_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+ 4
+
+ 1
+
+
+ 0
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ProjectExplorer.DefaultDeployConfiguration
+
+
+
+
+
+
+
+
+ false
+
+ true
+ Собрать
+ ApplicationManagerPlugin.Deploy.CMakePackageStep
+
+
+ install-package --acknowledge
+ true
+ Install Application Manager package
+ ApplicationManagerPlugin.Deploy.InstallPackageStep
+
+
+
+
+
+
+
+ 2
+ Развёртывание
+ Развёртывание
+ ProjectExplorer.BuildSteps.Deploy
+
+ 1
+
+ false
+ ApplicationManagerPlugin.Deploy.Configuration
+
+ 2
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ exe
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_telemetry_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_coord_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+
+ true
+ true
+ 0
+ true
+ /usr/bin/valgrind
+
+ 2
+
+ false
+ -e cpu-cycles --call-graph dwarf,4096 -F 250
+
+ CMakeProjectManager.CMakeRunConfiguration.
+ mcc_pzone_test
+ false
+
+ true
+ true
+ true
+ %{RunConfig:Executable:Path}
+
+ 4
+
+
+
+ ProjectExplorer.Project.TargetCount
+ 2
+
+
+ Version
+ 22
+
+
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d691f5e..cdd9a37 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,11 +1,9 @@
cmake_minimum_required(VERSION 3.14)
-
# ******* MOUNT CONTROL COMPONENTS *******
project(mcc LANGUAGES C CXX Fortran VERSION 0.1)
-
# set(CMAKE_BUILD_TYPE Release)
set(CMAKE_CXX_STANDARD 23)
@@ -13,13 +11,27 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")
-
# ******* LIBRARY OPTIONS *******
-option(USE_SPDLOG "Use of SPDLOG library (add implementation of logger class based on this library)" ON)
-option(USE_ERFA "Use of ERFA library (add implementation of CCTE based on this library)" ON)
+option(
+ USE_SPDLOG
+ "Use of SPDLOG library (add implementation of logger class based on this library)"
+ ON
+)
+
+option(
+ USE_ERFA
+ "Use of ERFA library (add implementation of CCTE based on this library)"
+ ON
+)
+
option(USE_BSPLINE_PCM "Use of FITPACK bivariate splines for PCM" ON)
+option(
+ USE_ASIO
+ "Use of ASIO-library (add generic implementation of network capabilities)"
+ ON
+)
option(BUILD_TESTS "Build tests" ON)
@@ -30,20 +42,22 @@ include(ExternalProject)
# ******* SPDLOG LIBRARY *******
-if (USE_SPDLOG)
+if(USE_SPDLOG)
set(SPDLOG_USE_STD_FORMAT ON CACHE INTERNAL "Use of C++20 std::format")
set(SPDLOG_FMT_EXTERNAL OFF CACHE INTERNAL "Turn off external fmt library")
find_package(spdlog CONFIG)
- if (NOT ${spdlog_FOUND})
- FetchContent_Declare(spdlog
- GIT_REPOSITORY "https://github.com/gabime/spdlog.git"
- GIT_TAG "v1.15.1"
- GIT_SHALLOW TRUE
- GIT_SUBMODULES ""
- GIT_PROGRESS TRUE
- CMAKE_ARGS "-DSPDLOG_USE_STD_FORMAT=ON -DSPDLOG_FMT_EXTERNAL=OFF"
- OVERRIDE_FIND_PACKAGE
+ if(NOT ${spdlog_FOUND})
+ FetchContent_Declare(
+ spdlog
+ GIT_REPOSITORY "https://github.com/gabime/spdlog.git"
+ GIT_TAG "v1.15.1"
+ GIT_SHALLOW TRUE
+ GIT_SUBMODULES ""
+ GIT_PROGRESS TRUE
+ CMAKE_ARGS
+ "-DSPDLOG_USE_STD_FORMAT=ON -DSPDLOG_FMT_EXTERNAL=OFF"
+ OVERRIDE_FIND_PACKAGE
)
find_package(spdlog CONFIG)
endif()
@@ -51,133 +65,192 @@ endif()
# ******* ERFA LIBRARY *******
-
find_package(PkgConfig REQUIRED)
-pkg_check_modules(ERFALIB IMPORTED_TARGET GLOBAL erfa)
-
-if (NOT ERFALIB_FOUND)
- message(STATUS "\tfetch erfa-lib ...")
- # ExternalProject_Add(erfalib
- # PREFIX ${CMAKE_BINARY_DIR}/erfa_lib
- # GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
- # GIT_TAG "v2.0.1"
- # UPDATE_COMMAND ""
- # PATCH_COMMAND ""
- # LOG_CONFIGURE 1
- # CONFIGURE_COMMAND meson setup --reconfigure -Ddefault_library=static -Dbuildtype=release
- # -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib -Dlibdir= -Dincludedir= -Ddatadir=
- # BUILD_COMMAND ninja -C
- # INSTALL_COMMAND meson install -C
- # BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a
- # )
-
- # add_library(PkgConfig::ERFALIB STATIC IMPORTED GLOBAL)
- # set_target_properties(PkgConfig::ERFALIB PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a)
- # set_target_properties(PkgConfig::ERFALIB PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/erfa_lib)
- # add_dependencies(PkgConfig::ERFALIB erfalib)
-
- # set(CACHE{ERFALIB_INCLUDE_DIRS} TYPE PATH VALUE "${CMAKE_BINARY_DIR}/erfa_lib")
- # set(CACHE{ERFALIB_LIBRARY_DIRS} TYPE PATH VALUE "${CMAKE_BINARY_DIR}/erfa_lib")
- # set(CACHE{ERFALIB_LIBRARIES} TYPE STRING VALUE "erfa;m")
-
- find_program(MESON_PROG NAMES meson HINTS ENV PATHS)
-
- if (NOT MESON_PROG)
- message(FATAL "meson executable can not be found!!!")
- endif()
-
- find_program(NINJA_PROG NAMES ninja ninja-build)
-
- if (NOT NINJA_PROG)
- message(FATAL "ninja executable can not be found!!!")
- endif()
-
-
- FetchContent_Declare(erfalib_project
- GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
- GIT_TAG "v2.0.1"
- GIT_SHALLOW TRUE
- GIT_PROGRESS TRUE
- )
-
- FetchContent_MakeAvailable(erfalib_project)
- # message(STATUS "ERFA: ${erfalib_project_SOURCE_DIR}")
-
- message(STATUS "\tbuild erfa-lib ...")
- execute_process(
- COMMAND meson setup --reconfigure -Ddefault_library=static -Dbuildtype=release
- -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib -Dlibdir= -Dincludedir= -Ddatadir= ${CMAKE_BINARY_DIR}/erfa_lib ${erfalib_project_SOURCE_DIR}
- )
-
- execute_process(
- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/erfa_lib
- COMMAND ninja -C ${CMAKE_BINARY_DIR}/erfa_lib
- )
-
- execute_process(
- WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/erfa_lib
- COMMAND meson install -C ${CMAKE_BINARY_DIR}/erfa_lib
- )
-
- set(ENV{PKG_CONFIG_PATH} "${CMAKE_BINARY_DIR}/erfa_lib/pkgconfig")
+if(USE_ERFA)
pkg_check_modules(ERFALIB IMPORTED_TARGET GLOBAL erfa)
+
+ if(NOT ERFALIB_FOUND)
+ message(STATUS "\tfetch erfa-lib ...")
+ # ExternalProject_Add(erfalib
+ # PREFIX ${CMAKE_BINARY_DIR}/erfa_lib
+ # GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
+ # GIT_TAG "v2.0.1"
+ # UPDATE_COMMAND ""
+ # PATCH_COMMAND ""
+ # LOG_CONFIGURE 1
+ # CONFIGURE_COMMAND meson setup --reconfigure -Ddefault_library=static -Dbuildtype=release
+ # -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib -Dlibdir= -Dincludedir= -Ddatadir=
+ # BUILD_COMMAND ninja -C
+ # INSTALL_COMMAND meson install -C
+ # BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a
+ # )
+
+ # add_library(PkgConfig::ERFALIB STATIC IMPORTED GLOBAL)
+ # set_target_properties(PkgConfig::ERFALIB PROPERTIES IMPORTED_LOCATION ${CMAKE_BINARY_DIR}/erfa_lib/liberfa.a)
+ # set_target_properties(PkgConfig::ERFALIB PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_BINARY_DIR}/erfa_lib)
+ # add_dependencies(PkgConfig::ERFALIB erfalib)
+
+ # set(CACHE{ERFALIB_INCLUDE_DIRS} TYPE PATH VALUE "${CMAKE_BINARY_DIR}/erfa_lib")
+ # set(CACHE{ERFALIB_LIBRARY_DIRS} TYPE PATH VALUE "${CMAKE_BINARY_DIR}/erfa_lib")
+ # set(CACHE{ERFALIB_LIBRARIES} TYPE STRING VALUE "erfa;m")
+
+ find_program(MESON_PROG NAMES meson HINTS ENV PATHS)
+
+ if(NOT MESON_PROG)
+ message(FATAL "meson executable can not be found!!!")
+ endif()
+
+ find_program(NINJA_PROG NAMES ninja ninja-build)
+
+ if(NOT NINJA_PROG)
+ message(FATAL "ninja executable can not be found!!!")
+ endif()
+
+ FetchContent_Declare(
+ erfalib_project
+ GIT_REPOSITORY "https://github.com/liberfa/erfa.git"
+ GIT_TAG "v2.0.1"
+ GIT_SHALLOW TRUE
+ GIT_PROGRESS TRUE
+ )
+
+ FetchContent_MakeAvailable(erfalib_project)
+ # message(STATUS "ERFA: ${erfalib_project_SOURCE_DIR}")
+
+ message(STATUS "\tbuild erfa-lib ...")
+ execute_process(
+ COMMAND
+ meson setup --reconfigure -Ddefault_library=static
+ -Dbuildtype=release -Dprefix=${CMAKE_BINARY_DIR}/erfa_lib
+ -Dlibdir= -Dincludedir= -Ddatadir= ${CMAKE_BINARY_DIR}/erfa_lib
+ ${erfalib_project_SOURCE_DIR}
+ )
+
+ execute_process(
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/erfa_lib
+ COMMAND ninja -C ${CMAKE_BINARY_DIR}/erfa_lib
+ )
+
+ execute_process(
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/erfa_lib
+ COMMAND meson install -C ${CMAKE_BINARY_DIR}/erfa_lib
+ )
+
+ set(ENV{PKG_CONFIG_PATH} "${CMAKE_BINARY_DIR}/erfa_lib/pkgconfig")
+ pkg_check_modules(ERFALIB IMPORTED_TARGET GLOBAL erfa)
+ endif()
+
+ message(STATUS "ERFA LIBS: ${ERFALIB_LIBRARIES}")
+ message(STATUS "ERFA LIB PATHS: ${ERFALIB_LIBRARY_DIRS}")
+ message(STATUS "ERFA INC PATHS: ${ERFALIB_INCLUDE_DIRS}")
endif()
+if(USE_ASIO)
+ pkg_check_modules(ASIOLIB IMPORTED_TARGET GLOBAL asio)
-message(STATUS "ERFA LIBS: ${ERFALIB_LIBRARIES}")
-message(STATUS "ERFA LIB PATHS: ${ERFALIB_LIBRARY_DIRS}")
-message(STATUS "ERFA INC PATHS: ${ERFALIB_INCLUDE_DIRS}")
+ if(NOT ASIOLIB_FOUND)
+ message(STATUS "\tfetch asio-lib ...")
+ FetchContent_Declare(
+ asiolib_project
+ PREFIX
+ ${CMAKE_BINARY_DIR}/asio
+ GIT_REPOSITORY "https://github.com/chriskohlhoff/asio.git"
+ GIT_TAG "asio-1-36-0"
+ GIT_SHALLOW 1
+ )
-if (USE_BSPLINE_PCM)
+ FetchContent_MakeAvailable(asiolib_project)
+ endif()
+
+ execute_process(
+ WORKING_DIRECTORY ${asiolib_project_SOURCE_DIR}/asio
+ COMMAND ./autogen.sh
+ )
+
+ execute_process(
+ WORKING_DIRECTORY ${asiolib_project_SOURCE_DIR}/asio
+ COMMAND ./configure --prefix=${asiolib_project_SOURCE_DIR}
+ )
+
+ set(ENV{PKG_CONFIG_PATH} "${asiolib_project_SOURCE_DIR}/asio")
+ pkg_check_modules(ASIOLIB IMPORTED_TARGET GLOBAL asio)
+
+ message(STATUS "ASIO INC PATHS: ${ASIOLIB_INCLUDE_DIRS}")
+endif()
+
+if(USE_BSPLINE_PCM)
# fitpack by P. Dierckx
add_subdirectory(fitpack)
endif()
-set(MCC_SRC mcc_concepts.h mcc_constants.h mcc_epoch.h mcc_angle.h mcc_coordinate.h mcc_error.h
- mcc_traits.h mcc_utils.h mcc_ccte_iers.h mcc_ccte_iers_default.h mcc_ccte_erfa.h mcc_pzone.h
- mcc_pzone_container.h mcc_pcm.h mcc_telemetry.h mcc_serializer.h)
+set(MCC_SRC
+ mcc_concepts.h
+ mcc_constants.h
+ mcc_epoch.h
+ mcc_angle.h
+ mcc_coordinate.h
+ mcc_error.h
+ mcc_traits.h
+ mcc_utils.h
+ mcc_pzone.h
+ mcc_pzone_container.h
+ mcc_pcm.h
+ mcc_telemetry.h
+ mcc_serialization_common.h
+ mcc_deserializer.h
+ mcc_serializer.h
+)
-if (USE_SPDLOG)
+if(USE_SPDLOG)
list(APPEND MCC_SRC mcc_spdlog.h)
endif()
+if(USE_ERFA)
+ list(APPEND MCC_SRC mcc_ccte_iers.h mcc_ccte_iers_default.h mcc_ccte_erfa.h)
+endif()
+
+if(USE_ASIO)
+ list(
+ APPEND MCC_SRC
+ mcc_netserver_endpoint.h
+ mcc_netserver_proto.h
+ mcc_netserver.h
+ )
+endif()
+
add_library(${PROJECT_NAME} INTERFACE ${MCC_SRC})
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_23)
-target_link_libraries(${PROJECT_NAME} INTERFACE PkgConfig::ERFALIB)
-# target_link_libraries(${PROJECT_NAME} INTERFACE PkgConfig::ERFALIB fitpack)
-target_include_directories(
- ${PROJECT_NAME}
- INTERFACE
- # $
- $
- $
-)
-if (USE_BSPLINE_PCM)
- target_compile_definitions(${PROJECT_NAME} INTERFACE USE_BSPLINE_PCM)
- target_link_libraries(${PROJECT_NAME} INTERFACE fitpack)
- # target_include_directories(
- # ${PROJECT_NAME}
- # INTERFACE
- # $)
+
+if(USE_ERFA)
+ target_link_libraries(${PROJECT_NAME} INTERFACE PkgConfig::ERFALIB)
+ target_include_directories(
+ ${PROJECT_NAME}
+ INTERFACE
+ # $
+ $
+ $
+ )
+else()
+ target_include_directories(
+ ${PROJECT_NAME}
+ INTERFACE
+ $
+ $
+ )
endif()
-# get_target_property(ZZ ${PROJECT_NAME} INTERFACE_INCLUDE_DIRECTORIES)
+if(USE_BSPLINE_PCM)
+ target_compile_definitions(${PROJECT_NAME} INTERFACE USE_BSPLINE_PCM)
+ target_link_libraries(${PROJECT_NAME} INTERFACE fitpack)
+endif()
-# message(STATUS "INT: ${ZZ}")
+if(USE_ASIO)
+ target_link_libraries(${PROJECT_NAME} INTERFACE PkgConfig::ASIOLIB)
+endif()
-
-
-# add_executable(exe EXCLUDE_FROM_ALL main.cpp)
-# target_link_libraries(exe PUBLIC ${PROJECT_NAME})
-
-# get_target_property(ZZ exe INCLUDE_DIRECTORIES)
-
-# message(STATUS "INT: ${ZZ_STRING}")
-
-
-if (BUILD_TESTS)
+if(BUILD_TESTS)
add_executable(mcc_telemetry_test tests/mcc_telemetry_test.cpp)
target_link_libraries(mcc_telemetry_test PRIVATE ${PROJECT_NAME})
@@ -192,34 +265,48 @@ else()
target_link_libraries(just_stub PUBLIC ${PROJECT_NAME})
endif()
-
include(CMakePackageConfigHelpers)
-write_basic_package_version_file("${PROJECT_NAME}ConfigVersion.cmake"
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY SameMajorVersion)
+write_basic_package_version_file(
+ "${PROJECT_NAME}ConfigVersion.cmake"
+ VERSION ${PROJECT_VERSION}
+ COMPATIBILITY SameMajorVersion
+)
+set(MCC_CONFIG_INSTALLDIR
+ ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}
+ CACHE PATH
+ "install path for generated library config files"
+)
+set(MCC_HEADERS_INSTALLDIR
+ ${CMAKE_INSTALL_INCLUDEDIR}
+ CACHE PATH
+ "install path for headers"
+)
-set(MCC_CONFIG_INSTALLDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} CACHE PATH "install path for generated library config files")
-set(MCC_HEADERS_INSTALLDIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE PATH "install path for headers")
-
-configure_package_config_file("${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in"
- "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
- INSTALL_DESTINATION
- ${MCC_CONFIG_INSTALLDIR}
- PATH_VARS MCC_HEADERS_INSTALLDIR)
+configure_package_config_file(
+ "${PROJECT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in"
+ "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
+ INSTALL_DESTINATION ${MCC_CONFIG_INSTALLDIR}
+ PATH_VARS MCC_HEADERS_INSTALLDIR
+)
#install(EXPORT ${PROJECT_NAME}_Targets FILE ${PROJECT_NAME}Targets.cmake NAMESPACE ${PROJECT_NAME_NAMESPACE}:: DESTINATION ${MCC_CONFIG_INSTALLDIR})
#install(FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" DESTINATION ${MCC_CONFIG_INSTALLDIR})
#install(FILES ${MCC_SRC} DESTINATION include/${PROJECT_NAME})
-
# uninstall target
if(NOT TARGET uninstall)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake"
- IMMEDIATE @ONLY)
+ IMMEDIATE
+ @ONLY
+ )
- add_custom_target(uninstall
- COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake)
+ add_custom_target(
+ uninstall
+ COMMAND
+ ${CMAKE_COMMAND} -P
+ ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
+ )
endif()
diff --git a/mcc_netserver.h b/mcc_netserver.h
new file mode 100644
index 0000000..daffd78
--- /dev/null
+++ b/mcc_netserver.h
@@ -0,0 +1,1245 @@
+#pragma once
+
+/* MOUNT CONTROL COMPONENTS LIBRARY */
+
+
+/* A VERY SIMPLE NETWORK SERVER IMPLEMENTATION */
+
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#if __has_include() // POSIX
+#define FORK_EXISTS 1
+#include
+#include
+#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(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(ec), MccGenericMountNetworkServerErrorCategory::get());
+}
+
+} // namespace mcc::network
+
+
+namespace std
+{
+template <>
+class is_error_code_enum : public true_type
+{
+};
+} // namespace std
+
+
+namespace mcc::network
+{
+
+
+namespace traits
+{
+
+template
+concept mcc_endpoint_c = std::derived_from || std::derived_from ||
+ std::derived_from ||
+ std::derived_from;
+
+template
+static constexpr bool is_serial_proto = std::derived_from;
+
+template
+static constexpr bool is_tcp_proto =
+ std::derived_from || std::derived_from;
+
+template
+static constexpr bool is_local_stream_proto = std::derived_from ||
+ std::derived_from;
+
+template
+static constexpr bool is_local_seqpack_proto = std::derived_from ||
+ std::derived_from;
+
+
+} // namespace traits
+
+
+
+/* A GENERIC NETWORK SERVER IMPLEMENTATION */
+
+template
+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 handle_message_func_result_t;
+ // handle received message user function
+ typedef std::function handle_message_func_t;
+
+
+ MccGenericNetworkServer(asio::io_context& ctx, const handle_message_func_t& func)
+ requires std::is_default_constructible_v
+ : _asioContext(ctx), _handleMessageFunc(func), _stopSignal(ctx), _restartSignal(ctx)
+ {
+ std::stringstream st;
+ st << std::this_thread::get_id();
+
+ logInfo(std::format("Create generic network server instance (thread ID = {})", st.str()));
+ }
+
+ template
+ MccGenericNetworkServer(asio::io_context& ctx, const handle_message_func_t& func, LoggerCtorArgsTs&&... log_args)
+ requires(not std::is_default_constructible_v)
+ : LoggerT(std::forward(log_args)...),
+ _asioContext(ctx),
+ _handleMessageFunc(func),
+ _stopSignal(ctx),
+ _restartSignal(ctx),
+ _sessionThreadPool(7)
+ {
+ std::stringstream st;
+ st << std::this_thread::get_id();
+
+ logInfo(std::format("Create MccGenericNetworkServer class 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 generic network server instance (thread ID = {}) ...", st.str()));
+
+ stopListening();
+
+ _sessionThreadPool.stop();
+ _sessionThreadPool.join();
+
+ disconnectClients();
+ }
+
+
+ template
+ asio::awaitable listen(std::derived_from 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(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
+ asio::awaitable listen(EpnT endpoint)
+ {
+ using epn_t = std::decay_t;
+
+ std::error_code ec;
+
+ if constexpr (traits::is_serial_proto) {
+ // 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 || traits::is_local_stream_proto ||
+ traits::is_local_seqpack_proto) {
+ 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) {
+ _tcpAcceptors.emplace_back(&acc);
+ } else if constexpr (traits::is_local_stream_proto) {
+ _localStreamAcceptors.emplace_back(&acc);
+ } else if constexpr (traits::is_local_seqpack_proto) {
+ _localSeqpackAcceptors.emplace_back(&acc);
+ } else {
+ static_assert(false, "INVALID ENDPOINT!!!");
+ }
+
+ // start accepting connections
+ for (;;) {
+ st.str("");
+ st << std::this_thread::get_id();
+ logDebug(std::format("Start accepting new connections (thread ID = {}) ...", st.str()));
+
+ auto sock = co_await acc.async_accept(asio::use_awaitable);
+
+ // start new client session
+ logDebug("Spawn new user session ...");
+
+ // asio::co_spawn(_asioContext, startSession(std::move(sock)), asio::detached);
+
+ // use of sessions own thread pool
+ asio::co_spawn(_sessionThreadPool, startSession(std::move(sock)), asio::detached);
+
+ logDebug("The session was spawned");
+ }
+
+
+ } 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 RRT = std::vector>
+ void setupSignals(const RST& stop_sig_num = {SIGINT, SIGTERM}, const RRT& restart_sig_num = {SIGUSR1})
+ requires(std::convertible_to, int> &&
+ std::convertible_to, 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));
+
+ // _handleMessageFunc(MCC_COMMPROTO_KEYWORD_STOP_STR);
+ // std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+ // stopListening();
+
+ // _sessionThreadPool.stop();
+ // _sessionThreadPool.join();
+
+ // 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 _serialPorts;
+ // std::vector _serialPorts;
+
+ std::vector _tcpAcceptors;
+ std::vector _localStreamAcceptors;
+ std::vector _localSeqpackAcceptors;
+
+ std::set _tcpSockets;
+ std::set _localStreamSockets;
+ std::set _localSeqpackSockets;
+ // std::vector _tcpSockets;
+ // std::vector _localStreamSockets;
+ // std::vector _localSeqpackSockets;
+
+ std::mutex _serialPortsMutex, _tcpSocketsMutex, _localStreamSocketsMutex, _localSeqpackSocketsMutex;
+
+ asio::thread_pool _sessionThreadPool;
+
+ // helpers
+ template
+ 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) {
+ opt_name = "baud rate";
+ } else if constexpr (std::same_as) {
+ opt_name = "parity";
+ } else if constexpr (std::same_as) {
+ opt_name = "flow control";
+ } else if constexpr (std::same_as) {
+ opt_name = "stop bits";
+ } else if constexpr (std::same_as) {
+ 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(opts)...);
+ }
+ }
+
+
+ // std::vector handleClientCommand(std::string_view command)
+ // {
+ // std::vector resp{MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR.begin(),
+ // MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR.end()};
+
+ // return resp;
+ // }
+
+
+ template
+ asio::awaitable 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;
+
+
+ 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) {
+ 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) {
+ logTrace("Set socket option KEEP_ALIVE to TRUE");
+ socket.set_option(asio::socket_base::keep_alive(true));
+ }
+
+ if constexpr (traits::is_serial_proto) {
+ std::lock_guard lock_g(_serialPortsMutex);
+ _serialPorts.insert(&socket);
+ } else if constexpr (traits::is_tcp_proto) {
+ std::lock_guard lock_g(_tcpSocketsMutex);
+ // _tcpSockets.emplace_back(&socket);
+ _tcpSockets.insert(&socket);
+ } else if constexpr (traits::is_local_stream_proto) {
+ std::lock_guard lock_g(_localStreamSocketsMutex);
+ // _localStreamSockets.emplace_back(&socket);
+ _localStreamSockets.insert(&socket);
+ } else if constexpr (traits::is_local_seqpack_proto) {
+ 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 snd_buff_seq{
+ {}, {MCC_COMMPROTO_STOP_SEQ.data(), MCC_COMMPROTO_STOP_SEQ.size()}};
+
+ asio::steady_timer timeout_timer(_asioContext);
+ std::variant 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) {
+ 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) {
+ 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) {
+ 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(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) {
+ 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) {
+ _serialPorts.erase(&socket);
+ } else if constexpr (traits::is_tcp_proto) {
+ _tcpSockets.erase(&socket);
+ } else if constexpr (traits::is_local_stream_proto) {
+ _localStreamSockets.erase(&socket);
+ } else if constexpr (traits::is_local_seqpack_proto) {
+ _localSeqpackSockets.erase(&socket);
+ } else {
+ static_assert(false, "INVALID SOCKET TTYPE!!!");
+ }
+
+ logInfo(std::format("Close client session: remote endpoint <{}> (thread ID = {})", r_epn, thr_id));
+
+ co_return;
+ }
+};
+
+
+template
+class MccGenericMountNetworkServer : public MccGenericNetworkServer
+{
+ using base_t = MccGenericNetworkServer;
+
+public:
+ using typename base_t::handle_message_func_result_t;
+
+ using base_t::logDebug;
+ using base_t::logError;
+ using base_t::logInfo;
+ using base_t::logTrace;
+ using base_t::logWarn;
+
+ template
+ MccGenericMountNetworkServer(asio::io_context& ctx, MountT& mount, LoggerCtorArgsTs&&... log_args)
+ : base_t(ctx, {}, std::forward(log_args)...)
+ {
+ std::stringstream st;
+ st << std::this_thread::get_id();
+
+ logInfo(std::format("Create MccGenericMountNetworkServer class instance (thread ID = {})", st.str()));
+
+ // 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;
+
+ auto nn = std::this_thread::get_id();
+
+ MccNetMessage input_msg;
+ using output_msg_t = MccNetMessage;
+ 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(input_msg, mount_ptr);
+ }
+
+ return output_msg.template byteRepr();
+ };
+
+ // special functor (used in the destructor)
+ _stopMountFunc = [mount_ptr]() { mount_ptr->stopMount(); };
+ }
+
+ virtual ~MccGenericMountNetworkServer()
+ {
+ std::stringstream st;
+ st << std::this_thread::get_id();
+
+ _stopMountFunc();
+
+ logInfo(std::format("Delete MccGenericMountNetworkServer class instance (thread ID = {})", st.str()));
+ }
+
+protected:
+ MccCoordinateSerializer::SerializedCoordFormat _coordFormat{
+ MccCoordinateSerializer::SerializedCoordFormat::CFMT_SGM};
+ MccCoordinateSerializer::SexagesimalCoordPrec _coordPrec{2, 1};
+
+ std::function _stopMountFunc{};
+
+ template
+ RESULT_MSG_T handleMessage(const INPUT_MSG_T& input_msg, MountT* mount_ptr)
+ requires(
+ std::derived_from> &&
+ std::derived_from>)
+ {
+ 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_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(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(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 vp = input_msg.template paramValue(0); // is it get operation?
+ if (vp) { // coordinate pair kind is given, it is get operation
+ 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,
+ _coordFormat, _coordPrec, cp);
+ }
+ } else { // here a celestial point must be given
+ auto vc = input_msg.template paramValue(0);
+ if (vc) { // 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 {
+ err = vc.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(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,
+ _coordFormat, _coordPrec, 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 if (input_msg.withKey(MCC_COMMPROTO_KEYWORD_TELEMETRY_STR)) {
+ MccTelemetryData tdata;
+
+ // auto t_err = mount_ptr->telemetryData(&tdata);
+ auto t_err = mount_ptr->waitForTelemetryData(&tdata);
+ if (t_err) {
+ err = mcc_deduce_error_code(t_err, MccGenericMountNetworkServerErrorCode::ERROR_MOUNT_GET_TELEMETRY);
+ } else {
+ output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_TELEMETRY_STR,
+ _coordFormat, _coordPrec, tdata);
+ }
+ } else if (input_msg.withKey(mcc::network::MCC_COMMPROTO_KEYWORD_STATUS_STR)) {
+ auto st = mount_ptr->mountStatus(); // according to mcc_generic_mount_c 'st' is formattable
+ using st_t = decltype(st);
+ if constexpr (std::is_enum_v) {
+ output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_STATUS_STR,
+ std::to_underlying(st));
+ } else {
+ output_msg.construct(MCC_COMMPROTO_KEYWORD_SERVER_ACK_STR, MCC_COMMPROTO_KEYWORD_STATUS_STR,
+ std::format(st));
+ }
+ } 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
+ std::error_code parseMessage(std::string_view msg_bytes, MSG_T& msg)
+ {
+ auto ec = msg.fromCharRange(msg_bytes);
+
+ if (ec != MSG_T::ERROR_OK) {
+ return std::make_error_code(std::errc::invalid_argument);
+ }
+ // return ec;
+
+ return {};
+ }
+
+
+ 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.waitForTelemetryData(&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
diff --git a/mcc_netserver_endpoint.h b/mcc_netserver_endpoint.h
new file mode 100644
index 0000000..72c0c56
--- /dev/null
+++ b/mcc_netserver_endpoint.h
@@ -0,0 +1,512 @@
+#pragma once
+
+/* MOUNT CONTROL COMPONENTS LIBRARY */
+
+
+
+/* NETWORK SERVER ENDPOINT CLASS IMPLEMENTATION */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "mcc_traits.h"
+
+namespace mcc::network
+{
+
+namespace utils
+{
+
+static constexpr bool mcc_char_range_compare(const traits::mcc_char_view auto& what,
+ const traits::mcc_char_view auto& where,
+ bool case_insensitive = false)
+{
+ if (std::ranges::size(what) == std::ranges::size(where)) {
+ if (case_insensitive) {
+ auto f = std::ranges::search(where,
+ std::views::transform(what, [](const char& ch) { return std::tolower(ch); }));
+ return !f.empty();
+ } else {
+ auto f = std::ranges::search(where, what);
+ return !f.empty();
+ }
+ }
+
+ return false;
+}
+
+} // namespace utils
+
+/*
+ * Very simple various protocols endpoint parser and holder class
+ *
+ * endpoint: proto_mark://host_name:port_num/path
+ * where "path" is optional for all non-local protocol kinds;
+ *
+ * for local kind of protocols the endpoint must be given as:
+ * local://stream/PATH
+ * local://seqpacket/PATH
+ * local://serial/PATH
+ * where 'stream' and 'seqpacket' "host_name"-field marks the
+ * stream-type and seqpacket-type UNIX domain sockets protocols;
+ * 'serial' marks a serial (RS232/485) protocol.
+ * here, possible "port_num" field is allowed but ignored.
+ *
+ * NOTE: "proto_mark" and "host_name" (for local kind) fields are parsed in case-insensitive manner!
+ *
+ * EXAMPLES: tcp://192.168.70.130:3131
+ * local://serial/dev/ttyS1
+ * local://seqpacket/tmp/BM70_SERVER_SOCK
+ *
+ *
+ */
+
+
+class MccNetServerEndpoint
+{
+public:
+ static constexpr std::string_view protoHostDelim = "://";
+ static constexpr std::string_view hostPortDelim = ":";
+ static constexpr std::string_view portPathDelim = "/";
+
+ enum proto_id_t : uint8_t {
+ PROTO_ID_LOCAL,
+ PROTO_ID_SEQLOCAL,
+ PROTO_ID_SERLOCAL,
+ PROTO_ID_TCP,
+ PROTO_ID_TLS,
+ PROTO_ID_UNKNOWN
+ };
+
+ static constexpr std::string_view protoMarkLocal{"local"}; // UNIX domain
+ static constexpr std::string_view protoMarkTCP{"tcp"}; // TCP
+ static constexpr std::string_view protoMarkTLS{"tls"}; // TLS
+
+ static constexpr std::array validProtoMarks{protoMarkLocal, protoMarkTCP, protoMarkTLS};
+
+
+ static constexpr std::string_view localProtoTypeStream{"stream"}; // UNIX domain stream
+ static constexpr std::string_view localProtoTypeSeqpacket{"seqpacket"}; // UNIX domain seqpacket
+ static constexpr std::string_view localProtoTypeSerial{"serial"}; // serial (RS232/485)
+
+ static constexpr std::array validLocalProtoTypes{localProtoTypeStream, localProtoTypeSeqpacket,
+ localProtoTypeSerial};
+
+
+ template
+ MccNetServerEndpoint(const R& ept)
+ {
+ fromRange(ept);
+ }
+
+ MccNetServerEndpoint(const MccNetServerEndpoint& other)
+ {
+ copyInst(other);
+ }
+
+ MccNetServerEndpoint(MccNetServerEndpoint&& other)
+ {
+ moveInst(std::move(other));
+ }
+
+ virtual ~MccNetServerEndpoint() = default;
+
+
+ MccNetServerEndpoint& operator=(const MccNetServerEndpoint& other)
+ {
+ copyInst(other);
+
+ return *this;
+ }
+
+
+ MccNetServerEndpoint& operator=(MccNetServerEndpoint&& other)
+ {
+ moveInst(std::move(other));
+
+ return *this;
+ }
+
+ template
+ requires std::ranges::contiguous_range
+ bool fromRange(const R& ept)
+ {
+ _isValid = false;
+
+ // at least 'ws://a' (proto, proto-host delimiter and at least a single character of hostname)
+ if (std::ranges::size(ept) < 6) {
+ return _isValid;
+ }
+
+ if constexpr (std::is_array_v>) {
+ _endpoint = ept;
+ } else {
+ _endpoint.clear();
+ std::ranges::copy(ept, std::back_inserter(_endpoint));
+ }
+
+ auto found = std::ranges::search(_endpoint, protoHostDelim);
+ if (found.empty()) {
+ return _isValid;
+ }
+
+
+ ssize_t idx;
+ if ((idx = checkProtoMark(std::string_view{_endpoint.begin(), found.begin()})) < 0) {
+ return _isValid;
+ }
+
+ _proto = validProtoMarks[idx];
+
+ _host = std::string_view{found.end(), _endpoint.end()};
+
+ auto f1 = std::ranges::search(_host, portPathDelim);
+ // std::string_view port_sv;
+ if (f1.empty() && isLocal()) { // no path, but it is mandatory for 'local'!
+ return _isValid;
+ } else {
+ _host = std::string_view(_host.begin(), f1.begin());
+
+ _path = std::string_view(f1.end(), &*_endpoint.end());
+
+ f1 = std::ranges::search(_host, hostPortDelim);
+ if (f1.empty() && !isLocal()) { // no port, but it is mandatory for non-local!
+ return _isValid;
+ }
+
+ _portView = std::string_view(f1.end(), _host.end());
+ if (_portView.size()) {
+ _host = std::string_view(_host.begin(), f1.begin());
+
+ if (!isLocal()) {
+ // convert port string to int
+ auto end_ptr = _portView.data() + _portView.size();
+
+ auto [ptr, ec] = std::from_chars(_portView.data(), end_ptr, _port);
+ if (ec != std::errc() || ptr != end_ptr) {
+ return _isValid;
+ }
+ } else { // ignore for local
+ _port = -1;
+ }
+ } else {
+ _port = -1;
+ }
+
+ if (isLocal()) { // check for special values
+ idx = 0;
+ if (std::ranges::any_of(validLocalProtoTypes, [&idx, this](const auto& el) {
+ bool ok = utils::mcc_char_range_compare(_host, el, true);
+ if (!ok) {
+ ++idx;
+ }
+ return ok;
+ })) {
+ _host = validLocalProtoTypes[idx];
+ } else {
+ return _isValid;
+ }
+ }
+ }
+
+ _isValid = true;
+
+ return _isValid;
+ }
+
+
+ bool isValid() const
+ {
+ return _isValid;
+ }
+
+
+ auto endpoint() const
+ {
+ return _endpoint;
+ }
+
+ template
+ R proto() const
+ {
+ return part(PROTO_PART);
+ }
+
+ std::string_view proto() const
+ {
+ return proto();
+ }
+
+ template
+ R host() const
+ {
+ return part(HOST_PART);
+ }
+
+ std::string_view host() const
+ {
+ return host();
+ }
+
+ int port() const
+ {
+ return _port;
+ }
+
+ template
+ R portView() const
+ {
+ return part(PORT_PART);
+ }
+
+ std::string_view portView() const
+ {
+ return portView();
+ }
+
+ template
+ R path(RR&& root_path) const
+ {
+ if (_path.empty()) {
+ if constexpr (traits::mcc_output_char_range) {
+ R res;
+ std::ranges::copy(std::forward(root_path), std::back_inserter(res));
+
+ return res;
+ } else { // can't add root path!!!
+ return part(PATH_PART);
+ }
+ }
+
+ auto N = std::ranges::distance(root_path.begin(), root_path.end());
+
+ if (N) {
+ R res;
+ std::filesystem::path pt(root_path.begin(), root_path.end());
+
+ if (isLocal() && _path[0] == '\0') {
+ std::ranges::copy(std::string_view(" "), std::back_inserter(res));
+ pt /= _path.substr(1);
+ std::ranges::copy(pt.string(), std::back_inserter(res));
+ *res.begin() = '\0';
+ } else {
+ pt /= _path;
+ std::ranges::copy(pt.string(), std::back_inserter(res));
+ }
+
+ return res;
+ } else {
+ return part(PATH_PART);
+ }
+ }
+
+ template
+ std::string path(RR&& root_path) const
+ {
+ return path(std::forward(root_path));
+ }
+
+ template
+ R path() const
+ {
+ return part(PATH_PART);
+ }
+
+ std::string_view path() const
+ {
+ return path();
+ }
+
+
+ bool isLocal() const
+ {
+ return proto() == protoMarkLocal;
+ }
+
+ bool isLocalStream() const
+ {
+ return host() == localProtoTypeStream;
+ }
+
+ bool isLocalSerial() const
+ {
+ return host() == localProtoTypeSerial;
+ }
+
+ bool isLocalSeqpacket() const
+ {
+ return host() == localProtoTypeSeqpacket;
+ }
+
+
+ bool isTCP() const
+ {
+ return proto() == protoMarkTCP;
+ }
+
+ bool isTLS() const
+ {
+ return proto() == protoMarkTLS;
+ }
+
+
+ // add '\0' char (or replace special-meaning char/char-sequence) to construct UNIX abstract namespace
+ // endpoint path
+ template
+ MccNetServerEndpoint& makeAbstract(const T& mark = nullptr)
+ requires(traits::mcc_input_char_range || std::same_as, char> ||
+ std::is_null_pointer_v>)
+ {
+ if (!(isLocalStream() || isLocalSeqpacket())) { // only local proto is valid!
+ return *this;
+ }
+
+ if constexpr (std::is_null_pointer_v) { // just insert '\0'
+ auto it = _endpoint.insert(std::string::const_iterator(_path.begin()), '\0');
+ _path = std::string_view(it, _endpoint.end());
+ } else if constexpr (std::same_as, char>) { // replace a character (mark)
+ auto pos = std::distance(_endpoint.cbegin(), std::string::const_iterator(_path.begin()));
+ if (_endpoint[pos] == mark) {
+ _endpoint[pos] = '\0';
+ }
+ } else { // replace a character range (mark)
+ if (std::ranges::equal(_path | std::views::take(std::ranges::size(mark), mark))) {
+ auto pos = std::distance(_endpoint.cbegin(), std::string::const_iterator(_path.begin()));
+ _endpoint.replace(pos, std::ranges::size(mark), 1, '\0');
+ _path = std::string_view(_endpoint.begin() + pos, _endpoint.end());
+ }
+ }
+
+ return *this;
+ }
+
+protected:
+ std::string _endpoint;
+ std::string_view _proto, _host, _path, _portView;
+ int _port;
+ bool _isValid;
+
+
+ virtual ssize_t checkProtoMark(std::string_view proto_mark)
+ {
+ ssize_t idx = 0;
+
+ // case-insensitive look-up
+ bool found = std::ranges::any_of(MccNetServerEndpoint::validProtoMarks, [&idx, &proto_mark](const auto& el) {
+ bool ok = utils::mcc_char_range_compare(proto_mark, el, true);
+
+ if (!ok) {
+ ++idx;
+ }
+
+ return ok;
+ });
+
+ return found ? idx : -1;
+ }
+
+ enum EndpointPart { PROTO_PART, HOST_PART, PATH_PART, PORT_PART };
+
+ template
+ R part(EndpointPart what) const
+ {
+ R res;
+
+ // if (!_isValid) {
+ // return res;
+ // }
+
+ auto part = _proto;
+
+ switch (what) {
+ case PROTO_PART:
+ part = _proto;
+ break;
+ case HOST_PART:
+ part = _host;
+ break;
+ case PATH_PART:
+ part = _path;
+ break;
+ case PORT_PART:
+ part = _portView;
+ break;
+ default:
+ break;
+ }
+
+ if constexpr (std::ranges::view) {
+ return {part.begin(), part.end()};
+ } else {
+ std::ranges::copy(part, std::back_inserter(res));
+ }
+
+ return res;
+ }
+
+ void copyInst(const MccNetServerEndpoint& other)
+ {
+ if (&other != this) {
+ if (other._isValid) {
+ _isValid = other._isValid;
+ _endpoint = other._endpoint;
+ _proto = other._proto;
+
+ std::iterator_traits::difference_type idx;
+ if (other.isLocal()) { // for 'local' host is one of static class constants
+ _host = other._host;
+ } else {
+ idx = std::distance(other._endpoint.c_str(), other._host.data());
+ _host = std::string_view(_endpoint.c_str() + idx, other._host.size());
+ }
+
+ idx = std::distance(other._endpoint.c_str(), other._path.data());
+ _path = std::string_view(_endpoint.c_str() + idx, other._path.size());
+
+ idx = std::distance(other._endpoint.c_str(), other._portView.data());
+ _portView = std::string_view(_endpoint.c_str() + idx, other._portView.size());
+
+ _port = other._port;
+ } else {
+ _isValid = false;
+ _endpoint = std::string();
+ _proto = std::string_view();
+ _host = std::string_view();
+ _path = std::string_view();
+ _portView = std::string_view();
+ _port = -1;
+ }
+ }
+ }
+
+
+ void moveInst(MccNetServerEndpoint&& other)
+ {
+ if (&other != this) {
+ if (other._isValid) {
+ _isValid = std::move(other._isValid);
+ _endpoint = std::move(other._endpoint);
+ _proto = other._proto;
+ _host = std::move(other._host);
+ _path = std::move(other._path);
+ _port = std::move(other._port);
+ _portView = std::move(other._portView);
+ } else {
+ _isValid = false;
+ _endpoint = std::string();
+ _proto = std::string_view();
+ _host = std::string_view();
+ _path = std::string_view();
+ _portView = std::string_view();
+ _port = -1;
+ }
+ }
+ }
+};
+
+} // namespace mcc::network
diff --git a/mcc_netserver_proto.h b/mcc_netserver_proto.h
new file mode 100644
index 0000000..03f092c
--- /dev/null
+++ b/mcc_netserver_proto.h
@@ -0,0 +1,821 @@
+#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());
+
+
+
+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(); };
+
+
+
+template
+class MccNetMessage
+{
+protected:
+ class DefaultDeserializer : protected mcc::utils::MccSimpleDeserializer
+ {
+ protected:
+ using base_t = mcc::utils::MccSimpleDeserializer;
+
+ inline static mcc::MccCelestialPointDeserializer _cpDeserializer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ};
+ inline static mcc::MccEqtHrzCoordsDeserializer _eqhrDeserializer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ};
+ inline static mcc::MccTelemetryDataDeserializer _telemetryDeserializer{MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ};
+
+ public:
+ DefaultDeserializer() : base_t(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ) {}
+
+ template
+ std::error_code operator()(IR&& bytes, VT& value) const
+ {
+ if constexpr (mcc_telemetry_data_c) {
+ return _telemetryDeserializer(std::forward(bytes), value);
+ } else if constexpr (mcc_eqt_hrz_coord_c) {
+ return _eqhrDeserializer(std::forward(bytes), value);
+ } else if constexpr (mcc_celestial_point_c) {
+ return _cpDeserializer(std::forward(bytes), 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);
+ return MccCoordinateConvErrorCode::ERROR_INVALID_CPAIR;
+ }
+ } 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 = MccCoordinateSerializer::SerializedCoordFormat::CFMT_SGM;
+ } else if (v.compare(MCC_COMMPROTO_KEYWORD_COORDFMT_FIXED_STR) == 0) {
+ value = MccCoordinateSerializer::SerializedCoordFormat::CFMT_DEGREES;
+ } else {
+ // return std::make_error_code(std::errc::invalid_argument);
+ return MccCoordinateConvErrorCode::ERROR_INVALID_COORD_FMT;
+ }
+ } else if constexpr (std::same_as) {
+ std::vector v;
+ auto ec = (*this)(std::forward(bytes), v);
+ if (ec) {
+ // return ec;
+ return MccCoordinateConvErrorCode::ERROR_INVALID_COORD_PREC;
+ }
+
+ 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
+ {
+ friend class MccNetMessage;
+
+ MccCoordinateSerializer::SerializedCoordFormat _coordFmt{};
+ MccCoordinateSerializer::SexagesimalCoordPrec _coordPrec{};
+
+ public:
+ 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_telemetry_data_c) {
+ static MccTelemetryDataSerializer sr;
+
+ sr.setDelimiter(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ);
+ sr.setFormat(_coordFmt);
+ sr.setPrecision(_coordPrec);
+
+ sr(value, bytes);
+ } else if constexpr (mcc_eqt_hrz_coord_c) {
+ static MccEqtHrzCoordsSerializer sr;
+
+ sr.setDelimiter(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ);
+ sr.setFormat(_coordFmt);
+ sr.setPrecision(_coordPrec);
+
+ sr(value, bytes);
+ } else if constexpr (mcc_celestial_point_c) {
+ static MccCelestialPointSerializer sr;
+
+ sr.setDelimiter(MCC_COMMPROTO_RANGEPARAM_DELIM_SEQ);
+ sr.setFormat(_coordFmt);
+ sr.setPrecision(_coordPrec);
+
+ sr(value, bytes);
+ } 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(_defaultSerializer, 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);
+ // }
+
+ virtual ~MccNetMessage() = default;
+
+ template
+ constexpr bool withKey(const KT& key) const
+ {
+ 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()) const
+ requires(traits::mcc_view_or_output_char_range || traits::mcc_range_of_char_range