start new 10-micron stellarium daemon

This commit is contained in:
2026-06-19 12:29:39 +03:00
parent e33a7b9186
commit 413f7bf75e
49 changed files with 2564 additions and 528 deletions

View File

@@ -0,0 +1,64 @@
cmake_minimum_required(VERSION 4.0)
set(PROJ mountdaemon_10micron)
project(${PROJ})
set(MAJOR_VERSION "0")
set(MID_VERSION "1")
set(MINOR_VERSION "1")
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
enable_language(C)
message("VER: ${VERSION}")
# options
option(DEBUG "Compile in debug mode" OFF)
# default flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -std=c23")
if(DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -fno-builtin-strlen -Werror")
add_definitions(-DEBUG)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_VERBOSE_MAKEFILE "ON")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto=auto")
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto=auto")
set(CMAKE_BUILD_TYPE RELEASE)
endif()
message("Build type: ${CMAKE_BUILD_TYPE}")
set(CMAKE_COLOR_MAKEFILE ON)
# here is one of two variants: all .c in directory or .c files in list
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
###### pkgconfig ######
find_package(PkgConfig REQUIRED)
pkg_check_modules(MODULES REQUIRED usefull_macros>=0.3.2 erfa>=2.0.0)
# change wrong behaviour with install prefix
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND CMAKE_INSTALL_PREFIX MATCHES "/usr/local")
else()
message("Change default install path to /usr/local")
set(CMAKE_INSTALL_PREFIX "/usr/local")
endif()
message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}")
# executable file
add_executable(${PROJ} ${SOURCES})
# -I
target_include_directories(${PROJ} PUBLIC ${MODULES_INCLUDE_DIRS})
# -L
target_link_directories(${PROJ} PUBLIC ${MODULES_LIBRARY_DIRS})
# -l
target_link_libraries(${PROJ} ${MODULES_LIBRARIES} -lweather -lm)
# -D
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\"
-DMID_VERSION=\"${MID_VERSION}\" -DMAJOR_VERSION=\"${MAJOR_VESION}\"
)
# Installation of the program
INSTALL(TARGETS ${PROJ} DESTINATION "bin")

View File

@@ -1,23 +0,0 @@
PROGRAM = stellariumdaemon
LDFLAGS = -lerfa -pthread
SRCS = $(wildcard *.c)
CC = gcc
DEFINES = -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=1111
#DEFINES += -DEBUG
CXX = gcc
CFLAGS = -Wall -Werror -Wextra -Wno-trampolines $(DEFINES)
OBJS = $(SRCS:.c=.o)
all : $(PROGRAM)
$(PROGRAM) : $(OBJS)
$(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $(PROGRAM)
# some addition dependencies
# %.o: %.c
# $(CC) $(LDFLAGS) $(CFLAGS) $< -o $@
#$(SRCS) : %.c : %.h $(INDEPENDENT_HEADERS)
# @touch $@
clean:
/bin/rm -f *.o *~
depend:
$(CXX) -MM $(CXX.SRCS)

View File

@@ -1,6 +0,0 @@
Stellarium control of 10-micron mount
Special commands in terminal mode:
PAUSE - pause output to port from everywhere except terminal thread
CONTINUE - allow to write to port for everyone

View File

@@ -1 +0,0 @@
-std=c17

View File

@@ -1,160 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.12.3, 2021-06-10T00:51:20. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/astrosib/Doc/C-sources/10micron_stellarium</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="GenericProjectManager.GenericMakeStep.Clean">false</value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeArguments"></value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeCommand"></value>
<value type="bool" key="GenericProjectManager.GenericMakeStep.OverrideMakeflags">false</value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="GenericProjectManager.GenericMakeStep.Clean">false</value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeArguments"></value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeCommand"></value>
<value type="bool" key="GenericProjectManager.GenericMakeStep.OverrideMakeflags">false</value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.CustomExecutableRunConfiguration.Executable"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="bool" key="RunConfiguration.Arguments.multi">false</value>
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default"></value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.10.1, 2020-02-24T16:18:36. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/astrosib/Doc/C-sources/10micron_stellarium</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="GenericProjectManager.GenericMakeStep.Clean">false</value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeArguments"></value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeCommand"></value>
<value type="bool" key="GenericProjectManager.GenericMakeStep.OverrideMakeflags">false</value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="GenericProjectManager.GenericMakeStep.Clean">false</value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeArguments"></value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeCommand"></value>
<value type="bool" key="GenericProjectManager.GenericMakeStep.OverrideMakeflags">false</value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Конфигурация развёртывания</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.CustomExecutableRunConfiguration.Executable"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Особая программа</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="uint" key="RunConfiguration.QmlDebugServerPort">3768</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default"></value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -1,167 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.8.2, 2020-02-22T18:11:42. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">2</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap"/>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/as/Doc/C-sources/10micron_stellarium</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="GenericProjectManager.GenericMakeStep.Clean">false</value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeArguments"></value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeCommand"></value>
<value type="bool" key="GenericProjectManager.GenericMakeStep.OverrideMakeflags">false</value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="GenericProjectManager.GenericMakeStep.Clean">false</value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeArguments"></value>
<value type="QString" key="GenericProjectManager.GenericMakeStep.MakeCommand"></value>
<value type="bool" key="GenericProjectManager.GenericMakeStep.OverrideMakeflags">false</value>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Установка</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Конфигурация установки</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value>
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value>
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value>
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.CustomExecutableRunConfiguration.Executable"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Особая программа</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="uint" key="RunConfiguration.QmlDebugServerPort">3768</value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default"></value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">20</value>
</data>
<data>
<variable>Version</variable>
<value type="int">20</value>
</data>
</qtcreator>

View File

@@ -1,17 +0,0 @@
cmdlnopts.c
cmdlnopts.h
daemon.c
emulation.c
emulation.h
libsofa.c
libsofa.h
main.c
main.h
parseargs.c
parseargs.h
socket.c
socket.h
telescope.c
telescope.h
usefull_macro.c
usefull_macro.h

View File

@@ -1 +0,0 @@
.

View File

@@ -0,0 +1,343 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <erfa.h>
#include <erfam.h>
#include <math.h>
#include <time.h>
#include <usefull_macros.h>
#include "angles.h"
static placeData_t place = {.slong = 0.7232763200, .slat = 0.7618977414, .salt = 2070.};
static almDut_t AlmDut = {0};
// set/get polar/DUT1 data
bool setDUT(almDut_t *D){
if(!D){
WARNX("setDUT(): no args");
return false;
}
if(fabs(D->DUT1) > 1.){
WARNX("setDUT(): DUT1 too large (%g)", D->DUT1);
return false;
}
if(fabs(D->px) > 1000. || fabs(D->py) > 1000.){
WARNX("setDUT(): bad polar coordinates (%g, %g)", D->px, D->py);
return false;
}
AlmDut = *D;
return true;
}
void getDUT(almDut_t *D){
if(!D) return;
*D = AlmDut;
}
/**
* @brief setPlaceData - correct place data to given values
* @param longitude - degrees, (-180, 180], minus to west
* @param latitude - degrees, [-90, 90], minus to south
* @param altitude - meters
* @return false if some of data wrong
*/
bool setPlaceData(double longitude, double latitude, double altitude){
if(longitude <= -180. || longitude > 180.){
WARNX("setPlaceData(): bad longitude (%g)", longitude);
return false;
}
if(latitude < -90. || latitude > 90.){
WARNX("setPlaceData(): bad latitude (%g)", latitude);
return false;
}
if(altitude < -500. || altitude > 9000.){
WARNX("setPlaceData(): bad altitude (%g)", altitude);
return false;
}
place.salt = altitude;
place.slat = latitude;
place.slong = longitude;
return true;
}
void getPlaceData(placeData_t *pd){
if(!pd) return;
*pd = place;
}
/**
* convert RA/DEC to string in forman RA: HH:MM:SS.SS, DEC: DD:MM:SS.S
*/
char *radec2str(double ra, double dec, char buf[RADEC_STR_MAXLEN]){
char sign = '+';
if(dec < 0){
sign = '-';
dec = -dec;
}
int h = (int)ra;
ra -= h; ra *= 60.;
int m = (int)ra;
ra -= m; ra *= 60.;
int d = (int) dec;
dec -= d; dec *= 60.;
int dm = (int)dec;
dec -= dm; dec *= 60.;
snprintf(buf, RADEC_STR_MAXLEN, "%d:%d:%.2f %c%d:%d:%.1f", h,m,ra, sign,d,dm,dec);
buf[RADEC_STR_MAXLEN-1] = 0;
return buf;
}
// normalize RA/DEC to [0..24) for RA and [-90, 90] for DEC
void norm_RA(double *ra){
if(!ra) return;
if(*ra >= 0. && *ra < 24.) return;
double r = fmod(*ra, 24.);
if(r < 0.) r += 24.;
*ra = r;
}
void norm_RADEC(double *ra, double *dec){
if(!ra || !dec) return;
if(*dec >= -90. && *dec <= 90.){ // need only check RA
norm_RA(ra);
return;
}
// 1: convert to (-180..+180)
norm_angle180(dec);
// 2: fix dec & ra together
if(*dec > 90.) *dec = 180. - *dec;
else *dec = -180. - *dec;
*ra += 12.;
norm_RA(ra);
}
// normalize angle to (-180, 180]
void norm_angle180(double *a){
if(!a) return;
double d = fmod(*a + 180.0, 360.0);
if(d < 0.) d += 360.0;
else d -= 180;
*a = d;
}
/**
* @brief hor2eq - convert horizontal coordinates to polar
* @param h (i) - horizontal coordinates
* @param pc (o) - polar coordinates
* @param sidTime - sidereal time
*/
void hor2eq(horizCrds_t *h, polarCrds_t *pc, double sidTime){
if(!h || !pc) return;
eraAe2hd(h->az, ERFA_DPI/2. - h->zd, place.slat, &pc->ha, &pc->dec); // A,H -> HA,DEC; phi - site latitude
pc->ra = sidTime - pc->ha;
pc->eo = 0.;
}
/**
* @brief eq2horH - convert polar coordinates to horizontal
* @param pc (i) - polar coordinates (only HA used)
* @param h (o) - horizontal coordinates
* @param sidTime - sidereal time
*/
void eq2horH(polarCrds_t *pc, horizCrds_t *h){
if(!h || !pc) return;
double alt;
eraHd2ae(pc->ha, pc->dec, place.slat, &h->az, &alt);
h->zd = ERFA_DPI/2. - alt;
}
/**
* @brief eq2hor - convert polar coordinates to horizontal
* @param pc (i) - polar coordinates (only RA used)
* @param h (o) - horizontal coordinates
* @param sidTime - sidereal time
*/
void eq2hor(polarCrds_t *pc, horizCrds_t *h, double sidTime){
if(!h || !pc) return;
double ha = sidTime - pc->ra + pc->eo;
double alt;
eraHd2ae(ha, pc->dec, place.slat, &h->az, &alt);
h->zd = ERFA_DPI/2. - alt;
}
/**
* @brief r2sHMS - convert angle in radians into string "'HH:MM:SS.SS'"
* @param radians - angle
* @param hms (o) - string
* @param len - length of hms
*/
void r2sHMS(double radians, char *hms, int len){
char pm;
int i[4];
eraA2tf(2, radians, &pm, i);
snprintf(hms, len, "'%c%02d:%02d:%02d.%02d'", pm, i[0],i[1],i[2],i[3]);
}
/**
* @brief r2sDMS - convert angle in radians into string "'DD:MM:SS.S'"
* @param radians - angle
* @param dms (o) - string
* @param len - length of hms
*/
void r2sDMS(double radians, char *dms, int len){
char pm;
int i[4];
eraA2af(1, radians, &pm, i);
snprintf(dms, len, "'%c%02d:%02d:%02d.%d'", pm, i[0],i[1],i[2],i[3]);
}
/**
* @brief get_MJDt - calculate MJD of date from argument
* @param tval (i) - given date (or NULL for current)
* @param MJD (o) - time (or NULL just to check)
* @return true if all OK
*/
bool get_MJDt(struct timeval *tval, sMJD_t *MJD){
struct tm tms;
double tSeconds;
if(!MJD) return false;
if(!tval){
struct timespec ts;
if(clock_gettime(CLOCK_REALTIME, &ts)){
WARN("clock_gettime()");
return false;
}
gmtime_r(&ts.tv_sec, &tms);
tSeconds = tms.tm_sec + ((double)ts.tv_nsec)/1e9;
}else{
gmtime_r(&tval->tv_sec, &tms);
tSeconds = tms.tm_sec + ((double)tval->tv_usec)/1e6;
}
int y, m, d;
y = 1900 + tms.tm_year;
m = tms.tm_mon + 1;
d = tms.tm_mday;
double utc1, utc2;
/* UTC date. */
if(eraDtf2d("UTC", y, m, d, tms.tm_hour, tms.tm_min, tSeconds, &utc1, &utc2) < 0) return false;
if(!MJD) return false;
MJD->MJD = utc1 - ERFA_DJM0 + utc2;
MJD->utc1 = utc1;
MJD->utc2 = utc2;
//DBG("UTC(m): %g, %.8f\n", utc1 - 2400000.5, utc2);
if(eraUtctai(utc1, utc2, &MJD->tai1, &MJD->tai2)) return false;
//DBG("TAI");
if(eraTaitt(MJD->tai1, MJD->tai2, &MJD->tt1, &MJD->tt2)) return false;
//DBG("TT");
return true;
}
/**
* @brief get_LST - calculate local siderial time
* @param mjd (i) - date/time for LST (utc1 & tt used)
* @param dUT1 - (UT1-UTC)
* @param slong - site longitude (radians)
* @param LST (o) - local sidereal time (radians)
* @return true if all OK
*/
bool get_LST(sMJD_t *mjd, double dUT1, double slong, double *LST){
double ut11, ut12;
sMJD_t Mjd;
if(!LST) return false;
if(!mjd){
if(!get_MJDt(NULL, &Mjd)) return false;
}else Mjd = *mjd;
if(eraUtcut1(Mjd.utc1, Mjd.utc2, dUT1, &ut11, &ut12)) return false;
double ST = eraGst06a(ut11, ut12, Mjd.tt1, Mjd.tt2);
ST += slong;
if(ST > ERFA_D2PI) ST -= ERFA_D2PI;
else if(ST < 0.) ST += ERFA_D2PI;
*LST = ST;
return 0;
}
/**
* @brief get_ObsPlace - calculate observed place (without PM etc) for given date @550nm
* @param tval (i) - time
* @param p2000 (i) - polar coordinates for J2000 (only ra/dec used), ICRS (catalog)
* @param pnow (o) - polar coordinates for given epoch (or NULL)
* @param hnow (o) - horizontal coordinates for given epoch (or NULL)
* @return true if all OK
*/
bool get_ObsPlace(struct timeval *tval, polarCrds_t *p2000, polarCrds_t *pnow, horizCrds_t *hnow){
double pr = 0.0; // RA proper motion (radians/year; Note 2)
double pd = 0.0; // Dec proper motion (radians/year)
double px = 0.0; // parallax (arcsec)
double rv = 0.0; // radial velocity (km/s, positive if receding)
sMJD_t MJD;
if(!tval || !p2000) return false;
if(get_MJDt(tval, &MJD)) return false;
/* Effective wavelength (microns) */
double wl = 0.55;
/* ICRS to observed. */
double aob, zob, hob, dob, rob, eo;
double p = 1000., t = 0., h = place.salt;
weather_data_t weath;
if(get_weather_data(&weath)){
p = 1.3332239 * weath.pressure;
t = weath.exttemp;
}else WARNX("Can't get weather - use default values");
if(eraAtco13(p2000->ra, p2000->dec,
pr, pd, px, rv,
MJD.utc1, MJD.utc2,
AlmDut.DUT1,
place.slong, place.slat, place.salt,
AlmDut.px, AlmDut.py,
p, t, h,
wl,
&aob, &zob,
&hob, &dob, &rob, &eo)) return false;
if(pnow){
pnow->eo = eo;
pnow->ha = hob;
pnow->ra = rob;
pnow->dec = dob;
}
if(hnow){
hnow->az = aob;
hnow->zd = zob;
}
return true;
}
#if 0
typedef enum {
WEATHER_GOOD = 0, // may start observations
WEATHER_BAD = 1, // cannot start but can continue if want
WEATHER_TERRIBLE = 2, // close & park: wind, precipitation, humidity etc.
WEATHER_PROHIBITED = 3, // force closing & parking; power off equipment, ready to power off computer
} weather_condition_t;
typedef struct {
weather_condition_t weather; // conditions: field "WEATHER"
float windmax; // maximal wind for last hour, m/s: "WINDMAX1"
float wind; // current wind speed, m/s: "WIND"
float clouds; // sky "quality" (>2500 - OK): "CLOUDS"
float exttemp; // external temperature, degC: "EXTTEMP"
float pressure; // atm. pressure, mmHg: "PRESSURE"
float humidity; // humidity, percents: "HUMIDITY"
int rain; // ==1 when rainy: "PRECIP"
int forceoff; // force power off (AC power lost or lightning)
time_t last_update; // value of "TMEAS"
} weather_data_t;
int get_weather_data(weather_data_t *data);
#endif

View File

@@ -1,6 +1,6 @@
/*
* This file is part of the StelD project.
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +15,25 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef LIBSOFA_H__
#define LIBSOFA_H__
#include <erfa.h>
#include <erfam.h>
#include <sys/time.h>
#include <weather_data.h>
// JD2451544.5 == 2000.0
#define MJD2000 (51544)
#define RADEC_STR_MAXLEN 72
#define RAD2DEG(angle) (angle * ERFA_DR2D)
#define DEG2RAD(angle) (angle * ERFA_DD2R)
#define RAD2HRS(angle) (angle * 24. / ERFA_DPI)
#define HRS2RAD(hour) (hour * ERFA_DPI / 24.)
typedef struct{
double utc1; double utc2; // UTC JD, commonly used MJD = utc1+utc2-2400000.5
double MJD;
double tai1; double tai2; // TAI JD
double tt1; double tt2; // TT JD
} sMJD;
} sMJD_t;
// polar coordinates & equation of origins (all in radians)
typedef struct{
@@ -39,48 +41,49 @@ typedef struct{
double dec; // declination
double ra; // right ascension
double eo; // equation of origins
} polarCrds;
} polarCrds_t;
// horizontal coordinates (all in radians)
typedef struct{
double az; // azimuth, 0 @ south, positive clockwise
double zd; // zenith distance
} horizCrds;
} horizCrds_t;
// observational place coordinates and altitude; all coordinates are in radians!
typedef struct{
double slong; // longitude
double slat; // lattitude
double slong; // longitude, radians
double slat; // lattitude, radians
double salt; // altitude, m
} placeData;
// place weather data
typedef struct{
double relhum; // rel. humidity, 0..100%
double pres; // atm. pressure (mmHg)
double tc; // temperature, degrC
double rain; // rain value (0..1)
double clouds; // clouds (0 - bad, >2500 - good)
double wind; // wind speed, m/s
double time; // measurements time
} localWeather;
} placeData_t;
// DUT/polar almanach data
// TODO: add function to read this data from almanach
typedef struct{
double DUT1; // UT1-UTC, sec
double px; // polar coordinates, arcsec
double py;
} almDut;
} almDut_t;
char *radec2str(double ra, double dec, char buf[RADEC_STR_MAXLEN]);
void norm_RA(double *ra);
void norm_RADEC(double *ra, double *dec);
void norm_angle180(double *a);
void hor2eq(horizCrds_t *h, polarCrds_t *pc, double sidTime);
void eq2horH(polarCrds_t *pc, horizCrds_t *h);
void eq2hor(polarCrds_t *pc, horizCrds_t *h, double sidTime);
void r2sHMS(double radians, char *hms, int len);
void r2sDMS(double radians, char *hms, int len);
void hor2eq(horizCrds *h, polarCrds *pc, double sidTime);
void eq2horH(polarCrds *pc, horizCrds *h);
void eq2hor(polarCrds *pc, horizCrds *h, double sidTime);
int get_MJDt(struct timeval *tval, sMJD *MJD);
int get_LST(sMJD *mjd, double dUT1, double slong, double *LST);
int get_ObsPlace(struct timeval *tval, polarCrds *p2000, localWeather *weath, polarCrds *pnow, horizCrds *hnow);
almDut *getDUT();
localWeather *getWeath();
placeData *getPlace();
#endif // LIBSOFA_H__
void hor2eq(horizCrds_t *h, polarCrds_t *pc, double sidTime);
void eq2horH(polarCrds_t *pc, horizCrds_t *h);
void eq2hor(polarCrds_t *pc, horizCrds_t *h, double sidTime);
bool get_MJDt(struct timeval *tval, sMJD_t *MJD);
bool get_LST(sMJD_t *mjd, double dUT1, double slong, double *LST);
bool get_ObsPlace(struct timeval *tval, polarCrds_t *p2000, polarCrds_t *pnow, horizCrds_t *hnow);
bool setDUT(almDut_t *D);
void getDUT(almDut_t *D);
bool setPlaceData(double longitude, double latitude, double altitude);
void getPlaceData(placeData_t *pd);

View File

@@ -0,0 +1,140 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <usefull_macros.h>
#include "args.h"
#include "server.h"
#define DEFAULT_COMDEV "/dev/ttyUSB0"
// TCP socket port
#define DEFAULT_PORT ":10000"
#define DEFAULT_CMDNODE "localhost:10001"
// default PID filename:
#define DEFAULT_PIDFILE "/tmp/mountdaemon_10micron.pid"
// default file with headers
#define DEFAULT_FITSHDR "/tmp/10micron.fitsheader"
// baudrate - 115200
#define DEFAULT_SERSPEED 115200
// serial polling timeout - 100ms
#define DEFAULT_SERTMOUT 100000
// mount name
#define DEFAULT_MOUNT_NAME "10Micron GM4000HPS"
// non-config file options
static int help = 0;
static char *conffile = NULL;
// default command line options
static parameters_t G = {0};
// default config options
static parameters_t Conf = {0};
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#define OPTIONS(Stor) \
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&Stor.device), "serial device name"}, \
{"emulation",NO_ARGS, NULL, 'e', arg_int, APTR(&Stor.emulation), "run in emulation mode"}, \
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&Stor.logfile), "file to save logs"}, \
{"hdrfile", NEED_ARG, NULL, 'o', arg_string, APTR(&Stor.crdsfile), "file to save FITS-header with coordinates and time (default: " DEFAULT_FITSHDR ")"}, \
{"pidfile", NEED_ARG, NULL, 0, arg_string, APTR(&Stor.pidfile), "pidfile (default: " DEFAULT_PIDFILE ")"}, \
{"port", NEED_ARG, NULL, 'p', arg_string, APTR(&Stor.port), "port for stellaruim's connect (default: " DEFAULT_PORT ")"}, \
{"cmdport", NEED_ARG, NULL, 'P', arg_string, APTR(&Stor.cmdnode), "port or UNIX-socket path to connect for command console (default: " DEFAULT_CMDNODE ")"}, \
{"sleept", NEED_ARG, NULL, 't', arg_int, APTR(&Stor.sleept), "time of servers' sleeping in main cycle, us (default: " STR(DEFAULT_SLEEP_T) " us)"}, \
{"isunix", NO_ARGS, NULL, 'U', arg_int, APTR(&Stor.isunix), "use UNIX-socket for cmdport"}, \
{"sertmout",NEED_ARG, NULL, 'T', arg_int, APTR(&Stor.sertmout), "serial timeout, us (default: " STR(DEFAULT_SERTMOUT) " us)"}, \
{"serspeed",NEED_ARG, NULL, 'S', arg_int, APTR(&Stor.serspeed), "serial speed (default: " STR(DEFAULT_SERSPEED) ")"}, \
{"maxclients",NEED_ARG, NULL, 0, arg_int, APTR(&Stor.maxclients),"max amount of clients connected to one socket (default: " STR(DEFAULT_MAXCLIENTS) ")"}, \
{"mountname",NEED_ARG, NULL, 0, arg_string, APTR(&Stor.mountname), "mount name (default: " DEFAULT_MOUNT_NAME ")"}, \
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&Stor.verbose), "verbose level (each -v increases it)"}, \
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
{"config", NEED_ARG, NULL, 'c', arg_string, APTR(&conffile), "configuration file (command line options are in advantage)"},
OPTIONS(G)
end_option
};
// --help and --conf - options that should be parsed first
static sl_option_t confopts[] = {
OPTIONS(Conf)
end_option
};
static void chkstr(char **conf, char **cmd, const char *dflt){
if(!*conf){
if(!*cmd && dflt){
DBG("Set default parameter: %s", dflt);
*cmd = strdup(dflt);
}
return;
}
if(!*cmd){
DBG("Conf-only parameter %s", *conf);
*cmd = strdup(*conf);
}
FREE(*conf); // don't forget to clear
}
static void chkint(int *conf, int *cmd, int dflt){
if(!*conf){
if(!*cmd) *cmd = dflt;
return;
}
if(!*cmd){
DBG("Conf-only parameter %d", *conf);
*cmd = *conf;
}
}
#define STRCHK(field) chkstr(&Conf.field, &G.field, NULL)
#define STRCHKD(field, def) chkstr(&Conf.field, &G.field, def)
#define INTCHK(field) chkint(&Conf.field, &G.field, 0)
#define INTCHKD(field, def) chkint(&Conf.field, &G.field, def)
parameters_t *parse_cmdline(int *argc, char ***argv){
sl_parseargs(argc, argv, cmdlnopts);
if(help) sl_showhelp(-1, cmdlnopts);
if(!conffile) return &G;
// fix for conffile
if(!sl_conf_readopts(conffile, confopts)){ // show conf help
sl_conf_showhelp(-1, confopts);
return NULL;
}
// now fix for command line and check all:
DBG("Check all args");
STRCHKD(device, DEFAULT_COMDEV);
STRCHKD(port, DEFAULT_PORT);
STRCHKD(cmdnode, DEFAULT_CMDNODE);
STRCHKD(pidfile, DEFAULT_PIDFILE);
STRCHK(logfile);
STRCHKD(crdsfile, DEFAULT_FITSHDR);
STRCHKD(mountname, DEFAULT_MOUNT_NAME);
INTCHK(emulation);
INTCHK(verbose);
INTCHKD(sleept, DEFAULT_SLEEP_T);
INTCHK(isunix);
INTCHKD(sertmout, DEFAULT_SERTMOUT);
INTCHKD(serspeed, DEFAULT_SERSPEED);
INTCHKD(maxclients, DEFAULT_MAXCLIENTS);
return &G;
}

View File

@@ -0,0 +1,38 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
typedef struct{
char *device; // serial device name
char *port; // port to connect
char *cmdnode; // port for commands sending
char *pidfile; // name of PID file
char *logfile; // logging to this file
char *crdsfile; // file where FITS-header should be written
char *mountname; // set mount name for FITS-header
int emulation; // run in emulation mode
int verbose; // verbose level
int sleept; // server's sleeping in main cycle time
int isunix; // use UNIX-socket for command port
int sertmout; // serial timeout, us
int serspeed; // serial speed, baud
int maxclients; // max amount of clients connected to one socket
} parameters_t;
parameters_t *parse_cmdline(int *argc, char ***argv);

View File

@@ -1,110 +0,0 @@
/* geany_encoding=koi8-r
* cmdlnopts.c - the only function that parse cmdln args and returns glob parameters
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include "cmdlnopts.h"
#include "parseargs.h"
#include "usefull_macro.h"
/*
* here are global parameters initialisation
*/
int help;
glob_pars G;
glob_pars *GP = NULL;
#define DEFAULT_COMDEV "/dev/ttyUSB0"
// port for connections
#define DEFAULT_PORT "10000"
#define DEFAULT_DBGPORT "10001"
// weather server port and name
#define DEFAULT_WSPORT (12345)
#define DEFAULT_WSNAME "robotel1.sao.ru"
// default PID filename:
#define DEFAULT_PIDFILE "/tmp/stellariumdaemon.pid"
// default file with headers
#define DEFAULT_FITSHDR "/tmp/10micron.fitsheader"
// DEFAULTS
// default global parameters
glob_pars const Gdefault = {
.device = DEFAULT_COMDEV,
.port = DEFAULT_PORT,
.dbgport = DEFAULT_DBGPORT,
.pidfile = DEFAULT_PIDFILE,
.crdsfile = DEFAULT_FITSHDR,
.emulation = 0,
.weathserver = DEFAULT_WSNAME,
.weathport = DEFAULT_WSPORT,
.logfile = NULL // don't save logs
};
/*
* Define command line options by filling structure:
* name has_arg flag val type argptr help
*/
myoption cmdlnopts[] = {
// common options
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), _("serial device name (default: " DEFAULT_COMDEV ")")},
{"emulation",NO_ARGS, NULL, 'e', arg_int, APTR(&G.emulation), _("run in emulation mode")},
//{"hostname",NEED_ARG, NULL, 'H', arg_string, APTR(&G.hostname), _("hostname to connect (default: localhost)")},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), _("file to save logs")},
{"hdrfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.crdsfile), _("file to save FITS-header with coordinates and time")},
{"pidfile", NEED_ARG, NULL, 'P', arg_string, APTR(&G.pidfile), _("pidfile (default: " DEFAULT_PIDFILE ")")},
{"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("port to connect (default: " DEFAULT_PORT ")")},
{"dbgport", NEED_ARG, NULL, 'D', arg_string, APTR(&G.dbgport), _("port to connect for debug console (default: " DEFAULT_DBGPORT ")")},
{"wport", NEED_ARG, NULL, 'w', arg_int, APTR(&G.weathport), _("weather server port")},
{"wname", NEED_ARG, NULL, 'W', arg_string, APTR(&G.weathserver),_("weather server address")},
end_option
};
/**
* Parse command line options and return dynamically allocated structure
* to global parameters
* @param argc - copy of argc from main
* @param argv - copy of argv from main
* @return allocated structure with global parameters
*/
glob_pars *parse_args(int argc, char **argv){
int i;
void *ptr;
ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr);
size_t hlen = 1024;
char helpstring[1024], *hptr = helpstring;
snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n");
// format of help: "Usage: progname [args]\n"
change_helpstring(helpstring);
// parse arguments
parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts);
if(argc > 0){
G.rest_pars_num = argc;
G.rest_pars = calloc(argc, sizeof(char*));
for (i = 0; i < argc; i++)
G.rest_pars[i] = strdup(argv[i]);
}
return &G;
}

View File

@@ -1,47 +0,0 @@
/* geany_encoding=koi8-r
* cmdlnopts.h - comand line options for parceargs
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#pragma once
#ifndef __CMDLNOPTS_H__
#define __CMDLNOPTS_H__
/*
* here are some typedef's for global data
*/
typedef struct{
char *device; // serial device name
char *port; // port to connect
char *dbgport; // port for debug console
char *pidfile; // name of PID file
char *logfile; // logging to this file
char *crdsfile; // file where FITS-header should be written
char *weathserver; // weather server name
int emulation; // run in emulation mode
int rest_pars_num; // number of rest parameters
int weathport; // weather server port
char** rest_pars; // the rest parameters: array of char*
} glob_pars;
// global parameters
extern glob_pars *GP;
glob_pars *parse_args(int argc, char **argv);
#endif // __CMDLNOPTS_H__

View File

@@ -1,144 +0,0 @@
/*
* daemon.c - functions for running in background like a daemon
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#define PROC_BASE "/proc"
#include <stdio.h> // printf, fopen, ...
#include <unistd.h> // getpid
#include <stdio.h> // perror
#include <sys/types.h> // opendir
#include <dirent.h> // opendir
#include <sys/stat.h> // stat
#include <fcntl.h> // fcntl
#include <stdlib.h> // exit
#include <string.h> // memset
/**
* read process name from /proc/PID/cmdline
* @param pid - PID of interesting process
* @return filename or NULL if not found
* don't use this function twice for different names without copying
* its returning by strdup, because `name` contains in static array
*/
char *readname(pid_t pid){
static char name[256];
char *pp = name, byte, path[256];
FILE *file;
int cntr = 0;
size_t sz;
snprintf (path, 255, PROC_BASE "/%d/cmdline", pid);
file = fopen(path, "r");
if(!file) return NULL; // there's no such file
do{ // read basename
sz = fread(&byte, 1, 1, file);
if(sz != 1) break;
if(byte != '/') *pp++ = byte;
else{
pp = name;
cntr = 0;
}
}while(byte && cntr++ < 255);
name[cntr] = 0;
fclose(file);
return name;
}
void iffound_default(pid_t pid){
fprintf(stderr, "\nFound running process (pid=%d), exit.\n", pid);
exit(0);
}
/**
* check wether there is a same running process
* exit if there is a running process or error
* Checking have 3 steps:
* 1) lock executable file
* 2) check pidfile (if you run a copy?)
* 3) check /proc for executables with the same name (no/wrong pidfile)
* @param selfname - argv[0] or NULL for non-locking
* @param pidfilename - name of pidfile or NULL if none
* @param iffound - action to run if file found or NULL for exit(0)
*/
void check4running(char *selfname, char *pidfilename, void (*iffound)(pid_t pid)){
DIR *dir;
FILE *pidfile, *fself;
struct dirent *de;
struct stat s_buf;
pid_t pid = 0, self;
struct flock fl;
char *name, *myname;
if(!iffound) iffound = iffound_default;
if(selfname){ // block self
fself = fopen(selfname, "r"); // open self binary to lock
if(!fself){
perror("fopen");
goto selfpid;
}
memset(&fl, 0, sizeof(struct flock));
fl.l_type = F_WRLCK;
if(fcntl(fileno(fself), F_GETLK, &fl) == -1){ // check locking
perror("fcntl");
goto selfpid;
}
if(fl.l_type != F_UNLCK){ // file is locking - exit
printf("Found locker, PID = %d!\n", fl.l_pid);
exit(1);
}
fl.l_type = F_RDLCK;
if(fcntl(fileno(fself), F_SETLKW, &fl) == -1){
perror("fcntl");
}
}
selfpid:
self = getpid(); // get self PID
if(!(dir = opendir(PROC_BASE))){ // open /proc directory
perror(PROC_BASE);
}
if(!(name = readname(self))){ // error reading self name
perror("Can't read self name");
exit(1);
}
myname = strdup(name);
if(pidfilename && stat(pidfilename, &s_buf) == 0){ // pidfile exists
pidfile = fopen(pidfilename, "r");
if(pidfile){
if(fscanf(pidfile, "%d", &pid) > 0){ // read PID of (possibly) running process
if((name = readname(pid)) && strncmp(name, myname, 255) == 0)
iffound(pid);
}
fclose(pidfile);
}
}
// There is no pidfile or it consists a wrong record
while((de = readdir(dir))){ // scan /proc
if(!(pid = (pid_t)atoi(de->d_name)) || pid == self) // pass non-PID files and self
continue;
if((name = readname(pid)) && strncmp(name, myname, 255) == 0)
iffound(pid);
}
closedir(dir);
if(pidfilename){
pidfile = fopen(pidfilename, "w");
fprintf(pidfile, "%d\n", self); // write self PID to pidfile
fclose(pidfile);
}
free(myname);
}

View File

@@ -1,12 +1,10 @@
/*
* geany_encoding=koi8-r
* emulation.c
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -15,18 +13,18 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "math.h"
#include "emulation.h"
#include "usefull_macro.h"
// emulation speed over RA & DEC (0.5 degr per sec)
#define RA_SPEED (0.033)
#define DECL_SPEED (0.5)
#include <math.h>
#include <usefull_macros.h>
#include "angles.h"
#include "emulation.h"
// emulation speed over RA & DEC (degr per sec)
#define RA_SPEED (5.)
#define DECL_SPEED (11.)
// current coordinates
static double RA = 0., DECL = 0.;
@@ -35,8 +33,6 @@ static double RAtarg = 0., DECLtarg = 0.;
// coordinates @ guiding start
static double RA0 = 0., DECL0 = 0.;
static double raspeed = 0.;
// ==1 if pointing
static int pointing = 0;
// pointing start time
static double tstart = -1.;
@@ -44,20 +40,20 @@ static double tstart = -1.;
* send coordinates to telescope emulation
* @param ra - right ascention (hours)
* @param decl - declination (degrees)
* @return 1 if all OK
*/
int point_emulation(double ra, double decl){
bool point_emulation(double ra, double decl){
norm_RADEC(&ra, &decl);
DBG("(emul) Send ra=%g, decl=%g", ra, decl);
putlog("(emul) Send ra=%g, decl=%g", ra, decl);
LOGMSG("(emul) Send ra=%g, decl=%g", ra, decl);
// TODO: check that Z<90!
RAtarg = ra; DECLtarg = decl;
RA0 = RA; DECL0 = DECL;
raspeed = (RAtarg > RA) ? RA_SPEED : -RA_SPEED;
if(fabs(RAtarg - RA) > 12.){ // go to opposite direction
raspeed = -raspeed;
}
tstart = dtime();
pointing = 1;
return 0;
tstart = sl_dtime();
return true;
}
static double getradiff(){
@@ -70,20 +66,19 @@ static double getradiff(){
/**
* get coordinates (emulation)
* @return 1 if all OK
*/
int get_emul_coords(double *ra, double *decl){
if(pointing){
void get_emul_coords(double *ra, double *decl){
if(tstart > 0.){
DBG("RA/DEC: targ: %g/%g, cur: %g/%g, start: %g/%g", RAtarg, DECLtarg, RA, DECL, RA0, DECL0);
// diff < speed? stop
if((fabs(RAtarg - RA) < RA_SPEED && fabs(DECLtarg - DECL) < DECL_SPEED)){
RA = RAtarg;
DECL = DECLtarg;
pointing = 0; // guiding
tstart = -1.; // "guiding"
DBG("@ target");
}else{ // calculate new coordinates
double radiff = getradiff(), decldiff = fabs(DECLtarg - DECL);
double tdiff = dtime() - tstart;
double tdiff = sl_dtime() - tstart;
RA = RA0 + raspeed * tdiff;
DBG("RA=%g", RA);
if(getradiff() > radiff) RA = RAtarg;
@@ -99,5 +94,5 @@ int get_emul_coords(double *ra, double *decl){
}
if(ra) *ra = RA;
if(decl) *decl = DECL;
return 1;
}

View File

@@ -1,12 +1,10 @@
/*
* geany_encoding=koi8-r
* emulation.h
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -15,16 +13,12 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef __EMULATION_H__
#define __EMULATION_H__
int point_emulation(double ra, double decl);
int get_emul_coords(double *ra, double *decl);
bool point_emulation(double ra, double decl);
void get_emul_coords(double *ra, double *decl);
#endif // __EMULATION_H__

View File

@@ -1,381 +0,0 @@
/*
* This file is part of the StelD project.
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <time.h>
#include "libsofa.h"
#include "socket.h"
#include "usefull_macro.h"
#ifdef EBUG
void reprd(char* s, double ra, double dc){
char pm;
int i[4];
printf ( "%s:", s );
eraA2tf ( 7, ra, &pm, i );
printf ( " %2.2d %2.2d %2.2d.%7.7d", i[0],i[1],i[2],i[3] );
eraA2af ( 6, dc, &pm, i );
printf ( " %c%2.2d %2.2d %2.2d.%6.6d\n", pm, i[0],i[1],i[2],i[3] );
}
void radtodeg(double r){
int i[4]; char pm;
int rem = (int)(r / ERFA_D2PI);
if(rem) r -= ERFA_D2PI * rem;
if(r > ERFA_DPI) r -= ERFA_D2PI;
else if(r < -ERFA_DPI) r += ERFA_D2PI;
eraA2af (2, r, &pm, i);
printf("%c%02d %02d %02d.%2.d", pm, i[0],i[1],i[2],i[3]);
}
#define REP(a,b,c) reprd(a,b,c)
#else
#define REP(a,b,c)
#endif
// temporal stubs for weather/place/DUT1 data; user can change values of these variables
static placeData place = {.slong = 0.7232763200, .slat = 0.7618977414, .salt = 2070.};
placeData *getPlace(){
return &place;
}
static localWeather weather = {0};
typedef struct{
const char *name;
double *valptr;
} weathpars;
#define WPCOUNT (7)
static weathpars WPars[WPCOUNT] = {
{"BTAHumid", &weather.relhum},
{"BTAPres", &weather.pres},
{"Exttemp", &weather.tc},
{"Rain", &weather.rain},
{"Clouds", &weather.clouds},
{"Wind", &weather.wind},
{"Time", &weather.time}
};
localWeather *getWeath(){
//DBG("DT=%zd", time(NULL) - (time_t)weather.time);
char *w = getweathbuffer();
//DBG("w=%s", w);
if(w){ // get new data - check it
int ctr = 0;
for(int i = 0; i < WPCOUNT; ++i){
if(getparval(WPars[i].name, w, WPars[i].valptr)) ++ctr;
}
if(ctr != WPCOUNT) WARN("Not full set of parameters in %s", w);
FREE(w);
}
if((time_t)weather.time == 0 || time(NULL) - (time_t)weather.time > 3600) return NULL;
return &weather;
}
static almDut dut1 = {0};
almDut *getDUT(){
// check DUT1 data HERE once per some time
return &dut1;
}
/**
* @brief r2sHMS - convert angle in radians into string "'HH:MM:SS.SS'"
* @param radians - angle
* @param hms (o) - string
* @param len - length of hms
*/
void r2sHMS(double radians, char *hms, int len){
char pm;
int i[4];
eraA2tf(2, radians, &pm, i);
snprintf(hms, len, "'%c%02d:%02d:%02d.%02d'", pm, i[0],i[1],i[2],i[3]);
}
/**
* @brief r2sDMS - convert angle in radians into string "'DD:MM:SS.S'"
* @param radians - angle
* @param dms (o) - string
* @param len - length of hms
*/
void r2sDMS(double radians, char *dms, int len){
char pm;
int i[4];
eraA2af(1, radians, &pm, i);
snprintf(dms, len, "'%c%02d:%02d:%02d.%d'", pm, i[0],i[1],i[2],i[3]);
}
/**
* @brief get_MJDt - calculate MJD of date from argument
* @param tval (i) - given date (or NULL for current)
* @param MJD (o) - time (or NULL just to check)
* @return 0 if all OK
*/
int get_MJDt(struct timeval *tval, sMJD *MJD){
struct tm tms;
double tSeconds;
if(!tval){
//DBG("MJD for current time");
struct timeval currentTime;
gettimeofday(&currentTime, NULL);
gmtime_r(&currentTime.tv_sec, &tms);
tSeconds = tms.tm_sec + ((double)currentTime.tv_usec)/1e6;
}else{
gmtime_r(&tval->tv_sec, &tms);
tSeconds = tms.tm_sec + ((double)tval->tv_usec)/1e6;
}
int y, m, d;
y = 1900 + tms.tm_year;
m = tms.tm_mon + 1;
d = tms.tm_mday;
double utc1, utc2;
/* UTC date. */
if(eraDtf2d("UTC", y, m, d, tms.tm_hour, tms.tm_min, tSeconds, &utc1, &utc2) < 0) return -1;
if(!MJD) return 0;
MJD->MJD = utc1 - 2400000.5 + utc2;
MJD->utc1 = utc1;
MJD->utc2 = utc2;
//DBG("UTC(m): %g, %.8f\n", utc1 - 2400000.5, utc2);
if(eraUtctai(utc1, utc2, &MJD->tai1, &MJD->tai2)) return -1;
//DBG("TAI");
if(eraTaitt(MJD->tai1, MJD->tai2, &MJD->tt1, &MJD->tt2)) return -1;
//DBG("TT");
return 0;
}
/**
* @brief get_LST - calculate local siderial time
* @param mjd (i) - date/time for LST (utc1 & tt used)
* @param dUT1 - (UT1-UTC)
* @param slong - site longitude (radians)
* @param LST (o) - local sidereal time (radians)
* @return 0 if all OK
*/
int get_LST(sMJD *mjd, double dUT1, double slong, double *LST){
double ut11, ut12;
sMJD Mjd;
if(!mjd){
if(get_MJDt(NULL, &Mjd)) return 1;
}else memcpy(&Mjd, mjd, sizeof(sMJD));
if(eraUtcut1(Mjd.utc1, Mjd.utc2, dUT1, &ut11, &ut12)) return 2;
/*double era = iauEra00(ut11, ut12) + slong;
double eo = iauEe06a(mjd->tt1, mjd->tt2);
printf("ERA = %s; ", radtohrs(era));
printf("ERA-eo = %s\n", radtohrs(era-eo));*/
if(!LST) return 0;
double ST = eraGst06a(ut11, ut12, Mjd.tt1, Mjd.tt2);
ST += slong;
if(ST > ERFA_D2PI) ST -= ERFA_D2PI;
else if(ST < 0.) ST += ERFA_D2PI;
*LST = ST;
return 0;
}
/**
* @brief hor2eq - convert horizontal coordinates to polar
* @param h (i) - horizontal coordinates
* @param pc (o) - polar coordinates
* @param sidTime - sidereal time
*/
void hor2eq(horizCrds *h, polarCrds *pc, double sidTime){
if(!h || !pc) return;
placeData *p = getPlace();
eraAe2hd(h->az, ERFA_DPI/2. - h->zd, p->slat, &pc->ha, &pc->dec); // A,H -> HA,DEC; phi - site latitude
pc->ra = sidTime - pc->ha;
pc->eo = 0.;
}
/**
* @brief eq2horH - convert polar coordinates to horizontal
* @param pc (i) - polar coordinates (only HA used)
* @param h (o) - horizontal coordinates
* @param sidTime - sidereal time
*/
void eq2horH(polarCrds *pc, horizCrds *h){
if(!h || !pc) return;
placeData *p = getPlace();
double alt;
eraHd2ae(pc->ha, pc->dec, p->slat, &h->az, &alt);
h->zd = ERFA_DPI/2. - alt;
}
/**
* @brief eq2hor - convert polar coordinates to horizontal
* @param pc (i) - polar coordinates (only RA used)
* @param h (o) - horizontal coordinates
* @param sidTime - sidereal time
*/
void eq2hor(polarCrds *pc, horizCrds *h, double sidTime){
if(!h || !pc) return;
double ha = sidTime - pc->ra + pc->eo;
placeData *p = getPlace();
double alt;
eraHd2ae(ha, pc->dec, p->slat, &h->az, &alt);
h->zd = ERFA_DPI/2. - alt;
}
/**
* @brief get_ObsPlace - calculate observed place (without PM etc) for given date @550nm
* @param tval (i) - time
* @param p2000 (i) - polar coordinates for J2000 (only ra/dec used), ICRS (catalog)
* @param weath (i) - weather data (relhum, temp, press) or NULL if none
* @param pnow (o) - polar coordinates for given epoch (or NULL)
* @param hnow (o) - horizontal coordinates for given epoch (or NULL)
* @return 0 if all OK
*/
int get_ObsPlace(struct timeval *tval, polarCrds *p2000, localWeather *weath, polarCrds *pnow, horizCrds *hnow){
double pr = 0.0; // RA proper motion (radians/year; Note 2)
double pd = 0.0; // Dec proper motion (radians/year)
double px = 0.0; // parallax (arcsec)
double rv = 0.0; // radial velocity (km/s, positive if receding)
sMJD MJD;
if(get_MJDt(tval, &MJD)) return -1;
if(!p2000) return -1;
/* Effective wavelength (microns) */
double wl = 0.55;
/* ICRS to observed. */
double aob, zob, hob, dob, rob, eo;
double p = 0., t = 0., h = 0.;
if(weath){
p = weath->pres; t = weath->tc; h = weath->relhum;
}
/*
DBG("iauAtco13(%g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g, %g)",
p2000->ra, p2000->dec, pr, pd, px, rv, MJD.utc1, MJD.utc2, d.DUT1, p.slong, p.slat, p.salt,
d.px, d.py, p, t, h, wl);
*/
if(eraAtco13(p2000->ra, p2000->dec,
pr, pd, px, rv,
MJD.utc1, MJD.utc2,
dut1.DUT1,
place.slong, place.slat, place.salt,
dut1.px, dut1.py,
p, t, h,
wl,
&aob, &zob,
&hob, &dob, &rob, &eo)) return -1;
REP("ICRS->observed", rob, dob);
if(pnow){
pnow->eo = eo;
pnow->ha = hob;
pnow->ra = rob;
pnow->dec = dob;
}
if(hnow){
hnow->az = aob;
hnow->zd = zob;
}
#ifdef EBUG
printf("A(bta)/Z: ");
radtodeg(aob);
printf("("); radtodeg(ERFA_DPI-aob);
printf(")/"); radtodeg(zob);
printf("\n");
#endif
return 0;
}
// azimuth: north=zero, east=90deg
// parallactic angle: iauHd2pa ( ha, dec, phi );
// refraction coefficients: iauRefco
// iauAe2hd ( az, el, phi, &ha, &dec ); A,H -> HA,DEC; phi - site latitude
// iauHd2ae ( ha, dec, phi, &az, &el ); HA,DEC -> A,H
// iauAtoc13 - obs->ICRS(catalog)
// iauAtoi13 - obs->CIRS
// iauAtio13 - CIRS->observed
#if 0
/**
* convert geocentric coordinates (nowadays, CIRS) to mean (JD2000, ICRS)
* appRA, appDecl in seconds
* r, d in seconds
*/
void JnowtoJ2000(double appRA, double appDecl, double *r, double *dc){
double ra=0., dec=0., utc1, utc2, tai1, tai2, tt1, tt2, fd, eo, ri;
int y, m, d, H, M;
DBG("appRa: %g'', appDecl'': %g", appRA, appDecl);
appRA *= DS2R;
appDecl *= DAS2R;
#define SOFA(f, ...) do{if(f(__VA_ARGS__)){WARNX("Error in " #f); goto rtn;}}while(0)
// 1. convert system JDate to UTC
SOFA(iauJd2cal, JDate, 0., &y, &m, &d, &fd);
fd *= 24.;
H = (int)fd;
fd = (fd - H)*60.;
M = (int)fd;
fd = (fd - M)*60.;
SOFA(iauDtf2d, "UTC", y, m, d, H, M, fd, &utc1, &utc2);
SOFA(iauUtctai, utc1, utc2, &tai1, &tai2);
SOFA(iauTaitt, tai1, tai2, &tt1, &tt2);
iauAtic13(appRA, appDecl, tt1, tt2, &ri, &dec, &eo);
ra = iauAnp(ri + eo);
ra *= DR2S;
dec *= DR2AS;
DBG("SOFA: r=%g'', d=%g''", ra, dec);
#undef SOFA
rtn:
if(r) *r = ra;
if(dc) *dc = dec;
}
/**
* @brief J2000toJnow - convert ra/dec between epochs
* @param in - J2000 (degrees)
* @param out - Jnow (degrees)
* @return
*/
int J2000toJnow(const polar *in, polar *out){
if(!out) return 1;
double utc1, utc2;
time_t tsec;
struct tm *ts;
tsec = time(0); // number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)
ts = gmtime(&tsec);
int result = 0;
result = iauDtf2d ( "UTC", ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec, &utc1, &utc2 );
if (result != 0) {
fprintf(stderr, "iauDtf2d call failed\n");
return 1;
}
// Make TT julian date for Atci13 call
double tai1, tai2;
double tt1, tt2;
result = iauUtctai(utc1, utc2, &tai1, &tai2);
if(result){
fprintf(stderr, "iauUtctai call failed\n");
return 1;
}
result = iauTaitt(tai1, tai2, &tt1, &tt2);
if(result){
fprintf(stderr, "iauTaitt call failed\n");
return 1;
}
double pr = 0.0; // RA proper motion (radians/year; Note 2)
double pd = 0.0; // Dec proper motion (radians/year)
double px = 0.0; // parallax (arcsec)
double rv = 0.0; // radial velocity (km/s, positive if receding)
double rc = DD2R * in->ra, dc = DD2R * in->dec; // convert into radians
double ri, di, eo;
iauAtci13(rc, dc, pr, pd, px, rv, tt1, tt2, &ri, &di, &eo);
out->ra = iauAnp(ri - eo) * DR2D;
out->dec = di * DR2D;
return 0;
}
#endif

View File

@@ -1,11 +1,10 @@
/*
* main.c
* This file is part of the Snippets project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* Copyright 2014 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
@@ -14,509 +13,106 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <arpa/inet.h>
#include <endian.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h> // wait
#include <sys/prctl.h> //prctl
#include <usefull_macros.h>
#include "emulation.h"
#include "libsofa.h"
#include "main.h"
#include "socket.h"
#include "telescope.h"
#include "args.h"
#include "server.h"
#include "mount.h"
// daemon.c
extern void check4running(char *self, char *pidfilename, void (*iffound)(pid_t pid));
static pid_t childpid = -1; // PID of child process
static const char *pidfile = NULL;
// Max amount of connections
#define BACKLOG (10)
#define BUFLEN (1024)
// pause for incoming message waiting (out coordinates sent after that timeout)
#define SOCK_TMOUT (1)
static pid_t childpid = 1; // PID of child process
volatile int global_quit = 0;
// quit by signal
void signals(int sig){
signal(sig, SIG_IGN);
if(childpid){ // parent process
restore_tty(); // restore all parameters
unlink(GP->pidfile); // and remove pidfile
weatherserver_disconnect();
}
DBG("Get signal %d, quit.\n", sig);
global_quit = 1;
if(childpid) putlog("PID %d exit with status %d after child's %d death", getpid(), sig, childpid);
else WARNX("Child %d died with %d", getpid(), sig);
sleep(1);
exit(sig);
}
// search a first word after needle without spaces
char* stringscan(char *str, char *needle){
char *a, *e;
char *end = str + strlen(str);
a = strstr(str, needle);
if(!a) return NULL;
a += strlen(needle);
while (a < end && (*a == ' ' || *a == '\r' || *a == '\t')) a++;
if(a >= end) return NULL;
e = strchr(a, ' ');
if(e) *e = 0;
return a;
}
/**
* Send data to user
* @param data - data to send
* @param dlen - data length
* @param sockfd - socket fd for sending data
* @return 0 if failed
*/
int send_data(uint8_t *data, size_t dlen, int sockfd){
size_t sent = write(sockfd, data, dlen);
if(sent != dlen){
WARN("write()");
return 0;
if(childpid){ // single or parent process
if(pidfile) unlink(pidfile); // remove pidfile
if(childpid > 0) kill(childpid, SIGTERM);
}
return 1;
}
//read: 0x14 0x0 0x0 0x0 0x5b 0x5a 0x2e 0xc6 0x8c 0x23 0x5 0x0 0x23 0x9 0xe5 0xaf 0x23 0x2e 0x34 0xed
// command: goto 16h29 24.45 -26d25 55.62
/*
LITTLE-ENDIAN!!!
from client:
LENGTH (2 bytes, integer): length of the message
TYPE (2 bytes, integer): 0
TIME (8 bytes, integer): current time on the server computer in microseconds
since 1970.01.01 UT. Currently unused.
RA (4 bytes, unsigned integer): right ascension of the telescope (J2000)
a value of 0x100000000 = 0x0 means 24h=0h,
a value of 0x80000000 means 12h
DEC (4 bytes, signed integer): declination of the telescope (J2000)
a value of -0x40000000 means -90degrees,
a value of 0x0 means 0degrees,
a value of 0x40000000 means 90degrees
to client:
LENGTH (2 bytes, integer): length of the message
TYPE (2 bytes, integer): 0
TIME (8 bytes, integer): current time on the server computer in microseconds
since 1970.01.01 UT. Currently unused.
RA (4 bytes, unsigned integer): right ascension of the telescope (J2000)
a value of 0x100000000 = 0x0 means 24h=0h,
a value of 0x80000000 means 12h
DEC (4 bytes, signed integer): declination of the telescope (J2000)
a value of -0x40000000 means -90degrees,
a value of 0x0 means 0degrees,
a value of 0x40000000 means 90degrees
STATUS (4 bytes, signed integer): status of the telescope, currently unused.
status=0 means ok, status<0 means some error
*/
#define DEG2DEC(degr) ((int32_t)(degr / 90. * ((double)0x40000000)))
#define HRS2RA(hrs) ((uint32_t)(hrs / 12. * ((double)0x80000000)))
#define DEC2DEG(i32) (((double)i32)*90./((double)0x40000000))
#define RA2HRS(u32) (((double)u32)*12. /((double)0x80000000))
typedef struct __attribute__((__packed__)){
uint16_t len;
uint16_t type;
uint64_t time;
uint32_t ra;
int32_t dec;
} indata;
typedef struct __attribute__((__packed__)){
uint16_t len;
uint16_t type;
uint64_t time;
uint32_t ra;
int32_t dec;
int32_t status;
} outdata;
/**
* convert RA/DEC to string in forman RA: HH:MM:SS.SS, DEC: DD:MM:SS.S
*/
char *radec2str(double ra, double dec){
static char buf[1024];
char sign = '+';
if(dec < 0){
sign = '-';
dec = -dec;
if(sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
}
int h = (int)ra;
ra -= h; ra *= 60.;
int m = (int)ra;
ra -= m; ra *= 60.;
int d = (int) dec;
dec -= d; dec *= 60.;
int dm = (int)dec;
dec -= dm; dec *= 60.;
snprintf(buf, 1024, "%d:%d:%.2f %c%d:%d:%.1f", h,m,ra, sign,d,dm,dec);
return buf;
}
/**
* send input RA/Decl (j2000!) coordinates to tel
* ra in hours (0..24), decl in degrees (-90..90)
* @return 1 if all OK
*/
int setCoords(double ra, double dec){
char *radec = radec2str(ra, dec);
DBG("Set RA/Decl to %s", radec);
putlog("Try to set RA/Decl to %s", radec);
int (*pointfunction)(double, double) = point_telescope;
if(GP->emulation) pointfunction = point_emulation;
return pointfunction(ra, dec);
}
/**
* @brief proc_data - process data received from Stellarium
* @param data - raw data
* @param len - its length
* @return 1 if all OK
*/
int proc_data(uint8_t *data, ssize_t len){
FNAME();
if(len != sizeof(indata)){
WARNX("Bad data size: got %zd instead of %zd!", len, sizeof(indata));
return 0;
if(childpid > 0){ // parent process
LOGERR("PID %d exit with status %d", getpid(), sig);
}else{
LOGWARN("Child %d died with %d", getpid(), sig);
server_stop();
}
indata *dat = (indata*)data;
uint16_t L, T;
//uint64_t tim;
uint32_t ra;
int32_t dec;
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
L = le16toh(dat->len); T = le16toh(dat->type);
//tim = le64toh(dat->time);
ra = le32toh(dat->ra);
dec = (int32_t)le32toh((uint32_t)dat->dec);
#else
L = dat->len; T = dat->type;
//tim = dat->time;
ra = dat->ra; dec = dat->dec;
#endif
DBG("got message with len %u & type %u", L, T);
if(L != len){
WARNX("Length of message != msg->len");
return 0;
}
if(T){
WARNX("Wrong message type");
return 0;
}
// convert RA/DEC to hours/degrees
double tagRA = RA2HRS(ra), tagDec = DEC2DEG(dec);
DBG("RA: %u (%g), DEC: %d (%g)", ra, tagRA, dec, tagDec);
putlog("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec);
// check RA/DEC
horizCrds hnow; // without refraction
polarCrds p2000, pnow;
p2000.ra = tagRA/12. * M_PI;
p2000.dec = tagDec * ERFA_DD2R;
// now J2000 obs Jnow
if(get_ObsPlace(NULL, &p2000, NULL, &pnow, &hnow)){
WARNX("Can't convert coordinates to Jnow");
return 0;
}
/*
int i[4], j[4]; char pm, pm1;
eraA2af(2, hnow.az, &pm, i);
eraA2af(2, hnow.zd, &pm1, j);
DBG("az: %c%02d %02d %02d.%2.d, zd: %c%02d %02d %02d.%2.d",
pm, i[0],i[1],i[2],i[3],
pm1,j[0],j[1],j[2],j[3]);
eraA2af(2, M_PI_2 - hnow.zd, &pm, i);
DBG("h: %c%02d %02d %02d.%2.d", pm, i[0],i[1],i[2],i[3]);
*/
if(hnow.zd > 80.*ERFA_DD2R){
WARNX("Z > 80degr (%g), stop telescope", hnow.zd * ERFA_DR2D);
putlog("Z=%.1f > 80 - stop!", hnow.zd * ERFA_DR2D);
stop_telescope();
return 0;
}
tagRA = (pnow.ra - pnow.eo) / M_PI * 12.;
tagDec = pnow.dec / ERFA_DD2R;
if(!setCoords(tagRA, tagDec)) return 0;
return 1;
}
/**
* main socket service procedure
*/
void *handle_socket(void *sockd){
FNAME();
if(global_quit) return NULL;
outdata dout;
int sock = *(int*)sockd;
dout.len = htole16(sizeof(outdata));
dout.type = 0;
int (*getcoords)(double*, double*) = get_telescope_coords;
if(GP->emulation) getcoords = get_emul_coords;
while(!global_quit){
// get coordinates
double RA = 0., Decl = 0.;
if((dout.status = getcoords(&RA, &Decl)) < 0){
WARNX("Error: can't get coordinates");
sleep(1);
continue;
}
//DBG("got : %g/%g", RA, Decl);
dout.ra = htole32(HRS2RA(RA));
dout.dec = (int32_t)htole32(DEG2DEC(Decl));
if(!send_data((uint8_t*)&dout, sizeof(outdata), sock)) break;
//DBG("sent ra = %g, dec = %g", RA2HRS(dout.ra), DEC2DEG(dout.dec));
fd_set readfds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
timeout.tv_sec = SOCK_TMOUT; // wait not more than SOCK_TMOUT second
timeout.tv_usec = 0;
int sel = select(sock + 1 , &readfds , NULL , NULL , &timeout);
if(sel < 0){
if(errno != EINTR)
WARN("select()");
continue;
}
if(!(FD_ISSET(sock, &readfds))) continue;
// fill incoming buffer
uint8_t buff[BUFLEN+1];
ssize_t rd = read(sock, buff, BUFLEN);
buff[rd] = 0;
DBG("read %zd (%s)", rd, buff);
if(rd <= 0){ // error or disconnect
DBG("Nothing to read from fd %d (ret: %zd)", sock, rd);
break;
}
/**************************************
* DO SOMETHING WITH DATA *
**************************************/
if(!proc_data(buff, rd)) dout.status = -1;
else dout.status = 0;
}
close(sock);
return NULL;
}
// thread writing FITS-header file
static void *hdrthread(_U_ void *buf){
// write FITS-header at most once per second
while(!global_quit){
wrhdr();
usleep(100000); // give a chance to write/read for others
}
return NULL;
}
/**
* @brief opensocket - open socket to port `port`
* @return socket fd or <0 if failed
*/
static int opensocket(char *port){
if(!port) return -1;
int reuseaddr = 1;
int sock;
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
DBG("try to open port %s", port);
if(getaddrinfo(NULL, port, &hints, &res) != 0){
WARN("getaddrinfo()");
return 0;
}
/*
struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr;
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(ia->sin_addr), str, INET_ADDRSTRLEN);
*/
// loop through all the results and bind to the first we can
for(p = res; p != NULL; p = p->ai_next){
if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
WARN("socket()");
continue;
}
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){
WARN("setsockopt()");
close(sock);
continue;
}
if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){
WARN("bind()");
close(sock);
continue;
}
break; // if we get here, we must have connected successfully
}
freeaddrinfo(res);
// Listen
if(listen(sock, BACKLOG) == -1){
WARN("listen");
putlog("listen() error");
}
DBG("listen at %s", port);
putlog("listen at %s", port);
return sock;
}
/**
* @brief waitconn
* @param sock - socket fd to accept()
* @param connthread - thread which to run when connection accepted (it's parameter - socket fd)
*/
static void waitconn(int sock, void *(*connthread)(void*)){
// Main loop
while(!global_quit){
socklen_t size = sizeof(struct sockaddr_in);
struct sockaddr_in myaddr;
int newsock;
newsock = accept(sock, (struct sockaddr*)&myaddr, &size);
if(newsock <= 0){
WARN("accept()");
sleep(1);
continue;
}
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
if(getpeername(newsock, (struct sockaddr*)&peer, &peer_len) == -1){
WARN("getpeername()");
close(newsock);
continue;
}
int sockport = -1;
if(getsockname(newsock, (struct sockaddr*)&peer, &peer_len) == 0){
sockport = ntohs(peer.sin_port);
}
char *peerIP = inet_ntoa(peer.sin_addr);
putlog("Got connection from %s @ %d", peerIP, sockport);
DBG("Peer's IP address is: %s (@port %d)\n", peerIP, sockport);
/*if(strcmp(peerIP, ACCEPT_IP) && strcmp(peerIP, "127.0.0.1")){
WARNX("Wrong IP");
close(newsock);
continue;
}*/
pthread_t rthrd;
if(pthread_create(&rthrd, NULL, connthread, (void*)&newsock)){
putlog("Error creating listen thread");
ERR(_("Can't create socket thread"));
}else{
DBG("Thread created, detouch");
pthread_detach(rthrd); // don't care about thread state
}
}
close(sock);
}
// thread working with terminal
static void *termthread(_U_ void *buf){
int sock = opensocket(GP->dbgport);
if(sock < 0){
putlog("Can't open debugging socket @ port %s", GP->dbgport);
ERRX("Can't open debug socket");
}
waitconn(sock, term_thread);
return NULL;
}
static inline void main_proc(){
pthread_t hthrd, termthrd;
// connect to telescope
if(!GP->emulation){
if(!connect_telescope(GP->device, GP->crdsfile)){
ERRX(_("Can't connect to telescope device"));
}
if(pthread_create(&hthrd, NULL, hdrthread, NULL))
ERR(_("Can't create writing thread"));
if(pthread_create(&termthrd, NULL, termthread, NULL))
ERR(_("Can't create terminal thread"));
}
// connect to weather daemon
if(!weatherserver_connect()){
DBG("Can't connect to weather server, will try later");
}
// open socket
int sock = opensocket(GP->port);
if(sock < 0){
putlog("Can't open socket @ port %s", GP->port);
ERRX("Can't open stellarium socket");
}
waitconn(sock, handle_socket);
usleep(10000);
pthread_cancel(hthrd); // cancel reading thread
pthread_cancel(termthrd);
pthread_join(hthrd, NULL);
pthread_join(termthrd, NULL);
DBG("EXIT");
}
int main(int argc, char **argv){
char *self = strdup(argv[0]);
GP = parse_args(argc, argv);
initial_setup();
signal(SIGTERM, signals); // kill (-15) - quit
signal(SIGKILL, signals); // kill (-9) - quit
signal(SIGHUP, SIG_IGN); // hup - ignore
signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
sl_init();
parameters_t *G = parse_cmdline(&argc, &argv);
if(!G) return 1;
sl_loglevel_e lvl = G->verbose + LOGLEVEL_ERR;
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
DBG("verb: %d, level: %d", G->verbose, lvl);
int fd;
if((fd = open(GP->crdsfile, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) // test FITS-header file for writing
ERR(_("Can't open %s for writing"), GP->crdsfile);
if((fd = open(G->crdsfile, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) // test FITS-header file for writing
ERR(_("Can't open %s for writing"), G->crdsfile);
close(fd);
printf("Daemonize\n");
#ifndef EBUG // daemonize only in release mode
if(daemon(1, 0)){
putlog("Err: daemon()");
ERR("daemon()");
}
#endif // EBUG
check4running(self, GP->pidfile, NULL);
if(GP->logfile) openlogfile(GP->logfile);
putlog("Starting, master PID=%d", getpid());
if(G->sleept < 1) ERRX("Sleeping time sould be positive value");
if(!server_setsleept(G->sleept)) ERRX("Can't set sleep time to %d", G->sleept);
if(G->emulation) set_emulation_mode();
else if(!mount_set_dev(G->device, G->serspeed, G->sertmout))
ERRX("Can't open device %s @ %d", G->device, G->serspeed);
if(!mount_set_name(G->mountname))
ERRX("Can't set mount name to %s", G->mountname);
signal(SIGTERM, signals);
signal(SIGINT, signals);
signal(SIGQUIT, signals);
signal(SIGTSTP, SIG_IGN);
signal(SIGHUP, signals);
sl_check4running((char*)__progname, G->pidfile);
if(G->logfile) OPENLOG(G->logfile, lvl, 1);
DBG("Started");
LOGMSG("Started, master PID=%d", getpid());
#ifndef EBUG
while(1){
time_t lastd = 0;
while(1){ // guard for dead processes
childpid = fork();
if(childpid < 0){
putlog("fork() error");
ERR("ERROR on fork");
LOGERR("fork() returns error");
sleep(5);
continue;
}
if(childpid){
WARNX(_("Created child with PID %d\n"), childpid);
DBG("Created child with PID %d\n", childpid);
wait(NULL);
WARNX(_("Child %d died\n"), childpid);
DBG("Child %d died\n", childpid);
time_t t = time(NULL);
if(t - lastd > 600){ // at least 10 minutes of work
LOGERR("Child %d died\n", childpid);
}
lastd = t;
WARNX("Child %d died\n", childpid);
sleep(1);
}else{
prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies
main_proc();
return 0;
break; // go out to normal functional
}
}
#else
main_proc();
#endif
return 0;
server_sock_t sockt = {
.cmd_isunix = G->isunix,
.cmdnode = G->cmdnode,
.stellport = G->port,
.maxclients = G->maxclients,
};
if(!server_check(&sockt)){
LOGERR("Can't run servers");
ERRX("Can't run servers");
}
DBG("Run server");
server_run();
DBG("Server died");
return 1;
}

View File

@@ -1,43 +0,0 @@
/*
* main.h
*
* Copyright 2014 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#pragma once
#ifndef __MAIN_H__
#define __MAIN_H__
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <libintl.h>
#include <locale.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "cmdlnopts.h"
#include "usefull_macro.h"
// global parameters
extern glob_pars *Global_parameters;
// global quit flag
extern volatile int global_quit;
#endif // __MAIN_H__

View File

@@ -0,0 +1,288 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <pthread.h>
#include <stdatomic.h>
#include <stdint.h>
#include <string.h>
#include "angles.h"
#include "emulation.h"
#include "mount.h"
#define MNAME_LEN 31
static bool isemulated = false; // ==true for emulation mode
static char mount_name[MNAME_LEN+3] = "'noname'";
static sl_tty_t *mount_dev = NULL;
// device mutex, blocking only in non-local functions
static pthread_mutex_t mntdev_mutex = PTHREAD_MUTEX_INITIALIZER;
// ring buffer for data incoming from serial port
static sl_ringbuffer_t *RBin = NULL;
// status
static atomic_int mountstatus = MNT_S_ERROR;
// input and current target coordinates
polarCrds_t InpCoords = {0}, TagCoords = {0};
horizCrds_t InpHoriz = {0};
// change input coordinates
/**
* @brief mount_setInpHA - set hour angle
* @param ha (HOURS!!)
* @return false if `ha` isn't in [0,24)
*/
bool mount_setInpHA(double ha){
if(ha < 0. || ha >= 24.) return false;
InpCoords.ha = HRS2RAD(ha);
return true;
}
/**
* @brief mount_setInpRA - set right ascension
* @param ra (DEGREES!)
* @return fale if `ra` isn't in [0, 360)
*/
bool mount_setInpRA(double ra){
if(ra < 0. || ra >= 360.) return false;
InpCoords.ra = DEG2RAD(ra);
return true;
}
/**
* @brief mount_setInpDec - set declination
* @param dec (DEGREES!)
* @return false if `dec` isn't in [-90, 90]
*/
bool mount_setInpDec(double dec){
if(dec < -90. || dec > 90.) return false;
InpCoords.dec = DEG2RAD(dec);
return true;
}
/**
* @brief mount_setInpA - set azimuth
* @param A (DEGREES)
* @return false if A isn't in [0, 360)
*/
bool mount_setInpA(double A){
if(A < 0. || A >= 360.) return false;
InpHoriz.az = DEG2RAD(A);
return true;
}
/**
* @brief mount_setInpZ - set zenith distance
* @param Z (DEGREES)
* @return false if Z isn't in [0, 90]
*/
bool mount_setInpZ(double Z){
if(Z < 0 || Z > 90) return false;
InpHoriz.zd = DEG2RAD(Z);
return true;
}
/**
* @brief mount_set_name - set mount name for FITS header
* @param name - new string with name
* @return false if failed
*/
bool mount_set_name(const char *name){
if(!name || !*name) return false;
int l = strlen(name);
if(l > MNAME_LEN) return false;
sprintf(mount_name, "'%s'", name);
return true;
}
/**
* @brief mount_set_dev - open mount device without checking that mount is alive
* @param dev - path to device
* @param speed - baudrate
* @return false if failed to open device `dev`
*/
bool mount_set_dev(char *dev, int speed, int timeout){
pthread_mutex_lock(&mntdev_mutex);
if(mount_dev) sl_tty_close(&mount_dev);
mount_dev = sl_tty_new(dev, speed, 4096);
if(mount_dev) mount_dev = sl_tty_open(mount_dev, 1);
if(!mount_dev){
pthread_mutex_unlock(&mntdev_mutex);
return false;
}
sl_tty_tmout(timeout);
if(!RBin) RBin = sl_RB_new(BUFSIZ);
else sl_RB_clearbuf(RBin);
pthread_mutex_unlock(&mntdev_mutex);
return true;
}
static const char *statuses[MNT_S_STATAMOUNT] = {
[MNT_S_TRACKING] = "'Tracking'",
[MNT_S_STOPHOM] = "'Stopped or homing'",
[MNT_S_PARKING] = "'Slewing to park'",
[MNT_S_UNPARKING] = "'Unparking'",
[MNT_S_HOMING] = "'Slewing to home'",
[MNT_S_PARKED] = "'Parked'",
[MNT_S_SLEWING] = "'Slewing or going to stop'",
[MNT_S_STOPPED] = "'Stopped'",
[MNT_S_INHIBITED] = "'Motors inhibited, T too low'",
[MNT_S_OUTLIMIT] = "'Outside tracking limit'",
[MNT_S_FOLSAT]= "'Following satellite'",
[MNT_S_DATINCOSIST]= "'Data inconsistency'",
[MNT_S_ERROR] = "'Error (disconnected?)'"
};
/**
* @brief strstatus - return string explanation of mount status
* @param status - integer status code
* @return statically allocated string with explanation
*/
const char* mount_status_str(){
int curst = atomic_load(&mountstatus);
if(curst > -1 && curst < MNT_S_STATAMOUNT) return statuses[curst];
return "'Unknown status'";
}
// return current mount status
mount_status_t mount_status(){
return (mount_status_t)atomic_load(&mountstatus);
}
/**
* @brief write_cmd - try to write command to mount
* @param cmd - string with command or NULL just to clear all incoming data
* @return false on write/read error; all read information is in RBin (even if `false` returned, you SHOULD read all available strings from it)
*/
static bool write_cmd(const char *cmd){
bool ret = true;
if(cmd){
size_t l = strlen(cmd);
if(sl_tty_write(mount_dev->comfd, cmd, l)) ret = false;
}
int got = 0;
while((got = sl_tty_read(mount_dev)) > 0){
if(sl_RB_write(RBin, (uint8_t*) mount_dev->buf, got)){
got = -1;
break;
}
}
if(got < 0) return false;
return ret;
}
// check if mount connected
static bool chkconn(){
int r = 0;
do{ // clear incoming buffer @ start
r = sl_tty_read(mount_dev);
}while(r > 0);
if(r < 0) return false;
write_cmd("#"); // clear cmd buffer
sl_RB_clearbuf(RBin);
bool w = write_cmd(":SB0#");
sl_RB_clearbuf(RBin);
return w;
}
// try to guess serial speed & set 115200
static bool guess_speed(){
if(!mount_dev) return false;
close(mount_dev->comfd);
#define SPDBUFSZ 7
const int speeds[SPDBUFSZ] = {57600, 38400, 19200, 9600, 4800, 2400, 1200};
int idx = 0;
for(; idx < SPDBUFSZ; ++idx){
mount_dev->speed = speeds[idx];
sl_tty_t *trydev = sl_tty_open(mount_dev, 1);
if(!trydev) continue;
if(chkconn()) break;
close(mount_dev->comfd);
}
if(idx == SPDBUFSZ) return false; // device not responding
close(mount_dev->comfd);
mount_dev->speed = 115200;
if(!sl_tty_open(mount_dev, 1)) return false;
#undef SPDBUFSZ
return true;
}
// connect to mount
bool mount_connect(){
if(isemulated){
atomic_store(&mountstatus, MNT_S_STOPPED);
return true;
}
if(!mount_dev) return false;
pthread_mutex_lock(&mntdev_mutex);
if(!chkconn() && !guess_speed()) return false;
bool ret = true;
if(!write_cmd(":STOP#")) ret = false; // stop tracking after poweron
sl_RB_clearbuf(RBin);
if(!write_cmd(":U2#")) ret = false; // set high precision
sl_RB_clearbuf(RBin);
if(!write_cmd(":So10#")) ret = false; // set minimum altitude to 10 degrees
sl_RB_clearbuf(RBin);
pthread_mutex_unlock(&mntdev_mutex);
if(ret) LOGMSG("Connected to %s@115200", mount_dev->portname);
else LOGERR("Can't write commands to mount");
return ret;
}
void mount_disconnect(){
if(isemulated) return;
pthread_mutex_trylock(&mntdev_mutex); // at least, try
if(mount_dev) close(mount_dev->comfd);
pthread_mutex_unlock(&mntdev_mutex);
}
// point to ra/dec over serial
static bool mount_pointto(double ra, double dec){
(void) ra; (void) dec;
;
return true;
}
/**
* send input RA/Decl (j2000!) coordinates to tel
* ra in hours (0..24), decl in degrees (-90..90)
* @return true if all OK
*/
bool mount_point(double ra, double dec){
char buf[RADEC_STR_MAXLEN];
radec2str(ra, dec, buf);
DBG("Set RA/Decl to %s", buf);
LOGMSG("Try to set RA/Decl to %s", buf);
norm_RADEC(&ra, &dec);
bool (*pointfunction)(double, double) = mount_pointto;
if(isemulated) pointfunction = point_emulation;
return pointfunction(ra, dec);
}
void set_emulation_mode(){
isemulated = true;
}
mount_status_t mount_getcoords(double *ra, double *dec){
if(!ra || !dec) return MNT_S_ERROR;
if(isemulated){
get_emul_coords(ra, dec);
DBG("Emulated coordinates: %g, %g", *ra, *dec);
}else{
; // get real coordinates
}
return mount_status();
}

View File

@@ -0,0 +1,57 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <usefull_macros.h>
// mount statuses
typedef enum{
MNT_S_TRACKING = 0,
MNT_S_STOPHOM = 1,
MNT_S_PARKING = 2,
MNT_S_UNPARKING = 3,
MNT_S_HOMING = 4,
MNT_S_PARKED = 5,
MNT_S_SLEWING = 6,
MNT_S_STOPPED = 7,
MNT_S_INHIBITED = 8,
MNT_S_OUTLIMIT = 9,
MNT_S_FOLSAT = 10,
MNT_S_DATINCOSIST = 11,
MNT_S_ERROR = 12, // my status
MNT_S_STATAMOUNT = 13 // number of statuses
} mount_status_t;
void set_emulation_mode();
bool mount_setInpHA(double ha);
bool mount_setInpRA(double ra);
bool mount_setInpDec(double dec);
bool mount_setInpA(double A);
bool mount_setInpZ(double Z);
bool mount_set_name(const char *name);
bool mount_set_dev(char *dev, int speed, int timeout);
const char* mount_status_str();
mount_status_t mount_status();
bool mount_connect();
void mount_disconnect();
mount_status_t mount_getcoords(double *ra, double *dec);
bool mount_point(double ra, double dec);

View File

@@ -0,0 +1 @@
-std=c23

View File

@@ -1,3 +1,7 @@
// Add predefined macros for your project here. For example:
// #define THE_ANSWER 42
#define EBUG 1
#define _XOPEN_SOURCE 1234
#define _DEFAULT_SOURCE
#define _GNU_SOURCE
#define EBUG

View File

@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 19.0.2, 2026-06-10T13:41:37. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/10micron/C-sources/mountdaemon_10micron</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1,16 @@
angles.c
angles.h
args.c
args.h
astrosib_proto.h
dome.c
dome.h
emulation.c
emulation.h
main.c
server.c
server.h
mount.c
mount.h
stellarium.c
stellarium.h

View File

@@ -1,497 +0,0 @@
/* geany_encoding=koi8-r
* parseargs.c - parsing command line arguments & print help
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <stdio.h> // printf
#include <getopt.h> // getopt_long
#include <stdlib.h> // calloc, exit, strtoll
#include <assert.h> // assert
#include <string.h> // strdup, strchr, strlen
#include <strings.h>// strcasecmp
#include <limits.h> // INT_MAX & so on
#include <libintl.h>// gettext
#include <ctype.h> // isalpha
#include "parseargs.h"
#include "usefull_macro.h"
char *helpstring = "%s\n";
/**
* Change standard help header
* MAY consist ONE "%s" for progname
* @param str (i) - new format
*/
void change_helpstring(char *s){
int pcount = 0, scount = 0;
char *str = s;
// check `helpstring` and set it to default in case of error
for(; pcount < 2; str += 2){
if(!(str = strchr(str, '%'))) break;
if(str[1] != '%') pcount++; // increment '%' counter if it isn't "%%"
else{
str += 2; // pass next '%'
continue;
}
if(str[1] == 's') scount++; // increment "%s" counter
};
if(pcount > 1 || pcount != scount){ // amount of pcount and/or scount wrong
/// "îÅÐÒÁ×ÉÌØÎÙÊ ÆÏÒÍÁÔ ÓÔÒÏËÉ ÐÏÍÏÝÉ"
ERRX(_("Wrong helpstring!"));
}
helpstring = s;
}
/**
* Carefull atoll/atoi
* @param num (o) - returning value (or NULL if you wish only check number) - allocated by user
* @param str (i) - string with number must not be NULL
* @param t (i) - T_INT for integer or T_LLONG for long long (if argtype would be wided, may add more)
* @return TRUE if conversion sone without errors, FALSE otherwise
*/
static bool myatoll(void *num, char *str, argtype t){
long long tmp, *llptr;
int *iptr;
char *endptr;
assert(str);
assert(num);
tmp = strtoll(str, &endptr, 0);
if(endptr == str || *str == '\0' || *endptr != '\0')
return FALSE;
switch(t){
case arg_longlong:
llptr = (long long*) num;
*llptr = tmp;
break;
case arg_int:
default:
if(tmp < INT_MIN || tmp > INT_MAX){
/// "ãÅÌÏÅ ×ÎÅ ÄÏÐÕÓÔÉÍÏÇÏ ÄÉÁÐÁÚÏÎÁ"
WARNX(_("Integer out of range"));
return FALSE;
}
iptr = (int*)num;
*iptr = (int)tmp;
}
return TRUE;
}
// the same as myatoll but for double
// There's no NAN & INF checking here (what if they would be needed?)
static bool myatod(void *num, const char *str, argtype t){
double tmp, *dptr;
float *fptr;
char *endptr;
assert(str);
tmp = strtod(str, &endptr);
if(endptr == str || *str == '\0' || *endptr != '\0')
return FALSE;
switch(t){
case arg_double:
dptr = (double *) num;
*dptr = tmp;
break;
case arg_float:
default:
fptr = (float *) num;
*fptr = (float)tmp;
break;
}
return TRUE;
}
/**
* Get index of current option in array options
* @param opt (i) - returning val of getopt_long
* @param options (i) - array of options
* @return index in array
*/
static int get_optind(int opt, myoption *options){
int oind;
myoption *opts = options;
assert(opts);
for(oind = 0; opts->name && opts->val != opt; oind++, opts++);
if(!opts->name || opts->val != opt) // no such parameter
showhelp(-1, options);
return oind;
}
/**
* reallocate new value in array of multiple repeating arguments
* @arg paptr - address of pointer to array (**void)
* @arg type - its type (for realloc)
* @return pointer to new (next) value
*/
void *get_aptr(void *paptr, argtype type){
int i = 1;
void **aptr = *((void***)paptr);
if(aptr){ // there's something in array
void **p = aptr;
while(*p++) ++i;
}
size_t sz = 0;
switch(type){
default:
case arg_none:
/// "îÅ ÍÏÇÕ ÉÓÐÏÌØÚÏ×ÁÔØ ÎÅÓËÏÌØËÏ ÐÁÒÁÍÅÔÒÏ× ÂÅÚ ÁÒÇÕÍÅÎÔÏ×!"
ERRX("Can't use multiple args with arg_none!");
break;
case arg_int:
sz = sizeof(int);
break;
case arg_longlong:
sz = sizeof(long long);
break;
case arg_double:
sz = sizeof(double);
break;
case arg_float:
sz = sizeof(float);
break;
case arg_string:
sz = 0;
break;
/* case arg_function:
sz = sizeof(argfn *);
break;*/
}
aptr = realloc(aptr, (i + 1) * sizeof(void*));
*((void***)paptr) = aptr;
aptr[i] = NULL;
if(sz){
aptr[i - 1] = malloc(sz);
}else
aptr[i - 1] = &aptr[i - 1];
return aptr[i - 1];
}
/**
* Parse command line arguments
* ! If arg is string, then value will be strdup'ed!
*
* @param argc (io) - address of argc of main(), return value of argc stay after `getopt`
* @param argv (io) - address of argv of main(), return pointer to argv stay after `getopt`
* BE CAREFUL! if you wanna use full argc & argv, save their original values before
* calling this function
* @param options (i) - array of `myoption` for arguments parcing
*
* @exit: in case of error this function show help & make `exit(-1)`
*/
void parseargs(int *argc, char ***argv, myoption *options){
char *short_options, *soptr;
struct option *long_options, *loptr;
size_t optsize, i;
myoption *opts = options;
// check whether there is at least one options
assert(opts);
assert(opts[0].name);
// first we count how much values are in opts
for(optsize = 0; opts->name; optsize++, opts++);
// now we can allocate memory
short_options = calloc(optsize * 3 + 1, 1); // multiply by three for '::' in case of args in opts
long_options = calloc(optsize + 1, sizeof(struct option));
opts = options; loptr = long_options; soptr = short_options;
// in debug mode check the parameters are not repeated
#ifdef EBUG
char **longlist = MALLOC(char*, optsize);
char *shortlist = MALLOC(char, optsize);
#endif
// fill short/long parameters and make a simple checking
for(i = 0; i < optsize; i++, loptr++, opts++){
// check
assert(opts->name); // check name
#ifdef EBUG
longlist[i] = strdup(opts->name);
#endif
if(opts->has_arg){
assert(opts->type != arg_none); // check error with arg type
assert(opts->argptr); // check pointer
}
if(opts->type != arg_none) // if there is a flag without arg, check its pointer
assert(opts->argptr);
// fill long_options
// don't do memcmp: what if there would be different alignment?
loptr->name = opts->name;
loptr->has_arg = (opts->has_arg < MULT_PAR) ? opts->has_arg : 1;
loptr->flag = opts->flag;
loptr->val = opts->val;
// fill short options if they are:
if(!opts->flag && opts->val){
#ifdef EBUG
shortlist[i] = (char) opts->val;
#endif
*soptr++ = opts->val;
if(loptr->has_arg) // add ':' if option has required argument
*soptr++ = ':';
if(loptr->has_arg == 2) // add '::' if option has optional argument
*soptr++ = ':';
}
}
// sort all lists & check for repeating
#ifdef EBUG
int cmpstringp(const void *p1, const void *p2){
return strcmp(* (char * const *) p1, * (char * const *) p2);
}
int cmpcharp(const void *p1, const void *p2){
return (int)(*(char * const)p1 - *(char *const)p2);
}
qsort(longlist, optsize, sizeof(char *), cmpstringp);
qsort(shortlist,optsize, sizeof(char), cmpcharp);
char *prevl = longlist[0], prevshrt = shortlist[0];
for(i = 1; i < optsize; ++i){
if(longlist[i]){
if(prevl){
if(strcmp(prevl, longlist[i]) == 0) ERRX("double long arguments: --%s", prevl);
}
prevl = longlist[i];
}
if(shortlist[i]){
if(prevshrt){
if(prevshrt == shortlist[i]) ERRX("double short arguments: -%c", prevshrt);
}
prevshrt = shortlist[i];
}
}
#endif
// now we have both long_options & short_options and can parse `getopt_long`
while(1){
int opt;
int oindex = 0, optind = 0; // oindex - number of option in argv, optind - number in options[]
if((opt = getopt_long(*argc, *argv, short_options, long_options, &oindex)) == -1) break;
if(opt == '?'){
opt = optopt;
optind = get_optind(opt, options);
if(options[optind].has_arg == NEED_ARG || options[optind].has_arg == MULT_PAR)
showhelp(optind, options); // need argument
}
else{
if(opt == 0 || oindex > 0) optind = oindex;
else optind = get_optind(opt, options);
}
opts = &options[optind];
// if(opt == 0 && opts->has_arg == NO_ARGS) continue; // only long option changing integer flag
// now check option
if(opts->has_arg == NEED_ARG || opts->has_arg == MULT_PAR)
if(!optarg) showhelp(optind, options); // need argument
void *aptr;
if(opts->has_arg == MULT_PAR){
aptr = get_aptr(opts->argptr, opts->type);
}else
aptr = opts->argptr;
bool result = TRUE;
// even if there is no argument, but argptr != NULL, think that optarg = "1"
if(!optarg) optarg = "1";
switch(opts->type){
default:
case arg_none:
if(opts->argptr) *((int*)aptr) += 1; // increment value
break;
case arg_int:
result = myatoll(aptr, optarg, arg_int);
break;
case arg_longlong:
result = myatoll(aptr, optarg, arg_longlong);
break;
case arg_double:
result = myatod(aptr, optarg, arg_double);
break;
case arg_float:
result = myatod(aptr, optarg, arg_float);
break;
case arg_string:
result = (*((void**)aptr) = (void*)strdup(optarg));
break;
case arg_function:
result = ((argfn)aptr)(optarg);
break;
}
if(!result){
showhelp(optind, options);
}
}
*argc -= optind;
*argv += optind;
}
/**
* compare function for qsort
* first - sort by short options; second - sort arguments without sort opts (by long options)
*/
static int argsort(const void *a1, const void *a2){
const myoption *o1 = (myoption*)a1, *o2 = (myoption*)a2;
const char *l1 = o1->name, *l2 = o2->name;
int s1 = o1->val, s2 = o2->val;
int *f1 = o1->flag, *f2 = o2->flag;
// check if both options has short arg
if(f1 == NULL && f2 == NULL && s1 && s2){ // both have short arg
return (s1 - s2);
}else if((f1 != NULL || !s1) && (f2 != NULL || !s2)){ // both don't have short arg - sort by long
return strcmp(l1, l2);
}else{ // only one have short arg -- return it
if(f2 || !s2) return -1; // a1 have short - it is 'lesser'
else return 1;
}
}
/**
* Show help information based on myoption->help values
* @param oindex (i) - if non-negative, show only help by myoption[oindex].help
* @param options (i) - array of `myoption`
*
* @exit: run `exit(-1)` !!!
*/
void showhelp(int oindex, myoption *options){
int max_opt_len = 0; // max len of options substring - for right indentation
const int bufsz = 255;
char buf[bufsz+1];
myoption *opts = options;
assert(opts);
assert(opts[0].name); // check whether there is at least one options
if(oindex > -1){ // print only one message
opts = &options[oindex];
printf(" ");
if(!opts->flag && isalpha(opts->val)) printf("-%c, ", opts->val);
printf("--%s", opts->name);
if(opts->has_arg == 1) printf("=arg");
else if(opts->has_arg == 2) printf("[=arg]");
printf(" %s\n", _(opts->help));
exit(-1);
}
// header, by default is just "progname\n"
printf("\n");
if(strstr(helpstring, "%s")) // print progname
printf(helpstring, __progname);
else // only text
printf("%s", helpstring);
printf("\n");
// count max_opt_len
do{
int L = strlen(opts->name);
if(max_opt_len < L) max_opt_len = L;
}while((++opts)->name);
max_opt_len += 14; // format: '-S , --long[=arg]' - get addition 13 symbols
opts = options;
// count amount of options
int N; for(N = 0; opts->name; ++N, ++opts);
if(N == 0) exit(-2);
// Now print all help (sorted)
opts = options;
qsort(opts, N, sizeof(myoption), argsort);
do{
int p = sprintf(buf, " "); // a little indent
if(!opts->flag && opts->val) // .val is short argument
p += snprintf(buf+p, bufsz-p, "-%c, ", opts->val);
p += snprintf(buf+p, bufsz-p, "--%s", opts->name);
if(opts->has_arg == 1) // required argument
p += snprintf(buf+p, bufsz-p, "=arg");
else if(opts->has_arg == 2) // optional argument
p += snprintf(buf+p, bufsz-p, "[=arg]");
assert(p < max_opt_len); // there would be magic if p >= max_opt_len
printf("%-*s%s\n", max_opt_len+1, buf, _(opts->help)); // write options & at least 2 spaces after
++opts;
}while(--N);
printf("\n\n");
exit(-1);
}
/**
* get suboptions from parameter string
* @param str - parameter string
* @param opt - pointer to suboptions structure
* @return TRUE if all OK
*/
bool get_suboption(char *str, mysuboption *opt){
int findsubopt(char *par, mysuboption *so){
int idx = 0;
if(!par) return -1;
while(so[idx].name){
if(strcasecmp(par, so[idx].name) == 0) return idx;
++idx;
}
return -1; // badarg
}
bool opt_setarg(mysuboption *so, int idx, char *val){
mysuboption *soptr = &so[idx];
bool result = FALSE;
void *aptr = soptr->argptr;
switch(soptr->type){
default:
case arg_none:
if(soptr->argptr) *((int*)aptr) += 1; // increment value
result = TRUE;
break;
case arg_int:
result = myatoll(aptr, val, arg_int);
break;
case arg_longlong:
result = myatoll(aptr, val, arg_longlong);
break;
case arg_double:
result = myatod(aptr, val, arg_double);
break;
case arg_float:
result = myatod(aptr, val, arg_float);
break;
case arg_string:
result = (*((void**)aptr) = (void*)strdup(val));
break;
case arg_function:
result = ((argfn)aptr)(val);
break;
}
return result;
}
char *tok;
bool ret = FALSE;
char *tmpbuf;
tok = strtok_r(str, ":,", &tmpbuf);
do{
char *val = strchr(tok, '=');
int noarg = 0;
if(val == NULL){ // no args
val = "1";
noarg = 1;
}else{
*val++ = '\0';
if(!*val || *val == ':' || *val == ','){ // no argument - delimeter after =
val = "1"; noarg = 1;
}
}
int idx = findsubopt(tok, opt);
if(idx < 0){
/// "îÅÐÒÁ×ÉÌØÎÙÊ ÐÁÒÁÍÅÔÒ: %s"
WARNX(_("Wrong parameter: %s"), tok);
goto returning;
}
if(noarg && opt[idx].has_arg == NEED_ARG){
/// "%s: ÎÅÏÂÈÏÄÉÍ ÁÒÇÕÍÅÎÔ!"
WARNX(_("%s: argument needed!"), tok);
goto returning;
}
if(!opt_setarg(opt, idx, val)){
/// "îÅÐÒÁ×ÉÌØÎÙÊ ÁÒÇÕÍÅÎÔ \"%s\" ÐÁÒÁÍÅÔÒÁ \"%s\""
WARNX(_("Wrong argument \"%s\" of parameter \"%s\""), val, tok);
goto returning;
}
}while((tok = strtok_r(NULL, ":,", &tmpbuf)));
ret = TRUE;
returning:
return ret;
}

View File

@@ -1,124 +0,0 @@
/* geany_encoding=koi8-r
* parseargs.h - headers for parsing command line arguments
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#pragma once
#ifndef __PARSEARGS_H__
#define __PARSEARGS_H__
#include <stdbool.h>// bool
#include <stdlib.h>
#ifndef TRUE
#define TRUE true
#endif
#ifndef FALSE
#define FALSE false
#endif
// macro for argptr
#define APTR(x) ((void*)x)
// if argptr is a function:
typedef bool(*argfn)(void *arg);
/*
* type of getopt's argument
* WARNING!
* My function change value of flags by pointer, so if you want to use another type
* make a latter conversion, example:
* char charg;
* int iarg;
* myoption opts[] = {
* {"value", 1, NULL, 'v', arg_int, &iarg, "char val"}, ..., end_option};
* ..(parse args)..
* charg = (char) iarg;
*/
typedef enum {
arg_none = 0, // no arg
arg_int, // integer
arg_longlong, // long long
arg_double, // double
arg_float, // float
arg_string, // char *
arg_function // parse_args will run function `bool (*fn)(char *optarg, int N)`
} argtype;
/*
* Structure for getopt_long & help
* BE CAREFUL: .argptr is pointer to data or pointer to function,
* conversion depends on .type
*
* ATTENTION: string `help` prints through macro PRNT(), bu default it is gettext,
* but you can redefine it before `#include "parseargs.h"`
*
* if arg is string, then value wil be strdup'ed like that:
* char *str;
* myoption opts[] = {{"string", 1, NULL, 's', arg_string, &str, "string val"}, ..., end_option};
* *(opts[1].str) = strdup(optarg);
* in other cases argptr should be address of some variable (or pointer to allocated memory)
*
* NON-NULL argptr should be written inside macro APTR(argptr) or directly: (void*)argptr
*
* !!!LAST VALUE OF ARRAY SHOULD BE `end_option` or ZEROS !!!
*
*/
typedef enum{
NO_ARGS = 0, // first three are the same as in getopt_long
NEED_ARG = 1,
OPT_ARG = 2,
MULT_PAR
} hasarg;
typedef struct{
// these are from struct option:
const char *name; // long option's name
hasarg has_arg; // 0 - no args, 1 - nesessary arg, 2 - optionally arg, 4 - need arg & key can repeat (args are stored in null-terminated array)
int *flag; // NULL to return val, pointer to int - to set its value of val (function returns 0)
int val; // short opt name (if flag == NULL) or flag's value
// and these are mine:
argtype type; // type of argument
void *argptr; // pointer to variable to assign optarg value or function `bool (*fn)(char *optarg, int N)`
const char *help; // help string which would be shown in function `showhelp` or NULL
} myoption;
/*
* Suboptions structure, almost the same like myoption
* used in parse_subopts()
*/
typedef struct{
const char *name;
hasarg has_arg;
argtype type;
void *argptr;
} mysuboption;
// last string of array (all zeros)
#define end_option {0,0,0,0,0,0,0}
#define end_suboption {0,0,0,0}
extern const char *__progname;
void showhelp(int oindex, myoption *options);
void parseargs(int *argc, char ***argv, myoption *options);
void change_helpstring(char *s);
bool get_suboption(char *str, mysuboption *opt);
#endif // __PARSEARGS_H__

View File

@@ -0,0 +1,186 @@
/*
* This file is part of the Snippets project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <usefull_macros.h>
#include <weather_data.h>
#include "mount.h"
#include "server.h"
#include "stellarium.h"
// max age time of last status - 30s
#define STATUS_MAX_AGE (30.)
// commands
#define CMD_UNIXT "unixt"
#define CMD_STATUS "status"
#define CMD_RELAY "relay"
#define CMD_OPEN "open"
#define CMD_CLOSE "close"
#define CMD_STOP "stop"
#define CMD_HALF "half"
// main command socket
static sl_sock_t *cmd_socket = NULL;
// socket for stellarium purposes
static int stellarium_sockfd = -1;
// sleep time (us)
static unsigned int sleept = DEFAULT_SLEEP_T;
// running flag
static volatile bool isrunning = false;
unsigned int server_getsleept(){ return sleept; }
bool server_setsleept(unsigned int t){
if(t == 0 || t > MAX_SLEEP_T) return false;
sleept = t;
return true;
}
/////// handlers
// unixt - send to ALL clients
static sl_sock_hresult_e dtimeh(sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){
char buf[32];
snprintf(buf, 31, "%s=%.2f\n", item->key, sl_dtime());
LOGDBG("Client %d asks time: %s", c->fd, buf);
sl_sock_sendstrmessage(c, buf);
return RESULT_SILENCE;
}
// statust - text format status
static sl_sock_hresult_e status(sl_sock_t *c, sl_sock_hitem_t *item, _U_ const char *req){
char buf[BUFSIZ];
snprintf(buf, BUFSIZ-1, "%s=%s\n", item->key, mount_status_str());
LOGDBG("Client %d asks status: %s", c->fd, buf);
sl_sock_sendstrmessage(c, buf);
return RESULT_SILENCE;
}
// and all handlers collection
static sl_sock_hitem_t handlers[] = {
{dtimeh, CMD_UNIXT, "get server's UNIX time", NULL},
{status, CMD_STATUS, "get mount status", NULL},
{NULL, NULL, NULL, NULL}
};
// Too much clients handler
static void toomuch(int fd){
const char m[] = "Try later: too much clients connected\n";
send(fd, m, sizeof(m)-1, MSG_NOSIGNAL);
shutdown(fd, SHUT_WR);
DBG("shutdown, wait");
double t0 = sl_dtime();
uint8_t buf[8];
while(sl_dtime() - t0 < 11.){
if(sl_canread(fd)){
ssize_t got = read(fd, buf, 8);
DBG("Got=%zd", got);
if(got < 1) break;
}
}
DBG("Disc after %gs", sl_dtime() - t0);
LOGWARN("Client fd=%d tried to connect after MAX reached", fd);
}
// new connections handler: can check IP and reject client by returning FALSE
static int connected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd);
else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP);
return TRUE;
}
// disconnected handler
static void disconnected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("Disconnected client fd=%d", c->fd);
else LOGMSG("Disconnected client fd=%d, IP=%s", c->fd, c->IP);
}
bool server_check(server_sock_t *sockt){
sl_socktype_e type = (sockt->cmd_isunix) ? SOCKT_UNIX : SOCKT_NETLOCAL;
cmd_socket = sl_sock_run_server(type, sockt->cmdnode, BUFSIZ, handlers);
if(!cmd_socket){
LOGERR("Can't start main server");
return false;
}
LOGMSG("Main server started: %s", sockt->cmdnode);
sl_sock_changemaxclients(cmd_socket, sockt->maxclients);
sl_sock_maxclhandler(cmd_socket, toomuch);
sl_sock_connhandler(cmd_socket, connected);
sl_sock_dischandler(cmd_socket, disconnected);
stellarium_sockfd = sl_sock_open(SOCKT_NET, sockt->stellport, 1, 0);
if(stellarium_sockfd < 0){
LOGERR("Can't start stellarium socket");
sl_sock_delete(&cmd_socket);
return false;
}
if(listen(stellarium_sockfd, sockt->maxclients) == -1){
WARN("listen() for stellarium socket");
LOGERR("Can't run listen() for stellarium socket");
sl_sock_delete(&cmd_socket);
close(stellarium_sockfd);
return false;
}
DBG("stellarium_sockfd=%d", stellarium_sockfd);
LOGMSG("Prepared stellarium socket: %s", sockt->stellport);
DBG("Prepared stellarium socket: %s", sockt->stellport);
return true;
}
void server_run(){
if(isrunning){
LOGERR("server_run(): still running!");
return;
}
if(stellarium_sockfd == -1 || !cmd_socket){
LOGERR("server_run(): not initialized");
if(cmd_socket) sl_sock_delete(&cmd_socket);
ERRX("server_run(): not initialized");
}
if(!stellarium_start(stellarium_sockfd)){
LOGERR("Can't start stellarium server");
return;
}
if(!mount_connect()){
LOGERR("Can't connect to mount");
sl_sock_delete(&cmd_socket);
return;
}
isrunning = true;
DBG("While");
while(isrunning && cmd_socket && cmd_socket->connected){
usleep(sleept);
if(!cmd_socket->rthread){
LOGERR("Server handlers thread is dead");
break;
}
// finite state machine polling
//dome_poll(DOME_POLL, 0);
}
DBG("Stop command socket");
sl_sock_delete(&cmd_socket);
WARNX("Server is dead");
LOGERR("Server is dead");
isrunning = false;
}
void server_stop(){
isrunning = false;
}

View File

@@ -0,0 +1,39 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <usefull_macros.h>
// default and max available time for "usleep"
#define DEFAULT_SLEEP_T 100
#define MAX_SLEEP_T 10000
#define DEFAULT_MAXCLIENTS 5
typedef struct{
int cmd_isunix; // UNIX-socket instead of INET for `cmdnode`
const char *stellport; // port of stellarium server; could be "localhost:port" for local-only work
const char *cmdnode; // node of command socket
int maxclients; // maximal amount of clients connected
} server_sock_t;
bool server_check(server_sock_t *sockt);
void server_run();
void server_stop();
unsigned int server_getsleept();
bool server_setsleept(unsigned int t);

View File

@@ -1,208 +0,0 @@
/*
* This file is part of the StelD project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <arpa/inet.h>
#include <netdb.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "cmdlnopts.h"
#include "socket.h"
#include "usefull_macro.h"
// max time to wait answer from server
#define WAITANSTIME (1.0)
static int sockfd = -1; // server file descriptor
static pthread_t sock_thread;
static char buf[BUFSIZ]; // buffer for messages
static int Nread; // amount of bytes in buf
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void *getmessages(_U_ void *par);
/**
* @brief weatherserver_connect - connect to a weather server
* @return FALSE if failed
*/
int weatherserver_connect(){
if(sockfd > 0) return TRUE;
DBG("connect to %s:%d", GP->weathserver, GP->weathport);
char port[10];
snprintf(port, 10, "%d", GP->weathport);
struct addrinfo hints = {0}, *res, *p;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if(getaddrinfo(GP->weathserver, port, &hints, &res) != 0){
WARN("getaddrinfo()");
return FALSE;
}
// loop through all the results and connect to the first we can
for(p = res; p; p = p->ai_next){
if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
WARN("socket");
continue;
}
if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1){
WARN("connect()");
close(sockfd);
continue;
}
break; // if we get here, we have a successfull connection
}
if(!p){
WARNX("Can't connect to socket");
sockfd = -1;
return FALSE;
}
freeaddrinfo(res);
if(pthread_create(&sock_thread, NULL, getmessages, NULL)){
WARN("pthread_create()");
weatherserver_disconnect();
return FALSE;
}
DBG("connected, fd=%d", sockfd);
return TRUE;
}
void weatherserver_disconnect(){
if(sockfd > -1){
pthread_kill(sock_thread, 9);
pthread_join(sock_thread, NULL);
close(sockfd);
}
sockfd = -1;
}
/**
* @brief getparval - return value of parameter
* @param par (i) - parameter value
* @param ansbuf (i) - buffer with server answer
* @param val (o) - value of parameter
* @return TRUE if parameter found and set `val` to its value
*/
int getparval(const char *par, const char *ansbuf, double *val){
if(!par || !ansbuf) return FALSE;
int ret = FALSE;
char *b = strdup(ansbuf);
char *parval = NULL, *token = strtok(b, "\n");
int l = strlen(par);
if(!token) goto rtn;
while(token){
if(strncmp(token, par, l) == 0){ // found
//DBG("token: '%s'", token);
parval = strchr(token, '=');
if(!parval) goto rtn;
++parval; while(*parval == ' ' || *parval == '\t') ++parval;
//DBG("parval: '%s'", parval);
ret = TRUE;
break;
}
token = strtok(NULL, "\n");
}
if(parval && val){
*val = atof(parval);
//DBG("Set %s to %g", par, *val);
}
rtn:
FREE(b);
return ret;
}
/**
* wait for answer from socket
* @return FALSE in case of error or timeout, TRUE if socket is ready
*/
static int canread(){
if(sockfd < 0) return FALSE;
fd_set fds;
struct timeval timeout;
int rc;
// wait not more than 10ms
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
FD_ZERO(&fds);
FD_SET(sockfd, &fds);
do{
rc = select(sockfd+1, &fds, NULL, NULL, &timeout);
if(rc < 0){
if(errno != EINTR){
WARN("select()");
return FALSE;
}
continue;
}
break;
}while(1);
if(FD_ISSET(sockfd, &fds)) return TRUE;
return FALSE;
}
/**
* @brief getmessages - continuosly read data from server and fill buffer
*/
static void *getmessages(_U_ void *par){
write(sockfd, "get\n", 4);
while(sockfd > 0){
pthread_mutex_lock(&mutex);
if(Nread == 0){
double t0 = dtime();
while(dtime() - t0 < WAITANSTIME && Nread < BUFSIZ){
if(!canread()) continue;
int n = read(sockfd, buf+Nread, BUFSIZ-Nread);
if(n == 0) break;
if(n < 0){
close(sockfd);
sockfd = -1;
return NULL;
}
Nread += n;
}
if(Nread){
buf[Nread] = 0;
//DBG("got %d: %s", Nread, buf);
}
}
pthread_mutex_unlock(&mutex);
if(Nread == 0){
sleep(1);
}
}
return NULL;
}
/**
* @brief getweathbuffer - read whole buffer with data and set Nread to zero
* @return NULL if no data or buffer (allocated here)
*/
char *getweathbuffer(){
if(!weatherserver_connect()) return NULL; // not connected & can't connect
char *ret = NULL;
pthread_mutex_lock(&mutex);
if(Nread){
ret = strdup(buf);
Nread = 0;
}
pthread_mutex_unlock(&mutex);
return ret;
}

View File

@@ -0,0 +1,284 @@
/*
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// base functions for sending/receiving data in stellarium protocol
#include <stdint.h>
#include <usefull_macros.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "angles.h"
#include "mount.h"
#include "stellarium.h"
#define BUFLEN 256
// running flag
static volatile bool isrunning = false;
static pthread_t mainthread;
//read: 0x14 0x0 0x0 0x0 0x5b 0x5a 0x2e 0xc6 0x8c 0x23 0x5 0x0 0x23 0x9 0xe5 0xaf 0x23 0x2e 0x34 0xed
// command: goto 16h29 24.45 -26d25 55.62
/*
LITTLE-ENDIAN!!!
from client:
LENGTH (2 bytes, integer): length of the message
TYPE (2 bytes, integer): 0
TIME (8 bytes, integer): current time on the server computer in microseconds
since 1970.01.01 UT. Currently unused.
RA (4 bytes, unsigned integer): right ascension of the telescope (J2000)
a value of 0x100000000 = 0x0 means 24h=0h,
a value of 0x80000000 means 12h
DEC (4 bytes, signed integer): declination of the telescope (J2000)
a value of -0x40000000 means -90degrees,
a value of 0x0 means 0degrees,
a value of 0x40000000 means 90degrees
to client:
LENGTH (2 bytes, integer): length of the message
TYPE (2 bytes, integer): 0
TIME (8 bytes, integer): current time on the server computer in microseconds
since 1970.01.01 UT. Currently unused.
RA (4 bytes, unsigned integer): right ascension of the telescope (J2000)
a value of 0x100000000 = 0x0 means 24h=0h,
a value of 0x80000000 means 12h
DEC (4 bytes, signed integer): declination of the telescope (J2000)
a value of -0x40000000 means -90degrees,
a value of 0x0 means 0degrees,
a value of 0x40000000 means 90degrees
STATUS (4 bytes, signed integer): status of the telescope, currently unused.
status=0 means ok, status<0 means some error
*/
#define DEG2DEC(degr) ((int32_t)(degr / 90. * ((double)0x40000000)))
#define DEG2RA(degr) ((uint32_t)(degr / 180. * ((double)0x80000000)))
#define HRS2RA(degr) ((uint32_t)(degr / 12. * ((double)0x80000000)))
#define DEC2DEG(i32) (((double)i32)*90./((double)0x40000000))
#define RA2DEG(u32) (((double)u32)*180. /((double)0x80000000))
#define RA2HRS(u32) (((double)u32)*12. /((double)0x80000000))
typedef struct __attribute__((__packed__)){
uint16_t len;
uint16_t type;
uint64_t time;
uint32_t ra;
int32_t dec;
} indata;
typedef struct __attribute__((__packed__)){
uint16_t len;
uint16_t type;
uint64_t time;
uint32_t ra;
int32_t dec;
int32_t status;
} outdata;
/**
* @brief proc_data - process data received from Stellarium
* @param data - raw data
* @param len - its length
* @return true if all OK
*/
static bool proc_data(uint8_t *data, ssize_t len){
FNAME();
if(len != sizeof(indata)){
WARNX("Bad data size: got %zd instead of %zd!", len, sizeof(indata));
return false;
}
indata *dat = (indata*)data;
uint16_t L, T;
uint32_t ra;
int32_t dec;
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
L = le16toh(dat->len); T = le16toh(dat->type);
ra = le32toh(dat->ra);
dec = (int32_t)le32toh((uint32_t)dat->dec);
#else
L = dat->len; T = dat->type;
ra = dat->ra; dec = dat->dec;
#endif
DBG("got message with len %u & type %u", L, T);
if(L != len){
WARNX("Length of message != msg->len");
return false;
}
if(T){
WARNX("Wrong message type");
return false;
}
// convert RA/DEC to degrees
double tagRA = RA2DEG(ra), tagDec = DEC2DEG(dec);
DBG("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec);
LOGMSG("(stellarium) RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec);
// check RA/DEC
horizCrds_t hnow; // without refraction
polarCrds_t p2000, pnow;
p2000.ra = DEG2RAD(tagRA);
p2000.dec = DEG2RAD(tagDec);
// now J2000 obs Jnow
if(!get_ObsPlace(NULL, &p2000, &pnow, &hnow)){
WARNX("Can't convert coordinates to Jnow");
return false;
}
tagRA = RAD2DEG(pnow.ra - pnow.eo);
tagDec = RAD2DEG(pnow.dec);
return (mount_setInpRA(tagRA) && mount_setInpDec(tagDec));
}
/**
* Send data to user
* @param data - data to send
* @param dlen - data length
* @param sockfd - socket fd for sending data
* @return false if client disconnected
*/
float send_data(uint8_t *data, size_t dlen, int sockfd){
ssize_t sent = send(sockfd, data, dlen, MSG_NOSIGNAL);
if(sent != (ssize_t)dlen){
if(sent == -1 && errno != EINTR){
WARN("Disconnected?");
return false;
}
WARN("write()");
}
return true;
}
/**
* main socket service procedure
*/
static void *handle_socket(void *sockd){
FNAME();
if(!isrunning) return NULL;
outdata dout;
int sock = *(int*)sockd;
dout.len = htole16(sizeof(outdata));
dout.type = 0;
while(isrunning){
// get coordinates
double RA = 0., Decl = 0.;
if((dout.status = mount_getcoords(&RA, &Decl)) == MNT_S_ERROR){
WARNX("Error: can't get coordinates");
sleep(1);
continue;
}
//DBG("got : %g/%g", RA, Decl);
dout.ra = htole32(HRS2RA(RA));
dout.dec = (int32_t)htole32(DEG2DEC(Decl));
if(!send_data((uint8_t*)&dout, sizeof(outdata), sock)) break;
//DBG("sent ra = %g, dec = %g", RA2HRS(dout.ra), DEC2DEG(dout.dec));
if(!sl_canread(sock)) continue;
// fill incoming buffer
uint8_t buff[BUFLEN];
ssize_t rd = read(sock, buff, BUFLEN-1);
if(rd <= 0){ // error or disconnect
DBG("Nothing to read from fd %d (ret: %zd)", sock, rd);
WARNX("Client disconnected?");
break;
}
buff[rd] = 0;
DBG("read %zd (%s)", rd, buff);
if(!proc_data(buff, rd)) dout.status = -1;
else dout.status = 0;
}
close(sock);
return NULL;
}
// Main loop thread: wait connections over socket and create one thread for each
static void* start(void *F){
if(isrunning){
WARNX("already running");
return NULL;
}
if(!F){
WARNX("start(): No arg");
return NULL;
}
int sockfd = *((int*)F);
if(sockfd < 0){
WARNX("start(): sockfd=%d", sockfd);
return NULL;
}
DBG("Start main loop, sockfd=%d", sockfd);
isrunning = true;
// Main loop
while(isrunning){
socklen_t size = sizeof(struct sockaddr_in);
struct sockaddr_in myaddr;
int newsock;
newsock = accept(sockfd, (struct sockaddr*)&myaddr, &size);
if(newsock <= 0){
if(errno == EAGAIN) continue; // nothing available
WARN("accept()");
LOGWARN("Stellarium socket error in accept()");
sleep(1);
continue;
}
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
if(getpeername(newsock, (struct sockaddr*)&peer, &peer_len) == -1){
LOGWARN("Stellarium socket error in getpeername()");
WARN("getpeername()");
close(newsock);
continue;
}
int sockport = -1;
if(getsockname(newsock, (struct sockaddr*)&peer, &peer_len) == 0){
sockport = ntohs(peer.sin_port);
}
char *peerIP = inet_ntoa(peer.sin_addr);
LOGMSG("Got connection from %s @ %d", peerIP, sockport);
DBG("Peer's IP address is: %s (@port %d)\n", peerIP, sockport);
/*if(strcmp(peerIP, ACCEPT_IP) && strcmp(peerIP, "127.0.0.1")){
WARNX("Wrong IP");
close(newsock);
continue;
}*/
pthread_t rthrd;
if(pthread_create(&rthrd, NULL, handle_socket, (void*)&newsock)){
WARN("Can't create socket thread");
LOGERR("Stellarium socket: error creating listen thread");
}else{
DBG("Thread created, detouch");
pthread_detach(rthrd); // don't care about thread state
}
}
close(sockfd);
return NULL;
}
bool stellarium_start(int sockfd){
int fd = sockfd;
if(pthread_create(&mainthread, NULL, start, (void*)&fd)){
WARN("pthread_create()");
return false;
}
DBG("Stellarium server started");
return true;
}
void stellarium_stop(){
if(!isrunning) return;
isrunning = false;
pthread_join(mainthread, NULL);
}

View File

@@ -1,6 +1,6 @@
/*
* This file is part of the StelD project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
* This file is part of the mountdaemon_10micron project.
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,12 +17,6 @@
*/
#pragma once
#ifndef SOCKET_H__
#define SOCKET_H__
void weatherserver_disconnect();
int weatherserver_connect();
int getparval(const char *par, const char *ansbuf, double *val);
char *getweathbuffer();
#endif // SOCKET_H__
bool stellarium_start(int sockfd);
void stellarium_stop();

View File

@@ -1,670 +0,0 @@
/*
* geany_encoding=koi8-r
* telescope.c
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <arpa/inet.h> // ntoa
#include <netinet/in.h> // ntoa
#include <pthread.h>
#include <sys/socket.h> // getpeername
#include "libsofa.h"
#include "main.h" // global_quit
#include "telescope.h"
#include "usefull_macro.h"
// polling timeout for answer from mount
#ifndef T_POLLING_TMOUT
#define T_POLLING_TMOUT (0.5)
#endif
// wait for '\n' after last data read
#ifndef WAIT_TMOUT
#define WAIT_TMOUT (0.01)
#endif
#define BUFLEN 80
static char *hdname = NULL;
static double ptRAdeg, ptDECdeg; // target RA/DEC J2000
static int Target = 0; // target coordinates entered
static double r = 0., d = 0.; // RA/DEC from wrhdr
static int mountstatus = 0; // return of :Gstat#
static time_t tlast = 0; // last time coordinates were refreshed
static int pause_communication = 0; // ==1 to prevent writing to port outside of terminal thread
/**
* read strings from terminal (ending with '\n') with timeout
* @return NULL if nothing was read or pointer to static buffer
* THREAD UNSAFE!
*/
static char *read_string(){
static char buf[BUFLEN];
size_t r = 0, l;
int LL = BUFLEN - 1;
char *ptr = NULL;
static char *optr = NULL;
if(optr && *optr){
ptr = optr;
optr = strchr(optr, '\n');
if(optr) ++optr;
return ptr;
}
ptr = buf;
double d0 = dtime();
do{
if((l = read_tty(ptr, LL))){
r += l; LL -= l; ptr += l;
if(ptr[-1] == '\n') break;
d0 = dtime();
}
}while(dtime() - d0 < WAIT_TMOUT && LL);
if(r){
buf[r] = 0;
optr = strchr(buf, '\n');
if(optr) ++optr;
return buf;
}
return NULL;
}
/**
* write command, thread-safe
* @param cmd (i) - command to write
* @param buff (o) - buffer (WHICH SIZE = BUFLEN!!!) to which write data (or NULL if don't need)
* @return answer or NULL if error occured (or no answer)
* WARNING!!! data returned is allocated by strdup! You MUST free it when don't need
*/
static char *write_cmd(const char *cmd, char *buff){
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
//DBG("Write %s", cmd);
if(write_tty(cmd, strlen(cmd))) return NULL;
double t0 = dtime();
char *ans;
while(dtime() - t0 < T_POLLING_TMOUT){ // read answer
if((ans = read_string())){ // parse new data
//DBG("got answer: %s", ans);
pthread_mutex_unlock(&mutex);
if(!buff) return NULL;
strncpy(buff, ans, BUFLEN-1);
return buff;
}
}
pthread_mutex_unlock(&mutex);
return NULL;
}
// write to telescope mount corrections: datetime, pressure and temperature
// @return 1 if time and weather was corrected
static int makecorr(){
if(pause_communication) return 0;
int ret = 1;
// write current date&time
char buf[64], ibuff[BUFLEN], *ans;
DBG("curtime: %s", write_cmd(":GUDT#", ibuff));
ans = write_cmd(":Gstat#", ibuff);
if(ans){
mountstatus = atoi(ans);
// if system is in tracking or unknown state - don't update data!
if(mountstatus == TEL_SLEWING || mountstatus == TEL_TRACKING) return 0;
}
/*
* there's no GPS on this mount and there's no need for it!
write_cmd(":gT#", NULL); // correct time by GPS
ans = write_cmd(":gtg#", ibuff);
*/
WARNX("Refresh datetime");
time_t t = time(NULL);
struct tm *stm = localtime(&t);
struct timeval tv;
gettimeofday(&tv,NULL);
snprintf(buf, 64, ":SLDT%04d-%02d-%02d,%02d:%02d:%02d.%02ld#", 1900+stm->tm_year, stm->tm_mon+1, stm->tm_mday,
stm->tm_hour, stm->tm_min, stm->tm_sec, tv.tv_usec/10000);
DBG("write: %s", buf);
ans = write_cmd(buf, ibuff);
if(!ans || *ans != '1'){
WARNX("Can't write current date/time");
putlog("Can't set system time");
ret = 0;
}else{
putlog("Set system time by command %s", buf);
}
DBG("curtime: %s", write_cmd(":GUDT#", ibuff));
localWeather *w = getWeath();
if(!w){
ret = 0;
putlog("Can't determine weather data");
}else{ // set refraction model data
snprintf(buf, 64, ":SRPRS%.1f#", w->pres*1013./760.);
ans = write_cmd(buf, ibuff);
if(!ans || *ans != '1'){
ret = 0;
putlog("Can't set pressure data of refraction model");
}else putlog("Correct pressure to %gmmHg", w->pres);
snprintf(buf, 64, ":SRTMP%.1f#", w->tc);
ans = write_cmd(buf, ibuff);
if(!ans || *ans != '1'){
ret = 0;
putlog("Can't set temperature data of refraction model");
}else putlog("Correct temperature to %g", w->tc);
}
sprintf(buf, ":SREF1#"); // turn on refraction correction
write_cmd(buf, ibuff);
sprintf(buf, ":Sdat1#"); // turn on dual-axis tracking
write_cmd(buf, ibuff);
return ret;
}
int chkconn(){
char tmpbuf[4096];
read_tty(tmpbuf, 4096); // clear rbuf
write_cmd("#", NULL); // clear cmd buffer
if(!write_cmd(":SB0#", tmpbuf)) return 0; // 115200
//if(!write_cmd(":GR#")) return 0;
return 1;
}
/**
* connect telescope device
* @param dev (i) - device name to connect
* @param hdrname (i) - output file with FITS-headers
* @return 1 if all OK
*/
int connect_telescope(char *dev, char *hdrname){
if(!dev) return 0;
tcflag_t spds[] = {B9600, B115200, B57600, B38400, B19200, B4800, B2400, B1200, 0}, *speeds = spds;
DBG("Connection to device %s...", dev);
while(*speeds){
DBG("Try %d", *speeds);
tty_init(dev, *speeds);
if(chkconn()) break;
++speeds;
}
if(!*speeds) return 0;
if(*speeds != B115200){
restore_tty();
tty_init(dev, B115200);
if(!chkconn()) return 0;
}
write_cmd("#", NULL); // clear previous buffer
write_cmd(":STOP#", NULL); // stop tracking after poweron
write_cmd(":U2#", NULL); // set high precision
write_cmd(":So10#", NULL); // set minimum altitude to 10 degrees
putlog("Connected to %s@115200, will write FITS-header into %s", dev, hdrname);
FREE(hdname);
hdname = strdup(hdrname);
DBG("connected");
Target = 0;
getWeath(); getPlace(); getDUT(); // determine starting values
//write_cmd(":gT#", NULL); // correct time by GPS
return 1;
}
/*
:MS# - move to target, return: 0 if all OK or text with error
:SrHH:MM:SS.SS# - set target RA (return 1 if all OK)
:SdsDD*MM:SS.S# - set target DECL (return 1 if all OK)
*/
/**
* send coordinates to telescope
* @param ra - right ascention (hours), Jnow without refraction
* @param dec - declination (degrees), Jnow without refraction
* @return 1 if all OK
*/
int point_telescope(double ra, double dec){
if(pause_communication){
putlog("Can't point telescope in paused mode");
return 0;
}
DBG("try to send ra=%g, decl=%g", ra, dec);
ptRAdeg = ra * 15.;
ptDECdeg = dec;
Target = 0;
int err = 0;
char buf[80], ibuff[BUFLEN];
char sign = '+';
if(dec < 0){
sign = '-';
dec = -dec;
}
int h = (int)ra;
ra -= h; ra *= 60.;
int m = (int)ra;
ra -= m; ra *= 60.;
int d = (int) dec;
dec -= d; dec *= 60.;
int dm = (int)dec;
dec -= dm; dec *= 60.;
snprintf(buf, 80, ":Sr%d:%d:%.2f#", h,m,ra);
char *ans = write_cmd(buf, ibuff);
if(!ans || *ans != '1'){
err = 1;
goto ret;
}
snprintf(buf, 80, ":Sd%c%d:%d:%.1f#", sign,d,dm,dec);
ans = write_cmd(buf, ibuff);
if(!ans || *ans != '1'){
err = 2;
goto ret;
}
DBG("Move");
ans = write_cmd(":MS#", ibuff);
if(!ans || *ans != '0'){
putlog("move error, answer: %s", ans);
err = 3;
goto ret;
}
ret:
if(err){
putlog("error sending coordinates (err = %d: RA/DEC/MOVE)!", err);
return 0;
}else{
Target = 1;
putlog("Send ra=%g degr, dec=%g degr", ptRAdeg, ptDECdeg);
}
return 1;
}
/**
* convert str into RA/DEC coordinate
* @param str (i) - string with angle
* @param val (o) - output angle value
* @return 1 if all OK
*/
static int str2coord(char *str, double *val){
if(!str || !val) return 0;
int d, m;
float s;
int sign = 1;
if(*str == '+') ++str;
else if(*str == '-'){
sign = -1;
++str;
}
int n = sscanf(str, "%d:%d:%f#", &d, &m, &s);
if(n != 3) return 0;
double ang = d + ((double)m)/60. + s/3600.;
if(sign == -1) *val = -ang;
else *val = ang;
return 1;
}
/**
* @brief printhdr - write FITS record into output file
* @param fd - fd to write
* @param key - key
* @param val - value
* @param cmnt - comment
* @return 0 if all OK
*/
static int printhdr(int fd, const char *key, const char *val, const char *cmnt){
char tmp[81];
char tk[9];
if(strlen(key) > 8){
snprintf(tk, 9, "%s", key);
key = tk;
}
if(cmnt){
snprintf(tmp, 81, "%-8s= %-21s / %s", key, val, cmnt);
}else{
snprintf(tmp, 81, "%-8s= %s", key, val);
}
size_t l = strlen(tmp);
tmp[l] = '\n';
++l;
if(write(fd, tmp, l) != (ssize_t)l){
WARN("write()");
return 1;
}
return 0;
}
/**
* get coordinates
* @param ra (o) - right ascension (hours)
* @param decl (o) - declination (degrees)
* @return telescope status or -1 if coordinates are too old
*/
int get_telescope_coords(double *ra, double *decl){
if(!tlast) tlast = time(NULL);
if(time(NULL) - tlast > COORDS_TOO_OLD_TIME) return -1; // coordinates are too old
if(ra) *ra = r;
if(decl) *decl = d;
return mountstatus;
}
void stop_telescope(){ // work even in paused mode if moving!
Target = 0;
if(pause_communication){
if(mountstatus == TEL_PARKED || mountstatus == TEL_STOPPED || mountstatus == TEL_INHIBITED
|| mountstatus == TEL_OUTLIMIT) return;
}
write_cmd(":RT9#", NULL); // stop tracking
write_cmd(":AL#", NULL); // stop tracking
write_cmd(":STOP#", NULL); // halt moving
}
// site characteristics
static char
*elevation = NULL,
*longitude = NULL,
*latitude = NULL;
// make duplicate of buf without trailing `#`
// if astr == 1, surround content with ''
static char *dups(const char *buf, int astr){
if(!buf) return NULL;
char *newbuf = malloc(strlen(buf)+5), *bptr = newbuf+1;
if(!newbuf) return NULL;
strcpy(bptr, buf);
char *sharp = strrchr(bptr, '#');
if(sharp) *sharp = 0;
if(astr){
bptr = newbuf;
*bptr = '\'';
int l = strlen(newbuf);
newbuf[l] = '\'';
newbuf[l+1] = 0;
}
char *d = strdup(bptr);
free(newbuf);
return d;
}
static void getplace(){
char *ans, ibuff[BUFLEN];
if(!elevation){
ans = write_cmd(":Gev#", ibuff);
elevation = dups(ans, 0);
}
if(!longitude){
ans = write_cmd(":Gg#", ibuff);
longitude = dups(ans, 1);
}
if(!latitude){
ans = write_cmd(":Gt#", ibuff);
latitude = dups(ans, 1);
}
}
static const char *statuses[12] = {
[TEL_TRACKING] = "'Tracking'",
[TEL_STOPHOM] = "'Stopped or homing'",
[TEL_PARKING] = "'Slewing to park'",
[TEL_UNPARKING] = "'Unparking'",
[TEL_HOMING] = "'Slewing to home'",
[TEL_PARKED] = "'Parked'",
[TEL_SLEWING] = "'Slewing or going to stop'",
[TEL_STOPPED] = "'Stopped'",
[TEL_INHIBITED] = "'Motors inhibited, T too low'",
[TEL_OUTLIMIT] = "'Outside tracking limit'",
[TEL_FOLSAT]= "'Following satellite'",
[TEL_DATINCOSIST]= "'Data inconsistency'"
};
/**
* @brief strstatus - return string explanation of mount status
* @param status - integer status code
* @return statically allocated string with explanation
*/
static const char* strstatus(int status){
if(status < 0) return "'Signal lost'";
if(status < TEL_MAXSTATUS) return statuses[status];
if(status == 99) return "'Error'";
return "'Unknown status'";
}
/**
* @brief wrhdr - try to write into header file
*/
void wrhdr(){
static time_t commWasPaused = 0;
if(pause_communication){ // don't allow pauses more for 15 minutes!
if(commWasPaused == 0){
commWasPaused = time(NULL);
return;
}else{
if(time(NULL) - commWasPaused > 15*60){
putlog("Clear communication pause after 15 minutes");
pause_communication = 0;
}else return;
}
}
static int failcounter = 0;
static time_t lastcorr = 0; // last time of corrections made
if(time(NULL) - lastcorr > CORRECTIONS_TIMEDIFF){ // make correction once per hour
if(makecorr()) lastcorr = time(NULL);
else lastcorr += 30; // failed -> check 30s later
}
char *ans = NULL, *jd = NULL, *lst = NULL, *date = NULL, *pS = NULL;
char ibuff[BUFLEN];
// get coordinates for writing to file & sending to stellarium client
ans = write_cmd(":GR#", ibuff);
if(!str2coord(ans, &r)){
if(++failcounter == 10){
putlog("Lost connection with mount");
DBG("Can't get RA!");
signals(9);
}
DBG("Failed");
return;
}
ans = write_cmd(":GD#", ibuff);
if(!str2coord(ans, &d)){
if(++failcounter == 10){
putlog("Lost connection with mount");
DBG("Can't get DEC!");
signals(9);
}
DBG("Failed");
return;
}
almDut *dut = getDUT();
localWeather *weather = getWeath();
double LST = 0; // local sidereal time IN RADIANS!
placeData *place = getPlace();
if(get_LST(NULL, dut->DUT1, place->slong, &LST)){
DBG("Can't calculate coordinates, get from mount");
ans = write_cmd(":GS#", ibuff);
lst = dups(ans, 1);
if(!str2coord(ans, &LST)){
if(++failcounter == 10){
putlog("Lost connection with mount");
DBG("Can't get LST!");
signals(9);
}
DBG("Failed");
return;
}
LST *= 15.*ERFA_DD2R; // convert hours to radians
}else{
lst = MALLOC(char, 32);
r2sHMS(LST, lst, 32);
}
sMJD mjd;
if(get_MJDt(NULL, &mjd)){
ans = write_cmd(":GJD1#", ibuff);
jd = dups(ans, 0);
}else{
jd = MALLOC(char, 32);
snprintf(jd, 32, "%.10f", mjd.MJD);
}
polarCrds pNow = {.ra = r*15.*ERFA_DD2R, .dec = d*ERFA_DD2R}; // coordinates now
horizCrds hNow;
eq2hor(&pNow, &hNow, LST);
failcounter = 0;
tlast = time(NULL);
// check it here, not in the beginning of function - to check connection with mount first
if(!hdname){
DBG("hdname not given!");
return;
}
if(!elevation || !longitude || !latitude) getplace();
ans = write_cmd(":GUDT#", ibuff);
if(ans){
char *comma = strchr(ans, ',');
if(comma){
*comma = 'T';
date = dups(ans, 1);
}
}
ans = write_cmd(":pS#", ibuff); pS = dups(ans, 1);
ans = write_cmd(":Gstat#", ibuff);
if(ans){
mountstatus = atoi(ans);
//DBG("Status: %d", mountstatus);
}
int l = strlen(hdname) + 7;
char *aname = MALLOC(char, l);
snprintf(aname, l, "%sXXXXXX", hdname);
int fd = mkstemp(aname);
if(fd < 0){
WARN("Can't write header file: mkstemp()");
FREE(aname);
FREE(jd); FREE(lst); FREE(date); FREE(pS);
return;
}
fchmod(fd, 0644);
char val[22];
#define WRHDR(k, v, c) do{if(printhdr(fd, k, v, c)){goto returning;}}while(0)
WRHDR("TIMESYS", "'UTC'", "Time system");
WRHDR("ORIGIN", "'SAO RAS'", "Organization responsible for the data");
WRHDR("TELESCOP", TELESCOPE_NAME, "Telescope name");
snprintf(val, 22, "%.10f", dut->px);
WRHDR("POLARX", val, "IERS pole X coordinate, arcsec");
snprintf(val, 22, "%.10f", dut->py);
WRHDR("POLARY", val, "IERS pole Y coordinate, arcsec");
snprintf(val, 22, "%.10f", dut->py);
WRHDR("DUT1", val, "IERS `UT1-UTC`, sec");
if(Target){ // target coordinates entered - store them @header
snprintf(val, 22, "%.10f", ptRAdeg);
WRHDR("TAGRA", val, "Target RA (J2000), degrees");
snprintf(val, 22, "%.10f", ptDECdeg);
WRHDR("TAGDEC", val, "Target DEC (J2000), degrees");
}
snprintf(val, 22, "%.10f", r*15.); // convert RA to degrees
WRHDR("RA", val, "Telescope right ascension, current epoch, deg");
snprintf(val, 22, "%.10f", d);
WRHDR("DEC", val, "Telescope declination, current epoch, deg");
snprintf(val, 22, "%.10f", hNow.az * ERFA_DR2D);
WRHDR("AZ", val, "Telescope azimuth, current epoch, deg");
snprintf(val, 22, "%.10f", hNow.zd * ERFA_DR2D);
WRHDR("ZD", val, "Telescope zenith distance, current epoch, deg");
WRHDR("TELSTAT", strstatus(mountstatus), "Telescope mount status");
if(!get_MJDt(NULL, &mjd)){
snprintf(val, 22, "%.10f", 2000.+(mjd.MJD-MJD2000)/365.25); // calculate EPOCH/EQUINOX
WRHDR("EQUINOX", val, "Equinox of celestial coordinate system");
if(!jd){
snprintf(val, 22, "%.10f", mjd.MJD);
WRHDR("MJD-END", val, "Modified julian date of observations end");
}
}
if(jd){
WRHDR("MJD-END", jd, "Modified julian date of observations end");
}
if(pS) WRHDR("PIERSIDE", pS, "Pier side of telescope mount");
if(elevation) WRHDR("ELEVAT", elevation, "Elevation of site over the sea level");
if(longitude) WRHDR("LONGITUD", longitude, "Geo longitude of site (east negative)");
if(latitude) WRHDR("LATITUDE", latitude, "Geo latitude of site (south negative)");
if(lst) WRHDR("LSTEND", lst, "Local sidereal time of observations end");
if(date) WRHDR("DATE-END", date, "Date (UTC) of observations end");
if(weather){
snprintf(val, 22, "%.1f", weather->relhum);
WRHDR("HUMIDITY", val, "Relative humidity, %%");
snprintf(val, 22, "%.1f", weather->pres);
WRHDR("PRESSURE", val, "Atmospheric pressure, mmHg");
snprintf(val, 22, "%.1f", weather->tc);
WRHDR("EXTTEMP", val, "External temperature, degrC");
snprintf(val, 22, "%.0f", weather->rain);
WRHDR("RAIN", val, "Rain conditions");
snprintf(val, 22, "%.1f", weather->clouds);
WRHDR("SKYQUAL", val, "Sky quality (0 - wery bad, >2500 - good)");
snprintf(val, 22, "%.1f", weather->wind);
WRHDR("WINDSPD", val, "Wind speed (m/s)");
snprintf(val, 22, "%.0f", weather->time);
WRHDR("WEATTIME", val, "Unix time of weather measurements");
}
// WRHDR("", , "");
#undef WRHDR
returning:
FREE(jd); FREE(lst); FREE(date); FREE(pS);
close(fd);
rename(aname, hdname);
FREE(aname);
}
// terminal thread: allows to work with terminal through socket
void *term_thread(void *sockd){
int sock = *(int*)sockd;
char buff[BUFLEN+1], ibuff[BUFLEN+2];
// get client IP from socket fd - for logging
struct sockaddr_in peer;
socklen_t peer_len = sizeof(peer);
char *peerIP = NULL;
if(getpeername(sock, (struct sockaddr*)&peer, &peer_len) == 0){
peerIP = inet_ntoa(peer.sin_addr);
}
while(!global_quit){ // blocking read
ssize_t rd = read(sock, buff, BUFLEN);
if(rd <= 0){ // error or disconnect
DBG("Nothing to read from fd %d (ret: %zd)", sock, rd);
break;
}
buff[rd] = 0;
char *ch = strchr(buff, '\n');
if(ch) *ch = 0;
if(!buff[0]) continue; // empty string
DBG("%s COMMAND: %s", peerIP, buff);
if(strcasecmp(buff, "pause") == 0){
DBG("PAUSED");
putlog("Port writing outside terminal thread is paused");
pause_communication = 1;
continue;
}
if(strcasecmp(buff, "continue") == 0){
DBG("CONTINUED");
putlog("Port writing outside terminal thread is restored by user");
pause_communication = 0;
continue;
}
char *ans = write_cmd(buff, ibuff);
putlog("%s COMMAND %s ANSWER %s", peerIP, buff, ibuff);
DBG("ANSWER: %s", ibuff);
if(ans){
ssize_t l = (ssize_t)strlen(ans);
if(l++){
ans[l-1] = '\n';
ans[l] = 0;
if(l != write(sock, ans, l)){
WARN("term_thread, write()");
break;
}
}
}
}
close(sock);
return NULL;
}

View File

@@ -1,58 +0,0 @@
/*
* geany_encoding=koi8-r
* telescope.h
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#pragma once
#ifndef __TELESCOPE_H__
#define __TELESCOPE_H__
// max time after last coordinates reading
#define COORDS_TOO_OLD_TIME (5)
// make datetime/pressure/temperature corrections each CORRECTIONS_TIMEDIFF seconds
#define CORRECTIONS_TIMEDIFF (3600)
#define TELESCOPE_NAME "'Astrosib-500 (2)'"
// telescope statuses
typedef enum{
TEL_TRACKING = 0,
TEL_STOPHOM = 1,
TEL_PARKING = 2,
TEL_UNPARKING = 3,
TEL_HOMING = 4,
TEL_PARKED = 5,
TEL_SLEWING = 6,
TEL_STOPPED = 7,
TEL_INHIBITED = 8,
TEL_OUTLIMIT = 9,
TEL_FOLSAT = 10,
TEL_DATINCOSIST = 11,
TEL_MAXSTATUS = 12 // number of statuses
} tel_status;
int connect_telescope(char *dev, char *hdrname);
int point_telescope(double ra, double decl);
int get_telescope_coords(double *ra, double *decl);
void stop_telescope();
void wrhdr();
void *term_thread(void *sockd);
#endif // __TELESCOPE_H__

View File

@@ -1,445 +0,0 @@
/* geany_encoding=koi8-r
* usefull_macros.h - a set of usefull functions: memory, color etc
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "usefull_macro.h"
#include <linux/limits.h> // PATH_MAX
#include <pthread.h>
/**
* function for different purposes that need to know time intervals
* @return double value: time in seconds
*/
double dtime(){
double t;
struct timeval tv;
gettimeofday(&tv, NULL);
t = tv.tv_sec + ((double)tv.tv_usec)/1e6;
return t;
}
/******************************************************************************\
* Coloured terminal
\******************************************************************************/
int globErr = 0; // errno for WARN/ERR
// pointers to coloured output printf
int (*red)(const char *fmt, ...);
int (*green)(const char *fmt, ...);
int (*_WARN)(const char *fmt, ...);
/*
* format red / green messages
* name: r_pr_, g_pr_
* @param fmt ... - printf-like format
* @return number of printed symbols
*/
int r_pr_(const char *fmt, ...){
va_list ar; int i;
printf(RED);
va_start(ar, fmt);
i = vprintf(fmt, ar);
va_end(ar);
printf(OLDCOLOR);
return i;
}
int g_pr_(const char *fmt, ...){
va_list ar; int i;
printf(GREEN);
va_start(ar, fmt);
i = vprintf(fmt, ar);
va_end(ar);
printf(OLDCOLOR);
return i;
}
/*
* print red error/warning messages (if output is a tty)
* @param fmt ... - printf-like format
* @return number of printed symbols
*/
int r_WARN(const char *fmt, ...){
va_list ar; int i = 1;
fprintf(stderr, RED);
va_start(ar, fmt);
if(globErr){
errno = globErr;
vwarn(fmt, ar);
errno = 0;
}else
i = vfprintf(stderr, fmt, ar);
va_end(ar);
i++;
fprintf(stderr, OLDCOLOR "\n");
return i;
}
static const char stars[] = "****************************************";
/*
* notty variants of coloured printf
* name: s_WARN, r_pr_notty
* @param fmt ... - printf-like format
* @return number of printed symbols
*/
int s_WARN(const char *fmt, ...){
va_list ar; int i;
i = fprintf(stderr, "\n%s\n", stars);
va_start(ar, fmt);
if(globErr){
errno = globErr;
vwarn(fmt, ar);
errno = 0;
}else
i = +vfprintf(stderr, fmt, ar);
va_end(ar);
i += fprintf(stderr, "\n%s\n", stars);
i += fprintf(stderr, "\n");
return i;
}
int r_pr_notty(const char *fmt, ...){
va_list ar; int i;
i = printf("\n%s\n", stars);
va_start(ar, fmt);
i += vprintf(fmt, ar);
va_end(ar);
i += printf("\n%s\n", stars);
return i;
}
/**
* Run this function in the beginning of main() to setup locale & coloured output
*/
void initial_setup(){
// setup coloured output
if(isatty(STDOUT_FILENO)){ // make color output in tty
red = r_pr_; green = g_pr_;
}else{ // no colors in case of pipe
red = r_pr_notty; green = printf;
}
if(isatty(STDERR_FILENO)) _WARN = r_WARN;
else _WARN = s_WARN;
// Setup locale
setlocale(LC_ALL, "");
setlocale(LC_NUMERIC, "C");
#if defined GETTEXT_PACKAGE && defined LOCALEDIR
bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
textdomain(GETTEXT_PACKAGE);
#endif
}
/******************************************************************************\
* Memory
\******************************************************************************/
/*
* safe memory allocation for macro ALLOC
* @param N - number of elements to allocate
* @param S - size of single element (typically sizeof)
* @return pointer to allocated memory area
*/
void *my_alloc(size_t N, size_t S){
void *p = calloc(N, S);
if(!p) ERR("malloc");
//assert(p);
return p;
}
/**
* Mmap file to a memory area
*
* @param filename (i) - name of file to mmap
* @return stuct with mmap'ed file or die
*/
mmapbuf *My_mmap(char *filename){
int fd;
char *ptr;
size_t Mlen;
struct stat statbuf;
/// "îÅ ÚÁÄÁÎÏ ÉÍÑ ÆÁÊÌÁ!"
if(!filename){
WARNX(_("No filename given!"));
return NULL;
}
if((fd = open(filename, O_RDONLY)) < 0){
/// "îÅ ÍÏÇÕ ÏÔËÒÙÔØ %s ÄÌÑ ÞÔÅÎÉÑ"
WARN(_("Can't open %s for reading"), filename);
return NULL;
}
if(fstat (fd, &statbuf) < 0){
/// "îÅ ÍÏÇÕ ×ÙÐÏÌÎÉÔØ stat %s"
WARN(_("Can't stat %s"), filename);
close(fd);
return NULL;
}
Mlen = statbuf.st_size;
if((ptr = mmap (0, Mlen, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){
/// "ïÛÉÂËÁ mmap"
WARN(_("Mmap error for input"));
close(fd);
return NULL;
}
/// "îÅ ÍÏÇÕ ÚÁËÒÙÔØ mmap'ÎÕÔÙÊ ÆÁÊÌ"
if(close(fd)) WARN(_("Can't close mmap'ed file"));
mmapbuf *ret = MALLOC(mmapbuf, 1);
ret->data = ptr;
ret->len = Mlen;
return ret;
}
void My_munmap(mmapbuf *b){
if(munmap(b->data, b->len)){
/// "îÅ ÍÏÇÕ munmap"
ERR(_("Can't munmap"));
}
FREE(b);
}
/******************************************************************************\
* Terminal in no-echo mode
\******************************************************************************/
static struct termios oldt, newt; // terminal flags
static int console_changed = 0;
// run on exit:
void restore_console(){
if(console_changed)
tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // return terminal to previous state
console_changed = 0;
}
// initial setup:
void setup_con(){
if(console_changed) return;
tcgetattr(STDIN_FILENO, &oldt);
newt = oldt;
newt.c_lflag &= ~(ICANON | ECHO);
if(tcsetattr(STDIN_FILENO, TCSANOW, &newt) < 0){
/// "îÅ ÍÏÇÕ ÎÁÓÔÒÏÉÔØ ËÏÎÓÏÌØ"
WARN(_("Can't setup console"));
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
signals(0); //quit?
}
console_changed = 1;
}
/**
* Read character from console without echo
* @return char readed
*/
int read_console(){
int rb;
struct timeval tv;
int retval;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(STDIN_FILENO, &rfds);
tv.tv_sec = 0; tv.tv_usec = 10000;
retval = select(1, &rfds, NULL, NULL, &tv);
if(!retval) rb = 0;
else {
if(FD_ISSET(STDIN_FILENO, &rfds)) rb = getchar();
else rb = 0;
}
return rb;
}
/**
* getchar() without echo
* wait until at least one character pressed
* @return character readed
*/
int mygetchar(){ // getchar() without need of pressing ENTER
int ret;
do ret = read_console();
while(ret == 0);
return ret;
}
/******************************************************************************\
* TTY with select()
\******************************************************************************/
static struct termios oldtty, tty; // TTY flags
static int comfd = -1; // TTY fd
// run on exit:
void restore_tty(){
if(comfd == -1) return;
ioctl(comfd, TCSANOW, &oldtty ); // return TTY to previous state
close(comfd);
comfd = -1;
}
#ifndef BAUD_RATE
#define BAUD_RATE B9600
#endif
// init: (speed = B9600 etc)
void tty_init(char *comdev, tcflag_t speed){
if(comfd == -1){ // not opened
if(!comdev){
WARNX("comdev == NULL");
signals(11);
}
DBG("Open port...");
do{
comfd = open(comdev,O_RDWR|O_NOCTTY|O_NONBLOCK);
}while (comfd == -1 && errno == EINTR);
if(comfd < 0){
WARN(_("Can't open port %s"),comdev);
signals(2);
}
/* DBG("OK\nGet current settings...");
if(ioctl(comfd, TCGETA, &oldtty) < 0){ // Get settings
/// "îÅ ÍÏÇÕ ÐÏÌÕÞÉÔØ ÎÁÓÔÒÏÊËÉ"
WARN(_("Can't get settings"));
signals(2);
}*/
DBG("Make exclusive");
// make exclusive open
if(ioctl(comfd, TIOCEXCL)){
WARN(_("Can't do exclusive open"));
close(comfd);
signals(2);
}
}
tty = oldtty;
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
tty.c_oflag = 0;
tty.c_cflag = speed|CS8|CREAD|CLOCAL; // 9.6k, 8N1, RW, ignore line ctrl
tty.c_cc[VMIN] = 0; // non-canonical mode
tty.c_cc[VTIME] = 5;
if(ioctl(comfd, TCSETA, &tty) < 0){
/// "îÅ ÍÏÇÕ ÕÓÔÁÎÏ×ÉÔØ ÎÁÓÔÒÏÊËÉ"
WARN(_("Can't set settings"));
signals(0);
}
DBG("OK");
}
/**
* Read data from TTY
* @param buff (o) - buffer for data read
* @param length - buffer len
* @return amount of bytes read
*/
size_t read_tty(char *buff, size_t length){
if(comfd < 0) return 0;
ssize_t L = 0, l;
char *ptr = buff;
fd_set rfds;
struct timeval tv;
int retval;
do{
l = 0;
FD_ZERO(&rfds);
FD_SET(comfd, &rfds);
// wait for 10ms
tv.tv_sec = 0; tv.tv_usec = 10000;
retval = select(comfd + 1, &rfds, NULL, NULL, &tv);
if (!retval) break;
if(FD_ISSET(comfd, &rfds)){
if((l = read(comfd, ptr, length)) < 1){
return 0;
}
ptr += l; L += l;
length -= l;
}
}while(l);
return (size_t)L;
}
int write_tty(const char *buff, size_t length){
ssize_t L = write(comfd, buff, length);
if((size_t)L != length){
/// "ïÛÉÂËÁ ÚÁÐÉÓÉ!"
WARN("Write error");
return 1;
}
return 0;
}
/**
* Safely convert data from string to double
*
* @param num (o) - double number read from string
* @param str (i) - input string
* @return 1 if success, 0 if fails
*/
int str2double(double *num, const char *str){
double res;
char *endptr;
if(!str) return 0;
res = strtod(str, &endptr);
if(endptr == str || *str == '\0' || *endptr != '\0'){
/// "îÅÐÒÁ×ÉÌØÎÙÊ ÆÏÒÍÁÔ ÞÉÓÌÁ double!"
WARNX("Wrong double number format!");
return FALSE;
}
if(num) *num = res; // you may run it like myatod(NULL, str) to test wether str is double number
return TRUE;
}
static char *logname = NULL;
static pthread_mutex_t logmutex;
/**
* @brief openlogfile - create log file: init mutex, test file open ability
* @param log - log structure
* @return 0 if all OK
*/
int openlogfile(char *name){
FREE(logname);
pthread_mutex_destroy(&logmutex);
FILE *logfd = fopen(name, "a");
if(!logfd){
WARN("Can't open log file");
return 2;
}
fclose(logfd);
if(pthread_mutex_init(&logmutex, NULL)){
WARN("Can't init log mutes");
return 3;
}
logname = strdup(name);
return 0;
}
int putlog(const char *fmt, ...){
if(!logname) return 0;
if(pthread_mutex_lock(&logmutex)){
WARN("Can't lock log mutex");
return 0;
}
int i = 0;
FILE *logfd = fopen(logname, "a");
if(!logfd) goto rtn;
char strtm[128];
time_t t = time(NULL);
struct tm *curtm = localtime(&t);
strftime(strtm, 128, "%Y/%m/%d-%H:%M:%S", curtm);
i = fprintf(logfd, "%s\t", strtm);
va_list ar;
va_start(ar, fmt);
i += vfprintf(logfd, fmt, ar);
va_end(ar);
i += fprintf(logfd, "\n");
fclose(logfd);
rtn:
pthread_mutex_unlock(&logmutex);
return i;
}

View File

@@ -1,145 +0,0 @@
/* geany_encoding=koi8-r
* usefull_macros.h - a set of usefull macros: memory, color etc
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#pragma once
#ifndef __USEFULL_MACROS_H__
#define __USEFULL_MACROS_H__
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <err.h>
#include <locale.h>
#include <sys/ioctl.h>
#if defined GETTEXT_PACKAGE && defined LOCALEDIR
/*
* GETTEXT
*/
#include <libintl.h>
#define _(String) gettext(String)
#define gettext_noop(String) String
#define N_(String) gettext_noop(String)
#else
#define _(String) (String)
#define N_(String) (String)
#endif
#include <stdlib.h>
#include <termios.h>
#include <sys/time.h>
#include <time.h>
#include <sys/types.h>
#include <stdint.h>
// unused arguments with -Wall -Werror
#define _U_ __attribute__((__unused__))
/*
* Coloured messages output
*/
#define RED "\033[1;31;40m"
#define GREEN "\033[1;32;40m"
#define OLDCOLOR "\033[0;0;0m"
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (1)
#endif
/*
* ERROR/WARNING messages
*/
extern int globErr;
extern void signals(int sig);
#define ERR(...) do{globErr=errno; _WARN(__VA_ARGS__); signals(9);}while(0)
#define ERRX(...) do{globErr=0; _WARN(__VA_ARGS__); signals(9);}while(0)
#define WARN(...) do{globErr=errno; _WARN(__VA_ARGS__);}while(0)
#define WARNX(...) do{globErr=0; _WARN(__VA_ARGS__);}while(0)
/*
* print function name, debug messages
* debug mode, -DEBUG
*/
#ifdef EBUG
#define FNAME() do{ fprintf(stderr, OLDCOLOR); \
fprintf(stderr, "\n%s (%s, line %d)\n", __func__, __FILE__, __LINE__);} while(0)
#define DBG(...) do{ fprintf(stderr, OLDCOLOR); \
fprintf(stderr, "%s (%s, line %d): ", __func__, __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n");} while(0)
#else
#define FNAME() do{}while(0)
#define DBG(...) do{}while(0)
#endif //EBUG
/*
* Memory allocation
*/
#define ALLOC(type, var, size) type * var = ((type *)my_alloc(size, sizeof(type)))
#define MALLOC(type, size) ((type *)my_alloc(size, sizeof(type)))
#define FREE(ptr) do{if(ptr){free(ptr); ptr = NULL;}}while(0)
#ifndef DBL_EPSILON
#define DBL_EPSILON (2.2204460492503131e-16)
#endif
double dtime();
// functions for color output in tty & no-color in pipes
extern int (*red)(const char *fmt, ...);
extern int (*_WARN)(const char *fmt, ...);
extern int (*green)(const char *fmt, ...);
void * my_alloc(size_t N, size_t S);
void initial_setup();
// mmap file
typedef struct{
char *data;
size_t len;
} mmapbuf;
mmapbuf *My_mmap(char *filename);
void My_munmap(mmapbuf *b);
void restore_console();
void setup_con();
int read_console();
int mygetchar();
void restore_tty();
void tty_init(char *comdev, tcflag_t speed);
size_t read_tty(char *buff, size_t length);
int write_tty(const char *buff, size_t length);
int str2double(double *num, const char *str);
int openlogfile(char *name);
int putlog(const char *fmt, ...);
#endif // __USEFULL_MACROS_H__