mirror of
https://github.com/eddyem/small_tel.git
synced 2026-05-07 13:27:06 +03:00
Compare commits
12 Commits
5be6876f9e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 46de782019 | |||
| 63f87d2283 | |||
| c5b9d43797 | |||
| 2413661e19 | |||
| 05e57ef012 | |||
| 27cfe60fe8 | |||
| 347d02e748 | |||
| 68febd02c4 | |||
|
|
7b2d93299d | ||
| 5acd1cd97d | |||
| af33a036c8 | |||
| 987cf022fe |
117
Daemons/2DO.txt
Normal file
117
Daemons/2DO.txt
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
(хорошо бы нигде не ориентироваться на DNS, везде писать IP)
|
||||||
|
|
||||||
|
### superweatherdaemon ###
|
||||||
|
|
||||||
|
Все "станции" (* - готово):
|
||||||
|
- старая метео *
|
||||||
|
- новая метео *
|
||||||
|
- датчик дождя *
|
||||||
|
- датчик грозы (молния <=5км -> FORCEOFF=1; молния >=10км - FORCEOFF=0)
|
||||||
|
поля: LIGTPWR, LIGTDIST, LIGTTIME
|
||||||
|
- ИБП в стойке (через минуту после выключения света FORCEOFF=1; через пять минут после включения - FORCEOFF=0)
|
||||||
|
поля: POWERED (0 - внешнее питание комплекса отключено, 1 - включено)
|
||||||
|
- ИК-allsky (как будет готов - получение "процента облачности" и "температуры неба")
|
||||||
|
поля: CLOUDS, SKYTEMP
|
||||||
|
|
||||||
|
В выдаваемую клиентам информацию добавить "стандартное" поле FORCEOFF (если ==1 - парковаться, закрываться и выключаться,
|
||||||
|
в т.ч. компьютер).
|
||||||
|
|
||||||
|
Только ИБП и датчик грозы влияют на поле FORCEOFF. FORCEOFF автоматом ставит WEATHER=3.
|
||||||
|
Флаг FORCEOFF - исключительная прерогатива "супердемона", как и поле WEATHER. Значение флага снимается лишь по устареванию,
|
||||||
|
как для прочих полей с проверкой на слишком старые данные. Лишь если снят FORCEOFF, поле WEATHER может начать понижать
|
||||||
|
уровень. Сразу со снятием флага FORCEOFF, снижаем уровень WEATHER до 2. Следовательно, запускать наблюдения можно будет
|
||||||
|
лишь через 2N секунд (кстати, добавить в конфиг время ожидания понижения уровня погоды) после подачи питания.
|
||||||
|
|
||||||
|
??? Добавить "стандартные" поля: LIGTPWR, LIGTDIST, LIGTTIME (например, если гроза еще дальше 5км, писать эти данные
|
||||||
|
в FITS-шапку).
|
||||||
|
|
||||||
|
Обязательно логгировать источники повышения и понижения уровня погоды, а также источник установки флага FORCEOFF.
|
||||||
|
|
||||||
|
В управляющий UNIX-сокет добавить комады:
|
||||||
|
- forceoff - чтобы можно было вручную ставить/снимать флаг
|
||||||
|
- weath - вручную сменять уровень погоды
|
||||||
|
- mute/unmute плагинов (по номеру из list); если плагин mute, то вся информация от него игнорируется
|
||||||
|
- ?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Демон общего питания (robometeo) ###
|
||||||
|
|
||||||
|
Мониторит FORCEOFF. При ==1 выжидает OFF_T_WAIT секунд, затем начинает проверять все 5 "роботелов": если купол закрыт и не пингуется,
|
||||||
|
отключаем питание оборудования и питание купола (кроме первого). Через OFF_T_FORCE секунд после сигнала даже если комп пингуется,
|
||||||
|
но купол закрыт, питание отключаем. Интервалы времени - в конфигурационный файл.
|
||||||
|
|
||||||
|
Если все отключено, ждет, пока не наступит 0. Потом выжидает еще ON_WAIT секунд, после чего последовательно подает питание
|
||||||
|
на все купола, выжидает ON_FORCE секунд и поочередно включает нагрузку всех ИБП куполов. Проверяет пингуемость компов и
|
||||||
|
пишет в лог, во сколько какой запинговался.
|
||||||
|
|
||||||
|
Аналогично с приборами в стойке: после OFF_R_WAIT секунд пинговать roboserv, robonas и robostorage; как не пингуются все -
|
||||||
|
выключать питание стойки. Либо же принудительно отключать после OFF_R_FORCE секунд. При снятии флага выжидаем ON_WAIT секунд,
|
||||||
|
затем включаем напряжение стойки и пингуем ее компы, логгируем.
|
||||||
|
|
||||||
|
В конфиг-файл: IP-адреса и номера релюшек оборудования телескопов и стойки, например,
|
||||||
|
|
||||||
|
telescopes=Astro-M1, Astro-M2...
|
||||||
|
Astro-M1=192.168.70.33:1:-1 # Название из telescopes = IP:номер реле оборудования:номер реле питания или -1
|
||||||
|
Astro-M2=192.168.70.35:2:22
|
||||||
|
...
|
||||||
|
equipment=roboserv,robonas,robostorage
|
||||||
|
roboserv=192.168.70.6:11:-1 # аналогично должен вести себя 192.168.70.6:-1:11
|
||||||
|
...
|
||||||
|
|
||||||
|
(при разборе конфига группировать по номеру реле оборудования и номеру реле питания; не отключать реле питания, пока
|
||||||
|
не отключены все компы, кроме выхода таймаута)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### weather proxy ###
|
||||||
|
|
||||||
|
Добавить флаг forceoff.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Clients ###
|
||||||
|
|
||||||
|
Все клиентские демоны используют weather_proxy и его библиотеку, чтобы получать из SHM погодные данные.
|
||||||
|
Демоны купола, телескопа и монтировки обязаны создавать в /tmp файлы с FITS-шапками (по ним "демон питания" узнает, что
|
||||||
|
можно выключать компьютер).
|
||||||
|
|
||||||
|
Клиенты реагируют на WEATHER:
|
||||||
|
0 - можно работать
|
||||||
|
1 - нельзя открываться, но можно продолжать работать
|
||||||
|
2 - форсированное закрывание купола, останов телескопа, остальное без изменений
|
||||||
|
3 - закрывание всего, парковка телескопа
|
||||||
|
|
||||||
|
|
||||||
|
## Демон купола ##
|
||||||
|
|
||||||
|
Форсированное закрывание при WEATHER>1 или FORCEOFF=1.
|
||||||
|
|
||||||
|
|
||||||
|
## Демон телескопа ##
|
||||||
|
|
||||||
|
Форсированное закрывание при WEATHER>2 или FORCEOFF=1.
|
||||||
|
|
||||||
|
|
||||||
|
## Демон монтировки ##
|
||||||
|
|
||||||
|
Останов при WEATHER=2 или FORCEOFF=1, парковка при WEATHER=3 (в случае FORCEOFF=1 ни в коем случае не парковать).
|
||||||
|
|
||||||
|
ДОБАВИТЬ ключ HDRTIME // UNIX-time of last activity -> в этом ключе содержится sl_dtime() обновления шапки (нужно для
|
||||||
|
демона питания, чтобы контролировать, когда отмерла монтировка).
|
||||||
|
|
||||||
|
|
||||||
|
## Демон питания оборудования и компьютера ##
|
||||||
|
|
||||||
|
Этот демон запускается из-под рута. Принимает от пользователя команды вклчения-выключения оборудования (сюда же можно
|
||||||
|
воткнуть управление плоским полем, пинание монтировки на включение, управление светом).
|
||||||
|
|
||||||
|
При получении FORCEOFF=1, сначала щелкается кнопка отключения монтировки. Далее
|
||||||
|
проверяются DOMFITSHDR (DOMESTAT= closed), TELFITSHDR (TELSTAT = closed) и MOUNTFITSHDR (HDRTIME должен быть минимум
|
||||||
|
на 30 секунд старше текущего времени).
|
||||||
|
Если все ОК, то выключается навесное оборудование и монтировка, а следом - и сам компьютер получает сигнал poweroff.
|
||||||
|
|
||||||
|
sync(); // Flush disk buffer to prevent data loss
|
||||||
|
reboot(RB_POWER_OFF);
|
||||||
|
или (правильней?): system("shutdown -P now")
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -Wall -Wextra -fPIC
|
CFLAGS = -Wall -Wextra -fPIC -DEBUG
|
||||||
LDFLAGS = -lrt -pthread
|
LDFLAGS = -lrt -pthread -lusefull_macros
|
||||||
|
|
||||||
all: weather_daemon libweather.so weather_clt_example
|
all: weather_daemon libweather.so weather_clt_example
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,21 @@
|
|||||||
#include "weather_data.h"
|
/*
|
||||||
|
* This file is part of the weather_proxy 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 <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -9,8 +26,7 @@
|
|||||||
#include <semaphore.h>
|
#include <semaphore.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define SHM_NAME "/weather_shm"
|
#include "weather_data.h"
|
||||||
#define SEM_NAME "/weather_sem"
|
|
||||||
|
|
||||||
int get_weather_data(weather_data_t *data) {
|
int get_weather_data(weather_data_t *data) {
|
||||||
int shm_fd;
|
int shm_fd;
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weather_proxy 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 "weather_data.h"
|
#include "weather_data.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
weather_data_t wd;
|
weather_data_t wd;
|
||||||
if (get_weather_data(&wd) == 0) {
|
if(get_weather_data(&wd) == 0){
|
||||||
printf("Weather: %d, Max wind: %.1f, Wind: %.1f, Temp: %.1f; updated @%.1f\n",
|
char strt[64];
|
||||||
wd.weather, wd.windmax, wd.wind, wd.exttemp, wd.last_update);
|
struct tm *T = localtime(&wd.last_update);
|
||||||
} else {
|
strftime(strt, 63, "%F %T", T);
|
||||||
|
printf("Prohibited: %d\nWeather: %d\nMax wind: %.1f\nWind: %.1f\nTemp: %.1f\nPressure: %.1f\nHumidity: %.1f\nupdated @%zd (%s)\n",
|
||||||
|
wd.prohibited, wd.weather, wd.windmax, wd.wind, wd.exttemp, wd.pressure, wd.humidity,
|
||||||
|
wd.last_update, strt);
|
||||||
|
}else{
|
||||||
fprintf(stderr, "Failed to get weather data\n");
|
fprintf(stderr, "Failed to get weather data\n");
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -1,284 +1,367 @@
|
|||||||
#include "weather_data.h"
|
/*
|
||||||
#include <stdio.h>
|
* This file is part of the weather_proxy project.
|
||||||
#include <stdlib.h>
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
#include <string.h>
|
*
|
||||||
#include <unistd.h>
|
* This program is free software: you can redistribute it and/or modify
|
||||||
#include <errno.h>
|
* it under the terms of the GNU General Public License as published by
|
||||||
#include <sys/types.h>
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
#include <sys/socket.h>
|
* (at your option) any later version.
|
||||||
#include <netinet/in.h>
|
*
|
||||||
#include <arpa/inet.h>
|
* This program is distributed in the hope that it will be useful,
|
||||||
#include <sys/mman.h>
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
#include <sys/stat.h>
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
#include <sys/types.h>
|
* 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 <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <semaphore.h>
|
#include <semaphore.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <stdarg.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#define DEAD_TMOUT 15
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "weather_data.h"
|
||||||
|
|
||||||
|
#define DEFAULT_PID "/tmp/weather_proxy.pid"
|
||||||
|
|
||||||
|
// if we have no fresh data more than `RECONN_TMOUT`, try to reconect
|
||||||
#define RECONN_TMOUT 5
|
#define RECONN_TMOUT 5
|
||||||
#define STAT_TMOUT 60
|
// don't ask new data less than `WEAT_TMOUT` seconds
|
||||||
#define WEAT_TMOUT 5
|
#define WEAT_TMOUT 1
|
||||||
|
|
||||||
#define SHM_NAME "/weather_shm"
|
typedef struct{
|
||||||
#define SEM_NAME "/weather_sem"
|
char *node; // node of server
|
||||||
|
int isunix; // use UNIX-sockets instead of net
|
||||||
|
char *logfile; // logfile name
|
||||||
|
int verb; // verbocity level
|
||||||
|
char *pidfile; // pidfile name
|
||||||
|
char *fitsheader; // FITS-header with collected weather data
|
||||||
|
} glob_pars;
|
||||||
|
|
||||||
|
static pid_t childpid;
|
||||||
static int shm_fd = -1;
|
static int shm_fd = -1;
|
||||||
|
static int forbidden = 0;
|
||||||
static sem_t *sem = NULL;
|
static sem_t *sem = NULL;
|
||||||
static weather_data_t *shared_data = NULL;
|
static weather_data_t *shared_data = NULL;
|
||||||
static volatile int running = 1;
|
static volatile int running = 1;
|
||||||
|
static glob_pars G = {.pidfile = DEFAULT_PID};
|
||||||
|
|
||||||
#define log_message(...) do{printf("message: "); printf(__VA_ARGS__); printf("\n");}while(0)
|
static sl_option_t opts[] = {
|
||||||
#define log_error(...) do{printf("error: "); printf(__VA_ARGS__); printf("\n");}while(0)
|
{"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)"},
|
||||||
|
{"fitsheader",NEED_ARG, NULL, 'f', arg_string, APTR(&G.fitsheader),"fits-header for weather data"},
|
||||||
|
end_option
|
||||||
|
};
|
||||||
|
|
||||||
static void signal_handler(int sig) {
|
void signals(int signo){
|
||||||
if (sig == SIGTERM || sig == SIGINT) {
|
if(signo){
|
||||||
running = 0;
|
if(signals != signal(signo, SIG_IGN)) exit(signo); // function called "as is", before sig registration
|
||||||
log_message("Received signal %d, shutting down", sig);
|
if(childpid == 0){ // child -> test USR1/USR2
|
||||||
|
LOGDBG("Child gotta signal %d", signo);
|
||||||
|
if(signo == SIGUSR1){
|
||||||
|
forbidden = 1;
|
||||||
|
LOGMSG("Got signal `observations forbidden`");
|
||||||
|
signal(signo, signals);
|
||||||
|
return;
|
||||||
|
}else if(signo == SIGUSR2){
|
||||||
|
forbidden = 0;
|
||||||
|
LOGMSG("Got signal `observations permitted`");
|
||||||
|
signal(signo, signals);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(childpid){ // master
|
||||||
|
LOGERR("Main process exits with status %d", signo);
|
||||||
|
if(G.pidfile) unlink(G.pidfile);
|
||||||
|
if(G.fitsheader) unlink(G.fitsheader);
|
||||||
|
exit(1);
|
||||||
|
}else{ // child
|
||||||
|
if(running){
|
||||||
|
LOGERR("Stop running");
|
||||||
|
running = 0; // let make cleanup
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int init_ipc(void) {
|
static int init_ipc(void){
|
||||||
umask(0); // for read-write semaphore
|
umask(0); // for read-write semaphore
|
||||||
shm_fd = shm_open(SHM_NAME, O_RDWR, 0644); // try to open existant SHM
|
shm_fd = shm_open(SHM_NAME, O_RDWR, 0644); // try to open existant SHM
|
||||||
if (shm_fd == -1) {
|
if(shm_fd == -1){
|
||||||
printf("Create new shared memory\n");
|
printf("Create new shared memory\n");
|
||||||
// no - create new
|
// no - create new
|
||||||
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
|
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
|
||||||
if (shm_fd == -1) {
|
if(shm_fd == -1){
|
||||||
log_error("shm_open (create) failed: %s", strerror(errno));
|
LOGERR("shm_open (create) failed: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (ftruncate(shm_fd, sizeof(weather_data_t)) == -1) {
|
if(ftruncate(shm_fd, sizeof(weather_data_t)) == -1){
|
||||||
log_error("ftruncate failed: %s", strerror(errno));
|
LOGERR("ftruncate failed: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ | PROT_WRITE,
|
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ | PROT_WRITE,
|
||||||
MAP_SHARED, shm_fd, 0);
|
MAP_SHARED, shm_fd, 0);
|
||||||
if (shared_data == MAP_FAILED) {
|
if(shared_data == MAP_FAILED){
|
||||||
log_error("mmap failed: %s", strerror(errno));
|
LOGERR("mmap failed: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
// default values to data
|
// default values to data
|
||||||
} else {
|
}else{
|
||||||
printf("Use existant SHM\n");
|
DBG("Use existant SHM\n");
|
||||||
// use existant SHM
|
|
||||||
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ | PROT_WRITE,
|
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ | PROT_WRITE,
|
||||||
MAP_SHARED, shm_fd, 0);
|
MAP_SHARED, shm_fd, 0);
|
||||||
if (shared_data == MAP_FAILED) {
|
if(shared_data == MAP_FAILED){
|
||||||
log_error("mmap failed: %s", strerror(errno));
|
LOGERR("mmap failed: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(shared_data, 0, sizeof(weather_data_t));
|
memset(shared_data, 0, sizeof(weather_data_t));
|
||||||
|
|
||||||
// create samaphore if no
|
// create samaphore if no
|
||||||
sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
|
sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
|
||||||
if (sem == SEM_FAILED) {
|
if(sem == SEM_FAILED){
|
||||||
log_error("sem_open failed: %s", strerror(errno));
|
LOGERR("sem_open failed: %s", strerror(errno));
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// free IPC
|
// free IPC
|
||||||
static void cleanup_ipc(void) {
|
static void cleanup_ipc(void){
|
||||||
if (sem != NULL) {
|
if (sem != NULL) {
|
||||||
sem_close(sem);
|
sem_close(sem);
|
||||||
printf("semaphore closed\n");
|
DBG("semaphore closed\n");
|
||||||
if(-1 == sem_unlink(SEM_NAME)) perror("Can't delete semaphore");
|
if(-1 == sem_unlink(SEM_NAME)) LOGERR("Can't delete semaphore");
|
||||||
}
|
}
|
||||||
if (shared_data != NULL) {
|
if(shared_data != NULL){
|
||||||
printf("memory unmapped\n");
|
DBG("memory unmapped\n");
|
||||||
munmap(shared_data, sizeof(weather_data_t));
|
munmap(shared_data, sizeof(weather_data_t));
|
||||||
}
|
}
|
||||||
if (shm_fd != -1) {
|
if(shm_fd != -1){
|
||||||
close(shm_fd);
|
close(shm_fd);
|
||||||
printf("close shared mem\n");
|
DBG("close shared mem\n");
|
||||||
if (shm_unlink(SHM_NAME) == -1) {
|
if(shm_unlink(SHM_NAME) == -1){
|
||||||
perror("can't unlink SHM");
|
LOGERR("can't unlink SHM");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update record of FITS header
|
||||||
|
static void FITS_update(const char *line, int finish){
|
||||||
|
static FILE *tmp = NULL;
|
||||||
|
static char templ[32]; // temporary file name
|
||||||
|
if(!tmp){ // try to create new temporary file
|
||||||
|
sprintf(templ, "/tmp/fitshdrXXXXXX");
|
||||||
|
int fd = mkstemp(templ);
|
||||||
|
if(fd < 0){
|
||||||
|
WARN("mkstemp()");
|
||||||
|
LOGERR("Can't create temporary file!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tmp = fdopen(fd, "w");
|
||||||
|
if(!tmp){
|
||||||
|
WARN("fdopen()");
|
||||||
|
LOGERR("Error in fdopen()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(tmp, "%s\n", line);
|
||||||
|
if(finish){ // move temporary file into new location
|
||||||
|
fclose(tmp);
|
||||||
|
tmp = NULL;
|
||||||
|
chmod(templ, 0644);
|
||||||
|
if(rename(templ, G.fitsheader) < 0){
|
||||||
|
WARN("rename(%s, %s)", templ, G.fitsheader);
|
||||||
|
LOGERR("Error in rename()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void update_shm(weather_data_t *data){
|
||||||
|
if(sem_wait(sem) == -1){
|
||||||
|
LOGWARN("sem_wait failed: %s", strerror(errno));
|
||||||
|
}else{
|
||||||
|
memcpy(shared_data, data, sizeof(weather_data_t));
|
||||||
|
sem_post(sem);
|
||||||
|
LOGDBG("Weather data updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void parse_line(const char *line, weather_data_t *data) {
|
static void parse_line(const char *line, weather_data_t *data) {
|
||||||
char key[64];
|
char key[SL_KEY_LEN];
|
||||||
char value[256];
|
char value[SL_VAL_LEN];
|
||||||
if (sscanf(line, "%63[^=]=%255s", key, value) == 2) {
|
|
||||||
if (strcmp(key, "weather") == 0) {
|
int update = 0; // 0 for updating, 1 for finishing, -1 for error
|
||||||
if (strcmp(value, "good") == 0)
|
|
||||||
data->weather = WEATHER_GOOD;
|
if(sl_get_keyval(line, key, value)){
|
||||||
else if (strcmp(value, "bad") == 0)
|
if(strcmp(key, "WEATHER") == 0){
|
||||||
data->weather = WEATHER_BAD;
|
data->weather = (weather_condition_t) atoi(value);
|
||||||
else if (strcmp(value, "terrible") == 0)
|
|
||||||
data->weather = WEATHER_TERRIBLE;
|
|
||||||
printf("got weather: %d\n", data->weather);
|
printf("got weather: %d\n", data->weather);
|
||||||
} else if (strcmp(key, "Windmax") == 0) {
|
}else if (strcmp(key, "WINDMAX1") == 0){
|
||||||
data->windmax = atof(value);
|
data->windmax = atof(value);
|
||||||
printf("got windmax: %g\n", data->windmax);
|
printf("got windmax: %g\n", data->windmax);
|
||||||
} else if (strcmp(key, "Rain") == 0) {
|
}else if (strcmp(key, "PRECIP") == 0){
|
||||||
data->rain = atoi(value);
|
data->rain = atoi(value);
|
||||||
printf("got rain: %d\n", data->rain);
|
printf("got rain: %d\n", data->rain);
|
||||||
} else if (strcmp(key, "Clouds") == 0) {
|
}else if (strcmp(key, "CLOUDS") == 0){
|
||||||
data->clouds = atof(value);
|
data->clouds = atof(value);
|
||||||
printf("got clouds: %g\n", data->clouds);
|
printf("got clouds: %g\n", data->clouds);
|
||||||
} else if (strcmp(key, "Wind") == 0) {
|
}else if (strcmp(key, "WIND") == 0){
|
||||||
data->wind = atof(value);
|
data->wind = atof(value);
|
||||||
printf("got wind: %g\n", data->wind);
|
printf("got wind: %g\n", data->wind);
|
||||||
} else if (strcmp(key, "Temperature") == 0) {
|
}else if (strcmp(key, "EXTTEMP") == 0){
|
||||||
data->exttemp = atof(value);
|
data->exttemp = atof(value);
|
||||||
printf("got temp: %g\n", data->exttemp);
|
printf("got temp: %g\n", data->exttemp);
|
||||||
} else if (strcmp(key, "Pressure") == 0) {
|
}else if (strcmp(key, "PRESSURE") == 0){
|
||||||
data->pressure = atof(value);
|
data->pressure = atof(value);
|
||||||
printf("got pressure: %g\n", data->pressure);
|
printf("got pressure: %g\n", data->pressure);
|
||||||
} else if (strcmp(key, "Humidity") == 0) {
|
}else if (strcmp(key, "HUMIDITY") == 0){
|
||||||
data->humidity = atof(value);
|
data->humidity = atof(value);
|
||||||
printf("got humidity: %g\n", data->humidity);
|
printf("got humidity: %g\n", data->humidity);
|
||||||
} else if (strcmp(key, "prohibited") == 0) {
|
}else if (strcmp(key, "PROHIBIT") == 0){
|
||||||
data->prohibited = atoi(value);
|
data->prohibited = atoi(value);
|
||||||
} else if (strcmp(key, "Time") == 0) {
|
}else if (strcmp(key, "TMEAS") == 0){ // last line in message -> update
|
||||||
data->last_update = atof(value);
|
data->last_update = atof(value);
|
||||||
|
if(data->weather == WEATHER_PROHIBITED || forbidden) data->prohibited = 1;
|
||||||
|
else if(data->weather < WEATHER_PROHIBITED) data->prohibited = 0;
|
||||||
// update all
|
// update all
|
||||||
if (sem_wait(sem) == -1) {
|
update_shm(data);
|
||||||
log_error("sem_wait failed: %s", strerror(errno));
|
update = 1;
|
||||||
} else {
|
}else update = -1;
|
||||||
memcpy(shared_data, data, sizeof(weather_data_t));
|
if(update > -1 && G.fitsheader){
|
||||||
sem_post(sem);
|
FITS_update(line, update);
|
||||||
log_message("Weather data updated");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sock = -1;
|
static int request_weather_data(sl_sock_t *sock){
|
||||||
static FILE *sock_file = NULL;
|
static time_t tcur = 0;
|
||||||
|
char *request = "get\n";
|
||||||
static int opensock(const char *server_ip, int port){
|
|
||||||
sock = socket(AF_INET, SOCK_STREAM, 0);
|
|
||||||
if (sock < 0) {
|
|
||||||
log_error("socket creation failed: %s", strerror(errno));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct sockaddr_in server_addr;
|
|
||||||
server_addr.sin_family = AF_INET;
|
|
||||||
server_addr.sin_port = htons(port);
|
|
||||||
if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
|
|
||||||
log_error("invalid address: %s", server_ip);
|
|
||||||
close(sock);
|
|
||||||
sock = -1;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
|
|
||||||
log_error("connect failed: %s", strerror(errno));
|
|
||||||
close(sock);
|
|
||||||
sock = -1;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int flags = fcntl(sock, F_GETFL, 0);
|
|
||||||
if (flags == -1){
|
|
||||||
perror("fcntl F_GETFL");
|
|
||||||
}else{
|
|
||||||
if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) perror("fcntl F_SETFL");
|
|
||||||
}
|
|
||||||
|
|
||||||
sock_file = fdopen(sock, "r");
|
|
||||||
if (!sock_file) {
|
|
||||||
log_error("fdopen failed: %s", strerror(errno));
|
|
||||||
close(sock);
|
|
||||||
sock = -1;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int request_weather_data() {
|
|
||||||
static time_t tstat = 0, tcur = 0;
|
|
||||||
|
|
||||||
char *request = "\n";
|
|
||||||
time_t tnow = time(NULL);
|
time_t tnow = time(NULL);
|
||||||
if(tnow - tcur >= WEAT_TMOUT){
|
if(tnow - tcur >= WEAT_TMOUT){
|
||||||
tcur = tnow;
|
tcur = tnow;
|
||||||
}else if(tnow - tstat >= STAT_TMOUT){
|
|
||||||
tstat = tnow;
|
|
||||||
request = "stat60\n";
|
|
||||||
}else return 1; // not now
|
}else return 1; // not now
|
||||||
|
|
||||||
printf("try to send request: '%s", request);
|
DBG("try to send request: '%s", request);
|
||||||
if (send(sock, request, strlen(request), 0) < 0) {
|
if(sl_sock_sendstrmessage(sock, request) < 1){
|
||||||
log_error("send failed: %s", strerror(errno));
|
LOGWARN("Can't poll new data");
|
||||||
close(sock);
|
|
||||||
sock = -1;
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void run_daemon(const char *server_ip, int port) {
|
static void run_daemon(){
|
||||||
char line[256];
|
char line[256];
|
||||||
weather_data_t new_data;
|
weather_data_t new_data;
|
||||||
opensock(server_ip, port); // just try: in case of error reopen next time
|
sl_socktype_e stype = (G.isunix) ? SOCKT_UNIX : SOCKT_NET;
|
||||||
|
DBG("Try to connect to %s", G.node);
|
||||||
|
sl_sock_t *sock = sl_sock_run_client(stype, G.node, 4096);
|
||||||
|
if(!sock){
|
||||||
|
DBG("Can't connect");
|
||||||
|
LOGERR("Can't connect to meteodaemon over socket with node %s", G.node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGMSG("Connected to meteodaemon %s", G.node);
|
||||||
memcpy(&new_data, shared_data, sizeof(weather_data_t));
|
memcpy(&new_data, shared_data, sizeof(weather_data_t));
|
||||||
time_t lastert = time(NULL);
|
time_t lastert = time(NULL);
|
||||||
while (running) {
|
|
||||||
time_t tnow = time(NULL);
|
|
||||||
if (-1 == sock || request_weather_data() == -1) {
|
|
||||||
if(tnow - lastert > RECONN_TMOUT){ // try to reconnect
|
|
||||||
log_error("Failed to request weather data, retry");
|
|
||||||
if(-1 == opensock(server_ip, port)) lastert += 5;
|
|
||||||
else lastert = tnow;
|
|
||||||
}
|
|
||||||
}else lastert = tnow;
|
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), sock_file)) {
|
while(running){
|
||||||
line[strcspn(line, "\r\n")] = '\0';
|
time_t tnow = time(NULL);
|
||||||
printf("parse '%s'\n", line);
|
int req = -1;
|
||||||
|
if(sock) req = request_weather_data(sock);
|
||||||
|
if(req == -1){
|
||||||
|
int diff = tnow - lastert;
|
||||||
|
DBG("diff = %d", diff);
|
||||||
|
if(diff > RECONN_TMOUT){ // try to reconnect
|
||||||
|
LOGERR("Failed to request weather data, retry");
|
||||||
|
if(sock) sl_sock_delete(&sock);
|
||||||
|
if(!(sock = sl_sock_run_client(stype, G.node, 4096))){
|
||||||
|
new_data.weather = WEATHER_TERRIBLE; // no connection to weather server, don't allow to open
|
||||||
|
update_shm(&new_data);
|
||||||
|
lastert += RECONN_TMOUT;
|
||||||
|
}else{
|
||||||
|
LOGMSG("Reconnected to %s", G.node);
|
||||||
|
lastert = tnow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else if(req == 0) lastert = tnow;
|
||||||
|
|
||||||
|
while(sl_sock_readline(sock, line, 255) > 0){
|
||||||
|
DBG("Parse '%s'", line);
|
||||||
parse_line(line, &new_data);
|
parse_line(line, &new_data);
|
||||||
}
|
}
|
||||||
|
usleep(500000);
|
||||||
}
|
}
|
||||||
close(sock);
|
sl_sock_delete(&sock); // disconnect and clear memory
|
||||||
|
DBG("run_daemon() exited");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]){
|
||||||
if (argc != 3) {
|
sl_init();
|
||||||
fprintf(stderr, "Usage: %s <server_ip> <port>\n", argv[0]);
|
sl_parseargs(&argc, &argv, opts);
|
||||||
exit(EXIT_FAILURE);
|
if(!G.node) ERRX("Point node to connect");
|
||||||
|
if(G.fitsheader){
|
||||||
|
FILE *fitsfile = fopen(G.fitsheader, "w");
|
||||||
|
if(!fitsfile){
|
||||||
|
WARN("Can't create FITS header %s", G.fitsheader);
|
||||||
|
FREE(G.fitsheader);
|
||||||
|
}else fclose(fitsfile);
|
||||||
}
|
}
|
||||||
|
sl_check4running(NULL, G.pidfile);
|
||||||
const char *server_ip = argv[1];
|
if(G.logfile){
|
||||||
int port = atoi(argv[2]);
|
sl_loglevel_e lvl = LOGLEVEL_ERR + G.verb;
|
||||||
if (port <= 0 || port > 65535) {
|
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||||
fprintf(stderr, "Invalid port\n");
|
DBG("Loglevel: %d", lvl);
|
||||||
exit(EXIT_FAILURE);
|
if(!OPENLOG(G.logfile, lvl, 1)) ERRX("Can't open log file %s", G.logfile);
|
||||||
|
LOGMSG("Started");
|
||||||
}
|
}
|
||||||
|
signal(SIGTERM, signals);
|
||||||
//daemonize();
|
signal(SIGINT, signals);
|
||||||
|
signal(SIGUSR1, SIG_IGN);
|
||||||
log_message("Starting weather daemon, server %s:%d", server_ip, port);
|
signal(SIGUSR2, SIG_IGN);
|
||||||
|
|
||||||
struct sigaction sa;
|
|
||||||
memset(&sa, 0, sizeof(sa));
|
|
||||||
sa.sa_handler = signal_handler;
|
|
||||||
sigemptyset(&sa.sa_mask);
|
|
||||||
sigaction(SIGTERM, &sa, NULL);
|
|
||||||
sigaction(SIGINT, &sa, NULL);
|
|
||||||
signal(SIGPIPE, SIG_IGN);
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
#ifndef EBUG
|
||||||
|
sl_daemonize();
|
||||||
|
while(1){ // guard for dead processes
|
||||||
|
childpid = fork();
|
||||||
|
if(childpid){
|
||||||
|
LOGDBG("create child with PID %d\n", childpid);
|
||||||
|
DBG("Created child with PID %d\n", childpid);
|
||||||
|
wait(NULL);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// react for USRx only in child
|
||||||
|
signal(SIGUSR1, signals);
|
||||||
|
signal(SIGUSR2, signals);
|
||||||
|
|
||||||
if (init_ipc() != 0) {
|
if(init_ipc() != 0){
|
||||||
log_error("IPC initialization failed");
|
LOGERR("IPC initialization failed");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
run_daemon();
|
||||||
run_daemon(server_ip, port);
|
LOGDBG("Daemon is dead");
|
||||||
|
|
||||||
cleanup_ipc();
|
cleanup_ipc();
|
||||||
log_message("Weather daemon stopped");
|
LOGDBG("IPC cleaned");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,27 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
|
#define SHM_NAME "/weather_shm"
|
||||||
|
#define SEM_NAME "/weather_sem"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
WEATHER_GOOD = 0,
|
WEATHER_GOOD = 0, // may start observations
|
||||||
WEATHER_BAD = 1,
|
WEATHER_BAD = 1, // cannot start but can continue if want
|
||||||
WEATHER_TERRIBLE = 2
|
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;
|
} weather_condition_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
weather_condition_t weather;
|
weather_condition_t weather; // conditions: field "WEATHER"
|
||||||
float windmax;
|
float windmax; // maximal wind for last hour, m/s: "WINDMAX1"
|
||||||
int rain;
|
float wind; // current wind speed, m/s: "WIND"
|
||||||
float clouds;
|
float clouds; // sky "quality" (>2500 - OK): "CLOUDS"
|
||||||
float wind;
|
float exttemp; // external temperature, degC: "EXTTEMP"
|
||||||
float exttemp;
|
float pressure; // atm. pressure, mmHg: "PRESSURE"
|
||||||
float pressure;
|
float humidity; // humidity, percents: "HUMIDITY"
|
||||||
float humidity;
|
int rain; // ==1 when rainy: "PRECIP"
|
||||||
int prohibited;
|
int prohibited; // ==1 if "weather == prohibited" or got `prohibited` signal -> ready to power off
|
||||||
double last_update;
|
time_t last_update; // value of "TMEAS"
|
||||||
} weather_data_t;
|
} weather_data_t;
|
||||||
|
|
||||||
int get_weather_data(weather_data_t *data);
|
int get_weather_data(weather_data_t *data);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ set(MAJOR_VERSION "0")
|
|||||||
set(MID_VERSION "0")
|
set(MID_VERSION "0")
|
||||||
set(MINOR_VERSION "1")
|
set(MINOR_VERSION "1")
|
||||||
|
|
||||||
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
|
set(SOURCES cmdlnopts.c main.c mainweather.c sensors.c server.c)
|
||||||
|
|
||||||
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
|
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
|
||||||
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
|
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
|
||||||
@@ -19,6 +19,8 @@ option(HYDREON "Hydreon rain sensor plugin" ON)
|
|||||||
option(BTAMETEO "BTA main meteostation plugin" ON)
|
option(BTAMETEO "BTA main meteostation plugin" ON)
|
||||||
option(REINHARDT "Old Reinhardt meteostation plugin" ON)
|
option(REINHARDT "Old Reinhardt meteostation plugin" ON)
|
||||||
option(WXA100 "WXA100-06 meteostation plugin" ON)
|
option(WXA100 "WXA100-06 meteostation plugin" ON)
|
||||||
|
option(SNMP "SNMP UPS monitoring module" ON)
|
||||||
|
option(LIGHTNING "AS3935-based lightning sensor" ON)
|
||||||
|
|
||||||
# default flags
|
# default flags
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -fPIC")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -fPIC")
|
||||||
@@ -27,7 +29,7 @@ message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}")
|
|||||||
|
|
||||||
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
||||||
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\"
|
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\"
|
||||||
-DMAJOR_VERSION=\"${MAJOR_VESION}\")
|
-DMAJOR_VERSION=\"${MAJOR_VERSION}\")
|
||||||
|
|
||||||
set(CMAKE_COLOR_MAKEFILE ON)
|
set(CMAKE_COLOR_MAKEFILE ON)
|
||||||
|
|
||||||
@@ -55,8 +57,8 @@ pkg_check_modules(${PROJ} REQUIRED usefull_macros>=0.3.5)
|
|||||||
#endif()
|
#endif()
|
||||||
|
|
||||||
# static lib for sensors
|
# static lib for sensors
|
||||||
set(LIBSRC "weathlib.c")
|
set(LIBSRC fd.c weathlib.c)
|
||||||
set(LIBHEADER "weathlib.h")
|
set(LIBHEADER weathlib.h)
|
||||||
add_library(${PROJLIB} STATIC ${LIBSRC})
|
add_library(${PROJLIB} STATIC ${LIBSRC})
|
||||||
set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION})
|
set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION})
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,324 @@
|
|||||||
Weather daemon for several different weather stations
|
# superweatherdaemon Documentation
|
||||||
=====================================================
|
|
||||||
|
|
||||||
## Usage:
|
## Overview
|
||||||
|
|
||||||
```
|
**superweatherdaemon** is a weather monitoring daemon designed for astronomical observatories. It
|
||||||
Usage: weatherdaemon [args]
|
collects data from multiple heterogeneous weather stations (meteostations) via a plugin
|
||||||
Be careful: command line options have priority over config
|
architecture, computes a unified weather status, and provides control interfaces over TCP and local
|
||||||
Where args are:
|
UNIX sockets. The daemon can issue warnings, change weather level, and even trigger a forced
|
||||||
|
shutdown of instruments (e.g., close dome) when conditions become dangerous.
|
||||||
|
|
||||||
-P, --pidfile=arg pidfile name (default: /tmp/weatherdaemon.pid)
|
The project is written in C, uses CMake as its build system, and relies on the
|
||||||
-c, --conffile=arg configuration file name (consists all or a part of long-named parameters and their values (e.g. plugin=liboldweather.so)
|
[usefull_macros](https://github.com/eddyem/snippets_library) library for utilities and socket
|
||||||
-h, --help show this help
|
management.
|
||||||
-l, --logfile=arg save logs to file (default: none)
|
|
||||||
-p, --plugin=arg add this weather plugin (may be a lot of) (can occur multiple times)
|
## Dependencies
|
||||||
-v, --verb logfile verbocity level (each -v increased)
|
|
||||||
--port=arg network port to connect (default: 12345); hint: use "localhost:port" to make local net socket
|
- **Build tools**: CMake >= 4.0, C compiler with C11 support, `pkg-config`.
|
||||||
--sockpath=arg UNIX socket path (starting from '\0' for anonimous) of command socket
|
- **Library**: `usefull_macros` >= 0.3.5 (`sl_*` functions for logging, sockets, command-line parsing, etc.).
|
||||||
|
- **Optional**: `net-snmp` (for the SNMP UPS plugin).
|
||||||
|
|
||||||
|
On Gentoo/Calculate Linux, install the basic build dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
emerge dev-build/cmake dev-util/pkgconf
|
||||||
|
# usefull_macros must be installed from its own source; follow its documentation.
|
||||||
|
# For SNMP support:
|
||||||
|
emerge net-analyzer/net-snmp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Building and Installation
|
||||||
|
|
||||||
TODO: brief documentation will be here
|
1. Clone or download the source tree.
|
||||||
|
2. Create a build directory and run CMake:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Customise enabled plugins with `-D` options (all are `ON` by default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake .. -DDUMMY=OFF -DFDEXAMPLE=OFF -DHYDREON=ON -DBTAMETEO=ON ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Available plugin options:
|
||||||
|
- `DUMMY` Dummy weather station for testing.
|
||||||
|
- `FDEXAMPLE` Example file descriptor plugin.
|
||||||
|
- `HYDREON` Hydreon RG-11 rain sensor.
|
||||||
|
- `BTAMETEO` BTA 6-m telescope main meteostation (shared memory).
|
||||||
|
- `REINHARDT` Old Reinhardt meteostation.
|
||||||
|
- `WXA100` Vaisala WXA100 ultrasonic station.
|
||||||
|
- `SNMP` UPS monitoring via SNMP.
|
||||||
|
- `LIGHTNING` AS3935-based lightning sensor.
|
||||||
|
|
||||||
|
4. Building:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
su -c "make install"
|
||||||
|
```
|
||||||
|
|
||||||
|
The main executable `superweatherdaemon` is installed into `bin`; the plugin shared libraries
|
||||||
|
(`lib*.so`) go to the library directory.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The daemon can be configured entirely via command-line options, a configuration file, or both.
|
||||||
|
Command-line options take precedence.
|
||||||
|
|
||||||
|
### Command-line Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `-h`, `--help` | Show help message and exit. |
|
||||||
|
| `-c <file>`, `--conffile <file>` | Use a configuration file. Point non-existant file to get help. |
|
||||||
|
| `-l <path>`, `--logfile=<path>` | Write logs to a file (default: none). |
|
||||||
|
| `-P <path>`, `--pidfile=<path>` | PID file (default `/tmp/superweatherdaemon.pid`). |
|
||||||
|
| `-p <spec>`, `--plugin=<spec>` | Add a weather plugin; can be repeated for different plugins. Format: `path:type:device` (see below). |
|
||||||
|
| `--port=<node>` | Network port for clients (default `12345`). Use `localhost:port` for local access only. |
|
||||||
|
| `--sockpath=<path>` | UNIX socket path (start with `@` for an abstract socket). |
|
||||||
|
| `-T <seconds>`, `--pollt <seconds>` | Max polling interval in seconds (integer). |
|
||||||
|
| `-v`, `--verb` | Increase verbosity level (each `-v` adds 1). |
|
||||||
|
|
||||||
|
### Plugin Specification
|
||||||
|
|
||||||
|
A plugin is a shared library (`.so`) that provides a `sensor_init` function. It is loaded with the
|
||||||
|
`--plugin` option.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
|
||||||
|
```
|
||||||
|
--plugin=library:type:parameter
|
||||||
|
```
|
||||||
|
|
||||||
|
- `library`: path to the shared library, e.g. `libwxa100.so`.
|
||||||
|
- `type`: Connection type `D` for serial device, `U` for UNIX socket, `N` for INET socket.
|
||||||
|
- `parameter`: device path and optional speed (`/dev/ttyS0:9600`), UNIX socket name, or `host:port` for INET.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--plugin=libreinhardt.so:D:/dev/ttyS0
|
||||||
|
--plugin=libwxa100.so:D:/dev/pl2303_0
|
||||||
|
--plugin=libhydreon.so:D:/dev/ch340_0:1200
|
||||||
|
--plugin=libbtameteo.so (no device, uses shared memory)
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple plugins are listed in order of **importance** (first ones are considered primary for
|
||||||
|
weather level calculation).
|
||||||
|
|
||||||
|
### Configuration File
|
||||||
|
|
||||||
|
The configuration file uses a simple `key = value` syntax, with `#` for comments. Example:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# network port for clients
|
||||||
|
port = 4444
|
||||||
|
logfile = /var/log/meteo/superweather.log
|
||||||
|
verbose = 2
|
||||||
|
sockpath = "@weather"
|
||||||
|
pollt = 1
|
||||||
|
reinit_delay = 10
|
||||||
|
|
||||||
|
# Weather thresholds
|
||||||
|
ahtung_delay = 1800 # in seconds
|
||||||
|
good_wind = 5.0 # m/s
|
||||||
|
bad_wind = 10.0
|
||||||
|
terrible_wind = 15.0
|
||||||
|
good_humidity = 65.0 # percents
|
||||||
|
bad_humidity = 87.0
|
||||||
|
terrible_humidity = 94.0
|
||||||
|
good_clouds = 2500.0 # for reinhardt sensor in its units
|
||||||
|
bad_clouds = 2000.0
|
||||||
|
terrible_clouds = 500.0
|
||||||
|
clouds_negflag = 1 # 1 means the higher the value the better
|
||||||
|
good_sky = -40.0 # sky minus ambient temperature, degC
|
||||||
|
bad_sky = -10.0
|
||||||
|
terrible_sky = 0.0
|
||||||
|
# plugins - most important first
|
||||||
|
plugin = libwxa100.so:D:/dev/pl2303_0
|
||||||
|
plugin = libhydreon.so:D:/dev/ch340_0:1200
|
||||||
|
plugin = libbtameteo.so
|
||||||
|
plugin = libreinhardt.so:D:/dev/ttyS0
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
superweatherdaemon -c /etc/weather.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
To run on system start you can use OpenRó `rc.local` mechanism.
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
Each weather station is implemented as a shared library exporting a single function:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int sensor_init(sensordata_t *s);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `sensordata_t` structure contains all required callbacks, data pointers, and private fields.
|
||||||
|
See `weathlib.h` for the full definition.
|
||||||
|
|
||||||
|
### Plugin Lifecycle
|
||||||
|
|
||||||
|
1. **Loading**: The main daemon calls `s = sensor_new(...)` to create `sensordata_t` structure,
|
||||||
|
opens given library with `dlopen` and calls `sensor_init` on that new `s`.
|
||||||
|
2. **Initialisation**:
|
||||||
|
- Set `s->name`, `s->Nvalues`, `s->values` array.
|
||||||
|
- Configure the communication channel (file descriptor) using `getFD(s->path)`.
|
||||||
|
- Create a worker thread that periodically reads sensor data and updates `s->values`.
|
||||||
|
Don't forget to lock `s->valmutex` on any operation with `s->values`.
|
||||||
|
3. **Data delivery**: Each time new data is available, call `s->freshdatahandler(s)`
|
||||||
|
(this is set by the daemon to `dumpsensors`). The main daemon then merges the data into the
|
||||||
|
global weather evaluation.
|
||||||
|
4. **Shutdown**: The daemon calls `s->kill(s)`, which must join the thread, close the file
|
||||||
|
descriptor, and free resources. Default `common_kill` handles most of this; plugins can override it.
|
||||||
|
|
||||||
|
If the plugin is disconnected for some reason (for example, the network connection is lost), the
|
||||||
|
daemon will try to reconnect every `reinit_delay` seconds.
|
||||||
|
|
||||||
|
### Available Plugins
|
||||||
|
|
||||||
|
| Library | Sensor | Type |
|
||||||
|
|---------|--------|------|
|
||||||
|
| `libwsdummy.so` | Dummy station outputs random walk data around realistic values. | Test / Development |
|
||||||
|
| `libfdex.so` | Example of a filedescriptor based plugin. Prompts for commaseparated values. | Example |
|
||||||
|
| `libhydreon.so` | Hydreon RG-11 optical rain sensor. | Serial |
|
||||||
|
| `libbtameteo.so` | BTA 6-m telescope main meteostation (shared memory). | Shared Memory |
|
||||||
|
| `libreinhardt.so` | Old Reinhardt meteostation (serial, `?U` command). | Serial |
|
||||||
|
| `libwxa100.so` | Vaisala WXA100 ultrasonic meteostation (serial, `0R0` command). | Serial |
|
||||||
|
| `libsnmp.so` | UPS monitor via SNMP (requires net-snmp). | Network |
|
||||||
|
| `liblightning.so` | AS3935-based lightning sensor (serial). | Serial |
|
||||||
|
|
||||||
|
### Writing a New Plugin
|
||||||
|
|
||||||
|
Use the existing plugins as templates. A minimal plugin must:
|
||||||
|
|
||||||
|
- Include `weathlib.h`.
|
||||||
|
- Define an array of `val_t` describing each measured quantity.
|
||||||
|
- Implement `sensor_init`:
|
||||||
|
- Allocate `s->values`, copy the template array.
|
||||||
|
- Set `s->Nvalues`, `s->name`.
|
||||||
|
- Open the device (use `getFD(s->path)` for serial/sockets) and set `s->fdes`.
|
||||||
|
If your plugin don't need file descriptor, you must set `s->fdes` to any non-negative value.
|
||||||
|
- Create a ring buffer if needed (`sl_RB_new`).
|
||||||
|
- Start the worker thread that reads data, updates values inside `pthread_mutex_lock(&s->valmutex)`,
|
||||||
|
and calls `s->freshdatahandler(s)` (outside mutex locked).
|
||||||
|
- Return `TRUE` on success, `FALSE` on failure (call `s->kill(s)` to clean up).
|
||||||
|
- The `weathlib.h` provides helper functions: `common_onrefresh`, `common_getval`, `common_kill`.
|
||||||
|
|
||||||
|
## Weather Level Calculation
|
||||||
|
|
||||||
|
The daemon combines data from all active plugins and continuously evaluates a global **weather
|
||||||
|
level**:
|
||||||
|
|
||||||
|
- `0` **GOOD**: observations can start safely.
|
||||||
|
- `1` **BAD**: risky to start, but can continue.
|
||||||
|
- `2` **TERRIBLE**: dome must close, instruments park.
|
||||||
|
- `3` **PROHIBITED**: complete shutdown, power off equipment.
|
||||||
|
|
||||||
|
### Criteria
|
||||||
|
|
||||||
|
Each weather parameter (wind speed, humidity, clouds, sky temperature, lightning distance,
|
||||||
|
precipitation, etc.) has configurable thresholds:
|
||||||
|
|
||||||
|
- `good` below/above this (depending on sign) the condition is good.
|
||||||
|
- `bad` above this the condition is bad.
|
||||||
|
- `terrible` above this the condition is terrible.
|
||||||
|
- `prohibited` (if defined) above this the condition goes directly to PROHIBITED.
|
||||||
|
- `negflag` if `1`, a smaller value is worse (e.g., clouds).
|
||||||
|
- `shtdnflag` if `1`, entering the terrible/prohibited range also sets the **FORCE SHUTDOWN** flag.
|
||||||
|
|
||||||
|
### Special Flags
|
||||||
|
|
||||||
|
- **FORCE SHUTDOWN**: Some parameters (e.g., lightning within <= 5km, UPS on battery) carry the
|
||||||
|
`IS_FORCEDSHTDN` meaning and have `shtdnflag = 1`. When their value exceeds the terrible threshold,
|
||||||
|
the forced shutdown flag is raised, which immediately sets the weather level to PROHIBITED and is
|
||||||
|
typically used to cut power.
|
||||||
|
- **Manual FORBID**: An operator can send a signal (`SIGUSR1`) or a socket command to forbid
|
||||||
|
observations, which also forces PROHIBITED. `SIGUSR2` or a socket command clears forbidden flag.
|
||||||
|
|
||||||
|
### Hysteresis
|
||||||
|
|
||||||
|
Once a bad/terrible state is reached, the level is not lowered until `ahtung_delay` seconds have
|
||||||
|
passed since the last serious event. This prevents rapid toggling.
|
||||||
|
|
||||||
|
## Server Commands (Socket API)
|
||||||
|
|
||||||
|
The daemon listens on two interfaces:
|
||||||
|
|
||||||
|
1. **Network socket** (TCP, default port 12345) read-only (in meaning they cannot change any
|
||||||
|
parameters) access for remote clients.
|
||||||
|
2. **Local UNIX socket** (abstract or filesystem, default `@weather`) full control for local applications.
|
||||||
|
|
||||||
|
Commands are sent as plain text strings, terminated by a newline.
|
||||||
|
|
||||||
|
### Common (read-only) Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `get` | Return all collected weather data (all stations). |
|
||||||
|
| `get=<N>` | Return data from plugin `<N>`. |
|
||||||
|
| `list` | List all loaded plugins with their names and value counts. |
|
||||||
|
| `time` | Return server UNIX time (float seconds). |
|
||||||
|
| `chklevel` | Show the `sense` (importance) level of every collected parameter. |
|
||||||
|
| `chklevel=<N>` | Same for a specific plugin. |
|
||||||
|
|
||||||
|
### Local-only (read-write) Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `forbid` | Get current FORBID flag (0/1). |
|
||||||
|
| `forbid=<0/1>` | Set/clear manual FORBID. |
|
||||||
|
| `forceoff` | Get FORCE SHUTDOWN flag. |
|
||||||
|
| `forceoff=<0/1>` | Set/clear it manually. |
|
||||||
|
| `weathlevel` | Get current weather level (0-3 for GOOD-PROHIBITED). |
|
||||||
|
| `weathlevel=<0..3>` | Force weather level (use with caution). |
|
||||||
|
| `setlevel=<plugin>:<param>=<sense>,...` | Change the `sense` field of one or more sensor parameters. Example: `setlevel=1:WIND=3,HUMIDITY=3` disables wind and humidity from station 1. |
|
||||||
|
| `mute=<N>` | Stop refreshing data from plugin `<N>` (mute). |
|
||||||
|
| `unmute=<N>` | Resume refreshing. |
|
||||||
|
| `ismuted=<N>` | Return 1 if muted, 0 otherwise. |
|
||||||
|
|
||||||
|
Reply format: each line is a FITS-like `KEY = value / comment` string; commands that set something
|
||||||
|
usually echo back the variable and its new value. For `get=<N>` each `KEY` have a suffix in square
|
||||||
|
brackets number of plugin, e.g. `WIND[1]= 10.1 / Wind speed, m/s`.
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
| Signal | Effect |
|
||||||
|
|--------|--------|
|
||||||
|
| `SIGTERM`, `SIGINT`, `SIGQUIT` | Clean shutdown (removes PID file, kills plugins, destroys sockets). |
|
||||||
|
| `SIGHUP` | Ignored. |
|
||||||
|
| `SIGUSR1` | Set manual FORBID (weather level == PROHIBITED). |
|
||||||
|
| `SIGUSR2` | Clear manual FORBID. |
|
||||||
|
| `SIGPIPE` | Logged, used to detect network plugins disconnections. |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `CMakeLists.txt` | Top-level build definition. |
|
||||||
|
| `cmdlnopts.c/.h` | Command-line and configuration file parsing. |
|
||||||
|
| `main.c` | Daemon entry point, signal handlers, forking. |
|
||||||
|
| `mainweather.c/.h` | Global weather evaluation, data collection, forced shutdown. |
|
||||||
|
| `sensors.c/.h` | Plugin management (load, unload, getters). |
|
||||||
|
| `server.c/.h` | TCP and UNIX socket servers. |
|
||||||
|
| `weathlib.c/.h` | Common plugin API, value definitions, helper functions. |
|
||||||
|
| `fd.c` | Function `getFD()` to open serial devices or sockets for plugins. |
|
||||||
|
| `example.config` | Sample configuration file. |
|
||||||
|
| `plugins/CMakeLists.txt` | Build file for all plugins. |
|
||||||
|
| `plugins/*.c` | Individual plugin source files. |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The project is released under the **GNU General Public License v3.0** or later. See the headers in
|
||||||
|
the source files for the full legal text.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*For further assistance or to report issues, please contact the maintainer: Edward V. Emelianov
|
||||||
|
<edward.emelianoff@gmail.com>.*
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ int help;
|
|||||||
|
|
||||||
// default values for Gdefault & help
|
// default values for Gdefault & help
|
||||||
#define DEFAULT_PORT "12345"
|
#define DEFAULT_PORT "12345"
|
||||||
#define DEFAULT_PID "/tmp/weatherdaemon.pid"
|
#define DEFAULT_PID "/tmp/superweatherdaemon.pid"
|
||||||
|
|
||||||
// DEFAULTS
|
// DEFAULTS
|
||||||
// default global parameters
|
// default global parameters
|
||||||
@@ -49,22 +49,25 @@ static glob_pars defconf = {
|
|||||||
// only for config
|
// only for config
|
||||||
weather_conf_t WeatherConf = {
|
weather_conf_t WeatherConf = {
|
||||||
.ahtung_delay = 30*60, // 30 minutes
|
.ahtung_delay = 30*60, // 30 minutes
|
||||||
|
.reinit_delay = 60, // each 1 minute
|
||||||
.wind.good = 5., // < 5m/s - good weather
|
.wind.good = 5., // < 5m/s - good weather
|
||||||
.wind.bad = 10., // > 10m/s - bad weather
|
.wind.bad = 10., // > 10m/s - bad weather
|
||||||
.wind.terrible = 15., // > 15m/s - terrible weather
|
.wind.terrible = 15., // > 15m/s - terrible weather
|
||||||
.wind.negflag = 0,
|
|
||||||
.humidity.good = 65.,
|
.humidity.good = 65.,
|
||||||
.humidity.bad = 80.,
|
.humidity.bad = 87.,
|
||||||
.humidity.terrible = 90.,
|
.humidity.terrible = 94.,
|
||||||
.humidity.negflag = 0,
|
|
||||||
.clouds.good = 2500.,
|
.clouds.good = 2500.,
|
||||||
.clouds.bad = 2000.,
|
.clouds.bad = 2000.,
|
||||||
.clouds.terrible = 500.,
|
.clouds.terrible = 500.,
|
||||||
.clouds.negflag = 1,
|
.clouds.negflag = 1, // the higher values is the better
|
||||||
.sky.good = -40.,
|
.sky.good = -40.,
|
||||||
.sky.bad = -10.,
|
.sky.bad = -10.,
|
||||||
.sky.terrible = 0.,
|
.sky.terrible = 0.,
|
||||||
.sky.negflag = 0
|
.ligtdist.good = 60., // no lightnings near
|
||||||
|
.ligtdist.bad = 10., // 10km
|
||||||
|
.ligtdist.terrible = 5., // <=5km - ahtung!
|
||||||
|
.ligtdist.negflag = 1, // the nearest is the worse
|
||||||
|
.ligtdist.shtdnflag = 1, // force shutdown if too close
|
||||||
};
|
};
|
||||||
|
|
||||||
static glob_pars G;
|
static glob_pars G;
|
||||||
@@ -77,7 +80,7 @@ static glob_pars G;
|
|||||||
{"port", NEED_ARG, NULL, 0, arg_string, APTR(&G.port), "network port to connect (default: " DEFAULT_PORT "); hint: use \"localhost:port\" to make local net socket"}, \
|
{"port", NEED_ARG, NULL, 0, arg_string, APTR(&G.port), "network port to connect (default: " DEFAULT_PORT "); hint: use \"localhost:port\" to make local net socket"}, \
|
||||||
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file (default: none)"}, \
|
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file (default: none)"}, \
|
||||||
{"pidfile", NEED_ARG, NULL, 'P', arg_string, APTR(&G.pidfile), "pidfile name (default: " DEFAULT_PID ")"}, \
|
{"pidfile", NEED_ARG, NULL, 'P', arg_string, APTR(&G.pidfile), "pidfile name (default: " DEFAULT_PID ")"}, \
|
||||||
{"sockpath",NEED_ARG, NULL, 0, arg_string, APTR(&G.sockname), "UNIX socket path (starting from '\\0' for anonimous) of command socket"}, \
|
{"sockpath",NEED_ARG, NULL, 0, arg_string, APTR(&G.sockname), "UNIX socket path (starting from '@' for anonimous) of command socket"}, \
|
||||||
{"plugin", MULT_PAR, NULL, 'p', arg_string, APTR(&G.plugins), "add this weather plugin (may be a lot of); FORMAT: \"dlpath:l:dev\", where `dlpath` - path of plugin library; `l` - 'D' for device, 'U' for UNIX-socket or 'N' for INET socket; dev - path to device and speed (like /dev/ttyS0:9600), UNIX socket name or host:port for INET"}, \
|
{"plugin", MULT_PAR, NULL, 'p', arg_string, APTR(&G.plugins), "add this weather plugin (may be a lot of); FORMAT: \"dlpath:l:dev\", where `dlpath` - path of plugin library; `l` - 'D' for device, 'U' for UNIX-socket or 'N' for INET socket; dev - path to device and speed (like /dev/ttyS0:9600), UNIX socket name or host:port for INET"}, \
|
||||||
{"pollt", NEED_ARG, NULL, 'T', arg_int, APTR(&G.pollt), "set maximal polling interval (seconds, integer)"},
|
{"pollt", NEED_ARG, NULL, 'T', arg_int, APTR(&G.pollt), "set maximal polling interval (seconds, integer)"},
|
||||||
|
|
||||||
@@ -90,20 +93,22 @@ sl_option_t cmdlnopts[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sl_option_t confopts[] = {
|
sl_option_t confopts[] = {
|
||||||
{"verbose", NEED_ARG, NULL, 'a', arg_int, APTR(&G.verb), "logfile verbocity level"},
|
{"verbose", NEED_ARG, NULL, 0, arg_int, APTR(&G.verb), "logfile verbocity level"},
|
||||||
{"ahtung_delay",NEED_ARG,NULL, 'b', arg_int, APTR(&WeatherConf.ahtung_delay),"delay in seconds after bad weather to change to good"},
|
{"ahtung_delay",NEED_ARG, NULL, 0, arg_int, APTR(&WeatherConf.ahtung_delay), "delay in seconds after bad weather to change to good"},
|
||||||
{"good_wind",NEED_ARG, NULL, 'c', arg_double, APTR(&WeatherConf.wind.good), "good wind while less this"},
|
{"good_wind", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.wind.good), "good wind while less this"},
|
||||||
{"bad_wind", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.wind.bad), "bad wind if more than this"},
|
{"bad_wind", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.wind.bad), "bad wind if more than this"},
|
||||||
{"terrible_wind",NEED_ARG, NULL,0, arg_double, APTR(&WeatherConf.wind.terrible), "terrible wind if more than this"},
|
{"terrible_wind",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.wind.terrible), "terrible wind if more than this"},
|
||||||
{"good_humidity",NEED_ARG, NULL,0, arg_double, APTR(&WeatherConf.humidity.good), "humidity is good until this"},
|
{"good_humidity",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.humidity.good), "humidity is good until this"},
|
||||||
{"bad_humidity",NEED_ARG, NULL,0, arg_double, APTR(&WeatherConf.humidity.bad), "humidity is bad if greater"},
|
{"bad_humidity",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.humidity.bad), "humidity is bad if greater"},
|
||||||
{"terrible_humidity",NEED_ARG,NULL, 0, arg_double, APTR(&WeatherConf.humidity.terrible), "humidity is terrible if greater"},
|
{"terrible_humidity",NEED_ARG,NULL, 0, arg_double, APTR(&WeatherConf.humidity.terrible), "humidity is terrible if greater"},
|
||||||
{"good_clouds",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.good), "good weather when \"clouds value\" greater than this"},
|
{"good_clouds", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.good), "good weather when \"clouds value\" less than this"},
|
||||||
{"bad_clouds",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.bad), "if less than this, clouds are bad"},
|
{"bad_clouds", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.bad), "if greater than this, clouds are bad"},
|
||||||
{"terrible_clouds",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.terrible), "if less, clouds are terrible"},
|
{"terrible_clouds",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.terrible), "if greater, clouds are terrible"},
|
||||||
{"good_sky",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.sky.good), "sky-ambient less than this is good"},
|
{"clouds_negflag",NEED_ARG, NULL, 0, arg_int, APTR(&WeatherConf.clouds.negflag), "==1 to invert sign (lesser value is worst)"},
|
||||||
{"bad_sky",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.sky.bad), "sky-ambient greater than this is bad"},
|
{"good_sky", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.sky.good), "sky-ambient less than this is good"},
|
||||||
{"terrible_sky",NEED_ARG,NULL, 0, arg_double, APTR(&WeatherConf.sky.terrible), "sky-ambient greater than this is terrible"},
|
{"bad_sky", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.sky.bad), "sky-ambient greater than this is bad"},
|
||||||
|
{"terrible_sky",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.sky.terrible), "sky-ambient greater than this is terrible"},
|
||||||
|
{"reinit_delay",NEED_ARG, NULL, 0, arg_int, APTR(&WeatherConf.reinit_delay), "delay (s) to reinit dead sensors"},
|
||||||
COMMON_OPTS
|
COMMON_OPTS
|
||||||
end_option
|
end_option
|
||||||
};
|
};
|
||||||
@@ -140,7 +145,9 @@ static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
|||||||
}
|
}
|
||||||
// don't sort: we need leave priority as user pointed
|
// don't sort: we need leave priority as user pointed
|
||||||
//qsort(newarray, newsize, sizeof(char*), sortstrings);
|
//qsort(newarray, newsize, sizeof(char*), sortstrings);
|
||||||
|
#ifdef EBUG
|
||||||
DBG("NOW together:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
DBG("NOW together:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
||||||
|
#endif
|
||||||
for(int i = 0; i < newsize-1; ++i){
|
for(int i = 0; i < newsize-1; ++i){
|
||||||
if(NULL == newarray[i]) continue;
|
if(NULL == newarray[i]) continue;
|
||||||
for(int j = i+1; j < newsize; ++j){
|
for(int j = i+1; j < newsize; ++j){
|
||||||
@@ -174,7 +181,9 @@ static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
|||||||
i = j;
|
i = j;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef EBUG
|
||||||
DBG("Result:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
DBG("Result:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
||||||
|
#endif
|
||||||
cmdline->plugins = newarray;
|
cmdline->plugins = newarray;
|
||||||
cmdline->nplugins = nondoubleidx;
|
cmdline->nplugins = nondoubleidx;
|
||||||
}
|
}
|
||||||
|
|||||||
19
Daemons/weatherdaemon_multimeteo/example.config
Normal file
19
Daemons/weatherdaemon_multimeteo/example.config
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
logfile = /var/log/meteo/superweather.log
|
||||||
|
# node for clients connection, if you point localost:xxxx, then NET socket would have only local access
|
||||||
|
port = 4444
|
||||||
|
# logging level: 0 - ERR, 1 - WARN, 2 - MSG, 3 - DBG, 4 - all shit
|
||||||
|
verbose = 2
|
||||||
|
# path to local command UNIX-socket (first '@' means anonymous)
|
||||||
|
sockpath = "@weather"
|
||||||
|
# sensors polling time - 1s
|
||||||
|
pollt = 1
|
||||||
|
# try to reinit dead sensors each 10s
|
||||||
|
reinit_delay = 10
|
||||||
|
|
||||||
|
# !!! Point plugins in order of meaning: the most important are first !!!
|
||||||
|
# see help for plugins format
|
||||||
|
plugin = libwxa100.so:D:/dev/pl2303_0
|
||||||
|
plugin = libhydreon.so:D:/dev/ch340_0:1200
|
||||||
|
plugin = libbtameteo.so
|
||||||
|
# this should be last as almost a half of its sensors are broken
|
||||||
|
plugin = libreinhardt.so:D:/dev/ttyS0
|
||||||
@@ -29,105 +29,39 @@
|
|||||||
#include <sys/un.h> // unix socket
|
#include <sys/un.h> // unix socket
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "fd.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief openserial - try to open serial device
|
* @brief openserial - try to open serial device
|
||||||
* @param path - path to device and speed, colon-separated (without given speed assume 9600)
|
* @param path - path to device and speed, colon-separated (without given speed assume 9600)
|
||||||
* @return -1 if failed or opened FD
|
* @return -1 if failed or opened FD
|
||||||
* WARNING!!! Memory leakage danger. Don't call this function too much times!
|
* WARNING!!! Memory leakage danger. Don't call this function too much times!
|
||||||
*/
|
*/
|
||||||
static int openserial(char *path){
|
static int openserial(const char *path){
|
||||||
FNAME();
|
FNAME();
|
||||||
int speed = 9600; // default speed
|
int speed = 9600; // default speed
|
||||||
char *colon = strchr(path, ':');
|
char *str = strdup(path);
|
||||||
|
char *colon = strchr(str, ':');
|
||||||
if(colon){
|
if(colon){
|
||||||
*colon++ = 0;
|
*colon++ = 0;
|
||||||
if(!sl_str2i(&speed, colon)){
|
if(!sl_str2i(&speed, colon)){
|
||||||
WARNX("Wrong speed settings: '%s'", colon);
|
WARNX("Wrong speed settings: '%s'", colon);
|
||||||
|
FREE(str);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sl_tty_t *serial = sl_tty_new(path, speed, BUFSIZ);
|
sl_tty_t *serial = sl_tty_new(str, speed, BUFSIZ);
|
||||||
if(!serial || !sl_tty_open(serial, TRUE)){
|
if(!serial || !sl_tty_open(serial, TRUE)){
|
||||||
WARN("Can't open %s @ speed %d", path, speed);
|
WARN("Can't open %s @ speed %d", str, speed);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
DBG("Opened %s @ %d", path, speed);
|
|
||||||
return serial->comfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *convunsname(const char *path){
|
|
||||||
char *apath = MALLOC(char, 106);
|
|
||||||
if(*path == 0 || *path == '@'){
|
|
||||||
DBG("convert name starting from 0 or @");
|
|
||||||
apath[0] = 0;
|
|
||||||
strncpy(apath+1, path+1, 104);
|
|
||||||
}else if(strncmp("\\0", path, 2) == 0){
|
|
||||||
DBG("convert name starting from \\0");
|
|
||||||
apath[0] = 0;
|
|
||||||
strncpy(apath+1, path+2, 104);
|
|
||||||
}else strncpy(apath, path, 105);
|
|
||||||
return apath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief opensocket - try to open socket
|
|
||||||
* @param sock - UNIX socket path or hostname:port for INET socket
|
|
||||||
* @param type - UNIX or INET
|
|
||||||
* @return -1 if failed or opened FD
|
|
||||||
*/
|
|
||||||
static int opensocket(char *path, sl_socktype_e type){
|
|
||||||
FNAME();
|
|
||||||
DBG("path: '%s'", path);
|
|
||||||
int sock = -1;
|
|
||||||
struct addrinfo ai = {0}, *res = &ai;
|
|
||||||
struct sockaddr_un unaddr = {0};
|
|
||||||
char *node = path, *service = NULL;
|
|
||||||
ai.ai_socktype = 0; // try to get socket type from `getaddrinfo`
|
|
||||||
switch(type){
|
|
||||||
case SOCKT_UNIX:
|
|
||||||
{
|
|
||||||
char *str = convunsname(path);
|
|
||||||
if(!str) return -1;
|
|
||||||
unaddr.sun_family = AF_UNIX;
|
|
||||||
ai.ai_addr = (struct sockaddr*) &unaddr;
|
|
||||||
ai.ai_addrlen = sizeof(unaddr);
|
|
||||||
memcpy(unaddr.sun_path, str, 106);
|
|
||||||
FREE(str);
|
FREE(str);
|
||||||
ai.ai_family = AF_UNIX;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SOCKT_NET:
|
|
||||||
case SOCKT_NETLOCAL:
|
|
||||||
ai.ai_family = AF_INET;
|
|
||||||
char *delim = strchr(path, ':');
|
|
||||||
if(delim){
|
|
||||||
*delim = 0;
|
|
||||||
service = delim+1;
|
|
||||||
if(delim == path) node = NULL; // only port
|
|
||||||
}
|
|
||||||
DBG("node: '%s', service: '%s'", node, service);
|
|
||||||
int e = getaddrinfo(node, service, &ai, &res);
|
|
||||||
if(e){
|
|
||||||
WARNX("getaddrinfo(): %s", gai_strerror(e));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
for(struct addrinfo *p = res; p; p = p->ai_next){
|
|
||||||
if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue;
|
|
||||||
DBG("Try proto %d, type %d", p->ai_protocol, p->ai_socktype);
|
|
||||||
if(connect(sock, p->ai_addr, p->ai_addrlen) == -1){
|
|
||||||
WARN("connect()");
|
|
||||||
close(sock); sock = -1;
|
|
||||||
} else break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default: // never reached
|
|
||||||
WARNX("Unsupported socket type %d", type);
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
DBG("FD: %d", sock);
|
DBG("Opened %s @ %d", str, speed);
|
||||||
return sock;
|
FREE(str);
|
||||||
|
int comfd = serial->comfd;
|
||||||
|
FREE(serial->portname);
|
||||||
|
FREE(serial->buf);
|
||||||
|
FREE(serial->format);
|
||||||
|
FREE(serial);
|
||||||
|
return comfd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -136,18 +70,21 @@ static int opensocket(char *path, sl_socktype_e type){
|
|||||||
* WARNING!!! Contents of `path` would be modified in this function!
|
* WARNING!!! Contents of `path` would be modified in this function!
|
||||||
* @return opened file descriptor or -1 in case of error
|
* @return opened file descriptor or -1 in case of error
|
||||||
*/
|
*/
|
||||||
int getFD(char *path){
|
int getFD(const char *path){
|
||||||
if(!path || !*path || strlen(path) < 2) return -1;
|
if(!path || !*path || strlen(path) < 2) return -1;
|
||||||
char type = *path;
|
char type = *path;
|
||||||
if(path[1] != ':') return -1; // after protocol letter should be delimeter
|
if(path[1] != ':') return -1; // after protocol letter should be delimeter
|
||||||
path += 2;
|
path += 2;
|
||||||
|
if(!*path) return -1; // empty path
|
||||||
switch(type){
|
switch(type){
|
||||||
case 'D': // serial device
|
case 'D': // serial device
|
||||||
return openserial(path);
|
return openserial(path);
|
||||||
case 'N': // INET socket
|
case 'N': // INET socket
|
||||||
return opensocket(path, SOCKT_NET);
|
//return opensocket(path, SOCKT_NET);
|
||||||
|
return sl_sock_open(SOCKT_NET, path, 0, 0);
|
||||||
case 'U': // UNIX socket
|
case 'U': // UNIX socket
|
||||||
return opensocket(path, SOCKT_UNIX);
|
//return opensocket(path, SOCKT_UNIX);
|
||||||
|
return sl_sock_open(SOCKT_UNIX, path, 0, 0);
|
||||||
}
|
}
|
||||||
WARNX("Wrong plugin format: '%c', should be 'D', 'N' or 'U'", type);
|
WARNX("Wrong plugin format: '%c', should be 'D', 'N' or 'U'", type);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of the weatherdaemon 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
|
|
||||||
|
|
||||||
int getFD(char *path);
|
|
||||||
@@ -40,12 +40,12 @@ void signals(int signo){
|
|||||||
LOGDBG("Child gotta signal %d", signo);
|
LOGDBG("Child gotta signal %d", signo);
|
||||||
if(signo == SIGUSR1){
|
if(signo == SIGUSR1){
|
||||||
forbid_observations(1);
|
forbid_observations(1);
|
||||||
LOGMSG("Got signal `observations forbidden`");
|
LOGWARN("Got signal `observations forbidden`, set FORBIDDEN");
|
||||||
signal(signo, signals);
|
signal(signo, signals);
|
||||||
return;
|
return;
|
||||||
}else if(signo == SIGUSR2){
|
}else if(signo == SIGUSR2){
|
||||||
forbid_observations(0);
|
forbid_observations(0);
|
||||||
LOGMSG("Got signal `observations permitted`");
|
LOGWARN("Got signal `observations permitted`, clear FORBIDDEN");
|
||||||
signal(signo, signals);
|
signal(signo, signals);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -74,6 +74,7 @@ int main(int argc, char **argv){
|
|||||||
sl_init();
|
sl_init();
|
||||||
GP = parse_args(argc, argv);
|
GP = parse_args(argc, argv);
|
||||||
if(!GP) ERRX("Error parsing args");
|
if(!GP) ERRX("Error parsing args");
|
||||||
|
sl_check4running((char*)__progname, GP->pidfile);
|
||||||
if(!GP->sockname) ERRX("Point command socket name");
|
if(!GP->sockname) ERRX("Point command socket name");
|
||||||
if(GP->logfile){
|
if(GP->logfile){
|
||||||
sl_loglevel_e lvl = LOGLEVEL_ERR + GP->verb;
|
sl_loglevel_e lvl = LOGLEVEL_ERR + GP->verb;
|
||||||
@@ -99,7 +100,6 @@ int main(int argc, char **argv){
|
|||||||
ERRX("Can't find any sensor plugin");
|
ERRX("Can't find any sensor plugin");
|
||||||
}
|
}
|
||||||
if(GP->nplugins && GP->nplugins != nopened) LOGWARN("Work without some plugins");
|
if(GP->nplugins && GP->nplugins != nopened) LOGWARN("Work without some plugins");
|
||||||
sl_check4running((char*)__progname, GP->pidfile);
|
|
||||||
#ifndef EBUG
|
#ifndef EBUG
|
||||||
sl_daemonize();
|
sl_daemonize();
|
||||||
while(1){ // guard for dead processes
|
while(1){ // guard for dead processes
|
||||||
@@ -121,7 +121,7 @@ int main(int argc, char **argv){
|
|||||||
signal(SIGUSR1, signals);
|
signal(SIGUSR1, signals);
|
||||||
signal(SIGUSR2, signals);
|
signal(SIGUSR2, signals);
|
||||||
if(!start_servers(GP->port, GP->sockname)) ERRX("Can't run server's threads");
|
if(!start_servers(GP->port, GP->sockname)) ERRX("Can't run server's threads");
|
||||||
while(1);
|
//while(1) pause();
|
||||||
//WARNX("TEST ends");
|
//WARNX("TEST ends");
|
||||||
//signals(0);
|
//signals(0);
|
||||||
return 0; // never reached
|
return 0; // never reached
|
||||||
|
|||||||
@@ -52,29 +52,98 @@ enum{
|
|||||||
NSKYTEMP,
|
NSKYTEMP,
|
||||||
NCOMMWEATH,
|
NCOMMWEATH,
|
||||||
NLASTAHTUNG,
|
NLASTAHTUNG,
|
||||||
|
NAHTUNGRSN,
|
||||||
|
// NLIGHTDIST,
|
||||||
|
NBADWEATH,
|
||||||
|
NTERRWEATH,
|
||||||
|
NFORCEDSHTDN,
|
||||||
NAMOUNT_OF_DATA
|
NAMOUNT_OF_DATA
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// starting sense values are VAL_BROKEN except of calculated values
|
||||||
|
// they would be changed later in `fix_new_data` to lowest level
|
||||||
static val_t collected_data[NAMOUNT_OF_DATA] = {
|
static val_t collected_data[NAMOUNT_OF_DATA] = {
|
||||||
[NWIND] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WIND},
|
[NWIND] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||||
[NWINDMAX] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX", .comment = "Maximal wind speed for last 24 hours"},
|
[NWINDMAX] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX", .comment = "Maximal wind speed for last 24 hours"},
|
||||||
[NWINDMAX1] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX1", .comment = "Maximal wind speed for last hour"},
|
[NWINDMAX1] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX1", .comment = "Maximal wind speed for last hour"},
|
||||||
[NWINDDIR] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
[NWINDDIR] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
||||||
[NWINDDIR1] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR1", .comment = "Mean wind speed direction for last hour"},
|
[NWINDDIR1] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR1", .comment = "Mean wind speed direction for last hour"},
|
||||||
[NWINDDIR2] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR2", .comment = "Mean wind speed^2 direction for last hour"},
|
[NWINDDIR2] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR2", .comment = "Mean wind speed^2 direction for last hour"},
|
||||||
[NHUMIDITY] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
[NHUMIDITY] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||||
[NAMB_TEMP] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
[NAMB_TEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||||
[NPRESSURE] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
[NPRESSURE] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||||
[NPRECIP] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
[NPRECIP] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
[NPRECIP_LEVEL] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
[NPRECIP_LEVEL] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||||
[NMIST] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_MIST},
|
[NMIST] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_MIST},
|
||||||
[NCLOUDS] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_CLOUDS},
|
[NCLOUDS] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_CLOUDS},
|
||||||
[NSKYTEMP] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_SKYTEMP},
|
[NSKYTEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_SKYTEMP},
|
||||||
[NCOMMWEATH] = {.value.i = 0, .sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "WEATHER", .comment = "Weather level (0 - good, 3 - obs. prohibited)"},
|
// [NLIGHTDIST] = {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
|
||||||
[NLASTAHTUNG] = {.value.i = 0, .sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "EVTTIME", .comment = "UNIX-time of last weather level increasing"},
|
// these are calculated values
|
||||||
|
[NCOMMWEATH] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "WEATHER", .comment = "Weather (0..3: good/bad/terrible/prohibited)"},
|
||||||
|
[NLASTAHTUNG] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "EVTTIME", .comment = "UNIX-time of last weather level changing"},
|
||||||
|
[NAHTUNGRSN] = {.sense = VAL_RECOMMENDED, .type = VALT_STRING, .meaning = IS_OTHER, .name = "EVTRSN", .comment = "Last weather level increasing reason"},
|
||||||
|
// virtual values for weather level / flags changing
|
||||||
|
[NBADWEATH] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_BADWEATH, .name = "BADWEATH", .comment = "Flag changing weather level to 'BAD'"},
|
||||||
|
[NTERRWEATH] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_TERRIBLEWEATH, .name = "TERWEATH", .comment = "Flag changing weather level to 'TERRIBLE'"},
|
||||||
|
[NFORCEDSHTDN] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "FORCEOFF", .comment = "All should be powered off NOW"},
|
||||||
// {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER},
|
// {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// additional fields marked as `IS_OTHER` gathered from different stations
|
||||||
|
static int Nadditional = 0;
|
||||||
|
static val_t *additional_data = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief weather_level - set/clear weather level
|
||||||
|
* @param newlvl - -1 for getter or 0..3 for setter
|
||||||
|
* @return current weather level
|
||||||
|
*/
|
||||||
|
int weather_level(int newlvl){
|
||||||
|
if(newlvl > -1 && newlvl <= WEATHER_PROHIBITED){
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
uint32_t curt = time(NULL);
|
||||||
|
int oldlvl = collected_data[NCOMMWEATH].value.u;
|
||||||
|
collected_data[NCOMMWEATH].value.u = newlvl;
|
||||||
|
collected_data[NCOMMWEATH].time = curt;
|
||||||
|
sprintf(collected_data[NAHTUNGRSN].value.str, "MANUAL");
|
||||||
|
collected_data[NAHTUNGRSN].time = curt;
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curt;
|
||||||
|
collected_data[NLASTAHTUNG].time = curt;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
LOGWARN("Manual changing of weather level from %d to %d", oldlvl, newlvl);
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
int curlevel = collected_data[NCOMMWEATH].value.u;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
return curlevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief force_off - set/clear `force off` flag
|
||||||
|
* @param flag - 1 to set, 0 to clear or -1 to get
|
||||||
|
* @return current value
|
||||||
|
*/
|
||||||
|
int force_off(int flag){
|
||||||
|
DBG("Force OFF to %d", flag);
|
||||||
|
if(flag > -1 && flag < 2){
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
uint32_t curt = time(NULL);
|
||||||
|
int oldval = collected_data[NFORCEDSHTDN].value.u;
|
||||||
|
collected_data[NFORCEDSHTDN].value.u = (uint32_t)flag;
|
||||||
|
if(flag) collected_data[NFORCEDSHTDN].time = curt;
|
||||||
|
sprintf(collected_data[NAHTUNGRSN].value.str, "MANUAL");
|
||||||
|
collected_data[NAHTUNGRSN].time = curt;
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curt;
|
||||||
|
collected_data[NLASTAHTUNG].time = curt;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
LOGWARN("Manual changing of FORCED SHUTDOWN from %d to %d", oldval, flag);
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
flag = collected_data[NFORCEDSHTDN].value.u;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
double array[MAX_HISTORY];
|
double array[MAX_HISTORY];
|
||||||
double sum;
|
double sum;
|
||||||
@@ -166,40 +235,63 @@ static double get_max_forT(sliding_max_t *sm, time_t tcutoff){
|
|||||||
}
|
}
|
||||||
|
|
||||||
int collected_amount(){
|
int collected_amount(){
|
||||||
return NAMOUNT_OF_DATA;
|
return NAMOUNT_OF_DATA + Nadditional;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_collected(val_t *val, int N){
|
int get_collected(val_t *val, int N){
|
||||||
if(!val || N < 0 || N >= NAMOUNT_OF_DATA){
|
if(!val || N < 0 || N >= NAMOUNT_OF_DATA + Nadditional){
|
||||||
DBG("Wrong number (%d) requested or no place for data", N);
|
DBG("Wrong number (%d) requested or no place for data", N);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
pthread_mutex_lock(&datamutex);
|
pthread_mutex_lock(&datamutex);
|
||||||
DBG("Copied data of %d", N);
|
val_t *dptr = (N < NAMOUNT_OF_DATA) ? &collected_data[N] : &additional_data[N-NAMOUNT_OF_DATA];
|
||||||
*val = collected_data[N];
|
#ifdef EBUG
|
||||||
|
char buf[KEY_LEN+1];
|
||||||
|
get_fieldname(dptr, buf);
|
||||||
|
DBG("Copied data of %d (u=%d, nm=%s, t=%zd)", N, dptr->value.u, buf, dptr->time);
|
||||||
|
#endif
|
||||||
|
*val = *dptr;
|
||||||
pthread_mutex_unlock(&datamutex);
|
pthread_mutex_unlock(&datamutex);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void fix_new_data(val_t *collected, val_t *fresh){
|
/**
|
||||||
|
* @brief fix_new_data - take only data with `sense value` less than collected have and more recent
|
||||||
|
* @param collected - pointer to collected data
|
||||||
|
* @param fresh - pointer to fresh data
|
||||||
|
* @param force - ==1 to force changing even if this data is older
|
||||||
|
*/
|
||||||
|
static void fix_new_data(val_t *collected, const val_t *fresh, int force){
|
||||||
if(!collected || !fresh) return;
|
if(!collected || !fresh) return;
|
||||||
|
if(collected->time >= fresh->time){
|
||||||
|
if(!force) return;
|
||||||
|
//DBG("Forced, collected=%g, fresh=%g", val2d(collected), val2d(fresh));
|
||||||
|
if(collected->time - fresh->time > 60) return;
|
||||||
|
//DBG("Not too old");
|
||||||
|
}
|
||||||
|
// lower `collected` level if data is too old
|
||||||
|
if(fresh->time - collected->time > WeatherConf.ahtung_delay) collected->sense = VAL_UNNECESSARY;
|
||||||
|
if(collected->sense < fresh->sense) return;
|
||||||
|
if(collected->sense != fresh->sense) collected->sense = fresh->sense; // take new level
|
||||||
|
//DBG("Refresh collected value");
|
||||||
collected->time = fresh->time;
|
collected->time = fresh->time;
|
||||||
if(collected->type == fresh->type){ // good case
|
if(collected->type == fresh->type){ // good case
|
||||||
DBG("Types are the same");
|
memcpy(&collected->value, &fresh->value, sizeof(num_t));
|
||||||
collected->value = fresh->value;
|
//DBG("Types are the same");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// bad case: have different types
|
// bad case: have different types
|
||||||
|
// DON'T convert between string and number types!
|
||||||
switch(collected->type){
|
switch(collected->type){
|
||||||
case VALT_UINT:
|
case VALT_UINT:
|
||||||
switch(fresh->type){
|
switch(fresh->type){
|
||||||
case VALT_INT:
|
case VALT_INT:
|
||||||
collected->value.u = (uint32_t) fresh->value.i;
|
collected->value.u = (uint32_t) fresh->value.i;
|
||||||
DBG("i->u");
|
//DBG("i->u");
|
||||||
break;
|
break;
|
||||||
case VALT_FLOAT:
|
case VALT_FLOAT:
|
||||||
collected->value.u = (uint32_t) fresh->value.f;
|
collected->value.u = (uint32_t) fresh->value.f;
|
||||||
DBG("f->u");
|
//DBG("f->u");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -207,11 +299,11 @@ static void fix_new_data(val_t *collected, val_t *fresh){
|
|||||||
switch(fresh->type){
|
switch(fresh->type){
|
||||||
case VALT_UINT:
|
case VALT_UINT:
|
||||||
collected->value.i = (int32_t) fresh->value.u;
|
collected->value.i = (int32_t) fresh->value.u;
|
||||||
DBG("u->i");
|
//DBG("u->i");
|
||||||
break;
|
break;
|
||||||
case VALT_FLOAT:
|
case VALT_FLOAT:
|
||||||
collected->value.i = (int32_t) fresh->value.f;
|
collected->value.i = (int32_t) fresh->value.f;
|
||||||
DBG("f->i");
|
//DBG("f->i");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -219,75 +311,131 @@ static void fix_new_data(val_t *collected, val_t *fresh){
|
|||||||
switch(fresh->type){
|
switch(fresh->type){
|
||||||
case VALT_UINT:
|
case VALT_UINT:
|
||||||
collected->value.f = (float) fresh->value.u;
|
collected->value.f = (float) fresh->value.u;
|
||||||
DBG("u->f");
|
//DBG("u->f");
|
||||||
break;
|
break;
|
||||||
case VALT_INT:
|
case VALT_INT:
|
||||||
collected->value.f = (float) fresh->value.i;
|
collected->value.f = (float) fresh->value.i;
|
||||||
DBG("i->f");
|
//DBG("i->f");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void chkweatherlevel(int *curlevel, double curvalue, weather_cond_t *curcond, int *ahtungtime){
|
// find value.name in `additional_data`, if need - allocate new memory and update
|
||||||
double good = curcond->good, bad = curcond->bad, terrible = curcond->terrible;
|
static void update_additional(val_t *value){
|
||||||
|
if(!value) return;
|
||||||
|
int idx = 0;
|
||||||
|
for(; idx < Nadditional; ++idx){
|
||||||
|
if(0 == strcmp(additional_data[idx].name, value->name)) break;
|
||||||
|
}
|
||||||
|
if(idx == Nadditional){ // not found -> allocate
|
||||||
|
additional_data = realloc(additional_data, sizeof(val_t) * (++Nadditional));
|
||||||
|
if(!additional_data){
|
||||||
|
LOGERR("update_additional() can't realloc()");
|
||||||
|
ERR("realloc()");
|
||||||
|
}
|
||||||
|
memcpy(&additional_data[idx], value, sizeof(val_t));
|
||||||
|
DBG("Allocated new field: %s", value->name);
|
||||||
|
}else fix_new_data(&additional_data[idx], value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief chkweatherlevel - increase weather level if need, also check force shutdown flags
|
||||||
|
* @param curlevel - current max weather level
|
||||||
|
* @param curvalue - current value of sensor data
|
||||||
|
* @param curcond - conditions for given value
|
||||||
|
* @return 0 if level wasn't changed, or +1 if was increased
|
||||||
|
*/
|
||||||
|
static int chkweatherlevel(uint32_t *curlevel, double curvalue, weather_cond_t const *curcond){
|
||||||
|
int rtn = 0;
|
||||||
|
double good = curcond->good, bad = curcond->bad, terrible = curcond->terrible, prohibited = curcond->prohibited;
|
||||||
|
int haveproh = (prohibited > terrible) ? 1 : 0; // value have `prohibited` field
|
||||||
if(curcond->negflag){ // negate
|
if(curcond->negflag){ // negate
|
||||||
curvalue = -curvalue;
|
curvalue = -curvalue;
|
||||||
good = -good;
|
good = -good;
|
||||||
bad = -bad;
|
bad = -bad;
|
||||||
terrible = -terrible;
|
terrible = -terrible;
|
||||||
|
prohibited = -prohibited;
|
||||||
}
|
}
|
||||||
int newlevel = -1;
|
int newlevel = -1;
|
||||||
if(curvalue > terrible) newlevel = WEATHER_TERRIBLE;
|
if(haveproh && curvalue > prohibited){
|
||||||
else if(curvalue > bad) newlevel = WEATHER_BAD;
|
newlevel = WEATHER_PROHIBITED;
|
||||||
|
DBG("---> new level is PROHIBITED, val=%g", (curcond->negflag) ? -curvalue : curvalue);
|
||||||
|
}else if(curvalue > terrible){
|
||||||
|
newlevel = WEATHER_TERRIBLE;
|
||||||
|
DBG("---> new level is TERRIBLE, val=%g", (curcond->negflag) ? -curvalue : curvalue);
|
||||||
|
}else if(curvalue > bad) newlevel = WEATHER_BAD;
|
||||||
else if(curvalue < good) newlevel = WEATHER_GOOD;
|
else if(curvalue < good) newlevel = WEATHER_GOOD;
|
||||||
if(newlevel == -1) return;
|
if(newlevel == -1) return 0;
|
||||||
time_t curt = time(NULL);
|
time_t curt = time(NULL);
|
||||||
if(newlevel > *curlevel){
|
if(curcond->shtdnflag && newlevel >= WEATHER_TERRIBLE){
|
||||||
DBG("newlevel: %d, current: %d INCREASED", newlevel, *curlevel);
|
DBG("Forced shutdown flag is set, curvalue: %g", (curcond->negflag) ? -curvalue : curvalue);
|
||||||
*curlevel = newlevel;
|
// set to one collected data flag and its time
|
||||||
if(*ahtungtime < curt) *ahtungtime = (int) curt; // refresh event time
|
val_t *f = &collected_data[NFORCEDSHTDN];
|
||||||
}else if(newlevel < *curlevel){ // check timeout to make level lower
|
f->value.u = 1;
|
||||||
if(curt - *ahtungtime > WeatherConf.ahtung_delay){
|
f->time = (int) curt;
|
||||||
DBG("newlevel: %d, current: %d DECREASED", newlevel, *curlevel);
|
DBG("forced = %u", collected_data[NFORCEDSHTDN].value.u);
|
||||||
*curlevel = newlevel;
|
// and set current weather level to prohibited
|
||||||
}
|
newlevel = WEATHER_PROHIBITED;
|
||||||
|
rtn = 1;
|
||||||
}
|
}
|
||||||
|
if((uint32_t)newlevel > *curlevel){
|
||||||
|
// TODO: add logging
|
||||||
|
DBG("local level increased to %d", newlevel);
|
||||||
|
*curlevel = (uint32_t)newlevel;
|
||||||
|
rtn = 1;
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// conditions for "bad weather" flag (if it ==1 set BAD WEATH)
|
||||||
|
static weather_cond_t const badweathflag = {.good = 0.1, .bad = 0.5, .terrible = 2.};
|
||||||
|
// conditions for "terrible weather" flag
|
||||||
|
static weather_cond_t const terrweathflag = {.good = 0.1, .bad = 0.5, .terrible = 0.7};
|
||||||
|
// conditions for "prohibited weather" flag
|
||||||
|
static weather_cond_t const prohibweathflag = {.good = 0.1, .bad = 0.5, .terrible = 0.6, .prohibited = 0.7};
|
||||||
|
// conditions for "force shutdown" flag
|
||||||
|
static weather_cond_t const shtdnflag = {.good = 0.1, .bad = 0.5, .terrible = 0.7, .shtdnflag = 1, .prohibited = 0.8};
|
||||||
|
|
||||||
void refresh_sensval(sensordata_t *s){
|
void refresh_sensval(sensordata_t *s){
|
||||||
FNAME();
|
//FNAME();
|
||||||
static time_t poll_time = 0;
|
//static time_t poll_time = 0;
|
||||||
|
static char reason[KEY_LEN+1] = {0}; // reason of weather level increasing
|
||||||
val_t value;
|
val_t value;
|
||||||
if(!s || !s->get_value) return;
|
if(!s || !s->get_value) return;
|
||||||
if(poll_time == 0) poll_time = get_pollT();
|
//if(poll_time == 0) poll_time = get_pollT();
|
||||||
int curlevel = collected_data[NCOMMWEATH].value.i;
|
static uint32_t curlevel = 0; // this is worse weather leavel, start from best (collect by all sensors through 3*tpoll)
|
||||||
int curahtungtime = collected_data[NLASTAHTUNG].value.i;
|
static time_t lasttupdate = 0; // last update time of weather level
|
||||||
time_t curtime = time(NULL);
|
time_t curtime = time(NULL);
|
||||||
|
time_t tpoll = get_pollT(), _3tpoll = 3*tpoll;
|
||||||
double dir = -100., dir2 = -100.; // mean wind directions
|
double dir = -100., dir2 = -100.; // mean wind directions
|
||||||
DBG("%d meteo values", s->Nvalues);
|
//DBG("%d meteo values", s->Nvalues);
|
||||||
for(int i = 0; i < s->Nvalues; ++i){
|
for(int i = 0; i < s->Nvalues; ++i){
|
||||||
DBG("\nTry to get %dth value", i);
|
//DBG("\nTry to get %dth value", i);
|
||||||
if(!s->get_value(s, &value, i)) continue;
|
if(!s->get_value(s, &value, i) || value.sense > VAL_RECOMMENDED) continue;
|
||||||
DBG("got value");
|
//DBG("got value");
|
||||||
int idx = -1;
|
int idx = -1;
|
||||||
double curvalue;
|
double curvalue = val2d(&value);
|
||||||
weather_cond_t *curcond = NULL;
|
const weather_cond_t *curcond = NULL;
|
||||||
switch(value.meaning){
|
switch(value.meaning){
|
||||||
case IS_WIND:
|
case IS_WIND:
|
||||||
idx = NWIND;
|
idx = NWIND;
|
||||||
curvalue = (double) value.value.f;
|
|
||||||
curcond = &WeatherConf.wind;
|
curcond = &WeatherConf.wind;
|
||||||
|
// protect collected wind speeds from destruction in case of simultaneous acces from different plugins
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
add_windspeed(&windspeeds, curvalue, curtime);
|
add_windspeed(&windspeeds, curvalue, curtime);
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
break;
|
break;
|
||||||
case IS_WINDDIR:
|
case IS_WINDDIR:
|
||||||
idx = NWINDDIR;
|
idx = NWINDDIR;
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
wind_dir_add(collected_data[NWIND].value.f, value.value.f, &dir, &dir2);
|
wind_dir_add(collected_data[NWIND].value.f, value.value.f, &dir, &dir2);
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
break;
|
break;
|
||||||
case IS_HUMIDITY:
|
case IS_HUMIDITY:
|
||||||
idx = NHUMIDITY;
|
idx = NHUMIDITY;
|
||||||
curvalue = (double) value.value.f;
|
|
||||||
curcond = &WeatherConf.humidity;
|
curcond = &WeatherConf.humidity;
|
||||||
break;
|
break;
|
||||||
case IS_AMB_TEMP:
|
case IS_AMB_TEMP:
|
||||||
@@ -298,45 +446,67 @@ void refresh_sensval(sensordata_t *s){
|
|||||||
break;
|
break;
|
||||||
case IS_PRECIP:
|
case IS_PRECIP:
|
||||||
idx = NPRECIP;
|
idx = NPRECIP;
|
||||||
if(value.value.i && curlevel < WEATHER_TERRIBLE){
|
curcond = &prohibweathflag;
|
||||||
curlevel = WEATHER_TERRIBLE;
|
if(curvalue > 0.) DBG("IS_PRECIP == 1 !!!");
|
||||||
curahtungtime = curtime;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case IS_PRECIP_LEVEL:
|
case IS_PRECIP_LEVEL:
|
||||||
idx = NPRECIP_LEVEL;
|
idx = NPRECIP_LEVEL;
|
||||||
|
curcond = &terrweathflag;
|
||||||
break;
|
break;
|
||||||
case IS_MIST:
|
case IS_MIST:
|
||||||
idx = NMIST;
|
idx = NMIST;
|
||||||
if(value.value.i && curlevel < WEATHER_TERRIBLE){
|
curcond = &terrweathflag;
|
||||||
curahtungtime = curtime;
|
|
||||||
curlevel = WEATHER_TERRIBLE;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case IS_CLOUDS:
|
case IS_CLOUDS:
|
||||||
idx = NCLOUDS;
|
idx = NCLOUDS;
|
||||||
curvalue = (double) value.value.f;
|
|
||||||
curcond = &WeatherConf.clouds;
|
curcond = &WeatherConf.clouds;
|
||||||
break;
|
break;
|
||||||
case IS_SKYTEMP:
|
case IS_SKYTEMP:
|
||||||
idx = NSKYTEMP;
|
idx = NSKYTEMP;
|
||||||
curvalue = (double) value.value.f;
|
|
||||||
curcond = &WeatherConf.sky;
|
curcond = &WeatherConf.sky;
|
||||||
break;
|
break;
|
||||||
|
/*case IS_LIGTDIST:
|
||||||
|
idx = NLIGHTDIST;
|
||||||
|
curcond = &WeatherConf.ligtdist;
|
||||||
|
break;*/
|
||||||
|
case IS_BADWEATH:
|
||||||
|
idx = NBADWEATH;
|
||||||
|
curcond = &badweathflag;
|
||||||
|
break;
|
||||||
|
case IS_TERRIBLEWEATH:
|
||||||
|
idx = NTERRWEATH;
|
||||||
|
curcond = &terrweathflag;
|
||||||
|
break;
|
||||||
|
case IS_FORCEDSHTDN:
|
||||||
|
idx = NFORCEDSHTDN;
|
||||||
|
curcond = &shtdnflag;
|
||||||
|
//DBG("%s have shtdn flag", value.name);
|
||||||
|
break;
|
||||||
default : break;
|
default : break;
|
||||||
}
|
}
|
||||||
if(idx < 0 || idx >= NAMOUNT_OF_DATA) continue;
|
if(value.meaning == IS_OTHER){ // check for new or existant field in `additional_data`
|
||||||
DBG("IDX=%d", idx);
|
update_additional(&value);
|
||||||
time_t freshdelay = (s->PluginNo == 0) ? 0 : 90; // use data of less imrortant plugins only if our data is too old
|
continue;
|
||||||
time_t curmt = collected_data[idx].time + freshdelay;
|
|
||||||
if(value.time < curmt){
|
|
||||||
DBG("Data too old (value: %zd, curr: %zd", value.time, curmt);
|
|
||||||
continue; // old data
|
|
||||||
}
|
}
|
||||||
|
if(idx < 0 || idx >= NAMOUNT_OF_DATA) continue;
|
||||||
|
//DBG("IDX=%d", idx);
|
||||||
pthread_mutex_lock(&datamutex);
|
pthread_mutex_lock(&datamutex);
|
||||||
fix_new_data(&collected_data[idx], &value);
|
int force = 0;
|
||||||
|
if(curcond){
|
||||||
|
int oldshtdn = collected_data[NFORCEDSHTDN].value.u;
|
||||||
|
if(1 == chkweatherlevel(&curlevel, curvalue, curcond)){
|
||||||
|
get_fieldname(&value, reason); // copy to `reason` reason of last level increasing
|
||||||
|
force = 1;
|
||||||
|
DBG("reason: %s; forceflag=%u, old=%d", reason, collected_data[NFORCEDSHTDN].value.u, oldshtdn);
|
||||||
|
if(collected_data[NFORCEDSHTDN].value.u - oldshtdn == 1){ // got shutdown
|
||||||
|
LOGWARN("Forced shutdown flag is set by '%s' of '%s'", reason, s->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(idx == NFORCEDSHTDN && value.value.u == 0){
|
||||||
|
//DBG("Don't clear forced flag by other station");
|
||||||
|
}else fix_new_data(&collected_data[idx], &value, force);
|
||||||
pthread_mutex_unlock(&datamutex);
|
pthread_mutex_unlock(&datamutex);
|
||||||
if(!Forbidden && curcond) chkweatherlevel(&curlevel, curvalue, curcond, &curahtungtime);
|
|
||||||
}
|
}
|
||||||
pthread_mutex_lock(&datamutex);
|
pthread_mutex_lock(&datamutex);
|
||||||
// refresh max
|
// refresh max
|
||||||
@@ -348,43 +518,69 @@ void refresh_sensval(sensordata_t *s){
|
|||||||
collected_data[NWINDDIR2].value.f = (float) dir2;
|
collected_data[NWINDDIR2].value.f = (float) dir2;
|
||||||
collected_data[NWINDDIR2].time = curtime;
|
collected_data[NWINDDIR2].time = curtime;
|
||||||
}
|
}
|
||||||
collected_data[NWINDMAX].value.f = (float) get_current_max(&windspeeds);
|
if(curtime - collected_data[NWIND].time < tpoll + 1){
|
||||||
collected_data[NWINDMAX].time = curtime;
|
collected_data[NWINDMAX].value.f = (float) get_current_max(&windspeeds);
|
||||||
collected_data[NWINDMAX1].value.f = (float) get_max_forT(&windspeeds, curtime - T_ONE_HOUR);
|
collected_data[NWINDMAX].time = curtime;
|
||||||
collected_data[NWINDMAX1].time = curtime;
|
collected_data[NWINDMAX1].value.f = (float) get_max_forT(&windspeeds, curtime - T_ONE_HOUR);
|
||||||
DBG("check ahtung");
|
collected_data[NWINDMAX1].time = curtime;
|
||||||
if(Forbidden) collected_data[NCOMMWEATH].value.i = WEATHER_PROHIBITED;
|
}
|
||||||
else collected_data[NCOMMWEATH].value.i = curlevel;
|
//DBG("check ahtung");
|
||||||
if(collected_data[NLASTAHTUNG].value.i < curahtungtime) collected_data[NLASTAHTUNG].value.i = curahtungtime;
|
time_t _2update = lasttupdate + _3tpoll;
|
||||||
collected_data[NCOMMWEATH].time = curtime;
|
if(Forbidden){
|
||||||
collected_data[NLASTAHTUNG].time = curtime;
|
collected_data[NCOMMWEATH].value.u = WEATHER_PROHIBITED;
|
||||||
|
collected_data[NCOMMWEATH].time = curtime;
|
||||||
|
}else if(curtime >= _2update){ // need to update weather level
|
||||||
|
if(collected_data[NCOMMWEATH].value.u > curlevel){ // check timeout to make level lower
|
||||||
|
// DBG("curtime: %zd, curahtt: %d, diff: %zd, delay: %d", curtime, collected_data[NLASTAHTUNG].value.u, curtime - collected_data[NLASTAHTUNG].value.u, WeatherConf.ahtung_delay);
|
||||||
|
if(curtime - collected_data[NLASTAHTUNG].value.u > WeatherConf.ahtung_delay){
|
||||||
|
DBG("newlevel: %d, current: %d DECREASED", curlevel, collected_data[NCOMMWEATH].value.u);
|
||||||
|
if(curlevel < WEATHER_TERRIBLE){ // clear forced shutdown flag
|
||||||
|
if(collected_data[NFORCEDSHTDN].value.u){
|
||||||
|
LOGMSG("Clear forced shutdown flag by '%s' and set weather level to %d", s->name, WEATHER_TERRIBLE);
|
||||||
|
DBG("Clear FORCED SHUTDOWN flag");
|
||||||
|
collected_data[NCOMMWEATH].value.u = WEATHER_TERRIBLE;
|
||||||
|
collected_data[NFORCEDSHTDN].value.u = 0;
|
||||||
|
}else collected_data[NCOMMWEATH].value.u = curlevel;
|
||||||
|
}else --collected_data[NCOMMWEATH].value.u;
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curtime;
|
||||||
|
LOGMSG("Station '%s', decrease weather level to %d", s->name, collected_data[NCOMMWEATH].value.u);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if(collected_data[NCOMMWEATH].value.u < curlevel){ // set to worse
|
||||||
|
DBG("newlevel: %d, current: %d INCREASED", curlevel, collected_data[NCOMMWEATH].value.u);
|
||||||
|
LOGWARN("Station '%s', sensor '%s', increase weather level to %d", s->name, reason, curlevel);
|
||||||
|
collected_data[NCOMMWEATH].value.u = curlevel;
|
||||||
|
if(1 < snprintf(collected_data[NAHTUNGRSN].value.str, STRT_LEN+1, "%s", reason))
|
||||||
|
collected_data[NAHTUNGRSN].time = curtime;
|
||||||
|
}
|
||||||
|
if(curlevel){
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curtime; // refresh last ahtung time only for level > good
|
||||||
|
collected_data[NLASTAHTUNG].time = curtime;
|
||||||
|
collected_data[NAHTUNGRSN].time = curtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lasttupdate = curtime;
|
||||||
|
curlevel = 0; // wait for next collected max level
|
||||||
|
collected_data[NCOMMWEATH].time = curtime; // refresh `common weather` updating time
|
||||||
|
}
|
||||||
pthread_mutex_unlock(&datamutex);
|
pthread_mutex_unlock(&datamutex);
|
||||||
DBG("Refreshed");
|
//DBG("Refreshed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set/clear `forbid` flag (by signals USR1 and USR2)
|
||||||
void forbid_observations(int f){
|
void forbid_observations(int f){
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
if(f) Forbidden = 1;
|
if(f) Forbidden = 1;
|
||||||
else Forbidden = 0;
|
else Forbidden = 0;
|
||||||
|
int curt = (int) time(NULL);
|
||||||
|
// don't use mutexes here as this function called from signal handler
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curt;
|
||||||
|
collected_data[NLASTAHTUNG].time = curt;
|
||||||
|
sprintf(collected_data[NAHTUNGRSN].value.str, "FORBID");
|
||||||
|
collected_data[NAHTUNGRSN].time = curt;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
DBG("Change FORBID status to %d", f);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
// `forbid` flag getter
|
||||||
// main cycle
|
int is_forbidden(){ return Forbidden; }
|
||||||
void run_mainweather(){
|
|
||||||
int N = get_nplugins();
|
|
||||||
if(N < 1) return;
|
|
||||||
poll_time = get_pollT();
|
|
||||||
while(1){
|
|
||||||
int nactive = 0;
|
|
||||||
pthread_mutex_lock(&datamutex);
|
|
||||||
for(int i = N-1; i > -1; --i){ // the most important is the last
|
|
||||||
sensordata_t *s = get_plugin(i);
|
|
||||||
if(!s || !sensor_alive(s)) continue;
|
|
||||||
++nactive;
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&datamutex);
|
|
||||||
if(nactive == 0) break; // no active sensors
|
|
||||||
usleep(10000);
|
|
||||||
}
|
|
||||||
LOGERR("Main weather collector died: all sensors lost");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -25,18 +25,21 @@ enum{
|
|||||||
WEATHER_GOOD, // good to start observations
|
WEATHER_GOOD, // good to start observations
|
||||||
WEATHER_BAD, // bad for start, but can run
|
WEATHER_BAD, // bad for start, but can run
|
||||||
WEATHER_TERRIBLE, // need close the dome
|
WEATHER_TERRIBLE, // need close the dome
|
||||||
WEATHER_PROHIBITED, // need close all, park and power off <-- by SIGUSR1/SIGUSR2
|
WEATHER_PROHIBITED, // need close all, park and power off equipment <-- by SIGUSR1/SIGUSR2 + by FORCEOFF
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
double good; // if value less than this, weather is good
|
double good; // if value less than this, weather is good
|
||||||
double bad; // if value greater than this, weather is bad
|
double bad; // if value greater than this, weather is bad
|
||||||
double terrible; // if value greater than this, weather is terrible
|
double terrible; // if value greater than this, weather is terrible
|
||||||
|
double prohibited; // ...
|
||||||
int negflag; // reversal flag (good if > val, etc)
|
int negflag; // reversal flag (good if > val, etc)
|
||||||
|
int shtdnflag; // ==1 to shut down if `terrible`
|
||||||
} weather_cond_t;
|
} weather_cond_t;
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
int ahtung_delay; // delay to change "bad weather" to good after last "bad event"
|
int ahtung_delay; // delay to change "bad weather" to good after last "bad event"
|
||||||
|
int reinit_delay; // delay to check all sensors and reinit dead
|
||||||
// wind, m/s
|
// wind, m/s
|
||||||
weather_cond_t wind;
|
weather_cond_t wind;
|
||||||
// humidity, %%
|
// humidity, %%
|
||||||
@@ -45,6 +48,8 @@ typedef struct{
|
|||||||
weather_cond_t clouds;
|
weather_cond_t clouds;
|
||||||
// sky temperature minus ambient temperature, degC
|
// sky temperature minus ambient temperature, degC
|
||||||
weather_cond_t sky;
|
weather_cond_t sky;
|
||||||
|
// distance to lightning
|
||||||
|
weather_cond_t ligtdist;
|
||||||
} weather_conf_t;
|
} weather_conf_t;
|
||||||
|
|
||||||
// defined in cmdlnopts.c
|
// defined in cmdlnopts.c
|
||||||
@@ -54,5 +59,11 @@ int collected_amount();
|
|||||||
int get_collected(val_t *val, int N);
|
int get_collected(val_t *val, int N);
|
||||||
|
|
||||||
void forbid_observations(int f);
|
void forbid_observations(int f);
|
||||||
|
int is_forbidden();
|
||||||
|
|
||||||
void refresh_sensval(sensordata_t *s);
|
void refresh_sensval(sensordata_t *s);
|
||||||
|
|
||||||
|
int force_off(int flag);
|
||||||
|
int weather_level(int new);
|
||||||
|
|
||||||
//void run_mainweather();
|
//void run_mainweather();
|
||||||
|
|||||||
@@ -27,19 +27,40 @@ if(HYDREON)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(BTAMETEO)
|
if(BTAMETEO)
|
||||||
add_library(btameteo SHARED btameteo.c bta_shdata.c)
|
add_library(btameteo SHARED btameteo.c bta_shdata.c)
|
||||||
target_link_libraries(btameteo -lcrypt)
|
target_link_libraries(btameteo -lcrypt)
|
||||||
list(APPEND LIBS btameteo)
|
list(APPEND LIBS btameteo)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(REINHARDT)
|
if(REINHARDT)
|
||||||
add_library(reinhardt SHARED reinhardt.c)
|
add_library(reinhardt SHARED reinhardt.c)
|
||||||
list(APPEND LIBS reinhardt)
|
list(APPEND LIBS reinhardt)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WXA100)
|
if(WXA100)
|
||||||
add_library(wxa100 SHARED wxa100.c)
|
add_library(wxa100 SHARED wxa100.c)
|
||||||
list(APPEND LIBS wxa100)
|
list(APPEND LIBS wxa100)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(SNMP)
|
||||||
|
add_library(snmp SHARED snmp.c)
|
||||||
|
find_program(NETSNMP_CONFIG_BIN net-snmp-config)
|
||||||
|
if(NETSNMP_CONFIG_BIN)
|
||||||
|
# Capture linker libraries
|
||||||
|
execute_process(COMMAND ${NETSNMP_CONFIG_BIN} --libs
|
||||||
|
OUTPUT_VARIABLE NETSNMP_LIBS
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "net-snmp-config not found. Please install net-snmp package.")
|
||||||
|
endif()
|
||||||
|
message("SNMP: ${NETSNMP_LIBS}")
|
||||||
|
target_link_libraries(snmp PRIVATE ${NETSNMP_LIBS})
|
||||||
|
list(APPEND LIBS snmp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIGHTNING)
|
||||||
|
add_library(lightning SHARED lightning.c)
|
||||||
|
list(APPEND LIBS lightning)
|
||||||
|
endif()
|
||||||
|
|
||||||
install(TARGETS ${LIBS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
install(TARGETS ${LIBS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|||||||
149
Daemons/weatherdaemon_multimeteo/plugins/UPS-MIB.csv
Normal file
149
Daemons/weatherdaemon_multimeteo/plugins/UPS-MIB.csv
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
OBJECT_NAME,OBJECT_IDENTIFIER,OBJECT_DATA_TYPE,OBJECT_PREMISSIONS,OBJECT_CLASS,OBJECT_NODE_TYPE,OBJECT_DESCRIPTION
|
||||||
|
upsMIB,1.3.6.1.2.1.33,,,moduleidentity,,"The MIB module to describe Uninterruptible Power Supplies."
|
||||||
|
upsObjects,1.3.6.1.2.1.33.1,,,objectidentity,,""
|
||||||
|
upsIdent,1.3.6.1.2.1.33.1.1,,,objectidentity,,""
|
||||||
|
upsIdentManufacturer,1.3.6.1.2.1.33.1.1.1,displaystring,read-only,objecttype,scalar,"The name of the UPS manufacturer."
|
||||||
|
upsIdentModel,1.3.6.1.2.1.33.1.1.2,displaystring,read-only,objecttype,scalar,"The UPS Model designation."
|
||||||
|
upsIdentUPSSoftwareVersion,1.3.6.1.2.1.33.1.1.3,displaystring,read-only,objecttype,scalar,"The UPS firmware/software version(s). This variable may or may not have the same value as upsIdentAgentSoftwareVersion in some implementations."
|
||||||
|
upsIdentAgentSoftwareVersion,1.3.6.1.2.1.33.1.1.4,displaystring,read-only,objecttype,scalar,"The UPS agent software version. This variable may or may not have the same value as upsIdentUPSSoftwareVersion in some implementations."
|
||||||
|
upsIdentName,1.3.6.1.2.1.33.1.1.5,displaystring,read-write,objecttype,scalar,"A string identifying the UPS. This object should be set by the administrator."
|
||||||
|
upsIdentAttachedDevices,1.3.6.1.2.1.33.1.1.6,displaystring,read-write,objecttype,scalar,"A string identifying the devices attached to the output(s) of the UPS. This object should be set by the administrator."
|
||||||
|
upsBattery,1.3.6.1.2.1.33.1.2,,,objectidentity,,""
|
||||||
|
upsBatteryStatus,1.3.6.1.2.1.33.1.2.1,integer,read-only,objecttype,scalar,"The indication of the capacity remaining in the UPS system's batteries. A value of batteryNormal indicates that the remaining run-time is greater than upsConfigLowBattTime. A value of batteryLow indicates that the remaining battery run-time is less than or equal to upsConfigLowBattTime. A value of batteryDepleted indicates that the UPS will be unable to sustain the present load when and if the utility power is lost (including the possibility that the utility power is currently absent and the UPS is unable to sustain the output). Enumeration: 'unknown': 1, 'batteryNormal': 2, 'batteryDepleted': 4, 'batteryLow': 3."
|
||||||
|
upsSecondsOnBattery,1.3.6.1.2.1.33.1.2.2,nonnegativeinteger,read-only,objecttype,scalar,"If the unit is on battery power, the elapsed time since the UPS last switched to battery power, or the time since the network management subsystem was last restarted, whichever is less. Zero shall be returned if the unit is not on battery power."
|
||||||
|
upsEstimatedMinutesRemaining,1.3.6.1.2.1.33.1.2.3,positiveinteger,read-only,objecttype,scalar,"An estimate of the time to battery charge depletion under the present load conditions if the utility power is off and remains off, or if it were to be lost and remain off."
|
||||||
|
upsEstimatedChargeRemaining,1.3.6.1.2.1.33.1.2.4,integer,read-only,objecttype,scalar,"An estimate of the battery charge remaining expressed as a percent of full charge."
|
||||||
|
upsBatteryVoltage,1.3.6.1.2.1.33.1.2.5,nonnegativeinteger,read-only,objecttype,scalar,"The magnitude of the present battery voltage."
|
||||||
|
upsBatteryCurrent,1.3.6.1.2.1.33.1.2.6,integer32,read-only,objecttype,scalar,"The present battery current."
|
||||||
|
upsBatteryTemperature,1.3.6.1.2.1.33.1.2.7,integer32,read-only,objecttype,scalar,"The ambient temperature at or near the UPS Battery casing."
|
||||||
|
upsInput,1.3.6.1.2.1.33.1.3,,,objectidentity,,""
|
||||||
|
upsInputLineBads,1.3.6.1.2.1.33.1.3.1,counter32,read-only,objecttype,scalar,"A count of the number of times the input entered an out-of-tolerance condition as defined by the manufacturer. This count is incremented by one each time the input transitions from zero out-of-tolerance lines to one or more input lines out-of-tolerance."
|
||||||
|
upsInputNumLines,1.3.6.1.2.1.33.1.3.2,nonnegativeinteger,read-only,objecttype,scalar,"The number of input lines utilized in this device. This variable indicates the number of rows in the input table."
|
||||||
|
upsInputTable,1.3.6.1.2.1.33.1.3.3,,no-access,objecttype,table,"A list of input table entries. The number of entries is given by the value of upsInputNumLines."
|
||||||
|
upsInputEntry,1.3.6.1.2.1.33.1.3.3.1,,no-access,objecttype,row,"An entry containing information applicable to a particular input line."
|
||||||
|
upsInputLineIndex,1.3.6.1.2.1.33.1.3.3.1.1,positiveinteger,no-access,objecttype,column,"The input line identifier."
|
||||||
|
upsInputFrequency,1.3.6.1.2.1.33.1.3.3.1.2,nonnegativeinteger,read-only,objecttype,column,"The present input frequency."
|
||||||
|
upsInputVoltage,1.3.6.1.2.1.33.1.3.3.1.3,nonnegativeinteger,read-only,objecttype,column,"The magnitude of the present input voltage."
|
||||||
|
upsInputCurrent,1.3.6.1.2.1.33.1.3.3.1.4,nonnegativeinteger,read-only,objecttype,column,"The magnitude of the present input current."
|
||||||
|
upsInputTruePower,1.3.6.1.2.1.33.1.3.3.1.5,nonnegativeinteger,read-only,objecttype,column,"The magnitude of the present input true power."
|
||||||
|
upsOutput,1.3.6.1.2.1.33.1.4,,,objectidentity,,""
|
||||||
|
upsOutputSource,1.3.6.1.2.1.33.1.4.1,integer,read-only,objecttype,scalar,"The present source of output power. The enumeration none(2) indicates that there is no source of output power (and therefore no output power), for example, the system has opened the output breaker. Enumeration: 'none': 2, 'normal': 3, 'battery': 5, 'reducer': 7, 'other': 1, 'bypass': 4, 'booster': 6."
|
||||||
|
upsOutputFrequency,1.3.6.1.2.1.33.1.4.2,nonnegativeinteger,read-only,objecttype,scalar,"The present output frequency."
|
||||||
|
upsOutputNumLines,1.3.6.1.2.1.33.1.4.3,nonnegativeinteger,read-only,objecttype,scalar,"The number of output lines utilized in this device. This variable indicates the number of rows in the output table."
|
||||||
|
upsOutputTable,1.3.6.1.2.1.33.1.4.4,,no-access,objecttype,table,"A list of output table entries. The number of entries is given by the value of upsOutputNumLines."
|
||||||
|
upsOutputEntry,1.3.6.1.2.1.33.1.4.4.1,,no-access,objecttype,row,"An entry containing information applicable to a particular output line."
|
||||||
|
upsOutputLineIndex,1.3.6.1.2.1.33.1.4.4.1.1,positiveinteger,no-access,objecttype,column,"The output line identifier."
|
||||||
|
upsOutputVoltage,1.3.6.1.2.1.33.1.4.4.1.2,nonnegativeinteger,read-only,objecttype,column,"The present output voltage."
|
||||||
|
upsOutputCurrent,1.3.6.1.2.1.33.1.4.4.1.3,nonnegativeinteger,read-only,objecttype,column,"The present output current."
|
||||||
|
upsOutputPower,1.3.6.1.2.1.33.1.4.4.1.4,nonnegativeinteger,read-only,objecttype,column,"The present output true power."
|
||||||
|
upsOutputPercentLoad,1.3.6.1.2.1.33.1.4.4.1.5,integer,read-only,objecttype,column,"The percentage of the UPS power capacity presently being used on this output line, i.e., the greater of the percent load of true power capacity and the percent load of VA."
|
||||||
|
upsBypass,1.3.6.1.2.1.33.1.5,,,objectidentity,,""
|
||||||
|
upsBypassFrequency,1.3.6.1.2.1.33.1.5.1,nonnegativeinteger,read-only,objecttype,scalar,"The present bypass frequency."
|
||||||
|
upsBypassNumLines,1.3.6.1.2.1.33.1.5.2,nonnegativeinteger,read-only,objecttype,scalar,"The number of bypass lines utilized in this device. This entry indicates the number of rows in the bypass table."
|
||||||
|
upsBypassTable,1.3.6.1.2.1.33.1.5.3,,no-access,objecttype,table,"A list of bypass table entries. The number of entries is given by the value of upsBypassNumLines."
|
||||||
|
upsBypassEntry,1.3.6.1.2.1.33.1.5.3.1,,no-access,objecttype,row,"An entry containing information applicable to a particular bypass input."
|
||||||
|
upsBypassLineIndex,1.3.6.1.2.1.33.1.5.3.1.1,positiveinteger,no-access,objecttype,column,"The bypass line identifier."
|
||||||
|
upsBypassVoltage,1.3.6.1.2.1.33.1.5.3.1.2,nonnegativeinteger,read-only,objecttype,column,"The present bypass voltage."
|
||||||
|
upsBypassCurrent,1.3.6.1.2.1.33.1.5.3.1.3,nonnegativeinteger,read-only,objecttype,column,"The present bypass current."
|
||||||
|
upsBypassPower,1.3.6.1.2.1.33.1.5.3.1.4,nonnegativeinteger,read-only,objecttype,column,"The present true power conveyed by the bypass."
|
||||||
|
upsAlarm,1.3.6.1.2.1.33.1.6,,,objectidentity,,""
|
||||||
|
upsAlarmsPresent,1.3.6.1.2.1.33.1.6.1,gauge32,read-only,objecttype,scalar,"The present number of active alarm conditions."
|
||||||
|
upsAlarmTable,1.3.6.1.2.1.33.1.6.2,,no-access,objecttype,table,"A list of alarm table entries. The table contains zero, one, or many rows at any moment, depending upon the number of alarm conditions in effect. The table is initially empty at agent startup. The agent creates a row in the table each time a condition is detected and deletes that row when that condition no longer pertains. The agent creates the first row with upsAlarmId equal to 1, and increments the value of upsAlarmId each time a new row is created, wrapping to the first free value greater than or equal to 1 when the maximum value of upsAlarmId would otherwise be exceeded. Consequently, after multiple operations, the table may become sparse, e.g., containing entries for rows 95, 100, 101, and 203 and the entries should not be assumed to be in chronological order because upsAlarmId might have wrapped. Alarms are named by an AutonomousType (OBJECT IDENTIFIER), upsAlarmDescr, to allow a single table to reflect well known alarms plus alarms defined by a particular implementation, i.e., as documented in the private enterprise MIB definition for the device. No two rows will have the same value of upsAlarmDescr, since alarms define conditions. In order to meet this requirement, care should be taken in the definition of alarm conditions to insure that a system cannot enter the same condition multiple times simultaneously. The number of rows in the table at any given time is reflected by the value of upsAlarmsPresent."
|
||||||
|
upsAlarmEntry,1.3.6.1.2.1.33.1.6.2.1,,no-access,objecttype,row,"An entry containing information applicable to a particular alarm."
|
||||||
|
upsAlarmId,1.3.6.1.2.1.33.1.6.2.1.1,positiveinteger,no-access,objecttype,column,"A unique identifier for an alarm condition. This value must remain constant."
|
||||||
|
upsAlarmDescr,1.3.6.1.2.1.33.1.6.2.1.2,autonomoustype,read-only,objecttype,column,"A reference to an alarm description object. The object referenced should not be accessible, but rather be used to provide a unique description of the alarm condition."
|
||||||
|
upsAlarmTime,1.3.6.1.2.1.33.1.6.2.1.3,timestamp,read-only,objecttype,column,"The value of sysUpTime when the alarm condition was detected. If the alarm condition was detected at the time of agent startup and presumably existed before agent startup, the value of upsAlarmTime shall equal 0."
|
||||||
|
upsWellKnownAlarms,1.3.6.1.2.1.33.1.6.3,,,objectidentity,,""
|
||||||
|
upsAlarmBatteryBad,1.3.6.1.2.1.33.1.6.3.1,,,objectidentity,,"One or more batteries have been determined to require replacement."
|
||||||
|
upsAlarmOnBattery,1.3.6.1.2.1.33.1.6.3.2,,,objectidentity,,"The UPS is drawing power from the batteries."
|
||||||
|
upsAlarmLowBattery,1.3.6.1.2.1.33.1.6.3.3,,,objectidentity,,"The remaining battery run-time is less than or equal to upsConfigLowBattTime."
|
||||||
|
upsAlarmDepletedBattery,1.3.6.1.2.1.33.1.6.3.4,,,objectidentity,,"The UPS will be unable to sustain the present load when and if the utility power is lost."
|
||||||
|
upsAlarmTempBad,1.3.6.1.2.1.33.1.6.3.5,,,objectidentity,,"A temperature is out of tolerance."
|
||||||
|
upsAlarmInputBad,1.3.6.1.2.1.33.1.6.3.6,,,objectidentity,,"An input condition is out of tolerance."
|
||||||
|
upsAlarmOutputBad,1.3.6.1.2.1.33.1.6.3.7,,,objectidentity,,"An output condition (other than OutputOverload) is out of tolerance."
|
||||||
|
upsAlarmOutputOverload,1.3.6.1.2.1.33.1.6.3.8,,,objectidentity,,"The output load exceeds the UPS output capacity."
|
||||||
|
upsAlarmOnBypass,1.3.6.1.2.1.33.1.6.3.9,,,objectidentity,,"The Bypass is presently engaged on the UPS."
|
||||||
|
upsAlarmBypassBad,1.3.6.1.2.1.33.1.6.3.10,,,objectidentity,,"The Bypass is out of tolerance."
|
||||||
|
upsAlarmOutputOffAsRequested,1.3.6.1.2.1.33.1.6.3.11,,,objectidentity,,"The UPS has shutdown as requested, i.e., the output is off."
|
||||||
|
upsAlarmUpsOffAsRequested,1.3.6.1.2.1.33.1.6.3.12,,,objectidentity,,"The entire UPS has shutdown as commanded."
|
||||||
|
upsAlarmChargerFailed,1.3.6.1.2.1.33.1.6.3.13,,,objectidentity,,"An uncorrected problem has been detected within the UPS charger subsystem."
|
||||||
|
upsAlarmUpsOutputOff,1.3.6.1.2.1.33.1.6.3.14,,,objectidentity,,"The output of the UPS is in the off state."
|
||||||
|
upsAlarmUpsSystemOff,1.3.6.1.2.1.33.1.6.3.15,,,objectidentity,,"The UPS system is in the off state."
|
||||||
|
upsAlarmFanFailure,1.3.6.1.2.1.33.1.6.3.16,,,objectidentity,,"The failure of one or more fans in the UPS has been detected."
|
||||||
|
upsAlarmFuseFailure,1.3.6.1.2.1.33.1.6.3.17,,,objectidentity,,"The failure of one or more fuses has been detected."
|
||||||
|
upsAlarmGeneralFault,1.3.6.1.2.1.33.1.6.3.18,,,objectidentity,,"A general fault in the UPS has been detected."
|
||||||
|
upsAlarmDiagnosticTestFailed,1.3.6.1.2.1.33.1.6.3.19,,,objectidentity,,"The result of the last diagnostic test indicates a failure."
|
||||||
|
upsAlarmCommunicationsLost,1.3.6.1.2.1.33.1.6.3.20,,,objectidentity,,"A problem has been encountered in the communications between the agent and the UPS."
|
||||||
|
upsAlarmAwaitingPower,1.3.6.1.2.1.33.1.6.3.21,,,objectidentity,,"The UPS output is off and the UPS is awaiting the return of input power."
|
||||||
|
upsAlarmShutdownPending,1.3.6.1.2.1.33.1.6.3.22,,,objectidentity,,"A upsShutdownAfterDelay countdown is underway."
|
||||||
|
upsAlarmShutdownImminent,1.3.6.1.2.1.33.1.6.3.23,,,objectidentity,,"The UPS will turn off power to the load in less than 5 seconds; this may be either a timed shutdown or a low battery shutdown."
|
||||||
|
upsAlarmTestInProgress,1.3.6.1.2.1.33.1.6.3.24,,,objectidentity,,"A test is in progress, as initiated and indicated by the Test Group. Tests initiated via other implementation-specific mechanisms can indicate the presence of the testing in the alarm table, if desired, via a OBJECT-IDENTITY macro in the MIB document specific to that implementation and are outside the scope of this OBJECT-IDENTITY."
|
||||||
|
upsTest,1.3.6.1.2.1.33.1.7,,,objectidentity,,""
|
||||||
|
upsTestId,1.3.6.1.2.1.33.1.7.1,object identifier,read-write,objecttype,scalar,"The test is named by an OBJECT IDENTIFIER which allows a standard mechanism for the initiation of tests, including the well known tests identified in this document as well as those introduced by a particular implementation, i.e., as documented in the private enterprise MIB definition for the device. Setting this variable initiates the named test. Sets to this variable require the presence of upsTestSpinLock in the same SNMP message. The set request will be rejected with an appropriate error message if the requested test cannot be performed, including attempts to start a test when another test is already in progress. The status of the current or last test is maintained in upsTestResultsSummary. Tests in progress may be aborted by setting the upsTestId variable to upsTestAbortTestInProgress. Read operations return the value of the name of the test in progress if a test is in progress or the name of the last test performed if no test is in progress, unless no test has been run, in which case the well known value upsTestNoTestsInitiated is returned."
|
||||||
|
upsTestSpinLock,1.3.6.1.2.1.33.1.7.2,testandincr,read-write,objecttype,scalar,"A spin lock on the test subsystem. The spinlock is used as follows. Before starting a test, a manager-station should make sure that a test is not in progress as follows: try_again: get (upsTestSpinLock) while (upsTestResultsSummary == inProgress) { /* loop while a test is running for another manager */ short delay get (upsTestSpinLock) } lock_value = upsTestSpinLock /* no test in progress, start the test */ set (upsTestSpinLock = lock_value, upsTestId = requested_test) if (error_index == 1) { /* (upsTestSpinLock failed) */ /* if problem is not access control, then some other manager slipped in ahead of us */ goto try_again } if (error_index == 2) { /* (upsTestId) */ /* cannot perform the test */ give up } /* test started ok */ /* wait for test completion by polling upsTestResultsSummary */ get (upsTestSpinLock, upsTestResultsSummary, upsTestResultsDetail) while (upsTestResultsSummary == inProgress) { short delay get (upsTestSpinLock, upsTestResultsSummary, upsTestResultsDetail) } /* when test completes, retrieve any additional test results */ /* if upsTestSpinLock == lock_value + 1, then these are our test */ /* results (as opposed to another manager's */ The initial value of upsTestSpinLock at agent initialization shall be 1."
|
||||||
|
upsTestResultsSummary,1.3.6.1.2.1.33.1.7.3,integer,read-only,objecttype,scalar,"The results of the current or last UPS diagnostics test performed. The values for donePass(1), doneWarning(2), and doneError(3) indicate that the test completed either successfully, with a warning, or with an error, respectively. The value aborted(4) is returned for tests which are aborted by setting the value of upsTestId to upsTestAbortTestInProgress. Tests which have not yet concluded are indicated by inProgress(5). The value noTestsInitiated(6) indicates that no previous test results are available, such as is the case when no tests have been run since the last reinitialization of the network management subsystem and the system has no provision for non- volatile storage of test results. Enumeration: 'doneError': 3, 'noTestsInitiated': 6, 'donePass': 1, 'doneWarning': 2, 'aborted': 4, 'inProgress': 5."
|
||||||
|
upsTestResultsDetail,1.3.6.1.2.1.33.1.7.4,displaystring,read-only,objecttype,scalar,"Additional information about upsTestResultsSummary. If no additional information available, a zero length string is returned."
|
||||||
|
upsTestStartTime,1.3.6.1.2.1.33.1.7.5,timestamp,read-only,objecttype,scalar,"The value of sysUpTime at the time the test in progress was initiated, or, if no test is in progress, the time the previous test was initiated. If the value of upsTestResultsSummary is noTestsInitiated(6), upsTestStartTime has the value 0."
|
||||||
|
upsTestElapsedTime,1.3.6.1.2.1.33.1.7.6,timeinterval,read-only,objecttype,scalar,"The amount of time, in TimeTicks, since the test in progress was initiated, or, if no test is in progress, the previous test took to complete. If the value of upsTestResultsSummary is noTestsInitiated(6), upsTestElapsedTime has the value 0."
|
||||||
|
upsWellKnownTests,1.3.6.1.2.1.33.1.7.7,,,objectidentity,,""
|
||||||
|
upsTestNoTestsInitiated,1.3.6.1.2.1.33.1.7.7.1,,,objectidentity,,"No tests have been initiated and no test is in progress."
|
||||||
|
upsTestAbortTestInProgress,1.3.6.1.2.1.33.1.7.7.2,,,objectidentity,,"The test in progress is to be aborted / the test in progress was aborted."
|
||||||
|
upsTestGeneralSystemsTest,1.3.6.1.2.1.33.1.7.7.3,,,objectidentity,,"The manufacturer's standard test of UPS device systems."
|
||||||
|
upsTestQuickBatteryTest,1.3.6.1.2.1.33.1.7.7.4,,,objectidentity,,"A test that is sufficient to determine if the battery needs replacement."
|
||||||
|
upsTestDeepBatteryCalibration,1.3.6.1.2.1.33.1.7.7.5,,,objectidentity,,"The system is placed on battery to a discharge level, set by the manufacturer, sufficient to determine battery replacement and battery run-time with a high degree of confidence. WARNING: this test will leave the battery in a low charge state and will require time for recharging to a level sufficient to provide normal battery duration for the protected load."
|
||||||
|
upsControl,1.3.6.1.2.1.33.1.8,,,objectidentity,,""
|
||||||
|
upsShutdownType,1.3.6.1.2.1.33.1.8.1,integer,read-write,objecttype,scalar,"This object determines the nature of the action to be taken at the time when the countdown of the upsShutdownAfterDelay and upsRebootWithDuration objects reaches zero. Setting this object to output(1) indicates that shutdown requests should cause only the output of the UPS to turn off. Setting this object to system(2) indicates that shutdown requests will cause the entire UPS system to turn off. Enumeration: 'output': 1, 'system': 2."
|
||||||
|
upsShutdownAfterDelay,1.3.6.1.2.1.33.1.8.2,integer,read-write,objecttype,scalar,"Setting this object will shutdown (i.e., turn off) either the UPS output or the UPS system (as determined by the value of upsShutdownType at the time of shutdown) after the indicated number of seconds, or less if the UPS batteries become depleted. Setting this object to 0 will cause the shutdown to occur immediately. Setting this object to -1 will abort the countdown. If the system is already in the desired state at the time the countdown reaches 0, then nothing will happen. That is, there is no additional action at that time if upsShutdownType = system and the system is already off. Similarly, there is no additional action at that time if upsShutdownType = output and the output is already off. When read, upsShutdownAfterDelay will return the number of seconds remaining until shutdown, or -1 if no shutdown countdown is in effect. On some systems, if the agent is restarted while a shutdown countdown is in effect, the countdown may be aborted. Sets to this object override any upsShutdownAfterDelay already in effect."
|
||||||
|
upsStartupAfterDelay,1.3.6.1.2.1.33.1.8.3,integer,read-write,objecttype,scalar,"Setting this object will start the output after the indicated number of seconds, including starting the UPS, if necessary. Setting this object to 0 will cause the startup to occur immediately. Setting this object to -1 will abort the countdown. If the output is already on at the time the countdown reaches 0, then nothing will happen. Sets to this object override the effect of any upsStartupAfterDelay countdown or upsRebootWithDuration countdown in progress. When read, upsStartupAfterDelay will return the number of seconds until startup, or -1 if no startup countdown is in effect. If the countdown expires during a utility failure, the startup shall not occur until the utility power is restored. On some systems, if the agent is restarted while a startup countdown is in effect, the countdown is aborted."
|
||||||
|
upsRebootWithDuration,1.3.6.1.2.1.33.1.8.4,integer,read-write,objecttype,scalar,"Setting this object will immediately shutdown (i.e., turn off) either the UPS output or the UPS system (as determined by the value of upsShutdownType at the time of shutdown) for a period equal to the indicated number of seconds, after which time the output will be started, including starting the UPS, if necessary. If the number of seconds required to perform the request is greater than the requested duration, then the requested shutdown and startup cycle shall be performed in the minimum time possible, but in no case shall this require more than the requested duration plus 60 seconds. When read, upsRebootWithDuration shall return the number of seconds remaining in the countdown, or -1 if no countdown is in progress. If the startup should occur during a utility failure, the startup shall not occur until the utility power is restored."
|
||||||
|
upsAutoRestart,1.3.6.1.2.1.33.1.8.5,integer,read-write,objecttype,scalar,"Setting this object to 'on' will cause the UPS system to restart after a shutdown if the shutdown occurred during a power loss as a result of either a upsShutdownAfterDelay or an internal battery depleted condition. Setting this object to 'off' will prevent the UPS system from restarting after a shutdown until an operator manually or remotely explicitly restarts it. If the UPS is in a startup or reboot countdown, then the UPS will not restart until that delay has been satisfied. Enumeration: 'on': 1, 'off': 2."
|
||||||
|
upsConfig,1.3.6.1.2.1.33.1.9,,,objectidentity,,""
|
||||||
|
upsConfigInputVoltage,1.3.6.1.2.1.33.1.9.1,nonnegativeinteger,read-write,objecttype,scalar,"The magnitude of the nominal input voltage. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigInputFreq,1.3.6.1.2.1.33.1.9.2,nonnegativeinteger,read-write,objecttype,scalar,"The nominal input frequency. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigOutputVoltage,1.3.6.1.2.1.33.1.9.3,nonnegativeinteger,read-write,objecttype,scalar,"The magnitude of the nominal output voltage. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigOutputFreq,1.3.6.1.2.1.33.1.9.4,nonnegativeinteger,read-write,objecttype,scalar,"The nominal output frequency. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigOutputVA,1.3.6.1.2.1.33.1.9.5,nonnegativeinteger,read-only,objecttype,scalar,"The magnitude of the nominal Volt-Amp rating."
|
||||||
|
upsConfigOutputPower,1.3.6.1.2.1.33.1.9.6,nonnegativeinteger,read-only,objecttype,scalar,"The magnitude of the nominal true power rating."
|
||||||
|
upsConfigLowBattTime,1.3.6.1.2.1.33.1.9.7,nonnegativeinteger,read-write,objecttype,scalar,"The value of upsEstimatedMinutesRemaining at which a lowBattery condition is declared. For agents which support only discrete (discontinuous) values, then the agent shall round up to the next supported value. If the requested value is larger than the largest supported value, then the largest supported value shall be selected."
|
||||||
|
upsConfigAudibleStatus,1.3.6.1.2.1.33.1.9.8,integer,read-write,objecttype,scalar,"The requested state of the audible alarm. When in the disabled state, the audible alarm should never sound. The enabled state is self-describing. Setting this object to muted(3) when the audible alarm is sounding shall temporarily silence the alarm. It will remain muted until it would normally stop sounding and the value returned for read operations during this period shall equal muted(3). At the end of this period, the value shall revert to enabled(2). Writes of the value muted(3) when the audible alarm is not sounding shall be accepted but otherwise shall have no effect. Enumeration: 'disabled': 1, 'muted': 3, 'enabled': 2."
|
||||||
|
upsConfigLowVoltageTransferPoint,1.3.6.1.2.1.33.1.9.9,nonnegativeinteger,read-write,objecttype,scalar,"The minimum input line voltage allowed before the UPS system transfers to battery backup."
|
||||||
|
upsConfigHighVoltageTransferPoint,1.3.6.1.2.1.33.1.9.10,nonnegativeinteger,read-write,objecttype,scalar,"The maximum line voltage allowed before the UPS system transfers to battery backup."
|
||||||
|
upsTraps,1.3.6.1.2.1.33.2,,,objectidentity,,""
|
||||||
|
upsTrapOnBattery,1.3.6.1.2.1.33.2.1,,,notificationtype,,"The UPS is operating on battery power. This trap is persistent and is resent at one minute intervals until the UPS either turns off or is no longer running on battery."
|
||||||
|
upsTrapTestCompleted,1.3.6.1.2.1.33.2.2,,,notificationtype,,"This trap is sent upon completion of a UPS diagnostic test."
|
||||||
|
upsTrapAlarmEntryAdded,1.3.6.1.2.1.33.2.3,,,notificationtype,,"This trap is sent each time an alarm is inserted into to the alarm table. It is sent on the insertion of all alarms except for upsAlarmOnBattery and upsAlarmTestInProgress."
|
||||||
|
upsTrapAlarmEntryRemoved,1.3.6.1.2.1.33.2.4,,,notificationtype,,"This trap is sent each time an alarm is removed from the alarm table. It is sent on the removal of all alarms except for upsAlarmTestInProgress."
|
||||||
|
upsConformance,1.3.6.1.2.1.33.3,,,objectidentity,,""
|
||||||
|
upsCompliances,1.3.6.1.2.1.33.3.1,,,objectidentity,,""
|
||||||
|
upsSubsetCompliance,1.3.6.1.2.1.33.3.1.1,,,modulecompliance,,"The compliance statement for UPSs that only support the two-contact communication protocol."
|
||||||
|
upsBasicCompliance,1.3.6.1.2.1.33.3.1.2,,,modulecompliance,,"The compliance statement for UPSs that support full-featured functions, such as control."
|
||||||
|
upsFullCompliance,1.3.6.1.2.1.33.3.1.3,,,modulecompliance,,"The compliance statement for UPSs that support advanced full-featured functions."
|
||||||
|
upsGroups,1.3.6.1.2.1.33.3.2,,,objectidentity,,""
|
||||||
|
upsSubsetGroups,1.3.6.1.2.1.33.3.2.1,,,objectidentity,,""
|
||||||
|
upsSubsetIdentGroup,1.3.6.1.2.1.33.3.2.1.1,,,objectgroup,,"The upsSubsetIdentGroup defines objects which are common across all UPSs which meet subset compliance. Most devices which conform to the upsSubsetIdentGroup will provide access to these objects via a proxy agent. If the proxy agent is compatible with multiple UPS types, configuration of the proxy agent will require specifying some of these values, either individually, or as a group (perhaps through a table lookup mechanism based on the UPS model number)."
|
||||||
|
upsSubsetBatteryGroup,1.3.6.1.2.1.33.3.2.1.2,,,objectgroup,,"The upsSubsetBatteryGroup defines the objects that are common to battery groups of two-contact UPSs."
|
||||||
|
upsSubsetInputGroup,1.3.6.1.2.1.33.3.2.1.3,,,objectgroup,,"The upsSubsetInputGroup defines the objects that are common to the Input groups of two-contact UPSs."
|
||||||
|
upsSubsetOutputGroup,1.3.6.1.2.1.33.3.2.1.4,,,objectgroup,,"The upsSubsetOutputGroup defines the objects that are common to the Output groups of two-contact UPSs."
|
||||||
|
upsSubsetAlarmGroup,1.3.6.1.2.1.33.3.2.1.6,,,objectgroup,,"The upsSubsetAlarmGroup defines the objects that are common to the Alarm groups of two-contact UPSs."
|
||||||
|
upsSubsetControlGroup,1.3.6.1.2.1.33.3.2.1.8,,,objectgroup,,"The upsSubsetControlGroup defines the objects that are common to the Control groups of two-contact UPSs."
|
||||||
|
upsSubsetConfigGroup,1.3.6.1.2.1.33.3.2.1.9,,,objectgroup,,"The upsSubsetConfigGroup defines the objects that are common to the Config groups of two-contact UPSs."
|
||||||
|
upsBasicGroups,1.3.6.1.2.1.33.3.2.2,,,objectidentity,,""
|
||||||
|
upsBasicIdentGroup,1.3.6.1.2.1.33.3.2.2.1,,,objectgroup,,"The upsBasicIdentGroup defines objects which are common to the Ident group of compliant UPSs which support basic functions."
|
||||||
|
upsBasicBatteryGroup,1.3.6.1.2.1.33.3.2.2.2,,,objectgroup,,"The upsBasicBatteryGroup defines the objects that are common to the battery groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicInputGroup,1.3.6.1.2.1.33.3.2.2.3,,,objectgroup,,"The upsBasicInputGroup defines the objects that are common to the Input groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicOutputGroup,1.3.6.1.2.1.33.3.2.2.4,,,objectgroup,,"The upsBasicOutputGroup defines the objects that are common to the Output groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicBypassGroup,1.3.6.1.2.1.33.3.2.2.5,,,objectgroup,,"The upsBasicBypassGroup defines the objects that are common to the Bypass groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicAlarmGroup,1.3.6.1.2.1.33.3.2.2.6,,,objectgroup,,"The upsBasicAlarmGroup defines the objects that are common to the Alarm groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicTestGroup,1.3.6.1.2.1.33.3.2.2.7,,,objectgroup,,"The upsBasicTestGroup defines the objects that are common to the Test groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicControlGroup,1.3.6.1.2.1.33.3.2.2.8,,,objectgroup,,"The upsBasicControlGroup defines the objects that are common to the Control groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicConfigGroup,1.3.6.1.2.1.33.3.2.2.9,,,objectgroup,,"The upsBasicConfigGroup defines the objects that are common to the Config groups of UPSs which support basic functions."
|
||||||
|
upsFullGroups,1.3.6.1.2.1.33.3.2.3,,,objectidentity,,""
|
||||||
|
upsFullIdentGroup,1.3.6.1.2.1.33.3.2.3.1,,,objectgroup,,"The upsFullIdentGroup defines objects which are common to the Ident group of fully compliant UPSs."
|
||||||
|
upsFullBatteryGroup,1.3.6.1.2.1.33.3.2.3.2,,,objectgroup,,"The upsFullBatteryGroup defines the objects that are common to the battery groups of fully compliant UPSs."
|
||||||
|
upsFullInputGroup,1.3.6.1.2.1.33.3.2.3.3,,,objectgroup,,"The upsFullInputGroup defines the objects that are common to the Input groups of fully compliant UPSs."
|
||||||
|
upsFullOutputGroup,1.3.6.1.2.1.33.3.2.3.4,,,objectgroup,,"The upsFullOutputGroup defines the objects that are common to the Output groups of fully compliant UPSs."
|
||||||
|
upsFullBypassGroup,1.3.6.1.2.1.33.3.2.3.5,,,objectgroup,,"The upsFullBypassGroup defines the objects that are common to the Bypass groups of fully compliant UPSs."
|
||||||
|
upsFullAlarmGroup,1.3.6.1.2.1.33.3.2.3.6,,,objectgroup,,"The upsFullAlarmGroup defines the objects that are common to the Alarm groups of fully compliant UPSs."
|
||||||
|
upsFullTestGroup,1.3.6.1.2.1.33.3.2.3.7,,,objectgroup,,"The upsFullTestGroup defines the objects that are common to the Test groups of fully compliant UPSs."
|
||||||
|
upsFullControlGroup,1.3.6.1.2.1.33.3.2.3.8,,,objectgroup,,"The upsFullControlGroup defines the objects that are common to the Control groups of fully compliant UPSs."
|
||||||
|
upsFullConfigGroup,1.3.6.1.2.1.33.3.2.3.9,,,objectgroup,,"The upsFullConfigGroup defines the objects that are common to the Config groups of fully compliant UPSs."
|
||||||
|
@@ -41,9 +41,9 @@ static const val_t values[NAMOUNT] = {
|
|||||||
static void *mainthread(void *s){
|
static void *mainthread(void *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
sensordata_t *sensor = (sensordata_t *)s;
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
while(1){
|
while(sensor->fdes > -1){
|
||||||
if(check_shm_block(&sdat)){
|
if(check_shm_block(&sdat)){
|
||||||
DBG("Got next");
|
//DBG("Got next");
|
||||||
time_t tnow = time(NULL);
|
time_t tnow = time(NULL);
|
||||||
pthread_mutex_lock(&sensor->valmutex);
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
for(int i = 0; i < NAMOUNT; ++i)
|
for(int i = 0; i < NAMOUNT; ++i)
|
||||||
@@ -52,7 +52,7 @@ static void *mainthread(void *s){
|
|||||||
sensor->values[NPRESSURE].value.f = val_B;
|
sensor->values[NPRESSURE].value.f = val_B;
|
||||||
sensor->values[NAMB_TEMP].value.f = val_T1;
|
sensor->values[NAMB_TEMP].value.f = val_T1;
|
||||||
sensor->values[NHUMIDITY].value.f = val_Hmd;
|
sensor->values[NHUMIDITY].value.f = val_Hmd;
|
||||||
DBG("Tprecip=%.1f, tnow=%.1f", Precip_time, sl_dtime());
|
//DBG("Tprecip=%.1f, tnow=%.1f", Precip_time, sl_dtime());
|
||||||
sensor->values[NPRECIP].value.u = (tnow - (time_t)Precip_time < 60) ? 1 : 0;
|
sensor->values[NPRECIP].value.u = (tnow - (time_t)Precip_time < 60) ? 1 : 0;
|
||||||
pthread_mutex_unlock(&sensor->valmutex);
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||||
@@ -64,32 +64,24 @@ static void *mainthread(void *s){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t *sensor_new(int N, time_t pollt, int _U_ fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
sensordata_t *s = common_new();
|
if(!s) return FALSE;
|
||||||
if(!s) return NULL;
|
|
||||||
s->PluginNo = N;
|
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
if(!get_shm_block(&sdat, ClientSide)){
|
if(!get_shm_block(&sdat, ClientSide)){
|
||||||
WARNX("Can't get BTA shared memory block or create main thread");
|
WARNX("Can't get BTA shared memory block or create main thread");
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
s->values = MALLOC(val_t, NAMOUNT);
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
s->Nvalues = NAMOUNT;
|
s->Nvalues = NAMOUNT;
|
||||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
||||||
/*if(!(s->ringbuffer = sl_RB_new(BUFSIZ))){
|
|
||||||
WARNX("Can't init ringbuffer!");
|
|
||||||
common_kill(s);
|
|
||||||
return -1;
|
|
||||||
}*/
|
|
||||||
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
WARN("Can't create main thread");
|
WARN("Can't create main thread");
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
s->fdes = 0;
|
s->fdes = 0;
|
||||||
return s;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,15 @@ static const val_t values[NS] = { // fields `name` and `comment` have no sense u
|
|||||||
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||||
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||||
{.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
{.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
|
//{.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
|
||||||
};
|
};
|
||||||
|
|
||||||
static void *mainthread(void *s){
|
static void *mainthread(void *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
double t0 = sl_dtime();
|
double t0 = sl_dtime();
|
||||||
sensordata_t *sensor = (sensordata_t *)s;
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
while(1){
|
while(sensor->fdes > -1){
|
||||||
DBG("locked");
|
//DBG("locked");
|
||||||
pthread_mutex_lock(&sensor->valmutex);
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
float f = sensor->values[0].value.f + (drand48() - 0.5) / 2.;
|
float f = sensor->values[0].value.f + (drand48() - 0.5) / 2.;
|
||||||
if(f >= 0.) sensor->values[0].value.f = f;
|
if(f >= 0.) sensor->values[0].value.f = f;
|
||||||
@@ -48,13 +49,19 @@ static void *mainthread(void *s){
|
|||||||
if(f > 13. && f < 21.) sensor->values[2].value.f = f;
|
if(f > 13. && f < 21.) sensor->values[2].value.f = f;
|
||||||
f = sensor->values[3].value.f + (drand48() - 0.5) / 100.;
|
f = sensor->values[3].value.f + (drand48() - 0.5) / 100.;
|
||||||
if(f > 585. && f < 615.) sensor->values[3].value.f = f;
|
if(f > 585. && f < 615.) sensor->values[3].value.f = f;
|
||||||
f = sensor->values[4].value.f + (drand48() - 0.5)*10.;
|
f = sensor->values[4].value.f + (drand48() - 0.5) * 10.;
|
||||||
if(f > 60. && f <= 100.) sensor->values[4].value.f = f;
|
if(f > 60. && f <= 100.) sensor->values[4].value.f = f;
|
||||||
sensor->values[5].value.u = (f > 98.) ? 1 : 0;
|
sensor->values[5].value.u = (f > 98.) ? 1 : 0;
|
||||||
|
//if(!sensor->values[5].value.u && drand48() > 0.7) sensor->values[5].value.u = 1;
|
||||||
time_t cur = time(NULL);
|
time_t cur = time(NULL);
|
||||||
for(int i = 0; i < NS; ++i) sensor->values[i].time = cur;
|
for(int i = 0; i < NS-1; ++i) sensor->values[i].time = cur;
|
||||||
|
/*f = sensor->values[6].value.f - (drand48() - 0.52);
|
||||||
|
if(f > 0. && f < 60){
|
||||||
|
sensor->values[6].value.f = f;
|
||||||
|
sensor->values[6].time = cur;
|
||||||
|
}*/
|
||||||
pthread_mutex_unlock(&sensor->valmutex);
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
DBG("unlocked");
|
//DBG("unlocked");
|
||||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||||
while(sl_dtime() - t0 < sensor->tpoll) usleep(500);
|
while(sl_dtime() - t0 < sensor->tpoll) usleep(500);
|
||||||
t0 = sl_dtime();
|
t0 = sl_dtime();
|
||||||
@@ -62,26 +69,24 @@ static void *mainthread(void *s){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t *sensor_new(int N, time_t pollt, int _U_ fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
sensordata_t *s = common_new();
|
if(!s) return FALSE;
|
||||||
if(!s) return NULL;
|
|
||||||
s->Nvalues = NS;
|
s->Nvalues = NS;
|
||||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
s->values = MALLOC(val_t, NS);
|
s->values = MALLOC(val_t, NS);
|
||||||
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
||||||
s->values[0].value.f = 1.;
|
s->values[0].value.f = 1.;
|
||||||
s->values[1].value.f = 180.;
|
s->values[1].value.f = 180.;
|
||||||
s->values[2].value.f = 17.;
|
s->values[2].value.f = 17.;
|
||||||
s->values[3].value.f = 600.;
|
s->values[3].value.f = 600.;
|
||||||
s->values[4].value.f = 80.;
|
s->values[4].value.f = 89.;
|
||||||
s->values[5].value.u = 0;
|
s->values[5].value.u = 0;
|
||||||
s->PluginNo = N;
|
//s->values[6].value.f = 4.5;
|
||||||
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
s->fdes = 0;
|
s->fdes = 0;
|
||||||
return s;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,26 +120,24 @@ static void *mainthread(void *s){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t *sensor_new(int N, time_t pollt, int fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(fd < 0) return NULL;
|
if(!s) return FALSE;
|
||||||
sensordata_t *s = common_new();
|
int fd = getFD(s->path);
|
||||||
if(!s) return NULL;
|
if(fd < 0) return FALSE;
|
||||||
s->fdes = fd;
|
s->fdes = fd;
|
||||||
s->PluginNo = N;
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
|
||||||
s->values = MALLOC(val_t, NS);
|
s->values = MALLOC(val_t, NS);
|
||||||
// don't use memcpy, as `values` could be aligned
|
// don't use memcpy, as `values` could be aligned
|
||||||
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
||||||
if(!(s->ringbuffer = sl_RB_new(BUFSIZ))){
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ))){
|
||||||
WARNX("Can't init ringbuffer!");
|
WARNX("Can't init ringbuffer!");
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
return s;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,23 +194,21 @@ static void *mainthread(void *s){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t *sensor_new(int N, time_t pollt, int fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(fd < 0) return NULL;
|
if(!s) return FALSE;
|
||||||
sensordata_t *s = common_new();
|
int fd = getFD(s->path);
|
||||||
if(!s) return NULL;
|
if(fd < 0) return FALSE;
|
||||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
s->fdes = fd;
|
s->fdes = fd;
|
||||||
s->PluginNo = N;
|
|
||||||
s->Nvalues = NAMOUNT;
|
s->Nvalues = NAMOUNT;
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
s->values = MALLOC(val_t, NAMOUNT);
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
// don't use memcpy, as `values` could be aligned
|
// don't use memcpy, as `values` could be aligned
|
||||||
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
return s;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
201
Daemons/weatherdaemon_multimeteo/plugins/lightning.c
Normal file
201
Daemons/weatherdaemon_multimeteo/plugins/lightning.c
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin for AS3935-based lightning sensor
|
||||||
|
* https://github.com/eddyem/stm32samples/tree/master/F1:F103/AS3935-lightning
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
#define SENSOR_NAME "AS3935 lightning sensor"
|
||||||
|
// minimal distance for forced shutdown
|
||||||
|
#define MINDIST (5.0)
|
||||||
|
// time to check wether sensor is alive, seconds
|
||||||
|
#define TCHECK (30)
|
||||||
|
|
||||||
|
// indexes for text commands and answers
|
||||||
|
enum{
|
||||||
|
CMD_INTERRUPT,
|
||||||
|
CMD_ENERGY,
|
||||||
|
CMD_DISTANCE,
|
||||||
|
ANS_LIGHTNING,
|
||||||
|
ANS_NOICE,
|
||||||
|
ANS_DISTURBER,
|
||||||
|
CMD_AMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * commands[CMD_AMOUNT] = {
|
||||||
|
[CMD_INTERRUPT] = "INTERRUPT",
|
||||||
|
[CMD_ENERGY] = "energy",
|
||||||
|
[CMD_DISTANCE] = "distance",
|
||||||
|
[ANS_LIGHTNING] = "LIGHTNING",
|
||||||
|
[ANS_NOICE] = "NOICE",
|
||||||
|
[ANS_DISTURBER] = "DISTURBER",
|
||||||
|
};
|
||||||
|
|
||||||
|
// indexes of weather values
|
||||||
|
enum{
|
||||||
|
NINTERRUPT,
|
||||||
|
NENERGY,
|
||||||
|
NDISTANCE,
|
||||||
|
NLIGHTNING,
|
||||||
|
NSENSNO,
|
||||||
|
NAMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const val_t values[NAMOUNT] = {
|
||||||
|
[NINTERRUPT] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LINTR", .comment = "Lightning int.: 3 - lightning, 4 - noice, 5 - disturber"},
|
||||||
|
[NENERGY] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LENERGY", .comment = "Last lightning energy"},
|
||||||
|
[NDISTANCE] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LIGTDIST", .comment = "Distance to last lightning, km"},
|
||||||
|
[NLIGHTNING] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "LIGHTNIN", .comment = "Lightning event occured < 5km"},
|
||||||
|
[NSENSNO] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LSENSNO", .comment = "Last lightning event sensor number"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief parse_string - parsing of sensor's answer
|
||||||
|
* @param str - text string with data
|
||||||
|
* @param val - value of key
|
||||||
|
* @return index of key in `values` or -1 if not found
|
||||||
|
*/
|
||||||
|
int parse_string(const char *str, uint32_t *val, uint32_t *nsens){
|
||||||
|
if(!str) return -1;
|
||||||
|
char key[SL_KEY_LEN], value[SL_VAL_LEN];
|
||||||
|
DBG("String: %s", str);
|
||||||
|
if(2 != sl_get_keyval(str, key, value)) return -1;
|
||||||
|
DBG("key=%s, val=%s", key, value);
|
||||||
|
int l = strlen(key);
|
||||||
|
int SensNo = key[--l] - '0';
|
||||||
|
DBG("SensNo=%d", SensNo);
|
||||||
|
if(SensNo < 0 || SensNo > 1) return -1;
|
||||||
|
key[l] = 0;
|
||||||
|
int idx = 0;
|
||||||
|
for(; idx < NLIGHTNING; ++idx){
|
||||||
|
if(0 == strcmp(key, commands[idx])) break;
|
||||||
|
}
|
||||||
|
if(idx == NLIGHTNING) return -1;
|
||||||
|
uint32_t u32;
|
||||||
|
if(idx == 0){ // check interrupt source
|
||||||
|
if(strstr(value, commands[ANS_LIGHTNING])) u32 = ANS_LIGHTNING;
|
||||||
|
else if(strstr(value, commands[ANS_DISTURBER])) u32 = ANS_DISTURBER;
|
||||||
|
else u32 = ANS_NOICE;
|
||||||
|
}else u32 = (uint32_t)atoi(value);
|
||||||
|
DBG("idx = %u, val=%u", idx, u32);
|
||||||
|
if(val) *val = u32;
|
||||||
|
if(nsens) *nsens = (uint32_t)SensNo;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *mainthread(void *s){
|
||||||
|
FNAME();
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
time_t tpoll = 0;
|
||||||
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
|
time_t tnow = time(NULL);
|
||||||
|
if(tnow - tpoll > sensor->tpoll){
|
||||||
|
int dlen = sprintf(buf, "%s0\n%s1\n", commands[CMD_DISTANCE], commands[CMD_DISTANCE]);
|
||||||
|
if(dlen != write(sensor->fdes, buf, dlen)){
|
||||||
|
WARN("Can't ask new data from lightning monitor");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DBG("poll @%zd, pollt=%zd", tnow, sensor->tpoll);
|
||||||
|
tpoll = tnow;
|
||||||
|
}
|
||||||
|
int canread = sl_canread(sensor->fdes);
|
||||||
|
if(canread < 0){
|
||||||
|
WARNX("Disconnected fd %d", sensor->fdes);
|
||||||
|
break;
|
||||||
|
}else if(canread == 1){
|
||||||
|
ssize_t got = read(sensor->fdes, buf, BUFSIZ);
|
||||||
|
if(got > 0){
|
||||||
|
sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got);
|
||||||
|
}else if(got < 0){
|
||||||
|
WARNX("Disconnected?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(sl_RB_datalen(sensor->ringbuffer) > BUFSIZ-1){
|
||||||
|
WARNX("Overfull? Clear data from ring buffer");
|
||||||
|
sl_RB_clearbuf(sensor->ringbuffer);
|
||||||
|
}
|
||||||
|
int gotfresh = FALSE;
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
|
while(1){
|
||||||
|
if(sl_RB_readline(sensor->ringbuffer, buf, BUFSIZ-1) > 0){
|
||||||
|
tpoll = tnow;
|
||||||
|
uint32_t val, nsens;
|
||||||
|
int idx = parse_string(buf, &val, &nsens);
|
||||||
|
if(idx > -1){
|
||||||
|
DBG("Got index=%d", idx);
|
||||||
|
gotfresh = TRUE;
|
||||||
|
if(idx == NINTERRUPT && val == ANS_LIGHTNING){
|
||||||
|
DBG("Interrupt: lightning");
|
||||||
|
sensor->values[NSENSNO].value.u = nsens;
|
||||||
|
sensor->values[NSENSNO].time = tnow;
|
||||||
|
|
||||||
|
}
|
||||||
|
sensor->values[idx].value.u = val;
|
||||||
|
sensor->values[idx].time = tnow;
|
||||||
|
}
|
||||||
|
}else break;
|
||||||
|
}
|
||||||
|
// now check values
|
||||||
|
if(sensor->values[NINTERRUPT].time == tnow && sensor->values[NINTERRUPT].value.u == ANS_LIGHTNING){ // fresh strike
|
||||||
|
if(tnow - sensor->values[NDISTANCE].time < 3 && sensor->values[NDISTANCE].value.u <= MINDIST){ // ahtung!
|
||||||
|
if(sensor->values[NLIGHTNING].value.u == 0) DBG("Ahtung!");
|
||||||
|
sensor->values[NLIGHTNING].time = tnow;
|
||||||
|
sensor->values[NLIGHTNING].value.u = 1;
|
||||||
|
}
|
||||||
|
}else if(tnow - sensor->values[NINTERRUPT].time > TCHECK && sensor->values[NLIGHTNING].value.u){ // remove old lightning flag
|
||||||
|
DBG("Clear ahtung");
|
||||||
|
sensor->values[NLIGHTNING].value.u = 0;
|
||||||
|
sensor->values[NLIGHTNING].time = tnow;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
|
if(gotfresh) DBG("got fresh data");
|
||||||
|
if(gotfresh && sensor->freshdatahandler){
|
||||||
|
DBG("Run fresh data handler");
|
||||||
|
sensor->freshdatahandler(sensor);
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
DBG("suicide");
|
||||||
|
sensor->kill(sensor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sensor_init(sensordata_t *s){
|
||||||
|
FNAME();
|
||||||
|
if(!s) return FALSE;
|
||||||
|
int fd = getFD(s->path);
|
||||||
|
if(fd < 0) return FALSE;
|
||||||
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
|
s->fdes = fd;
|
||||||
|
s->Nvalues = NAMOUNT;
|
||||||
|
s->tpoll = TCHECK;
|
||||||
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -78,7 +78,7 @@ static void *mainthread(void *s){
|
|||||||
while(sensor->fdes > -1){
|
while(sensor->fdes > -1){
|
||||||
time_t tnow = time(NULL);
|
time_t tnow = time(NULL);
|
||||||
if(tnow - tpoll > sensor->tpoll){
|
if(tnow - tpoll > sensor->tpoll){
|
||||||
if(sl_tty_write(sensor->fdes, "?U\r\n", 4)){
|
if(4 != write(sensor->fdes, "?U\r\n", 4)){
|
||||||
WARN("Can't ask new data");
|
WARN("Can't ask new data");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -166,22 +166,20 @@ static void *mainthread(void *s){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sensordata_t *sensor_new(int N, time_t pollt, int fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(fd < 0) return NULL;
|
if(!s) return FALSE;
|
||||||
sensordata_t *s = common_new();
|
int fd = getFD(s->path);
|
||||||
if(!s) return NULL;
|
if(fd < 0) return FALSE;
|
||||||
s->Nvalues = NAMOUNT;
|
s->Nvalues = NAMOUNT;
|
||||||
s->PluginNo = N;
|
|
||||||
s->fdes = fd;
|
s->fdes = fd;
|
||||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
s->values = MALLOC(val_t, NAMOUNT);
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
return s;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
216
Daemons/weatherdaemon_multimeteo/plugins/snmp.c
Normal file
216
Daemons/weatherdaemon_multimeteo/plugins/snmp.c
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon 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 <net-snmp/net-snmp-config.h>
|
||||||
|
#include <net-snmp/net-snmp-includes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// set flag FORCE_OFF only within FORCEOFF_PAUSE seconds after power loss
|
||||||
|
#define FORCEOFF_PAUSE 30
|
||||||
|
|
||||||
|
#include "weathlib.h"
|
||||||
|
#define SENSOR_NAME "SNMP UPS monitor"
|
||||||
|
|
||||||
|
// https://mibs.observium.org/mib/XUPS-MIB/
|
||||||
|
|
||||||
|
// gcc $(net-snmp-config --cflags --libs) snmp.c -o snmptest
|
||||||
|
|
||||||
|
enum{
|
||||||
|
NBATSTAT,
|
||||||
|
NTONBAT,
|
||||||
|
NTREMAIN,
|
||||||
|
NBATCAP,
|
||||||
|
NSOURCE,
|
||||||
|
NONBAT,
|
||||||
|
NAMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
enum{
|
||||||
|
BATT_STAT_UNKN = 1,
|
||||||
|
BATT_STAT_NORMAL,
|
||||||
|
BATT_STAT_LOW,
|
||||||
|
BATT_STAT_DEPLETED,
|
||||||
|
BATT_STAT_AMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* batt_stat[BATT_STAT_AMOUNT]= {
|
||||||
|
"--",
|
||||||
|
[BATT_STAT_UNKN] = "Unknown",
|
||||||
|
[BATT_STAT_NORMAL] = "Normal",
|
||||||
|
[BATT_STAT_LOW] = "Low",
|
||||||
|
[BATT_STAT_DEPLETED] = "Depleted"
|
||||||
|
};
|
||||||
|
|
||||||
|
enum{
|
||||||
|
SOURCE_OTHER = 1,
|
||||||
|
SOURCE_NONE,
|
||||||
|
SOURCE_NORMAL,
|
||||||
|
SOURCE_BYPASS,
|
||||||
|
SOURCE_BATTERY,
|
||||||
|
SOURCE_BOOSTER,
|
||||||
|
SOURCE_REDUCER,
|
||||||
|
SOURCE_AMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
const char *sources[SOURCE_AMOUNT] = {
|
||||||
|
"--",
|
||||||
|
[SOURCE_OTHER] = "Other",
|
||||||
|
[SOURCE_NONE] = "None",
|
||||||
|
[SOURCE_NORMAL] = "Normal",
|
||||||
|
[SOURCE_BYPASS] = "Bypass",
|
||||||
|
[SOURCE_BATTERY] = "Battery",
|
||||||
|
[SOURCE_BOOSTER] = "Booster",
|
||||||
|
[SOURCE_REDUCER] = "Reducer"
|
||||||
|
};
|
||||||
|
|
||||||
|
enum{
|
||||||
|
OID_BATT_STATUS, // batt status: 1 - unkn, 2 - normal, 3 - low, 4 - depleted
|
||||||
|
OID_BATT_SECONDS_ONBAT, // seconds from ONBAT starts
|
||||||
|
OID_BATT_EST_MINUTES, // estimated minutes of work
|
||||||
|
OID_BATT_CAPACITY, // capacity of battery
|
||||||
|
OID_OUTPUT_SOURCE, // input source: 1 - other, 2 - none, 3 - normal, 4 - bypass, 5 - battery, 6 - booster, 7 - reducer
|
||||||
|
OID_AMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static netsnmp_session *snmp_session;
|
||||||
|
static oid anOID[OID_AMOUNT][MAX_OID_LEN];
|
||||||
|
static size_t anOID_len[OID_AMOUNT];
|
||||||
|
|
||||||
|
const char *oids[OID_AMOUNT] = {
|
||||||
|
[OID_BATT_STATUS] = ".1.3.6.1.2.1.33.1.2.1.0",
|
||||||
|
[OID_BATT_SECONDS_ONBAT] = ".1.3.6.1.2.1.33.1.2.2.0",
|
||||||
|
[OID_BATT_EST_MINUTES] = ".1.3.6.1.2.1.33.1.2.3.0",
|
||||||
|
[OID_BATT_CAPACITY] = ".1.3.6.1.2.1.33.1.2.4.0",
|
||||||
|
[OID_OUTPUT_SOURCE] = ".1.3.6.1.2.1.33.1.4.1.0",
|
||||||
|
};
|
||||||
|
|
||||||
|
static const val_t values[NAMOUNT] = {
|
||||||
|
[NBATSTAT] = {.sense = VAL_RECOMMENDED, .type = VALT_STRING, .meaning = IS_OTHER, .name = "UPSBTST", .comment = "UPS battery status"},
|
||||||
|
[NTONBAT] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "UPSTONBT", .comment = "UPS worked on battery time (s)"},
|
||||||
|
[NTREMAIN] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "UPSTREM", .comment = "UPS estimated time on battery (s)"},
|
||||||
|
[NBATCAP] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "UPSBATCP", .comment = "UPS battery capacity, percents"},
|
||||||
|
[NSOURCE] = {.sense = VAL_RECOMMENDED, .type = VALT_STRING, .meaning = IS_OTHER, .name = "UPSSRC", .comment = "UPS power source"},
|
||||||
|
[NONBAT] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "UPSONBAT", .comment = "AC power lost, works on battery"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *mainthread(void *s){
|
||||||
|
double t0 = sl_dtime();
|
||||||
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
netsnmp_pdu *pdu, *response;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
|
//DBG("run");
|
||||||
|
pdu = snmp_pdu_create(SNMP_MSG_GET);
|
||||||
|
for(int i = 0; i < OID_AMOUNT; ++i)
|
||||||
|
snmp_add_null_var(pdu, anOID[i], anOID_len[i]);
|
||||||
|
int status = snmp_synch_response(snmp_session, pdu, &response);
|
||||||
|
//DBG("status = %d", status);
|
||||||
|
if(status == STAT_SUCCESS && response->errstat == SNMP_ERR_NOERROR){
|
||||||
|
time_t curt = time(NULL);
|
||||||
|
netsnmp_variable_list *vars = response->variables; // OID_BATT_STATUS
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
|
int ival = *vars->val.integer;
|
||||||
|
if(ival > 0 && ival < BATT_STAT_AMOUNT){
|
||||||
|
snprintf(sensor->values[NBATSTAT].value.str, STRT_LEN+1, "%s", batt_stat[ival]);
|
||||||
|
}
|
||||||
|
vars = vars->next_variable; // OID_BATT_SECONDS_ONBAT
|
||||||
|
uint32_t tonbat = (uint32_t) *vars->val.integer;
|
||||||
|
sensor->values[NTONBAT].value.u = tonbat;
|
||||||
|
vars = vars->next_variable; // OID_BATT_EST_MINUTES
|
||||||
|
sensor->values[NTREMAIN].value.u = 60 * (uint32_t) *vars->val.integer;
|
||||||
|
vars = vars->next_variable; // OID_BATT_CAPACITY
|
||||||
|
sensor->values[NBATCAP].value.u = (uint32_t) *vars->val.integer;
|
||||||
|
vars = vars->next_variable; // OID_OUTPUT_SOURCE
|
||||||
|
ival = *vars->val.integer;
|
||||||
|
if(ival > 0 && ival < SOURCE_AMOUNT)
|
||||||
|
snprintf(sensor->values[NSOURCE].value.str, STRT_LEN+1, "%s", sources[ival]);
|
||||||
|
if(ival == SOURCE_BATTERY && tonbat > FORCEOFF_PAUSE){
|
||||||
|
sensor->values[NONBAT].value.u = 1;
|
||||||
|
}else sensor->values[NONBAT].value.u = 0;
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i)
|
||||||
|
sensor->values[i].time = curt;
|
||||||
|
//DBG("times updated to %zd", curt);
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
|
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||||
|
}else DBG("Error in packet");
|
||||||
|
if(response) snmp_free_pdu(response);
|
||||||
|
//DBG("sleep");
|
||||||
|
while(sl_dtime() - t0 < sensor->tpoll) usleep(500);
|
||||||
|
t0 = sl_dtime();
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void snmp_kill(sensordata_t *s){
|
||||||
|
s->fdes = -1;
|
||||||
|
pthread_join(s->thread, NULL);
|
||||||
|
snmp_close(snmp_session);
|
||||||
|
common_kill(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sensor_init(sensordata_t *s){
|
||||||
|
FNAME();
|
||||||
|
if(!s || !s->path[0]) return FALSE;
|
||||||
|
|
||||||
|
netsnmp_session session;
|
||||||
|
init_snmp("snmpapp");
|
||||||
|
|
||||||
|
snmp_sess_init(&session);
|
||||||
|
session.version = SNMP_VERSION_1;
|
||||||
|
session.community = (u_char *)"public";
|
||||||
|
session.community_len = strlen((const char *)session.community);
|
||||||
|
|
||||||
|
DBG("PATH: %s", s->path);
|
||||||
|
|
||||||
|
const char *colon = strchr(s->path, ':');
|
||||||
|
if(colon) ++colon; // omit "N:" in field "N:host"
|
||||||
|
session.peername = strdup(colon);
|
||||||
|
|
||||||
|
snmp_session = snmp_open(&session);
|
||||||
|
if(!snmp_session){
|
||||||
|
snmp_sess_perror("snmp_open", &session);
|
||||||
|
FREE(session.peername);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
s->kill = snmp_kill;
|
||||||
|
|
||||||
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
|
s->fdes = 0;
|
||||||
|
s->Nvalues = NAMOUNT;
|
||||||
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
|
|
||||||
|
DBG("init OIDs");
|
||||||
|
for(int i = 0; i < OID_AMOUNT; ++i){
|
||||||
|
anOID_len[i] = MAX_OID_LEN;
|
||||||
|
if(!read_objid(oids[i], anOID[i], &anOID_len[i])){
|
||||||
|
snmp_perror(oids[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
DBG("Got OID %s", oids[i]);
|
||||||
|
//snmp_add_null_var(snmp_pdu, anOID, anOID_len);
|
||||||
|
}
|
||||||
|
DBG("Start main thread");
|
||||||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
@@ -127,7 +127,7 @@ static void *mainthread(void *s){
|
|||||||
while(sensor->fdes > -1){
|
while(sensor->fdes > -1){
|
||||||
time_t tnow = time(NULL);
|
time_t tnow = time(NULL);
|
||||||
if(tnow - tpoll > sensor->tpoll){
|
if(tnow - tpoll > sensor->tpoll){
|
||||||
if(sl_tty_write(sensor->fdes, "!0R0\r\n", 6)){
|
if(6 != write(sensor->fdes, "!0R0\r\n", 6)){
|
||||||
WARN("Can't ask new data");
|
WARN("Can't ask new data");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -194,23 +194,21 @@ static void *mainthread(void *s){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t *sensor_new(int N, time_t pollt, int fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(fd < 0) return NULL;
|
if(!s) return FALSE;
|
||||||
sensordata_t *s = common_new();
|
int fd = getFD(s->path);
|
||||||
if(!s) return NULL;
|
if(fd < 0) return FALSE;
|
||||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
s->PluginNo = N;
|
|
||||||
s->fdes = fd;
|
s->fdes = fd;
|
||||||
s->Nvalues = NAMOUNT;
|
s->Nvalues = NAMOUNT;
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
s->values = MALLOC(val_t, NAMOUNT);
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
s->kill(s);
|
s->kill(s);
|
||||||
return NULL;
|
return FALSE;
|
||||||
}
|
}
|
||||||
return s;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "fd.h"
|
|
||||||
#include "mainweather.h"
|
#include "mainweather.h"
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
#include "weathlib.h"
|
#include "weathlib.h"
|
||||||
@@ -85,9 +84,10 @@ void *open_plugin(const char *name){
|
|||||||
* @param station - pointer to N'th station opened
|
* @param station - pointer to N'th station opened
|
||||||
*/
|
*/
|
||||||
static void dumpsensors(struct sensordata_t* station){
|
static void dumpsensors(struct sensordata_t* station){
|
||||||
FNAME();
|
//FNAME();
|
||||||
if(!station || !station->get_value || station->Nvalues < 1) return;
|
if(!sensor_alive(station) || !station->get_value || station->Nvalues < 1 || station->IsMuted) return;
|
||||||
refresh_sensval(station);
|
refresh_sensval(station);
|
||||||
|
#if 0
|
||||||
DBG("New values...");
|
DBG("New values...");
|
||||||
#ifdef EBUG
|
#ifdef EBUG
|
||||||
char buf[FULL_LEN+1];
|
char buf[FULL_LEN+1];
|
||||||
@@ -110,6 +110,7 @@ static void dumpsensors(struct sensordata_t* station){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,16 +136,20 @@ int openplugins(char **paths, int N){
|
|||||||
void* dlh = open_plugin(buf);
|
void* dlh = open_plugin(buf);
|
||||||
if(!dlh) continue;
|
if(!dlh) continue;
|
||||||
DBG("OPENED");
|
DBG("OPENED");
|
||||||
sensor_new_t sensnew = (sensor_new_t) dlsym(dlh, "sensor_new");
|
sensor_init_t sensinit = (sensor_init_t) dlsym(dlh, "sensor_init");
|
||||||
if(sensnew){
|
if(sensinit){
|
||||||
int fd = -1;
|
sensordata_t *S = sensor_new(nplugins, colon);
|
||||||
if(colon) fd = getFD(colon);
|
if(!S) WARNXL("Can't allocate memory for 'sensor' structure");
|
||||||
sensordata_t *S = sensnew(nplugins, poll_interval, fd); // here nplugins is index in array
|
|
||||||
if(!S) WARNXL("Can't init plugin %s", paths[i]);
|
|
||||||
else{
|
else{
|
||||||
if(!S->onrefresh || !S->onrefresh(S, dumpsensors)) WARNXL("Can't init refresh funtion");
|
S->init = sensinit;
|
||||||
LOGMSG("Plugin %s nave %d sensors", paths[i], S->Nvalues);
|
S->tpoll = poll_interval;
|
||||||
allplugins[nplugins++] = S;
|
allplugins[nplugins++] = S;
|
||||||
|
int inited = sensinit(S); // here nplugins is index in array
|
||||||
|
if(!inited) WARNXL("Can't init plugin %s", paths[i]);
|
||||||
|
else{
|
||||||
|
if(!S->onrefresh || !S->onrefresh(S, dumpsensors)) WARNXL("Can't init refresh funtion");
|
||||||
|
LOGMSG("Plugin %s nave %d sensors", paths[i], S->Nvalues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else WARNXL("Can't find initing function in plugin %s: %s", paths[i], dlerror());
|
}else WARNXL("Can't find initing function in plugin %s: %s", paths[i], dlerror());
|
||||||
}
|
}
|
||||||
@@ -159,11 +164,52 @@ void closeplugins(){
|
|||||||
if(!allplugins || nplugins < 1) return;
|
if(!allplugins || nplugins < 1) return;
|
||||||
for(int i = 0; i < nplugins; ++i){
|
for(int i = 0; i < nplugins; ++i){
|
||||||
if(allplugins[i]->kill) allplugins[i]->kill(allplugins[i]);
|
if(allplugins[i]->kill) allplugins[i]->kill(allplugins[i]);
|
||||||
|
FREE(allplugins[i]);
|
||||||
}
|
}
|
||||||
FREE(allplugins);
|
FREE(allplugins);
|
||||||
nplugins = 0;
|
nplugins = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* const NM[IS_OTHER] = { // names of standard fields
|
||||||
|
[IS_WIND] = "WIND",
|
||||||
|
[IS_WINDDIR] = "WINDDIR",
|
||||||
|
[IS_HUMIDITY] = "HUMIDITY",
|
||||||
|
[IS_AMB_TEMP] = "EXTTEMP",
|
||||||
|
[IS_INNER_TEMP] = "INTTEMP",
|
||||||
|
[IS_HW_TEMP] = "HWTEMP", // mirror?
|
||||||
|
[IS_PRESSURE] = "PRESSURE",
|
||||||
|
[IS_PRECIP] = "PRECIP",
|
||||||
|
[IS_PRECIP_LEVEL]="PRECIPLV",
|
||||||
|
[IS_MIST] = "MIST",
|
||||||
|
[IS_CLOUDS] = "CLOUDS",
|
||||||
|
[IS_SKYTEMP] = "SKYTEMP",
|
||||||
|
//[IS_LIGTDIST] = "LIGTDIST",
|
||||||
|
};
|
||||||
|
|
||||||
|
// format "sense" of sensor, like "[WIND][1]=2"
|
||||||
|
int format_senssense(const val_t *v, char *buf, int buflen, int Np){
|
||||||
|
if(!v || !buf || buflen < 1) return -1;
|
||||||
|
int idx = v->meaning;
|
||||||
|
const char *name = (idx < IS_OTHER) ? NM[idx] : v->name;
|
||||||
|
int got;
|
||||||
|
if(Np > -1) got = snprintf(buf, buflen, "[%s][%d]=%d", name, Np, v->sense);
|
||||||
|
else got = snprintf(buf, buflen, "[%s]=%d", name, v->sense);
|
||||||
|
return (got < buflen) ? got : buflen; // full or truncated
|
||||||
|
}
|
||||||
|
|
||||||
|
void get_fieldname(const val_t *v, char buf[KEY_LEN+1]){
|
||||||
|
if(!v || !buf) return;
|
||||||
|
int idx = v->meaning;
|
||||||
|
const char *name = NULL;
|
||||||
|
if(idx < IS_OTHER){
|
||||||
|
name = NM[idx];
|
||||||
|
}else{
|
||||||
|
name = v->name;
|
||||||
|
}
|
||||||
|
if(name) snprintf(buf, KEY_LEN+1, "%s", name);
|
||||||
|
else buf[0] = 0; // empty
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief format_sensval - snprintf sensor's value into buffer
|
* @brief format_sensval - snprintf sensor's value into buffer
|
||||||
* @param v - value to get
|
* @param v - value to get
|
||||||
@@ -173,32 +219,19 @@ void closeplugins(){
|
|||||||
* @return amount of symbols printed or -1 if error
|
* @return amount of symbols printed or -1 if error
|
||||||
*/
|
*/
|
||||||
int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
||||||
--buflen; // for trailing zero
|
if(!v || !buf || buflen < 1) return -1;
|
||||||
if(!v || !buf || buflen < FULL_LEN) return -1;
|
char strval[VAL_LEN];
|
||||||
char strval[VAL_LEN+1];
|
int fieldlen = 20; // minimal distance between '=' and '/' is 22 bytes
|
||||||
switch(v->type){
|
switch(v->type){
|
||||||
case VALT_UINT: snprintf(strval, VAL_LEN, "%u", v->value.u); break;
|
case VALT_UINT: snprintf(strval, VAL_LEN, "%u", v->value.u); break;
|
||||||
case VALT_INT: snprintf(strval, VAL_LEN, "%d", v->value.i); break;
|
case VALT_INT: snprintf(strval, VAL_LEN, "%d", v->value.i); break;
|
||||||
case VALT_FLOAT: snprintf(strval, VAL_LEN, "%.2f", v->value.f); break;
|
case VALT_FLOAT: snprintf(strval, VAL_LEN, "%.2f", v->value.f); break;
|
||||||
|
case VALT_STRING: sprintf(strval, "'%s'", v->value.str); fieldlen = -20; break;
|
||||||
default: sprintf(strval, "'ERROR'");
|
default: sprintf(strval, "'ERROR'");
|
||||||
}
|
}
|
||||||
const char* const NM[IS_OTHER] = { // names of standard fields
|
|
||||||
[IS_WIND] = "WIND",
|
|
||||||
[IS_WINDDIR] = "WINDDIR",
|
|
||||||
[IS_HUMIDITY] = "HUMIDITY",
|
|
||||||
[IS_AMB_TEMP] = "EXTTEMP",
|
|
||||||
[IS_INNER_TEMP] = "INTTEMP",
|
|
||||||
[IS_HW_TEMP] = "HWTEMP", // mirror?
|
|
||||||
[IS_PRESSURE] = "PRESSURE",
|
|
||||||
[IS_PRECIP] = "PRECIP",
|
|
||||||
[IS_PRECIP_LEVEL]="PRECIPLV",
|
|
||||||
[IS_MIST] = "MIST",
|
|
||||||
[IS_CLOUDS] = "CLOUDS",
|
|
||||||
[IS_SKYTEMP] = "SKYTEMP"
|
|
||||||
};
|
|
||||||
const char* const CMT[IS_OTHER] = { // comments for standard fields
|
const char* const CMT[IS_OTHER] = { // comments for standard fields
|
||||||
[IS_WIND] = "Wind, m/s",
|
[IS_WIND] = "Wind, m/s",
|
||||||
[IS_WINDDIR] = "Instant wind direction, degr (CW from north to FROM)",
|
[IS_WINDDIR] = "Wind direction, degr (CW from north to FROM)",
|
||||||
[IS_HUMIDITY] = "Humidity, percent",
|
[IS_HUMIDITY] = "Humidity, percent",
|
||||||
[IS_AMB_TEMP] = "Ambient temperature, degC",
|
[IS_AMB_TEMP] = "Ambient temperature, degC",
|
||||||
[IS_INNER_TEMP] = "In-dome temperature, degC",
|
[IS_INNER_TEMP] = "In-dome temperature, degC",
|
||||||
@@ -208,7 +241,8 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
|||||||
[IS_PRECIP_LEVEL]="Cumulative precipitation level (mm)",
|
[IS_PRECIP_LEVEL]="Cumulative precipitation level (mm)",
|
||||||
[IS_MIST] = "Mist (1 - yes, 0 - no)",
|
[IS_MIST] = "Mist (1 - yes, 0 - no)",
|
||||||
[IS_CLOUDS] = "Integral sky quality value (bigger - better)",
|
[IS_CLOUDS] = "Integral sky quality value (bigger - better)",
|
||||||
[IS_SKYTEMP] = "Mean sky temperatyre"
|
[IS_SKYTEMP] = "Mean sky temperatyre",
|
||||||
|
//[IS_LIGTDIST] = "Distance to last lightning, km",
|
||||||
};
|
};
|
||||||
const char *name = NULL, *comment = NULL;
|
const char *name = NULL, *comment = NULL;
|
||||||
int idx = v->meaning;
|
int idx = v->meaning;
|
||||||
@@ -219,18 +253,82 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
|||||||
name = v->name;
|
name = v->name;
|
||||||
comment = v->comment;
|
comment = v->comment;
|
||||||
}
|
}
|
||||||
|
if(!name) return 0; // no name pointed - don't show this value
|
||||||
|
if(!comment) comment = "-";
|
||||||
int got;
|
int got;
|
||||||
if(Np > -1) got = snprintf(buf, buflen, "%s[%d]=%s / %s", name, Np, strval, comment);
|
if(Np > -1) got = snprintf(buf, buflen, "%s[%d] = %s / %s", name, Np, strval, comment);
|
||||||
else got = snprintf(buf, buflen, "%s=%s / %s", name, strval, comment);
|
else got = snprintf(buf, buflen, "%-*s= %*s / %s", KEY_LEN, name, fieldlen, strval, comment);
|
||||||
return got;
|
return (got < buflen) ? got : buflen; // full or truncated
|
||||||
}
|
}
|
||||||
|
|
||||||
// the same for measurement time formatting
|
// the same for measurement time formatting
|
||||||
int format_msrmttm(time_t t, char *buf, int buflen){
|
int format_msrmttm(time_t t, char *buf, int buflen, int Np){
|
||||||
--buflen; // for trailing zero
|
if(!buf || buflen < 1) return -1;
|
||||||
if(!buf || buflen < FULL_LEN) return -1;
|
|
||||||
char cmt[COMMENT_LEN+1];
|
char cmt[COMMENT_LEN+1];
|
||||||
struct tm *T = localtime(&t);
|
struct tm *T = localtime(&t);
|
||||||
strftime(cmt, COMMENT_LEN, "%F %T", T);
|
strftime(cmt, COMMENT_LEN, "%F %T", T);
|
||||||
return snprintf(buf, buflen, "TMEAS=%zd / Last measurement time: %s", t, cmt);
|
int got;
|
||||||
|
if(Np > -1) got = snprintf(buf, buflen, "TWEATH[%d] = %zd / Last weather time: %s", Np, t, cmt);
|
||||||
|
else got = snprintf(buf, buflen, "TWEATH = %20zd / Last weather time: %s", t, cmt);
|
||||||
|
return (got < buflen) ? got : buflen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find sensor's value by its name; @return index or -1 if not found
|
||||||
|
int find_val_by_name(sensordata_t *s, const char *name){
|
||||||
|
if(!s || !name) return -1;
|
||||||
|
if(s->Nvalues < 1) return -1;
|
||||||
|
// check standard "meaning"
|
||||||
|
valmeaning_t mnng = 0;
|
||||||
|
for(; mnng < IS_OTHER; ++mnng){
|
||||||
|
if(0 == strcmp(NM[mnng], name)) break; // found in standard
|
||||||
|
}
|
||||||
|
for(int i = 0; i < s->Nvalues; ++i){
|
||||||
|
val_t val;
|
||||||
|
if(!s->get_value(s, &val, i) || val.meaning != mnng) continue;
|
||||||
|
if(mnng != IS_OTHER){ // found in standard values
|
||||||
|
return i;
|
||||||
|
}else{ // non-standard: check by name
|
||||||
|
if(0 == strcmp(val.name, name)){ // found by name
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1; // not found
|
||||||
|
}
|
||||||
|
|
||||||
|
// chane 'sense' field of given meteostation for value with index=`idx`; @return FALSE if failed
|
||||||
|
int change_val_sense(sensordata_t *s, int idx, valsense_t sense){
|
||||||
|
if(!s || sense < 0 || sense >= VAL_AMOUNT) return FALSE;
|
||||||
|
int N = s->Nvalues;
|
||||||
|
if(idx < 0 || idx >= N) return FALSE;
|
||||||
|
s->values[idx].sense = sense;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert val_t into double
|
||||||
|
double val2d(const val_t *value){
|
||||||
|
double curvalue;
|
||||||
|
switch(value->type){
|
||||||
|
case VALT_UINT: curvalue = (double) value->value.u; break;
|
||||||
|
case VALT_INT: curvalue = (double) value->value.i; break;
|
||||||
|
case VALT_FLOAT: curvalue = (double) value->value.f; break;
|
||||||
|
default: curvalue = 0.;
|
||||||
|
}
|
||||||
|
return curvalue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pause and continue sensors refresh
|
||||||
|
int station_mute(sensordata_t *s){
|
||||||
|
if(!s) return FALSE;
|
||||||
|
s->IsMuted = TRUE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
int station_unmute(sensordata_t *s){
|
||||||
|
if(!s) return FALSE;
|
||||||
|
s->IsMuted = FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
int station_is_muted(sensordata_t *s){
|
||||||
|
if(s && s->IsMuted) return TRUE;
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,21 @@ int openplugins(char **paths, int N);
|
|||||||
void closeplugins();
|
void closeplugins();
|
||||||
sensordata_t *get_plugin(int N);
|
sensordata_t *get_plugin(int N);
|
||||||
int get_nplugins();
|
int get_nplugins();
|
||||||
|
|
||||||
|
int find_val_by_name(sensordata_t *s, const char *name);
|
||||||
|
|
||||||
|
int format_senssense(const val_t *v, char *buf, int buflen, int Np);
|
||||||
int format_sensval(const val_t *v, char *buf, int buflen, int Np);
|
int format_sensval(const val_t *v, char *buf, int buflen, int Np);
|
||||||
int format_msrmttm(time_t t, char *buf, int buflen);
|
int format_msrmttm(time_t t, char *buf, int buflen, int Np);
|
||||||
|
|
||||||
|
int change_val_sense(sensordata_t *s, int idx, valsense_t sense);
|
||||||
|
|
||||||
int set_pollT(time_t t);
|
int set_pollT(time_t t);
|
||||||
time_t get_pollT();
|
time_t get_pollT();
|
||||||
|
|
||||||
|
double val2d(const val_t *v);
|
||||||
|
void get_fieldname(const val_t *v, char buf[KEY_LEN+1]);
|
||||||
|
|
||||||
|
int station_mute(sensordata_t *s);
|
||||||
|
int station_unmute(sensordata_t *s);
|
||||||
|
int station_is_muted(sensordata_t *s);
|
||||||
|
|||||||
@@ -16,7 +16,9 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
@@ -24,9 +26,6 @@
|
|||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
// if measurement time oldest than now minus `oldest_interval`, we think measurement are too old
|
|
||||||
static time_t oldest_interval = 60;
|
|
||||||
|
|
||||||
// server's sockets: net and local (UNIX)
|
// server's sockets: net and local (UNIX)
|
||||||
static sl_sock_t *netsocket = NULL, *localsocket;
|
static sl_sock_t *netsocket = NULL, *localsocket;
|
||||||
//static pthread_t netthread, locthread;
|
//static pthread_t netthread, locthread;
|
||||||
@@ -40,79 +39,72 @@ static sl_sock_hresult_e timehandler(sl_sock_t *client, _U_ sl_sock_hitem_t *ite
|
|||||||
return RESULT_SILENCE;
|
return RESULT_SILENCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define SSZ_ (PATH_MAX + 256)
|
||||||
// show all connected libraries
|
// show all connected libraries
|
||||||
static sl_sock_hresult_e listhandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
static sl_sock_hresult_e listhandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
||||||
if(!client) return RESULT_FAIL;
|
if(!client) return RESULT_FAIL;
|
||||||
char buf[256];
|
char buf[SSZ_];
|
||||||
int N = get_nplugins();
|
int N = get_nplugins();
|
||||||
if(N < 1) return RESULT_FAIL;
|
if(N < 1) return RESULT_FAIL;
|
||||||
sensordata_t *d = NULL;
|
sensordata_t *d = NULL;
|
||||||
for(int i = 0; i < N; ++i){
|
for(int i = 0; i < N; ++i){
|
||||||
if(!(d = get_plugin(i))) continue;
|
if(!(d = get_plugin(i))) continue;
|
||||||
snprintf(buf, 255, "PLUGIN[%d]=%s\nNVALUES[%d]=%d\n", i, d->name, i, d->Nvalues);
|
if(d->path[0]) snprintf(buf, SSZ_, "PLUGIN[%d]=%s @ %s\nNVALUES[%d]=%d\n", i, d->name, d->path, i, d->Nvalues);
|
||||||
|
else snprintf(buf, SSZ_, "PLUGIN[%d]=%s\nNVALUES[%d]=%d\n", i, d->name, i, d->Nvalues);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
}
|
}
|
||||||
return RESULT_SILENCE;
|
return RESULT_SILENCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// get N'th plugin or send error message
|
||||||
* @brief showdataN - send to user meteodata of Nth station
|
sensordata_t *get_plugin_w_message(sl_sock_t *client, int N){
|
||||||
* @param client - client data
|
char buf[FULL_LEN];
|
||||||
* @param N - index of station
|
|
||||||
*/
|
|
||||||
static void showdataN(sl_sock_t *client, int N){
|
|
||||||
char buf[FULL_LEN+1];
|
|
||||||
val_t v;
|
|
||||||
sensordata_t *s = NULL;
|
sensordata_t *s = NULL;
|
||||||
if(!(s = get_plugin(N)) || (s->Nvalues < 1)){
|
if(!(s = get_plugin(N)) || (s->Nvalues < 1)){
|
||||||
snprintf(buf, FULL_LEN, "Can't get plugin[%d]\n", N);
|
snprintf(buf, FULL_LEN, "Can't get plugin[%d]\n", N);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
return;
|
return NULL;
|
||||||
}
|
|
||||||
time_t oldest = time(NULL) - oldest_interval;
|
|
||||||
uint64_t Tsum = 0; int nsum = 0;
|
|
||||||
for(int i = 0; i < s->Nvalues; ++i){
|
|
||||||
if(!s->get_value(s, &v, i)) continue;
|
|
||||||
if(v.time < oldest) continue;
|
|
||||||
if(1 > format_sensval(&v, buf, FULL_LEN+1, N)) continue;
|
|
||||||
DBG("formatted: '%s'", buf);
|
|
||||||
sl_sock_sendstrmessage(client, buf);
|
|
||||||
sl_sock_sendbyte(client, '\n');
|
|
||||||
++nsum; Tsum += v.time;
|
|
||||||
}
|
|
||||||
if(nsum > 0){
|
|
||||||
oldest = (time_t)(Tsum / nsum);
|
|
||||||
if(0 < format_msrmttm(oldest, buf, FULL_LEN+1)){ // send mean measuring time
|
|
||||||
DBG("Formatted time: '%s'", buf);
|
|
||||||
sl_sock_sendstrmessage(client, buf);
|
|
||||||
sl_sock_sendbyte(client, '\n');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief showdata - send to client common gathered data
|
* @brief showdata - send to client sensor's data
|
||||||
* @param client - client data
|
* @param client - client data
|
||||||
|
* @param N - -1 for common data or station index for specific meteo
|
||||||
*/
|
*/
|
||||||
static void showdata(sl_sock_t *client){
|
static void showdata(sl_sock_t *client, int N){
|
||||||
char buf[FULL_LEN+1];
|
char buf[FULL_LEN];
|
||||||
val_t v;
|
val_t v;
|
||||||
int Ncoll = collected_amount();
|
int Ncoll = 0;
|
||||||
time_t oldest = time(NULL) - oldest_interval;
|
sensordata_t *s = NULL;
|
||||||
uint64_t Tsum = 0; int nsum = 0;
|
if(N < 0){
|
||||||
|
Ncoll = collected_amount();
|
||||||
|
}else{
|
||||||
|
s = get_plugin_w_message(client, N);
|
||||||
|
if(!s) return;
|
||||||
|
Ncoll = s->Nvalues;
|
||||||
|
}
|
||||||
|
time_t oldest = time(NULL) - 2 * WeatherConf.ahtung_delay, mstm = 0;
|
||||||
|
|
||||||
for(int i = 0; i < Ncoll; ++i){
|
for(int i = 0; i < Ncoll; ++i){
|
||||||
if(!get_collected(&v, i)){ DBG("Can't get %dth value", i); continue; }
|
int ans = 0;
|
||||||
if(v.time < oldest){ DBG("%dth value is too old", i); continue; }
|
if(N < 0) ans = get_collected(&v, i);
|
||||||
if(1 > format_sensval(&v, buf, FULL_LEN+1, -1)){ DBG("Can't format"); continue; }
|
else ans = s->get_value(s, &v, i);
|
||||||
DBG("formatted: '%s'", buf);
|
if(!ans){ DBG("Can't get %dth value", i); continue; }
|
||||||
|
// hide old and broken sensors data
|
||||||
|
if(v.time < oldest || v.sense > VAL_UNNECESSARY){ /*DBG("%dth value is too old", i);*/ continue; }
|
||||||
|
if(1 > format_sensval(&v, buf, FULL_LEN, N)){ DBG("Can't format %d", i); continue; }
|
||||||
|
//DBG("formatted: '%s'", buf);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
sl_sock_sendbyte(client, '\n');
|
sl_sock_sendbyte(client, '\n');
|
||||||
++nsum; Tsum += v.time;
|
if(v.time > mstm) mstm = v.time;
|
||||||
}
|
}
|
||||||
if(nsum > 0){
|
// also send FORCE flag if have
|
||||||
oldest = (time_t)(Tsum / nsum);
|
if(N < 0 && is_forbidden()) sl_sock_sendstrmessage(client, "FORBID = 1 / Observations are forbidden by operator\n");
|
||||||
if(0 < format_msrmttm(oldest, buf, FULL_LEN+1)){ // send mean measuring time
|
if(mstm){
|
||||||
DBG("Formatted time: '%s'", buf);
|
if(0 < format_msrmttm(mstm, buf, FULL_LEN, N)){ // send mean measuring time
|
||||||
|
//DBG("Formatted time: '%s'", buf);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
sl_sock_sendbyte(client, '\n');
|
sl_sock_sendbyte(client, '\n');
|
||||||
}
|
}
|
||||||
@@ -125,31 +117,212 @@ static sl_sock_hresult_e gethandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item
|
|||||||
if(!client) return RESULT_FAIL;
|
if(!client) return RESULT_FAIL;
|
||||||
int N = get_nplugins();
|
int N = get_nplugins();
|
||||||
if(N < 1) return RESULT_FAIL;
|
if(N < 1) return RESULT_FAIL;
|
||||||
if(!req) showdata(client);
|
if(!req) showdata(client, -1);
|
||||||
else{
|
else{
|
||||||
int n;
|
int n;
|
||||||
if(!sl_str2i(&n, req) || n < 0 || n >= N) return RESULT_BADVAL;
|
if(!sl_str2i(&n, req) || n < 0 || n >= N) return RESULT_BADVAL;
|
||||||
showdataN(client, n);
|
showdata(client, n);
|
||||||
}
|
}
|
||||||
return RESULT_SILENCE;
|
return RESULT_SILENCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get parameters' level
|
||||||
|
static sl_sock_hresult_e getlvlhandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
|
||||||
|
if(!client)return RESULT_FAIL;
|
||||||
|
int N = get_nplugins();
|
||||||
|
if(N < 1) return RESULT_FAIL;
|
||||||
|
val_t v;
|
||||||
|
char buf[FULL_LEN];
|
||||||
|
if(!req){ // level of collected parameters
|
||||||
|
DBG("User asks for collected");
|
||||||
|
int Ncoll = collected_amount();
|
||||||
|
if(Ncoll < 1) return RESULT_FAIL;
|
||||||
|
for(int i = 0; i < Ncoll; ++i){
|
||||||
|
if(!get_collected(&v, i)){ DBG("Can't get %dth value", i); continue; }
|
||||||
|
if(1 > format_senssense(&v, buf, FULL_LEN, -1)){ DBG("Can't format"); continue; }
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
sl_sock_sendbyte(client, '\n');
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
int n;
|
||||||
|
if(!sl_str2i(&n, req) || n < 0 || n >= N) return RESULT_BADVAL;
|
||||||
|
DBG("User asks for %d", n);
|
||||||
|
sensordata_t *s = get_plugin_w_message(client, n);
|
||||||
|
if(!s) return RESULT_SILENCE;
|
||||||
|
for(int i = 0; i < s->Nvalues; ++i){
|
||||||
|
if(!s->get_value(s, &v, i)) continue;
|
||||||
|
if(1 > format_senssense(&v, buf, FULL_LEN, n)){ DBG("Can't format"); continue; }
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
sl_sock_sendbyte(client, '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return RESULT_SILENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set parameters' level; format: setlevel=N1:par=level,...,N1:par=level...
|
||||||
|
// Nx - "station" number, par - parameter name (like "HUMIDITY"), level - new level (0..3)
|
||||||
|
static sl_sock_hresult_e setlvlhandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
int N = get_nplugins();
|
||||||
|
if(N < 1) return RESULT_FAIL;
|
||||||
|
if(!req) return RESULT_BADVAL;
|
||||||
|
sl_sock_hresult_e result = RESULT_OK;
|
||||||
|
char *s = (char *)req;
|
||||||
|
while(*s){
|
||||||
|
while (isspace(*s) || *s == ',') s++;
|
||||||
|
if (!*s) break;
|
||||||
|
// get station number
|
||||||
|
char *end;
|
||||||
|
long st_num = strtol(s, &end, 10);
|
||||||
|
if(s == end) break;
|
||||||
|
s = end;
|
||||||
|
// wait for ':'
|
||||||
|
while (isspace(*s)) s++;
|
||||||
|
if(*s != ':') break;
|
||||||
|
++s;
|
||||||
|
DBG("ST %ld:\n", st_num);
|
||||||
|
sensordata_t *sd;
|
||||||
|
if(st_num < 0 || st_num >= N || !(sd = get_plugin((int)st_num))){
|
||||||
|
result = RESULT_BADVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while(1){
|
||||||
|
while(isspace(*s)) ++s;
|
||||||
|
if(*s == '\0') break;
|
||||||
|
if(isdigit((unsigned char)*s)) break; // - next block
|
||||||
|
const char *par_start = s;
|
||||||
|
while(isalnum(*s)) ++s;
|
||||||
|
if(par_start == s) break;
|
||||||
|
int l = (int)(s - par_start);
|
||||||
|
DBG(" par: %.*s = ", l, par_start);
|
||||||
|
if(l > KEY_LEN){
|
||||||
|
result = RESULT_BADVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
char buf[KEY_LEN + 1];
|
||||||
|
memcpy(buf, par_start, l);
|
||||||
|
buf[l] = 0;
|
||||||
|
int validx = find_val_by_name(sd, buf);
|
||||||
|
// search for given value
|
||||||
|
if(validx < 0){
|
||||||
|
result = RESULT_BADVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while(isspace(*s)) ++s;
|
||||||
|
// fait for '='
|
||||||
|
if(*s != '='){
|
||||||
|
result = RESULT_BADVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++s;
|
||||||
|
// get new level
|
||||||
|
while(isspace(*s)) s++;
|
||||||
|
long val = strtol(s, &end, 10);
|
||||||
|
if(s == end){
|
||||||
|
result = RESULT_BADVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s = end;
|
||||||
|
val_t oldval;
|
||||||
|
int olds = -1;
|
||||||
|
if(sd->get_value && sd->get_value(sd, &oldval, validx)) olds = oldval.sense;
|
||||||
|
DBG("%ld\n", val);
|
||||||
|
if(!change_val_sense(sd, validx, (valsense_t)val)){
|
||||||
|
result = RESULT_BADVAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
LOGWARN("Change '%s' of '%s' sense from %d to %ld", sd->name, buf, olds, val);
|
||||||
|
while(isspace((unsigned char)*s)) s++;
|
||||||
|
if(*s == ','){ // omit delimeter
|
||||||
|
++s;
|
||||||
|
continue;
|
||||||
|
}else{
|
||||||
|
if(*s == ';') ++s; // omit ; as block's end
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sl_sock_hresult_e forbidhandler(sl_sock_t *client, sl_sock_hitem_t *item, const char *req){
|
||||||
|
char buf[256];
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
if(req){ // setter
|
||||||
|
int l;
|
||||||
|
if(!sl_str2i(&l, req)) return RESULT_BADVAL;
|
||||||
|
forbid_observations(l);
|
||||||
|
LOGWARN("Manual set by socket command FORBID=%d", is_forbidden());
|
||||||
|
}
|
||||||
|
snprintf(buf, 256, "%s = %d\n", item->key, is_forbidden());
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
return RESULT_SILENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sl_sock_hresult_e forceoffhandler(sl_sock_t *client, sl_sock_hitem_t *item, const char *req){
|
||||||
|
char buf[256];
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
int flag = -1;
|
||||||
|
if(req){ // setter
|
||||||
|
if(!sl_str2i(&flag, req) || flag < 0 || flag > 1) return RESULT_BADVAL;
|
||||||
|
}
|
||||||
|
snprintf(buf, 256, "%s = %d\n", item->key, force_off(flag));
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
return RESULT_SILENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sl_sock_hresult_e wlevhandler(sl_sock_t *client, sl_sock_hitem_t *item, const char *req){
|
||||||
|
char buf[256];
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
int newlevel = -1;
|
||||||
|
if(req){ // setter
|
||||||
|
if(!sl_str2i(&newlevel, req) || newlevel < 0 || newlevel > WEATHER_PROHIBITED) return RESULT_BADVAL;
|
||||||
|
}
|
||||||
|
snprintf(buf, 256, "%s = %d\n", item->key, weather_level(newlevel));
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
return RESULT_SILENCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sensordata_t *get_sd_by_num(const char *req, int *Num){
|
||||||
|
int N;
|
||||||
|
if(!req || !sl_str2i(&N, req) || N < 0) return NULL;
|
||||||
|
if(Num) *Num = N;
|
||||||
|
return get_plugin(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
static sl_sock_hresult_e mutehandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
sensordata_t *sd = get_sd_by_num(req, NULL);
|
||||||
|
if(!sd) return RESULT_BADVAL;
|
||||||
|
if(!station_mute(sd)) return RESULT_FAIL;
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sl_sock_hresult_e unmutehandler(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
sensordata_t *sd = get_sd_by_num(req, NULL);
|
||||||
|
if(!sd) return RESULT_BADVAL;
|
||||||
|
if(!station_unmute(sd)) return RESULT_FAIL;
|
||||||
|
return RESULT_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static sl_sock_hresult_e ismutedhandler(sl_sock_t *client, sl_sock_hitem_t *item, const char *req){
|
||||||
|
if(!client) return RESULT_FAIL;
|
||||||
|
int N;
|
||||||
|
sensordata_t *sd = get_sd_by_num(req, &N);
|
||||||
|
if(!sd) return RESULT_BADVAL;
|
||||||
|
char buf[256];
|
||||||
|
snprintf(buf, 256, "%s[%d] = %d\n", item->key, N, station_is_muted(sd));
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
return RESULT_SILENCE;
|
||||||
|
}
|
||||||
|
|
||||||
// graceful closing socket: let client know that he's told to fuck off
|
// graceful closing socket: let client know that he's told to fuck off
|
||||||
static void toomuch(int fd){
|
static void toomuch(int fd){
|
||||||
const char *m = "Try later: too much clients connected\n";
|
const char *m = "Try later: too much clients connected\n";
|
||||||
send(fd, m, sizeof(m)-1, MSG_NOSIGNAL);
|
send(fd, m, sizeof(m)-1, MSG_NOSIGNAL);
|
||||||
shutdown(fd, SHUT_WR);
|
shutdown(fd, SHUT_WR);
|
||||||
DBG("shutdown, wait");
|
DBG("shutdown");
|
||||||
double t0 = sl_dtime();
|
|
||||||
uint8_t buf[8];
|
|
||||||
while(sl_dtime() - t0 < 90.){ // change this value to smaller for real work
|
|
||||||
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);
|
LOGWARN("Client fd=%d tried to connect after MAX reached", fd);
|
||||||
}
|
}
|
||||||
// new connections handler (return FALSE to reject client)
|
// new connections handler (return FALSE to reject client)
|
||||||
@@ -173,6 +346,7 @@ static sl_sock_hresult_e defhandler(struct sl_sock *s, const char *str){
|
|||||||
|
|
||||||
#define COMMONHANDLERS \
|
#define COMMONHANDLERS \
|
||||||
{gethandler, "get", "get all meteo or only for given plugin number", NULL}, \
|
{gethandler, "get", "get all meteo or only for given plugin number", NULL}, \
|
||||||
|
{getlvlhandler,"chklevel", "check 'sense level' of given plugin parameters", NULL}, \
|
||||||
{listhandler, "list", "show all opened plugins", NULL}, \
|
{listhandler, "list", "show all opened plugins", NULL}, \
|
||||||
{timehandler, "time", "get server's UNIX time", NULL},
|
{timehandler, "time", "get server's UNIX time", NULL},
|
||||||
|
|
||||||
@@ -182,26 +356,17 @@ static sl_sock_hitem_t nethandlers[] = { // net - only getters and client-only s
|
|||||||
{NULL, NULL, NULL, NULL}
|
{NULL, NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
static sl_sock_hitem_t localhandlers[] = { // local - full amount of setters/getters
|
static sl_sock_hitem_t localhandlers[] = { // local - full amount of setters/getters
|
||||||
|
{forbidhandler, "forbid", "get/set/clear MANUAL FORBID flag", NULL},
|
||||||
|
{forceoffhandler, "forceoff", "get/set/clear FORCE SHUTDOWN flag", NULL},
|
||||||
|
{setlvlhandler, "setlevel", "set 'sense level' (0..3) for given plugin parameters, e.g. setlevel=1:WIND=3,HUMIDITY=3 - disable fields for station 1", NULL},
|
||||||
|
{wlevhandler, "weathlevel", "set/get current weather level (0..3): goog/bad/terrible/prohibited", NULL},
|
||||||
|
{ismutedhandler, "ismuted", "==1 if station is muted", NULL},
|
||||||
|
{mutehandler, "mute", "pause station's data capture", NULL},
|
||||||
|
{unmutehandler, "unmute", "continue station's data capture", NULL},
|
||||||
COMMONHANDLERS
|
COMMONHANDLERS
|
||||||
{NULL, NULL, NULL, NULL}
|
{NULL, NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
#if 0
|
|
||||||
// common parsers for both net and local sockets
|
|
||||||
static void *cmdparser(void *U){
|
|
||||||
if(!U) return NULL;
|
|
||||||
sl_sock_t *s = (sl_sock_t*) U;
|
|
||||||
while(s && s->connected){
|
|
||||||
if(!s->rthread){
|
|
||||||
LOGERR("Server's handlers' thread is dead");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOGDBG("cmdparser(): exit");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int start_servers(const char *netnode, const char *sockpath){
|
int start_servers(const char *netnode, const char *sockpath){
|
||||||
if(!netnode || !sockpath){
|
if(!netnode || !sockpath){
|
||||||
LOGERR("start_servers(): need arguments");
|
LOGERR("start_servers(): need arguments");
|
||||||
@@ -212,11 +377,13 @@ int start_servers(const char *netnode, const char *sockpath){
|
|||||||
LOGERR("start_servers(): can't run network socket");
|
LOGERR("start_servers(): can't run network socket");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
DBG("Network server started");
|
||||||
localsocket = sl_sock_run_server(SOCKT_UNIX, sockpath, BUFSIZ, localhandlers);
|
localsocket = sl_sock_run_server(SOCKT_UNIX, sockpath, BUFSIZ, localhandlers);
|
||||||
if(!localsocket){
|
if(!localsocket){
|
||||||
LOGERR("start_servers(): can't run local socket");
|
LOGERR("start_servers(): can't run local socket");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
DBG("Local server started");
|
||||||
sl_sock_changemaxclients(netsocket, MAX_CLIENTS);
|
sl_sock_changemaxclients(netsocket, MAX_CLIENTS);
|
||||||
sl_sock_changemaxclients(localsocket, 1);
|
sl_sock_changemaxclients(localsocket, 1);
|
||||||
sl_sock_maxclhandler(netsocket, toomuch);
|
sl_sock_maxclhandler(netsocket, toomuch);
|
||||||
@@ -227,35 +394,30 @@ int start_servers(const char *netnode, const char *sockpath){
|
|||||||
sl_sock_dischandler(localsocket, disconnected);
|
sl_sock_dischandler(localsocket, disconnected);
|
||||||
sl_sock_defmsghandler(netsocket, defhandler);
|
sl_sock_defmsghandler(netsocket, defhandler);
|
||||||
sl_sock_defmsghandler(localsocket, defhandler);
|
sl_sock_defmsghandler(localsocket, defhandler);
|
||||||
#if 0
|
// now run checking cycle
|
||||||
if(pthread_create(&netthread, NULL, cmdparser, (void*)netsocket)){
|
int Nplugins = get_nplugins();
|
||||||
LOGERR("Can't run server's net thread");
|
time_t tstart = time(NULL), tcur;
|
||||||
goto errs;
|
while(1){
|
||||||
|
for(int i = 0; i < Nplugins; ++i){
|
||||||
|
sensordata_t *s = get_plugin(i);
|
||||||
|
if(!s) continue;
|
||||||
|
if(sensor_alive(s)) continue;
|
||||||
|
// sensor isn't inited - try to do it
|
||||||
|
DBG("sensor with path %s isn't inited, try", s->path);
|
||||||
|
s->kill(s); // clear resources
|
||||||
|
if(s->init){
|
||||||
|
if(s->init(s)) LOGMSG("Sensor %s reinited @ %s", s->name, s->path);
|
||||||
|
else DBG("Can't reinit");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while((tcur = time(NULL)) - tstart < WeatherConf.reinit_delay) sleep(1);
|
||||||
|
tstart = tcur;
|
||||||
}
|
}
|
||||||
if(pthread_create(&locthread, NULL, cmdparser, (void*)localsocket)){
|
return TRUE; // should be never reached
|
||||||
LOGERR("Can't run server's local thread");
|
|
||||||
goto errs;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return TRUE;
|
|
||||||
#if 0
|
|
||||||
errs:
|
|
||||||
sl_sock_delete(&localsocket);
|
|
||||||
sl_sock_delete(&netsocket);
|
|
||||||
return FALSE;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void kill_servers(){
|
void kill_servers(){
|
||||||
//pthread_cancel(locthread);
|
|
||||||
//pthread_cancel(netthread);
|
|
||||||
//LOGMSG("Server threads canceled");
|
|
||||||
//usleep(500);
|
|
||||||
sl_sock_delete(&localsocket);
|
sl_sock_delete(&localsocket);
|
||||||
sl_sock_delete(&netsocket);
|
sl_sock_delete(&netsocket);
|
||||||
LOGMSG("Server sockets destroyed");
|
LOGMSG("Server sockets destroyed");
|
||||||
//usleep(500);
|
|
||||||
//pthread_join(locthread, NULL);
|
|
||||||
//pthread_join(netthread, NULL);
|
|
||||||
//LOGMSG("Server threads are dead");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
|
Readme.md
|
||||||
cmdlnopts.c
|
cmdlnopts.c
|
||||||
cmdlnopts.h
|
cmdlnopts.h
|
||||||
fd.c
|
fd.c
|
||||||
fd.h
|
|
||||||
main.c
|
main.c
|
||||||
mainweather.c
|
mainweather.c
|
||||||
mainweather.h
|
mainweather.h
|
||||||
@@ -12,7 +12,9 @@ plugins/btameteo.c
|
|||||||
plugins/dummy.c
|
plugins/dummy.c
|
||||||
plugins/fdexample.c
|
plugins/fdexample.c
|
||||||
plugins/hydreon.c
|
plugins/hydreon.c
|
||||||
|
plugins/lightning.c
|
||||||
plugins/reinhardt.c
|
plugins/reinhardt.c
|
||||||
|
plugins/snmp.c
|
||||||
plugins/wxa100.c
|
plugins/wxa100.c
|
||||||
sensors.c
|
sensors.c
|
||||||
sensors.h
|
sensors.h
|
||||||
|
|||||||
@@ -23,21 +23,24 @@
|
|||||||
#include "weathlib.h"
|
#include "weathlib.h"
|
||||||
|
|
||||||
// private functions (for plugins usage only)
|
// private functions (for plugins usage only)
|
||||||
static int common_onrefresh(sensordata_t*, void (*handler)(sensordata_t*));
|
//static int common_onrefresh(sensordata_t*, void (*handler)(sensordata_t*));
|
||||||
static void common_kill(sensordata_t *);
|
//static void common_kill(sensordata_t *);
|
||||||
static int common_getval(sensordata_t*, val_t*, int);
|
//static int common_getval(sensordata_t*, val_t*, int);
|
||||||
//static int common_init(sensordata_t*, int, time_t, int);
|
//static int common_init(sensordata_t*, int, time_t, int);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief common_new - call this function from your plugin's `sensor_new`
|
* @brief sensor_new - call this function before calling `sensor_init`
|
||||||
* @return
|
* @param N - plugin number in array
|
||||||
|
* @return pointer to allocated sensor's structure
|
||||||
*/
|
*/
|
||||||
sensordata_t *common_new(){
|
sensordata_t *sensor_new(int N, const char *descr){
|
||||||
sensordata_t *s = MALLOC(sensordata_t, 1);
|
sensordata_t *s = MALLOC(sensordata_t, 1);
|
||||||
s->fdes = -1; // not inited
|
s->fdes = -1; // not inited
|
||||||
s->onrefresh = common_onrefresh;
|
s->onrefresh = common_onrefresh; // `init` function can redefine basic stubs
|
||||||
s->get_value = common_getval;
|
s->get_value = common_getval;
|
||||||
s->kill = common_kill;
|
s->kill = common_kill;
|
||||||
|
s->PluginNo = N; // `init` shouldn't change this value
|
||||||
|
snprintf(s->path, PATH_MAX, "%s", descr); // `init` shouldn't change this value
|
||||||
pthread_mutex_init(&s->valmutex, NULL);
|
pthread_mutex_init(&s->valmutex, NULL);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@@ -66,19 +69,27 @@ int common_onrefresh(sensordata_t *s, void (*handler)(sensordata_t *)){
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief common_kill - common `die` function
|
* @brief common_kill - common `die` function (close, but don't destroy sensor)
|
||||||
* @param s - sensor
|
* @param s - sensor
|
||||||
*/
|
*/
|
||||||
void common_kill(sensordata_t *s){
|
void common_kill(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(!s) return;
|
if(!s) return;
|
||||||
if(s->fdes > -1){ // inited and maybe have opened file/socket
|
if(s->fdes > -1){ // inited and maybe have opened file/socket
|
||||||
if(0 == pthread_cancel(s->thread)){
|
|
||||||
DBG("%s main thread canceled, join", s->name);
|
|
||||||
pthread_join(s->thread, NULL);
|
|
||||||
DBG("Done");
|
|
||||||
}
|
|
||||||
close(s->fdes);
|
close(s->fdes);
|
||||||
|
s->fdes = -1;
|
||||||
|
DBG("FD closed");
|
||||||
|
usleep(5000);
|
||||||
|
if(pthread_equal(pthread_self(), s->thread)){
|
||||||
|
DBG("Don't cancel myself");
|
||||||
|
}else{
|
||||||
|
DBG("Cancel sensor's thread");
|
||||||
|
if(0 == pthread_cancel(s->thread)){
|
||||||
|
DBG("%s main thread canceled, join", s->name);
|
||||||
|
pthread_join(s->thread, NULL);
|
||||||
|
DBG("Done");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DBG("Delete RB");
|
DBG("Delete RB");
|
||||||
if(s->ringbuffer) sl_RB_delete(&s->ringbuffer);
|
if(s->ringbuffer) sl_RB_delete(&s->ringbuffer);
|
||||||
@@ -86,9 +97,8 @@ void common_kill(sensordata_t *s){
|
|||||||
if(s->privdatafree) s->privdatafree(s->privdata);
|
if(s->privdatafree) s->privdatafree(s->privdata);
|
||||||
else FREE(s->privdata);
|
else FREE(s->privdata);
|
||||||
DBG("Sensor '%s' killed", s->name);
|
DBG("Sensor '%s' killed", s->name);
|
||||||
LOGERR("Sensor '%s' killed", s->name);
|
if(s->path[0]) LOGERR("Sensor '%s' @ '%s' killed", s->name, s->path);
|
||||||
FREE(s);
|
else LOGERR("Sensor '%s' killed", s->name);
|
||||||
DBG("There's no more this sensor");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
// length (in symbols) of key, value and comment
|
// length (in symbols) of key, value and comment
|
||||||
#define KEY_LEN (8)
|
#define KEY_LEN (8)
|
||||||
|
// length of STR type (without terminal zero)
|
||||||
|
#define STRT_LEN (11)
|
||||||
#define VAL_LEN (31)
|
#define VAL_LEN (31)
|
||||||
#define COMMENT_LEN (63)
|
#define COMMENT_LEN (63)
|
||||||
// maximal full length of "KEY=val / comment" (as for sfitsio)
|
// maximal full length of "KEY=val / comment" (as for sfitsio)
|
||||||
@@ -36,10 +38,12 @@
|
|||||||
|
|
||||||
// importance of values
|
// importance of values
|
||||||
typedef enum{
|
typedef enum{
|
||||||
|
VAL_FORCEDSHTDN, // if this value is `terrible`, `forced sthudtown` flag will be set
|
||||||
VAL_OBLIGATORY, // can't be omitted
|
VAL_OBLIGATORY, // can't be omitted
|
||||||
VAL_RECOMMENDED, // recommended to show
|
VAL_RECOMMENDED, // recommended to show
|
||||||
VAL_UNNECESSARY, // may be shown by user request
|
VAL_UNNECESSARY, // may be shown by user request
|
||||||
VAL_BROKEN // sensor is broken, omit it
|
VAL_BROKEN, // sensor is broken, omit it
|
||||||
|
VAL_AMOUNT // amount of values
|
||||||
} valsense_t;
|
} valsense_t;
|
||||||
|
|
||||||
// meaning of values
|
// meaning of values
|
||||||
@@ -56,13 +60,19 @@ typedef enum{
|
|||||||
IS_MIST, // mist (1 - yes, 0 - no)
|
IS_MIST, // mist (1 - yes, 0 - no)
|
||||||
IS_CLOUDS, // integral clouds value (bigger - better)
|
IS_CLOUDS, // integral clouds value (bigger - better)
|
||||||
IS_SKYTEMP, // mean sky temperatyre
|
IS_SKYTEMP, // mean sky temperatyre
|
||||||
IS_OTHER // something other - read "name" and "comment"
|
//IS_LIGTDIST, // distance to lightning
|
||||||
|
IS_OTHER, // something other - read "name" and "comment"
|
||||||
|
// values after `IS_OTHER` have no pre-defined names and comments!!!
|
||||||
|
IS_BADWEATH, // if meet this flag, set weather level to "BAD"
|
||||||
|
IS_TERRIBLEWEATH, // -//- "TERRIBLE"
|
||||||
|
IS_FORCEDSHTDN, // like "on battery" flag from UPS
|
||||||
} valmeaning_t;
|
} valmeaning_t;
|
||||||
|
|
||||||
typedef union{
|
typedef union{
|
||||||
uint32_t u;
|
uint32_t u;
|
||||||
int32_t i;
|
int32_t i;
|
||||||
float f;
|
float f;
|
||||||
|
char str[STRT_LEN+1];// up to 8 symbols (with terminating zero)
|
||||||
} num_t;
|
} num_t;
|
||||||
|
|
||||||
// type of value
|
// type of value
|
||||||
@@ -70,7 +80,7 @@ typedef enum{
|
|||||||
VALT_UINT,
|
VALT_UINT,
|
||||||
VALT_INT,
|
VALT_INT,
|
||||||
VALT_FLOAT,
|
VALT_FLOAT,
|
||||||
//VALT_STRING,
|
VALT_STRING,
|
||||||
} valtype_t;
|
} valtype_t;
|
||||||
|
|
||||||
// value
|
// value
|
||||||
@@ -87,9 +97,12 @@ typedef struct{
|
|||||||
// all sensor's data
|
// all sensor's data
|
||||||
// all functions have `this` as first arg
|
// all functions have `this` as first arg
|
||||||
typedef struct sensordata_t{
|
typedef struct sensordata_t{
|
||||||
char name[NAME_LEN+1]; // max 31 symbol of sensor's name (e.g. "rain sensor")
|
char name[NAME_LEN+1]; // sensor's name (e.g. "rain sensor @ localhost")
|
||||||
|
char path[PATH_MAX]; // path to sensor's device or socket description in format D:/path, U:path, N:host:port
|
||||||
int Nvalues; // amount of values
|
int Nvalues; // amount of values
|
||||||
int PluginNo; // plugin number in array (if several)
|
int PluginNo; // plugin number in array (if several)
|
||||||
|
int IsMuted; // ==1 for "muted" station (don't refresh sensors' data)
|
||||||
|
int (*init)(struct sensordata_t*); // main init function
|
||||||
int (*onrefresh)(struct sensordata_t*, void (*handler)(struct sensordata_t*)); // handler of new data; return TRUE if OK
|
int (*onrefresh)(struct sensordata_t*, void (*handler)(struct sensordata_t*)); // handler of new data; return TRUE if OK
|
||||||
int (*get_value)(struct sensordata_t*, val_t*, int); // getter of Nth value
|
int (*get_value)(struct sensordata_t*, val_t*, int); // getter of Nth value
|
||||||
void (*kill)(struct sensordata_t*); // close everything and remove sensor
|
void (*kill)(struct sensordata_t*); // close everything and remove sensor
|
||||||
@@ -107,11 +120,16 @@ typedef struct sensordata_t{
|
|||||||
} sensordata_t;
|
} sensordata_t;
|
||||||
|
|
||||||
// type for function extraction
|
// type for function extraction
|
||||||
typedef sensordata_t* (*sensor_new_t)(int, time_t, int);
|
typedef int (*sensor_init_t)(sensordata_t *);
|
||||||
|
|
||||||
// init meteostation with given PluginNo, poll_interval and fd
|
// init meteostation with given PluginNo, poll_interval and descriptor
|
||||||
sensordata_t *sensor_new(int PluginNo, time_t poll_interval, int fd); // external initial function for any plugin
|
//sensordata_t *sensor_init(int PluginNo, time_t poll_interval, const char *descr); // external initial function for any plugin
|
||||||
int sensor_alive(sensordata_t *s);
|
int sensor_alive(sensordata_t *s);
|
||||||
|
sensordata_t *sensor_new(int N, const char *descr);
|
||||||
|
|
||||||
// private function (for plugins usage only)
|
// private function (for plugins usage only)
|
||||||
sensordata_t *common_new();
|
void common_kill(sensordata_t *s);
|
||||||
|
int common_onrefresh(sensordata_t *s, void (*handler)(sensordata_t *));
|
||||||
|
int common_getval(struct sensordata_t *s, val_t *o, int N);
|
||||||
|
|
||||||
|
int getFD(const char *path);
|
||||||
|
|||||||
Reference in New Issue
Block a user