mirror of
https://github.com/eddyem/small_tel.git
synced 2026-06-19 10:26:25 +03:00
started weather_logger
This commit is contained in:
@@ -10,5 +10,5 @@ Different daemons & tools
|
|||||||
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon (almost deprecated)
|
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon (almost deprecated)
|
||||||
- *teldaemon_astrosib* - work with Astrosib-500 scope equipment by network query
|
- *teldaemon_astrosib* - work with Astrosib-500 scope equipment by network query
|
||||||
- *weatherdaemon_multimeteo* - weather daemon for ALL meteo sensors on "Astro-M" complex
|
- *weatherdaemon_multimeteo* - weather daemon for ALL meteo sensors on "Astro-M" complex
|
||||||
- *weather_database* - make database by data of almost deprecated weather daemons
|
- *weather_logger* - make file-based database for all meteo sensors
|
||||||
- *weather_proxy* - daemon gathering meteo data from `weatherdaemon_multimeteo` and sharing it on localhost over SHM
|
- *weather_proxy* - daemon gathering meteo data from `weatherdaemon_multimeteo` and sharing it on localhost over SHM
|
||||||
|
|||||||
99
Daemons/weather_logger/CMakeLists.txt
Normal file
99
Daemons/weather_logger/CMakeLists.txt
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
cmake_minimum_required(VERSION 4.0)
|
||||||
|
set(PROJ meteologger)
|
||||||
|
set(MINOR_VERSION "1")
|
||||||
|
set(MID_VERSION "0")
|
||||||
|
set(MAJOR_VERSION "0")
|
||||||
|
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
|
||||||
|
|
||||||
|
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
|
||||||
|
message("${PROJ} version: ${VERSION}")
|
||||||
|
|
||||||
|
option(DEBUG "Compile in debug mode" OFF)
|
||||||
|
# option()
|
||||||
|
|
||||||
|
# default flags
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -fPIC")
|
||||||
|
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)
|
||||||
|
#list(REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/<file to remove>)
|
||||||
|
#set(SOURCES list_of_c_files)
|
||||||
|
|
||||||
|
# we can change file list
|
||||||
|
#if(NOT DEFINED something)
|
||||||
|
# set(SOURCES ${SOURCES} one_more_list)
|
||||||
|
# add_definitions(-DSOME_DEFS)
|
||||||
|
#endif()
|
||||||
|
|
||||||
|
# cmake -DDEBUG=1 -> debugging
|
||||||
|
if(DEBUG)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -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()
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
|
||||||
|
###### pkgconfig ######
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(${PROJ} REQUIRED usefull_macros>=0.3.5)
|
||||||
|
|
||||||
|
# external modules like OpenMP:
|
||||||
|
#include(FindOpenMP)
|
||||||
|
#if(OPENMP_FOUND)
|
||||||
|
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
|
||||||
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
|
||||||
|
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}")
|
||||||
|
#endif()
|
||||||
|
|
||||||
|
# append data from find_package:
|
||||||
|
#list(APPEND ${PROJ}_INCLUDE_DIRS ${<pkg1>_INCLUDE_DIR} ${<pkg2>_INCLUDE_DIR})
|
||||||
|
#list(APPEND ${PROJ}_LIBRARIES ${<pkg1>_LIBRARY} ${<pkg2>_LIBRARY})
|
||||||
|
#list(APPEND ${${PROJ}_LIBRARY_DIRS} ${<pkg1>_LIBRARY_DIRS})
|
||||||
|
|
||||||
|
# static library
|
||||||
|
#set(LIBSRC fd.c weathlib.c)
|
||||||
|
#set(LIBHEADER weathlib.h)
|
||||||
|
#add_library(${PROJLIB} STATIC ${LIBSRC})
|
||||||
|
#set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION})
|
||||||
|
|
||||||
|
# exe file
|
||||||
|
add_executable(${PROJ} ${SOURCES})
|
||||||
|
# another exe, depending on some other files
|
||||||
|
#add_executable(test_client client.c usefull_macros.c parceargs.c)
|
||||||
|
# -I
|
||||||
|
include_directories(${${PROJ}_INCLUDE_DIRS})
|
||||||
|
# -L
|
||||||
|
link_directories(${${PROJ}_LIBRARY_DIRS})
|
||||||
|
# -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_VERSION}\")
|
||||||
|
|
||||||
|
###### pthreads ######
|
||||||
|
#find_package(Threads REQUIRED)
|
||||||
|
#if(THREADS_HAVE_PTHREAD_ARG)
|
||||||
|
# set_property(TARGET ${PROJ} PROPERTY COMPILE_OPTIONS "-pthread")
|
||||||
|
# set_property(TARGET ${PROJ} PROPERTY INTERFACE_COMPILE_OPTIONS "-pthread")
|
||||||
|
#endif()
|
||||||
|
#if(CMAKE_THREAD_LIBS_INIT)
|
||||||
|
# list(APPEND ${PROJ}_LIBRARIES "${CMAKE_THREAD_LIBS_INIT}")
|
||||||
|
#endif()
|
||||||
|
|
||||||
|
# target libraries
|
||||||
|
target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} ${CMAKE_DL_LIBS})
|
||||||
|
|
||||||
|
# install
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
install(TARGETS ${PROJ} DESTINATION "bin")
|
||||||
|
# Script to be executed at installation time (kind of post-intallation script) to
|
||||||
|
# change the right accesses on the installed files
|
||||||
|
#INSTALL(SCRIPT inst.cmake)
|
||||||
|
|
||||||
|
# subdirs
|
||||||
|
#add_subdirectory("plugins")
|
||||||
126
Daemons/weather_logger/main.c
Normal file
126
Daemons/weather_logger/main.c
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the meteologger 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 <linux/prctl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "parseargs.h"
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
static volatile atomic_int catchsig = 0;
|
||||||
|
static pid_t childpid = 0;
|
||||||
|
|
||||||
|
void signals(int signo){
|
||||||
|
signal(signo, SIG_IGN);
|
||||||
|
if(catchsig == 0){
|
||||||
|
DBG("superloop not inited");
|
||||||
|
exit(signo);
|
||||||
|
}
|
||||||
|
if(0 == childpid){
|
||||||
|
if(signo == SIGUSR1){ // reload logs after rotating
|
||||||
|
DBG("Got USR1 -> reinit logs");
|
||||||
|
if(!reinit_logs()){
|
||||||
|
stop_server();
|
||||||
|
catchsig = signo;
|
||||||
|
}else signal(signo, signals);
|
||||||
|
}else{ // kill
|
||||||
|
DBG("Got %d -> kill", signo);
|
||||||
|
stop_server();
|
||||||
|
catchsig = signo; // could be 0 or error code / signo
|
||||||
|
}
|
||||||
|
}else{ // throw signal to child
|
||||||
|
if(signo > 0){
|
||||||
|
kill(childpid, signo);
|
||||||
|
LOGMSG("Send received signal %d to child", signo);
|
||||||
|
if(signo != SIGUSR1) catchsig = signo;
|
||||||
|
}else catchsig = signo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void prepare_and_run(glob_pars *G){
|
||||||
|
if(!G) return;
|
||||||
|
sl_socktype_e stype = (G->isunix) ? SOCKT_UNIX : SOCKT_NET;
|
||||||
|
set_reqinterval(G->req_interval);
|
||||||
|
set_nettimeout(G->net_timeout);
|
||||||
|
DBG("Run server");
|
||||||
|
run_server(G->node, stype, G->bddir);
|
||||||
|
DBG("Server died");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv){
|
||||||
|
sl_init();
|
||||||
|
glob_pars *G = parseargs(&argc, &argv);
|
||||||
|
if(!G) return 1;
|
||||||
|
sl_check4running(NULL, G->pidfile);
|
||||||
|
signal(SIGTERM, signals);
|
||||||
|
signal(SIGINT, signals);
|
||||||
|
signal(SIGQUIT, signals);
|
||||||
|
signal(SIGPIPE, SIG_IGN); // for sockets
|
||||||
|
signal(SIGUSR1, signals); // reload DB
|
||||||
|
#ifndef EBUG
|
||||||
|
if(sl_daemonize()) ERRX("Can't daemonize");
|
||||||
|
catchsig = INT_MAX; // now `signals` won't run exit()
|
||||||
|
while(catchsig == INT_MAX){ // guard for dead processes
|
||||||
|
childpid = fork();
|
||||||
|
if(childpid){
|
||||||
|
LOGDBG("create child with PID %d\n", childpid);
|
||||||
|
DBG("Created child with PID %d\n", childpid);
|
||||||
|
pid_t expid = 0;
|
||||||
|
while(catchsig == INT_MAX){
|
||||||
|
expid = waitpid(childpid, NULL, WNOHANG);
|
||||||
|
if(expid < 0){
|
||||||
|
LOGERR("waitpid() returns -1; exit");
|
||||||
|
ERRX("waitpid() returns -1; exit");
|
||||||
|
}
|
||||||
|
if(expid == childpid) break;
|
||||||
|
usleep(50000);
|
||||||
|
}
|
||||||
|
WARNX("Child %d died\n", childpid);
|
||||||
|
LOGWARN("Child %d died\n", childpid);
|
||||||
|
sleep(1);
|
||||||
|
}else{
|
||||||
|
prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies
|
||||||
|
break; // go out to normal functional
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// init for DEBUG mode
|
||||||
|
catchsig = INT_MAX;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(childpid){
|
||||||
|
LOGERR("Main process exits with status %d", catchsig);
|
||||||
|
if(G->pidfile) unlink(G->pidfile);
|
||||||
|
}else{
|
||||||
|
pid_t self = getpid();
|
||||||
|
prepare_and_run(G);
|
||||||
|
if(catchsig == INT_MAX) LOGERR("Child process %d died", self);
|
||||||
|
else LOGERR("Child process %d exits with status %d", self, catchsig);
|
||||||
|
; // cleanup child here
|
||||||
|
#ifdef EBUG
|
||||||
|
if(G->pidfile) unlink(G->pidfile); // unlink PID-file in debug-mode
|
||||||
|
#endif
|
||||||
|
usleep(10000); // wait processes to die
|
||||||
|
}
|
||||||
|
return catchsig;
|
||||||
|
}
|
||||||
1
Daemons/weather_logger/meteologger.cflags
Normal file
1
Daemons/weather_logger/meteologger.cflags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-std=c23
|
||||||
3
Daemons/weather_logger/meteologger.config
Normal file
3
Daemons/weather_logger/meteologger.config
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#define _XOPEN_SOURCE=1234
|
||||||
|
#define _DEFAULT_SOURCE
|
||||||
|
#define _GNU_SOURCE
|
||||||
1
Daemons/weather_logger/meteologger.creator
Normal file
1
Daemons/weather_logger/meteologger.creator
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[General]
|
||||||
1
Daemons/weather_logger/meteologger.cxxflags
Normal file
1
Daemons/weather_logger/meteologger.cxxflags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-std=c++23
|
||||||
6
Daemons/weather_logger/meteologger.files
Normal file
6
Daemons/weather_logger/meteologger.files
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CMakeLists.txt
|
||||||
|
main.c
|
||||||
|
parseargs.c
|
||||||
|
parseargs.h
|
||||||
|
server.c
|
||||||
|
server.h
|
||||||
0
Daemons/weather_logger/meteologger.includes
Normal file
0
Daemons/weather_logger/meteologger.includes
Normal file
82
Daemons/weather_logger/parseargs.c
Normal file
82
Daemons/weather_logger/parseargs.c
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the meteologger 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 "parseargs.h"
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
#define DEFAULT_PID "/tmp/meteologger.pid"
|
||||||
|
#define MIN_REQ_INTERVAL (0.2)
|
||||||
|
#define MAX_REQ_INTERVAL (900.)
|
||||||
|
#define MIN_NET_TMOUT (0.1)
|
||||||
|
#define MAX_NET_TMOUT (30.)
|
||||||
|
|
||||||
|
static int help;
|
||||||
|
|
||||||
|
static glob_pars G = {
|
||||||
|
.pidfile = DEFAULT_PID,
|
||||||
|
.req_interval = 0.5,
|
||||||
|
.net_timeout = 1.,
|
||||||
|
};
|
||||||
|
|
||||||
|
static sl_option_t opts[] = {
|
||||||
|
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
|
||||||
|
{"node", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), "node to connect (host:port or UNIX socket name)"},
|
||||||
|
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file"},
|
||||||
|
{"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), "pidfile name (default: " DEFAULT_PID ")"},
|
||||||
|
{"isunix", NO_ARGS, NULL, 'u', arg_string, APTR(&G.isunix), "use UNIX socket instead of network"},
|
||||||
|
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), "verbose level (each -v increases)"},
|
||||||
|
{"output", NEED_ARG, NULL, 'o', arg_string, APTR(&G.bddir), "output directory to store database"},
|
||||||
|
{"interval",NEED_ARG, NULL, 'i', arg_double, APTR(&G.req_interval), "weather request interval, s (min: 0.2, max: 900)"},
|
||||||
|
{"timeout", NEED_ARG, NULL, 't', arg_double, APTR(&G.net_timeout),"timeout for server's answer, s (min: 0.1, max: 30)"},
|
||||||
|
end_option
|
||||||
|
};
|
||||||
|
|
||||||
|
glob_pars *parseargs(int *argc, char ***argv){
|
||||||
|
sl_parseargs(argc, argv, opts);
|
||||||
|
if(help){
|
||||||
|
sl_showhelp(-1, opts);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if(!G.node) ERRX("Point node to correct");
|
||||||
|
if(!G.bddir) ERRX("Need to know path to save DB");
|
||||||
|
if(!checkDBpath(G.bddir)) ERRX("Can't create logs in %s", G.bddir);
|
||||||
|
// remove trailing '/'
|
||||||
|
int eol = strlen(G.bddir) - 1;
|
||||||
|
DBG("eol=%d", eol);
|
||||||
|
while(eol > 0){
|
||||||
|
DBG("before: %s", G.bddir);
|
||||||
|
if(G.bddir[eol] == '/') G.bddir[eol] = 0;
|
||||||
|
else break;
|
||||||
|
DBG("after: %s", G.bddir);
|
||||||
|
--eol;
|
||||||
|
}
|
||||||
|
if(G.req_interval < MIN_REQ_INTERVAL || G.req_interval > MAX_REQ_INTERVAL)
|
||||||
|
ERRX("Wrong time interval %g, should be in [%g, %g]", G.req_interval, MIN_REQ_INTERVAL, MAX_REQ_INTERVAL);
|
||||||
|
if(G.net_timeout < MIN_NET_TMOUT || G.net_timeout > MAX_NET_TMOUT)
|
||||||
|
ERRX("Wrong network timeout %g, should be in [%g, %g]", G.net_timeout, MIN_NET_TMOUT, MAX_NET_TMOUT);
|
||||||
|
if(G.logfile){
|
||||||
|
sl_loglevel_e lvl = LOGLEVEL_ERR + G.verb;
|
||||||
|
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||||
|
DBG("Loglevel: %d", lvl);
|
||||||
|
if(!OPENLOG(G.logfile, lvl, 1)) ERRX("Can't open log file %s", G.logfile);
|
||||||
|
}
|
||||||
|
return &G;
|
||||||
|
}
|
||||||
32
Daemons/weather_logger/parseargs.h
Normal file
32
Daemons/weather_logger/parseargs.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the meteologger 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{
|
||||||
|
int isunix; // use UNIX-sockets instead of net
|
||||||
|
int verb; // verbocity level
|
||||||
|
double req_interval; // requests interval
|
||||||
|
double net_timeout; // network timeout
|
||||||
|
char *bddir; // directory to store all data
|
||||||
|
char *logfile; // logfile name
|
||||||
|
char *node; // node of server
|
||||||
|
char *pidfile; // pidfile name
|
||||||
|
} glob_pars;
|
||||||
|
|
||||||
|
glob_pars *parseargs(int *argc, char ***argv);
|
||||||
413
Daemons/weather_logger/server.c
Normal file
413
Daemons/weather_logger/server.c
Normal file
@@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the meteologger 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 <dirent.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <regex.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
// some "standard" keys from server
|
||||||
|
static const char *key_pluginname = "PLUGIN";
|
||||||
|
static const char *key_nvalues = "NVALUES";
|
||||||
|
|
||||||
|
// sensor's db filename mask: DBpath/weatherXX.log
|
||||||
|
static const char *dbfilename_mask = "%s/weather%02d.log";
|
||||||
|
// and search regex
|
||||||
|
static const char *dbfilename_regex = "weather[[:digit:]]{2}\\.log";
|
||||||
|
|
||||||
|
static volatile atomic_bool isrunning = true;
|
||||||
|
static volatile atomic_bool logreinit = false;
|
||||||
|
static double req_interval = 0.5; // request interval, s
|
||||||
|
static double net_timeout = 1.; // timeout for server's answer, s
|
||||||
|
static char *DBpath = NULL; // path to storage
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
int fd; // log file descriptor
|
||||||
|
int idx; // index of station for requests
|
||||||
|
int nvalues; // maximal amount of values
|
||||||
|
char path[PATH_MAX];// path to file
|
||||||
|
char *sensname; // sensor's name
|
||||||
|
char **keys; // keyword names for header and index in string
|
||||||
|
int *levels; // array of `weather level` for each keyword
|
||||||
|
} senslog_t;
|
||||||
|
|
||||||
|
static int Nsensors = 0; // amount of sensors for logging
|
||||||
|
static senslog_t *sensors = NULL; // array of files for logging
|
||||||
|
|
||||||
|
// commands for communication with server
|
||||||
|
typedef enum{
|
||||||
|
CMD_LIST,
|
||||||
|
CMD_GET,
|
||||||
|
CMD_CHKLEVEL,
|
||||||
|
CMD_TIME,
|
||||||
|
CMD_AMOUNT
|
||||||
|
} req_commands;
|
||||||
|
static const char *commands[CMD_AMOUNT] = {
|
||||||
|
[CMD_LIST] = "list",
|
||||||
|
[CMD_GET] = "get",
|
||||||
|
[CMD_CHKLEVEL] = "chklevel",
|
||||||
|
[CMD_TIME] = "time",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void delete_senskeys(senslog_t *sensor){
|
||||||
|
if(!sensor) return;
|
||||||
|
for(int j = 0; j < sensor->nvalues; ++j)
|
||||||
|
FREE(sensor->keys[j]);
|
||||||
|
FREE(sensor->keys);
|
||||||
|
sensor->nvalues = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sensors_delete(){
|
||||||
|
if(!sensors) return;
|
||||||
|
for(int i = 0; i < Nsensors; ++i){
|
||||||
|
senslog_t *sensor = &sensors[i];
|
||||||
|
delete_senskeys(sensor);
|
||||||
|
FREE(sensor->levels);
|
||||||
|
FREE(sensor->sensname);
|
||||||
|
}
|
||||||
|
FREE(sensors);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop server process
|
||||||
|
void stop_server(){
|
||||||
|
FNAME();
|
||||||
|
isrunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set request interval to this value
|
||||||
|
void set_reqinterval(double dt){
|
||||||
|
req_interval = dt; // all checking is in `parseargs.c`
|
||||||
|
}
|
||||||
|
|
||||||
|
// set network timeout
|
||||||
|
void set_nettimeout(double dt){
|
||||||
|
net_timeout = dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief send_request - send request to server
|
||||||
|
* @param cmd - command index
|
||||||
|
* @return true if OK, false if disconnected
|
||||||
|
*/
|
||||||
|
static bool send_request(sl_sock_t *sock, const char *req){
|
||||||
|
if(sl_sock_sendstrmessage(sock, req) < 1){
|
||||||
|
LOGERR("Can't send request '%s'", req);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reinit logs at start or after rotating
|
||||||
|
// @return false if failed
|
||||||
|
bool reinit_logs(){
|
||||||
|
FNAME();
|
||||||
|
logreinit = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkDBpath(const char *path){
|
||||||
|
struct stat path_stat;
|
||||||
|
if(stat(path, &path_stat)){
|
||||||
|
WARNX("Can't stat() %s", path);
|
||||||
|
return false; // `stat` failed
|
||||||
|
}
|
||||||
|
if(!S_ISDIR(path_stat.st_mode)){
|
||||||
|
WARNX("%s isn't a directory", path);
|
||||||
|
return false; // not a directory
|
||||||
|
}
|
||||||
|
// now check if we can write there
|
||||||
|
if(access(path, W_OK)){
|
||||||
|
WARNX("Can't write to %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find if this sensor already have opened BD;
|
||||||
|
// if have, modify sensor->fd and sensor->path, open file for appending and return `true`
|
||||||
|
static bool find_old_file(senslog_t *sensor){
|
||||||
|
if(sensor->fd > -1){
|
||||||
|
DBG("found opened file %s with fd %d -> close", sensor->path, sensor->fd);
|
||||||
|
close(sensor->fd);
|
||||||
|
return false; // we need to open new file after logrotating
|
||||||
|
}
|
||||||
|
|
||||||
|
regex_t regex;
|
||||||
|
|
||||||
|
// Compile regex
|
||||||
|
if(regcomp(®ex, dbfilename_regex, REG_EXTENDED | REG_NOSUB) != 0){
|
||||||
|
LOGERR("find_old_file(): error in regcomp(), %s", strerror(errno));
|
||||||
|
WARNX("regcomp()");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
DIR *d = opendir(DBpath);
|
||||||
|
if(d){
|
||||||
|
struct dirent *dir;
|
||||||
|
while((dir = readdir(d))){
|
||||||
|
// Check if filename matches regex
|
||||||
|
if(regexec(®ex, dir->d_name, 0, NULL, 0) == 0){
|
||||||
|
DBG("Found: %s", dir->d_name);
|
||||||
|
char fname[PATH_MAX], line[BUFSIZ];
|
||||||
|
snprintf(fname, PATH_MAX, "%s/%s", DBpath, dir->d_name);
|
||||||
|
FILE *fp = fopen(fname, "r");
|
||||||
|
if(!fp){
|
||||||
|
LOGERR("Cannot open %s for reading: %s", fname, strerror(errno));
|
||||||
|
DBG("Can't open");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(fgets(line, sizeof(line), fp) == NULL){
|
||||||
|
LOGWARN("Found empty BD file %s - WTF???", fname);
|
||||||
|
fclose(fp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fclose(fp);
|
||||||
|
int len = strlen(line);
|
||||||
|
if(len > 0 && line[len-1] == '\n') line[len-1] = 0; // remove trailing newline
|
||||||
|
if(line[0] != '#' || line[1] != ' '){ // should starts from comment with station's name
|
||||||
|
LOGWARN("Found broken database file: %s", fname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const char *stname = line + 2; // station name from comment
|
||||||
|
if(0 == strcmp(stname, sensor->sensname)){ // good, we found this file!
|
||||||
|
DBG("Found existant file %s -> append to it", fname);
|
||||||
|
int newfd = open(fname, O_WRONLY | O_APPEND);
|
||||||
|
if(newfd < 0){
|
||||||
|
LOGERR("Can't open existant BD file %s for append, try to create new", fname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
sensor->fd = newfd;
|
||||||
|
ret = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
closedir(d);
|
||||||
|
}else{
|
||||||
|
LOGERR("Can't open %s: %s", DBpath, strerror(errno));
|
||||||
|
}
|
||||||
|
regfree(®ex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new database file in `DBpath`
|
||||||
|
static bool create_db_file(senslog_t *sensor){
|
||||||
|
FNAME();
|
||||||
|
int num = 0;
|
||||||
|
char path[PATH_MAX];
|
||||||
|
for(; num <= 99; ++num){
|
||||||
|
snprintf(path, PATH_MAX, dbfilename_mask, DBpath, num);
|
||||||
|
DBG("Try to create %s", path);
|
||||||
|
if(access(path, F_OK) != 0) break; // no such file
|
||||||
|
}
|
||||||
|
if(num > 99){
|
||||||
|
LOGERR("Can't find free filename for station '%s', all numbers from 0 to 99 are busy! WTF???",
|
||||||
|
sensor->sensname);
|
||||||
|
WARNX("No free numbers for sensors");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// create and open write-only
|
||||||
|
int newfd = open(path, O_WRONLY | O_CREAT, 0644);
|
||||||
|
if(newfd < 0){
|
||||||
|
LOGERR("Can't open file %s for station %s: %s", path, sensor->sensname, strerror(errno));
|
||||||
|
WARNX("Can't open %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DBG("OK, %s opened, try to write header", path);
|
||||||
|
int len = snprintf(path, PATH_MAX, "# %s\n", sensor->sensname);
|
||||||
|
if(write(newfd, path, len) != len){
|
||||||
|
LOGERR("Can't write sensor's name '%s' to file %s: %s", sensor->sensname, path, strerror(errno));
|
||||||
|
WARNX("Can't write header");
|
||||||
|
close(newfd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DBG("%s now have descriptor %d: %s", sensor->sensname, newfd, path);
|
||||||
|
sensor->fd = newfd;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_sensor_keys(senslog_t _U_ *sensor, sl_sock_t _U_ *sock){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare BD files and fill `sensors[i].fd` fields; make header in new file or move write pointer to the end of existant
|
||||||
|
// `sensors` should be prepared already
|
||||||
|
// this function called at start and on any logs reinit
|
||||||
|
static bool prepare_files(sl_sock_t *sock){
|
||||||
|
if(!sock || !DBpath || Nsensors < 1) return false;
|
||||||
|
for(int i = 0; i < Nsensors; ++i){
|
||||||
|
senslog_t *sensor = &sensors[i];
|
||||||
|
if(!get_sensor_keys(sensor, sock)) return false;
|
||||||
|
DBG("Check if there's something for sensor[%d]", i);
|
||||||
|
if(!find_old_file(sensor)){ // create new file
|
||||||
|
DBG("Nothing found, try to create new");
|
||||||
|
if(!create_db_file(sensor)){
|
||||||
|
DBG("Oops: can't create");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBG("All ready!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool analyse_list(sl_sock_t *sock){
|
||||||
|
FNAME();
|
||||||
|
char str[BUFSIZ], key[SL_KEY_LEN], value[SL_VAL_LEN];
|
||||||
|
double tlast = sl_dtime();
|
||||||
|
while(sl_dtime() - tlast < net_timeout){
|
||||||
|
ssize_t len = sl_sock_readline(sock, str, BUFSIZ-1);
|
||||||
|
if(len == 0) continue;
|
||||||
|
if(len < 0){
|
||||||
|
WARNX("Seems like server disconnected");
|
||||||
|
LOGWARN("Server disconnected?");
|
||||||
|
sensors_delete();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tlast = sl_dtime();
|
||||||
|
DBG("Got answer: %s", str);
|
||||||
|
if(2 != sl_get_keyval(str, key, value)){
|
||||||
|
LOGWARN("Wrong answer from meteodaemon for 'list' request: %s", str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DBG("key: '%s', value: '%s'", key, value);
|
||||||
|
int idx;
|
||||||
|
// we don't need `str` now and can use it again
|
||||||
|
if(2 != sscanf(key, "%[^[][%d]", str, &idx)){
|
||||||
|
LOGWARN("Wrong key format: '%s'", key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DBG("Got key '%s' with idx=%d", str, idx);
|
||||||
|
if(Nsensors <= idx){
|
||||||
|
int oldN = Nsensors;
|
||||||
|
Nsensors = idx + 1;
|
||||||
|
sensors = realloc(sensors, Nsensors * sizeof(senslog_t));
|
||||||
|
if(!sensors){
|
||||||
|
sensors_delete();
|
||||||
|
LOGERR("analyse_list(): error in realloc()");
|
||||||
|
WARNX("analyse_list(): error in realloc()");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bzero(&sensors[oldN], sizeof(senslog_t)*(Nsensors - oldN));
|
||||||
|
}
|
||||||
|
senslog_t *sensor = &sensors[idx];
|
||||||
|
if(0 == strcmp(str, key_pluginname)){ // found plugin name -> fill this field
|
||||||
|
if(sensor->sensname) free(sensor->sensname);
|
||||||
|
sensor->sensname = strdup(value);
|
||||||
|
sensor->idx = idx;
|
||||||
|
sensor->fd = -1; // not inited yet
|
||||||
|
DBG("name[%d]=%s", idx, sensor->sensname);
|
||||||
|
}else if(0 == strcmp(str, key_nvalues)){
|
||||||
|
int nvalues = atoi(value);
|
||||||
|
if(nvalues < 1){
|
||||||
|
LOGWARN("wrong server's responce for sensor[%d] values amount: %d", idx, nvalues);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(sensor->nvalues) delete_senskeys(sensor);
|
||||||
|
sensor->nvalues = nvalues;
|
||||||
|
DBG("sensor[%d] have %d values", idx, sensor->nvalues);
|
||||||
|
sensor->keys = MALLOC(char*, nvalues); // prepare empty array for keywords
|
||||||
|
sensor->levels = MALLOC(int, nvalues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// now check all we got
|
||||||
|
if(Nsensors < 1){
|
||||||
|
LOGWARN("Found 0 sensors in server's answer");
|
||||||
|
WARNX("Found 0 sensors in server's answer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ans = true;
|
||||||
|
for(int i = 0; i < Nsensors; ++i){
|
||||||
|
senslog_t *sensor = &sensors[i];
|
||||||
|
DBG("Check sensor %s with values %d", sensor->sensname, sensor->nvalues);
|
||||||
|
if(sensor->nvalues < 1){
|
||||||
|
LOGWARN("Zero keys for station %s", sensor->sensname);
|
||||||
|
ans = false; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ans) ans = prepare_files(sock);
|
||||||
|
if(ans == false) sensors_delete();
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*for(int i = 0; i < nvalues; ++i){
|
||||||
|
sensor->keys[i] = ;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// prepare DB files at start
|
||||||
|
static bool prepare_logfiles(sl_sock_t *sock, const char *path){
|
||||||
|
FNAME();
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
if(DBpath) return false; // already inited??
|
||||||
|
if(!checkDBpath(path)) return false;
|
||||||
|
DBpath = strdup(path);
|
||||||
|
if(!DBpath){
|
||||||
|
WARN("strdup()");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DBG("Store files in %s; send `list` request", DBpath);
|
||||||
|
snprintf(buf, 255, "%s\n", commands[CMD_LIST]);
|
||||||
|
if(!send_request(sock, buf)){
|
||||||
|
WARNX("Can't send inited request");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// now we have an answer: 2*N strings a la "PLUGIN[i]=...\nNVALUES[i]=...\n" ->
|
||||||
|
// prepare `sensors` and try to find opened files like `sensorXX.log`
|
||||||
|
if(!analyse_list(sock)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief run_server - run main server: send weather requests and store data
|
||||||
|
* @param node - node to connect
|
||||||
|
* @param type - socket type
|
||||||
|
* @param path - directory where to store data logs
|
||||||
|
*/
|
||||||
|
void run_server(const char *node, sl_socktype_e type, const char *path){
|
||||||
|
if(!node || !path) return;
|
||||||
|
sl_sock_t *sock = sl_sock_run_client(type, node, BUFSIZ);
|
||||||
|
if(!sock){
|
||||||
|
DBG("Can't connect");
|
||||||
|
LOGERR("Can't connect to %s", node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!prepare_logfiles(sock, path)) return;
|
||||||
|
|
||||||
|
DBG("Superloop");
|
||||||
|
int errctr = 0;
|
||||||
|
while(isrunning){
|
||||||
|
if(logreinit){
|
||||||
|
if(!prepare_files(sock)) ++errctr;
|
||||||
|
else logreinit = false;
|
||||||
|
}
|
||||||
|
if(errctr > 5){
|
||||||
|
LOGERR("Too much errors -> exit");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
if(sock) sl_sock_delete(&sock);
|
||||||
|
sensors_delete();
|
||||||
|
}
|
||||||
28
Daemons/weather_logger/server.h
Normal file
28
Daemons/weather_logger/server.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the meteologger 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>
|
||||||
|
|
||||||
|
bool checkDBpath(const char *path);
|
||||||
|
void stop_server();
|
||||||
|
void set_reqinterval(double dt);
|
||||||
|
void set_nettimeout(double dt);
|
||||||
|
void run_server(const char *node, sl_socktype_e type, const char *path);
|
||||||
|
bool reinit_logs();
|
||||||
Reference in New Issue
Block a user