weather check for astrosib dome daemon

This commit is contained in:
2026-05-15 14:44:21 +03:00
parent e24568b0bf
commit 7e369976b5
86 changed files with 361 additions and 55 deletions

View File

@@ -15,7 +15,7 @@ message("VER: ${VERSION}")
option(DEBUG "Compile in debug mode" OFF) option(DEBUG "Compile in debug mode" OFF)
# default flags # default flags
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -std=gnu99") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -std=c23")
if(DEBUG) if(DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -fno-builtin-strlen -Werror") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -fno-builtin-strlen -Werror")
add_definitions(-DEBUG) add_definitions(-DEBUG)
@@ -53,11 +53,12 @@ target_include_directories(${PROJ} PUBLIC ${MODULES_INCLUDE_DIRS})
# -L # -L
target_link_directories(${PROJ} PUBLIC ${MODULES_LIBRARY_DIRS}) target_link_directories(${PROJ} PUBLIC ${MODULES_LIBRARY_DIRS})
# -l # -l
target_link_libraries(${PROJ} ${MODULES_LIBRARIES}) target_link_libraries(${PROJ} ${MODULES_LIBRARIES} -lweather)
# -D # -D
add_definitions( add_definitions(
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\"
-DMID_VERSION=\"${MID_VERSION}\" -DMAJOR_VERSION=\"${MAJOR_VESION}\" -DMID_VERSION=\"${MID_VERSION}\" -DMAJOR_VERSION=\"${MAJOR_VESION}\"
-D_GNU_SOURCE
) )
# Installation of the program # Installation of the program

View File

@@ -21,6 +21,7 @@
#include "astrosib_proto.h" #include "astrosib_proto.h"
#include "dome.h" #include "dome.h"
#include "header.h"
// number of relay turning on/off motors power // number of relay turning on/off motors power
#define MOTRELAY_NO 1 #define MOTRELAY_NO 1
@@ -269,5 +270,6 @@ ret:
if(state == DOME_S_IDLE) status_req_interval = STATUSREQ_IDLE; if(state == DOME_S_IDLE) status_req_interval = STATUSREQ_IDLE;
else status_req_interval = STATUSREQ_MOVE; else status_req_interval = STATUSREQ_MOVE;
pthread_mutex_unlock(&serialmutex); pthread_mutex_unlock(&serialmutex);
write_header();
return st; return st;
} }

View File

@@ -1 +1 @@
-std=c17 -std=c23

View File

@@ -1,2 +1,3 @@
// Add predefined macros for your project here. For example: // Add predefined macros for your project here. For example:
// #define THE_ANSWER 42 // #define THE_ANSWER 42
#define _XOPEN_SOURCE 666

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 16.0.2, 2025-06-04T11:18:43. --> <!-- Written by QtCreator 19.0.1, 2026-05-15T14:43:04. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
@@ -86,12 +86,14 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/> <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value> <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap> </valuemap>
<value type="int" key="RcSync">0</value>
</valuemap> </valuemap>
</data> </data>
<data> <data>
<variable>ProjectExplorer.Project.Target.0</variable> <variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value> <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.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">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="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
@@ -109,8 +111,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Сборка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Сборка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap> </valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
@@ -122,8 +124,8 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Очистка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Очистка</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap> </valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value> <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
@@ -133,13 +135,49 @@
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</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> </valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value> <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value> <value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Развёртывание</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Развёртывание</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap> </valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value> <value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
@@ -153,6 +191,7 @@
<value type="bool" key="Analyzer.QmlProfiler.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="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</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> <value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/> <valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value> <value type="int" key="PE.EnvironmentAspect.Base">2</value>
@@ -163,6 +202,7 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</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.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap> </valuemap>
@@ -173,10 +213,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable> <variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value> <value type="qlonglong">1</value>
</data> </data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data> <data>
<variable>Version</variable> <variable>Version</variable>
<value type="int">22</value> <value type="int">22</value>

View File

@@ -1 +1 @@
-std=c++17 -std=c++23

View File

@@ -1,6 +1,8 @@
astrosib_proto.h astrosib_proto.h
dome.c dome.c
dome.h dome.h
header.c
header.h
main.c main.c
server.c server.c
server.h server.h

View File

@@ -0,0 +1,132 @@
/*
* This file is part of the baader_dome 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 <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <usefull_macros.h>
#include "header.h"
#include "dome.h"
#include "server.h"
#define VAL_LEN 22
static char *headername = NULL;
static char *dome_name = NULL;
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, 8, "%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;
}
// return TRUE if can write to given header file
int header_create(const char *file){
if(!file) return FALSE;
FILE *hf = fopen(file, "w");
if(!hf) return FALSE;
unlink(file);
if(headername) FREE(headername);
headername = strdup(file);
return TRUE;
}
// set given `name` for telescope name
void domename(const char *name){
if(dome_name) FREE(dome_name);
if(name){
int l = strlen(name) + 3;
dome_name = MALLOC(char, l);
snprintf(dome_name, l, "'%s'", name);
}
}
const char* stringst[] = {
[DOME_S_IDLE] = "idle",
[DOME_S_MOVING] = "moving",
[DOME_S_ERROR] = "error",
};
void write_header(){
if(!headername) return;
char aname[PATH_MAX];
char val[VAL_LEN];
#define WRHDR(k, v, c) do{if(printhdr(fd, k, v, c)){goto returning;}}while(0)
snprintf(aname, PATH_MAX-1, "%sXXXXXX", headername);
int fd = mkstemp(aname);
if(fd < 0){
LOGWARN("Can't write header file: mkstemp()");
return;
}
fchmod(fd, 0644);
dome_status_t st;
dome_state_t curstate = get_dome_state();
double stattime = get_dome_status(&st);
if(get_forbidden()) WRHDR("OPERATIO", "'FORBIDDEN'", "Observations are forbidden");
if(dome_name) WRHDR("DOME", dome_name, "Dome manufacturer/name");
int idx = (int)curstate;
if(idx < 0 || idx > DOME_S_ERROR) idx = DOME_S_ERROR;
snprintf(val, VAL_LEN, "'%s'", stringst[idx]);
WRHDR("DOMESTAT", val, "Dome status");
snprintf(val, VAL_LEN, "'%s'", textst(st.coverstate[0]));
WRHDR("DOMECVR1", val, "Dome cover 1 status");
snprintf(val, VAL_LEN, "'%s'", textst(st.coverstate[1]));
WRHDR("DOMECVR2", val, "Dome cover 2 status");
snprintf(val, VAL_LEN, "%d", st.encoder[0]);
WRHDR("DOMEANG1", val, "Dome cover 1 angle");
snprintf(val, VAL_LEN, "%d", st.encoder[1]);
WRHDR("DOMEANG2", val, "Dome cover 2 angle");
snprintf(val, VAL_LEN, "%d", st.relay[0]);
WRHDR("DOMERLY1", val, "Dome relay1 state");
snprintf(val, VAL_LEN, "%d", st.relay[1]);
WRHDR("DOMERLY2", val, "Dome relay2 state");
snprintf(val, VAL_LEN, "%d", st.relay[2]);
WRHDR("DOMERLY3", val, "Dome relay3 state");
if(st.israin) WRHDR("DOMERAIN", "1", "Dome rain sensor armed");
snprintf(val, VAL_LEN, "%.3f", stattime);
char timebuf[BUFSIZ];
time_t t = (time_t) stattime;
struct tm *tmp;
tmp = localtime(&t);
if(!tmp || 0 == strftime(timebuf, BUFSIZ, "Measurement time: %F %T", tmp)){
LOGERR("localtime() returned NULL");
goto returning;
}
WRHDR("TDOMMEAS", val, timebuf);
#undef WRHDR
returning:
close(fd);
rename(aname, headername);
}

View File

@@ -0,0 +1,23 @@
/*
* This file is part of the domedaemon-astrosib 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
void write_header();
int header_create(const char *file);
void domename(const char *name);

View File

@@ -23,6 +23,7 @@
#include <sys/prctl.h> //prctl #include <sys/prctl.h> //prctl
#include <usefull_macros.h> #include <usefull_macros.h>
#include "header.h"
#include "server.h" #include "server.h"
// TCP socket port // TCP socket port
@@ -31,45 +32,101 @@
#define DEFAULT_SERSPEED 9600 #define DEFAULT_SERSPEED 9600
// serial polling timeout - 100ms // serial polling timeout - 100ms
#define DEFAULT_SERTMOUT 100000 #define DEFAULT_SERTMOUT 100000
#define DEFAULT_PIDFILE "/tmp/domedaemon.pid"
#define DEFAULT_HEADERFILE "/tmp/dome.fits"
#define DEFAULT_DOMENAME "Astrosib"
static pid_t childpid = 0;
static sl_tty_t *serial = NULL;
typedef struct{ typedef struct{
int help;
char *device; // serial device name char *device; // serial device name
char *node; // port to connect or UNIX socket name char *node; // port to connect or UNIX socket name
char *logfile; // logfile name char *logfile; // logfile name
char *pidfile; // PID-file path
char *headerfile; // path to FITS-header with dome parameters
char *dome_name; // name of dome
int isunix; // open UNIX-socket instead of TCP int isunix; // open UNIX-socket instead of TCP
int verbose; // verbose level int verbose; // verbose level
} parameters; } parameters;
static parameters G = { static parameters G = {
.node = DEFAULT_PORT, .node = DEFAULT_PORT,
.pidfile = DEFAULT_PIDFILE,
.headerfile = DEFAULT_HEADERFILE,
.dome_name = DEFAULT_DOMENAME,
}; };
static int help;
static sl_option_t cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), "serial device name"}, {"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), "serial device name"},
{"node", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), "UNIX socket name or network port to connect (default: " DEFAULT_PORT ")"}, {"node", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), "UNIX socket name or network port to connect (default: " DEFAULT_PORT ")"},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file"}, {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file"},
{"unix", NO_ARGS, NULL, 'u', arg_int, APTR(&G.isunix), "open UNIX-socket instead of TCP"}, {"unix", NO_ARGS, NULL, 'u', arg_int, APTR(&G.isunix), "open UNIX-socket instead of TCP"},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "logging verbose level (each -v adds one)"}, {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "logging verbose level (each -v adds one)"},
{"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), "full path to PID-file (default: " DEFAULT_PIDFILE ")"},
{"headerfile",NEED_ARG, NULL, 'H', arg_string, APTR(&G.headerfile),"full path to output FITS-header (default: " DEFAULT_HEADERFILE ")"},
{"domename",NEED_ARG, NULL, 'N', arg_string, APTR(&G.dome_name), "dome name in FITS-header (default: " DEFAULT_DOMENAME ")"},
end_option end_option
}; };
void sl_iffound_deflt(pid_t pid){
WARNX("Another copy of this process found, pid=%d. Exit.", pid);
exit(1); // don't run `signals` to protect foreign PID-file from removal
}
// SIGUSR1 - FORBID observations
// SIGUSR2 - allow
void signals(int sig){ void signals(int sig){
if(sig){ if(sig){
signal(sig, SIG_IGN); if(signals != signal(sig, SIG_IGN)) exit(sig); // function called "as is", before sig registration
DBG("Get signal %d, quit.\n", sig); if(childpid == 0){ // child -> test USR1/USR2
LOGERR("Exit with status %d", sig); LOGDBG("Child gotta signal %d", sig);
}else LOGERR("Exit"); if(sig == SIGUSR1){
forbid_observations(1);
LOGMSG("Got signal `observations forbidden`");
signal(sig, signals);
return;
}else if(sig == SIGUSR2){
forbid_observations(0);
LOGMSG("Got signal `observations permitted`");
signal(sig, signals);
return;
}
}
LOGDBG("Get signal %d, quit.\n", sig);
}
if(childpid == 0){
DBG("Stop server");
LOGMSG("Stop server");
stopserver();
DBG("Close terminal");
LOGMSG("Close terminal");
if(serial) sl_tty_close(&serial);
}else{
if(G.pidfile){
LOGMSG("Unlink %s", G.pidfile);
usleep(10000);
unlink(G.pidfile);
}
}
LOGERR("Exit with status %d", sig);
exit(sig); exit(sig);
} }
int main(int argc, char **argv){ int main(int argc, char **argv){
sl_init(); sl_init();
sl_parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(help) sl_showhelp(-1, cmdlnopts); if(G.help) sl_showhelp(-1, cmdlnopts);
if(!G.node) ERRX("Point node"); if(!G.node) ERRX("Point node");
if(!G.device) ERRX("Point path to serial device"); if(!G.device) ERRX("Point path to serial device");
if(!header_create(G.headerfile))
ERRX("Cannot write into '%s'", G.headerfile);
domename(G.dome_name);
sl_check4running((char*)__progname, G.pidfile);
if(sl_daemonize()) ERR("Can't daemonize!");
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR; sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
if(G.logfile) OPENLOG(G.logfile, lvl, 1); if(G.logfile) OPENLOG(G.logfile, lvl, 1);
@@ -79,6 +136,8 @@ int main(int argc, char **argv){
signal(SIGQUIT, signals); signal(SIGQUIT, signals);
signal(SIGTSTP, SIG_IGN); signal(SIGTSTP, SIG_IGN);
signal(SIGHUP, signals); signal(SIGHUP, signals);
signal(SIGUSR1, SIG_IGN);
signal(SIGUSR2, SIG_IGN);
#ifndef EBUG #ifndef EBUG
time_t lastd = 0; time_t lastd = 0;
while(1){ // guard for dead processes while(1){ // guard for dead processes
@@ -101,7 +160,7 @@ int main(int argc, char **argv){
} }
#endif #endif
sl_socktype_e type = (G.isunix) ? SOCKT_UNIX : SOCKT_NETLOCAL; sl_socktype_e type = (G.isunix) ? SOCKT_UNIX : SOCKT_NETLOCAL;
sl_tty_t *serial = sl_tty_new(G.device, DEFAULT_SERSPEED, 4096); serial = sl_tty_new(G.device, DEFAULT_SERSPEED, 4096);
if(serial) serial = sl_tty_open(serial, 1); if(serial) serial = sl_tty_open(serial, 1);
if(!serial){ if(!serial){
LOGERR("Can't open serial device %s", G.device); LOGERR("Can't open serial device %s", G.device);

View File

@@ -18,15 +18,22 @@
#include <inttypes.h> #include <inttypes.h>
#include <pthread.h> #include <pthread.h>
#include <stdatomic.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <usefull_macros.h> #include <usefull_macros.h>
#include <weather_data.h>
#include "dome.h" #include "dome.h"
// max age time of last status - 30s // max age time of last status - 30s
#define STATUS_MAX_AGE (30.) #define STATUS_MAX_AGE (30.)
// if there's no signal from weather over `WEATHER_LOST` seconds, close the dome
#define WEATHER_LOST (300.)
// interval of weather polling
#define WEATH_POLL (5.)
// commands // commands
#define CMD_UNIXT "unixt" #define CMD_UNIXT "unixt"
@@ -41,6 +48,13 @@
// main socket // main socket
static sl_sock_t *s = NULL; static sl_sock_t *s = NULL;
// external (manual) signal "deny/allow"
static atomic_bool ForbidObservations = 0; // ==1 if all is forbidden -> close dome and not allow to open
// weather don't allow to open
static atomic_bool BadWeather = 0;
#define CHKALLOWED() do{if(ForbidObservations || BadWeather){return RESULT_FAIL;}}while(0)
/////// handlers /////// handlers
// unixt - send to ALL clients // 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){ static sl_sock_hresult_e dtimeh(sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){
@@ -60,7 +74,7 @@ static sl_sock_hresult_e statush(sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ co
sl_sock_sendstrmessage(c, buf); sl_sock_sendstrmessage(c, buf);
return RESULT_SILENCE; return RESULT_SILENCE;
} }
static const char *textst(int coverstate){ const char *textst(int coverstate){
switch(coverstate){ switch(coverstate){
case COVER_INTERMEDIATE: return "intermediate"; case COVER_INTERMEDIATE: return "intermediate";
case COVER_OPENED: return "opened"; case COVER_OPENED: return "opened";
@@ -110,16 +124,19 @@ static sl_sock_hresult_e domecmd(dome_cmd_t cmd){
return RESULT_OK; return RESULT_OK;
} }
static sl_sock_hresult_e opendome(_U_ sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){ static sl_sock_hresult_e opendome(_U_ sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){
CHKALLOWED();
return domecmd(DOME_OPEN); return domecmd(DOME_OPEN);
} }
static sl_sock_hresult_e closedome(_U_ sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){ static sl_sock_hresult_e closedome(_U_ sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){
return domecmd(DOME_CLOSE); return domecmd(DOME_CLOSE);
} }
static sl_sock_hresult_e stopdome(_U_ sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){ static sl_sock_hresult_e stopdome(_U_ sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){
CHKALLOWED(); // don't allow to stop closing on forbidden state
return domecmd(DOME_STOP); return domecmd(DOME_STOP);
} }
// half open/close // half open/close
static sl_sock_hresult_e halfmove(sl_sock_t *c, sl_sock_hitem_t *item, const char *req){ static sl_sock_hresult_e halfmove(sl_sock_t *c, sl_sock_hitem_t *item, const char *req){
CHKALLOWED();
char buf[128]; char buf[128];
int N = item->key[sizeof(CMD_HALF) - 1] - '0'; int N = item->key[sizeof(CMD_HALF) - 1] - '0';
if(N < 1 || N > 2) return RESULT_BADKEY; if(N < 1 || N > 2) return RESULT_BADKEY;
@@ -196,6 +213,7 @@ void server_run(sl_socktype_e type, const char *node, sl_tty_t *serial){
LOGERR("server_run(): wrong parameters"); LOGERR("server_run(): wrong parameters");
ERRX("server_run(): wrong parameters"); ERRX("server_run(): wrong parameters");
} }
weather_data_t weather;
dome_serialdev(serial); dome_serialdev(serial);
s = sl_sock_run_server(type, node, -1, handlers); s = sl_sock_run_server(type, node, -1, handlers);
if(!s) ERRX("Can't create socket and/or run threads"); if(!s) ERRX("Can't create socket and/or run threads");
@@ -203,14 +221,59 @@ void server_run(sl_socktype_e type, const char *node, sl_tty_t *serial){
sl_sock_maxclhandler(s, toomuch); sl_sock_maxclhandler(s, toomuch);
sl_sock_connhandler(s, connected); sl_sock_connhandler(s, connected);
sl_sock_dischandler(s, disconnected); sl_sock_dischandler(s, disconnected);
double tnow = sl_dtime(), tweather = 0.;
int cmdclosed = 0;
while(s && s->connected){ while(s && s->connected){
if(!s->rthread){ if(!s->rthread){
LOGERR("Server handlers thread is dead"); LOGERR("Server handlers thread is dead");
break; break;
} }
if(tnow - tweather > WEATH_POLL){
if(0 == get_weather_data(&weather)){ // got OK -> check if observations are forbidden
tweather = tnow;
int bad = 0;
if((double)weather.last_update - tnow > WEATHER_LOST) bad = 1;
if(weather.forceoff || weather.rain || weather.weather > WEATHER_BAD) bad = 1;
if(bad) BadWeather = 1;
else BadWeather = 0;
}else{
if(tnow - tweather > WEATHER_LOST) BadWeather = 1; // lost weather IPC
}
}
// finite state machine polling // finite state machine polling
dome_poll(DOME_POLL, 0); dome_poll(DOME_POLL, 0);
if(ForbidObservations || BadWeather){
if(0 == cmdclosed){
if(DOME_S_ERROR != dome_poll(DOME_CLOSE, 0)){
LOGERR("Send command 'close' due to forbidden state");
cmdclosed = 1;
}
}
}
} }
sl_sock_delete(&s); sl_sock_delete(&s);
ERRX("Server handlers thread is dead"); ERRX("Server handlers thread is dead");
} }
void stopserver(){
if(s && s->connected){
s->connected = 0;
usleep(5000);
if(s) sl_sock_delete(&s);
}
}
void forbid_observations(int forbid){
if(forbid){
ForbidObservations = true;
LOGWARN("Got forbidden signal");
}else{
ForbidObservations = false;
LOGWARN("Got allowed signal");
}
DBG("Change ForbidObservations=%d", forbid);
}
int get_forbidden(){
return (ForbidObservations || BadWeather);
}

View File

@@ -20,4 +20,13 @@
#include <usefull_macros.h> #include <usefull_macros.h>
// size of weather/status buffers
#define STATBUF_SZ 256
// dome polling interval (clear watchdog & get status)
#define T_INTERVAL (5.0)
void server_run(sl_socktype_e type, const char *node, sl_tty_t *serial); void server_run(sl_socktype_e type, const char *node, sl_tty_t *serial);
const char *textst(int coverstate);
void stopserver();
void forbid_observations(int forbid);
int get_forbidden();

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.0, 2026-04-01T11:06:44. --> <!-- Written by QtCreator 19.0.1, 2026-05-15T14:02:48. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
@@ -155,6 +155,7 @@
<value type="bool" key="Analyzer.QmlProfiler.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="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</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> <value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/> <valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value> <value type="int" key="PE.EnvironmentAspect.Base">2</value>
@@ -191,6 +192,7 @@
<value type="bool" key="Analyzer.QmlProfiler.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="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</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> <value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/> <valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value> <value type="int" key="PE.EnvironmentAspect.Base">2</value>

View File

@@ -136,10 +136,6 @@ int main(int argc, char **argv){
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
if(G.logfile) OPENLOG(G.logfile, lvl, 1); if(G.logfile) OPENLOG(G.logfile, lvl, 1);
LOGMSG("Started"); LOGMSG("Started");
if(!term_open(G.termpath, G.serspeed, G.sertmout)){
LOGERR("Can't open %s", G.termpath);
ERRX("Fatal error");
}
signal(SIGTERM, signals); signal(SIGTERM, signals);
signal(SIGINT, signals); signal(SIGINT, signals);
signal(SIGQUIT, signals); signal(SIGQUIT, signals);
@@ -163,6 +159,10 @@ int main(int argc, char **argv){
// react for USRx only in child // react for USRx only in child
signal(SIGUSR1, signals); signal(SIGUSR1, signals);
signal(SIGUSR2, signals); signal(SIGUSR2, signals);
if(!term_open(G.termpath, G.serspeed, G.sertmout)){
LOGERR("Can't open %s", G.termpath);
ERRX("Fatal error");
}
runserver(G.isunix, G.node, G.maxclients); runserver(G.isunix, G.node, G.maxclients);
LOGERR("Server error -> exit"); LOGERR("Server error -> exit");
return 0; return 0;

View File

@@ -309,7 +309,7 @@ void runserver(int isunix, const char *node, int maxclients){
if(bad) BadWeather = 1; if(bad) BadWeather = 1;
else BadWeather = 0; else BadWeather = 0;
}else{ }else{
if(tweather - tnow > WEATHER_LOST) BadWeather = 1; // lost weather IPC if(tnow - tweather > WEATHER_LOST) BadWeather = 1; // lost weather IPC
} }
if(poll_device()){ if(poll_device()){
tgot = tnow; tgot = tnow;

View File

@@ -1,24 +0,0 @@
#!/bin/bash
badsky=1700
[ $# = 1 ] && badsky=$1
export http_proxy=""
Q="192.168.70.33:12345"
ANS=$(curl $Q 2>/dev/null)
retval=$?
[ $retval -ne "0" ] && exit $retval
[ "$ANS" = "No data" ] && exit 2
Rain=1
Clouds=0
Wind=100
eval $ANS
retval=0
clouds=$(echo "$Clouds" | sed 's/\..*//g')
wind=$(echo "$Wind" | sed 's/\..*//g')
[ $Rain -ne "0" ] && retval=1
[ $clouds -lt "$badsky" ] && retval=1
[ $wind -gt "15" ] && retval=1
echo "$ANS"
exit $retval