mirror of
https://github.com/eddyem/small_tel.git
synced 2026-05-07 13:27:06 +03:00
Compare commits
20 Commits
0f0c87ee2f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 46de782019 | |||
| 63f87d2283 | |||
| c5b9d43797 | |||
| 2413661e19 | |||
| 05e57ef012 | |||
| 27cfe60fe8 | |||
| 347d02e748 | |||
| 68febd02c4 | |||
|
|
7b2d93299d | ||
| 5acd1cd97d | |||
| af33a036c8 | |||
| 987cf022fe | |||
| 5be6876f9e | |||
| e551b94499 | |||
| 39d4e22061 | |||
| 05a42b0a10 | |||
| cc870491f5 | |||
| 9d58bf1694 | |||
| a324302404 | |||
| 7318307d4d |
@@ -65,8 +65,12 @@ void signals(int sig){
|
||||
signal(sig, SIG_IGN);
|
||||
DBG("Get signal %d, quit.\n", sig);
|
||||
}
|
||||
DBG("Stop!");
|
||||
Mount.stop();
|
||||
usleep(10000);
|
||||
DBG("Quit");
|
||||
Mount.quit();
|
||||
usleep(10000);
|
||||
DBG("close");
|
||||
if(fcoords) fclose(fcoords);
|
||||
exit(sig);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 18.0.0, 2026-03-23T09:21:54. -->
|
||||
<!-- Written by QtCreator 18.0.0, 2026-04-03T10:35:41. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define NFILT (5)
|
||||
|
||||
static double filterK[NFILT];
|
||||
|
||||
static void buildFilter(){
|
||||
filterK[NFILT-1] = 1.;
|
||||
double sum = 1.;
|
||||
for(int i = NFILT-2; i > -1; --i){
|
||||
filterK[i] = (filterK[i+1] + 1.) * 1.1;
|
||||
sum += filterK[i];
|
||||
}
|
||||
for(int i = 0; i < NFILT; ++i){
|
||||
filterK[i] /= sum;
|
||||
fprintf(stderr, "%d: %g\n", i, filterK[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static double filter(double val){
|
||||
static int ctr = 0;
|
||||
static double lastvals[NFILT] = {0.};
|
||||
for(int i = NFILT-1; i > 0; --i) lastvals[i] = lastvals[i-1];
|
||||
lastvals[0] = val;
|
||||
double r = 0.;
|
||||
if(ctr < NFILT){
|
||||
++ctr;
|
||||
return val;
|
||||
}
|
||||
for(int i = 0; i < NFILT; ++i) r += filterK[i] * lastvals[i];
|
||||
return r;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv){
|
||||
buildFilter();
|
||||
printf("Signal\tNoiced\tFiltered\n");
|
||||
for(int i = 0; i < 100; ++i){
|
||||
double di = (double)i;
|
||||
double sig = di * di / 1e5 + sin(i * M_PI / 1500.);
|
||||
double noiced = sig + 0.1 * (drand48() - 0.5);
|
||||
printf("%.3f\t%.3f\t%.3f\n", sig, noiced, filter(noiced));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -481,6 +481,41 @@ static void chkModStopped(double *prev, double cur, int *nstopped, axis_status_t
|
||||
*prev = cur;
|
||||
}
|
||||
|
||||
// Next two functions runs under locked mountdata_t mutex and shouldn't lock it again!!
|
||||
static axis_status_t chkstopstat(int32_t *prev, int32_t cur, int32_t tag, int *nstopped, axis_status_t stat){
|
||||
if(*prev == INT32_MAX){
|
||||
stat = AXIS_STOPPED;
|
||||
DBG("START");
|
||||
}else if(stat == AXIS_GONNASTOP || (stat != AXIS_STOPPED && cur == tag)){ // got command "stop" or motor is on target
|
||||
if(*prev == cur){
|
||||
DBG("Test for stop, nstopped=%d", *nstopped);
|
||||
if(++(*nstopped) > MOTOR_STOPPED_CNT){
|
||||
stat = AXIS_STOPPED;
|
||||
DBG("AXIS stopped");
|
||||
}
|
||||
}else *nstopped = 0;
|
||||
}else if(*prev != cur){
|
||||
DBG("AXIS moving");
|
||||
*nstopped = 0;
|
||||
}
|
||||
*prev = cur;
|
||||
return stat;
|
||||
}
|
||||
|
||||
// check for stopped/pointing states
|
||||
static void ChkStopped(const SSstat *s, mountdata_t *m){
|
||||
static int32_t Xmot_prev = INT32_MAX, Ymot_prev = INT32_MAX; // previous coordinates
|
||||
static int Xnstopped = 0, Ynstopped = 0; // counters to get STOPPED state
|
||||
axis_status_t Xstat, Ystat;
|
||||
Xstat = chkstopstat(&Xmot_prev, s->Xmot, m->Xtarget, &Xnstopped, m->Xstate);
|
||||
Ystat = chkstopstat(&Ymot_prev, s->Ymot, m->Ytarget, &Ynstopped, m->Ystate);
|
||||
if(Xstat != m->Xstate || Ystat != m->Ystate){
|
||||
DBG("Status changed");
|
||||
mountdata.Xstate = Xstat;
|
||||
mountdata.Ystate = Ystat;
|
||||
}
|
||||
}
|
||||
|
||||
// main mount thread
|
||||
static void *mountthread(void _U_ *u){
|
||||
int errctr = 0;
|
||||
@@ -552,6 +587,7 @@ static void *mountthread(void _U_ *u){
|
||||
pthread_mutex_lock(&datamutex);
|
||||
// now change data
|
||||
SSconvstat(status, &mountdata, &tcur);
|
||||
ChkStopped(status, &mountdata);
|
||||
pthread_mutex_unlock(&datamutex);
|
||||
// allow writing & getters
|
||||
do{
|
||||
@@ -585,11 +621,12 @@ static int ttyopen(const char *path, speed_t speed){
|
||||
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
|
||||
tty.c_iflag = 0; // don't do any changes in input stream
|
||||
tty.c_oflag = 0; // don't do any changes in output stream
|
||||
tty.c_cflag = BOTHER | CS8 | CREAD | CLOCAL; // other speed, 8bit, RW, ignore line ctrl
|
||||
// wihthout "HUPCL" it doesn't disconnects
|
||||
tty.c_cflag = HUPCL | BOTHER | CS8 | CREAD | CLOCAL; // other speed, 8bit, RW, ignore line ctrl
|
||||
tty.c_ispeed = speed;
|
||||
tty.c_ospeed = speed;
|
||||
tty.c_cc[VMIN] = 0; // non-canonical mode
|
||||
tty.c_cc[VTIME] = 5;
|
||||
tty.c_cc[VTIME] = 0;
|
||||
if(ioctl(fd, TCSETS2, &tty)){
|
||||
DBG("Can't set TTY settings");
|
||||
close(fd);
|
||||
@@ -674,6 +711,7 @@ create_thread:
|
||||
// close all opened serial devices and quit threads
|
||||
void closeSerial(){
|
||||
GlobExit = 1;
|
||||
pthread_mutex_unlock(&datamutex);
|
||||
DBG("Give 100ms to proper close");
|
||||
usleep(100000);
|
||||
DBG("Force closed all devices");
|
||||
|
||||
@@ -44,39 +44,6 @@ uint16_t SScalcChecksum(uint8_t *buf, int len){
|
||||
return checksum;
|
||||
}
|
||||
|
||||
// Next three functions runs under locked mountdata_t mutex and shouldn't call locked it again!!
|
||||
static axis_status_t chkstopstat(int32_t *prev, int32_t cur, int32_t tag, int *nstopped, axis_status_t stat){
|
||||
if(*prev == INT32_MAX){
|
||||
stat = AXIS_STOPPED;
|
||||
DBG("START");
|
||||
}else if(stat == AXIS_GONNASTOP || (stat != AXIS_STOPPED && cur == tag)){ // got command "stop" or motor is on target
|
||||
if(*prev == cur){
|
||||
DBG("Test for stop, nstopped=%d", *nstopped);
|
||||
if(++(*nstopped) > MOTOR_STOPPED_CNT){
|
||||
stat = AXIS_STOPPED;
|
||||
DBG("AXIS stopped");
|
||||
}
|
||||
}else *nstopped = 0;
|
||||
}else if(*prev != cur){
|
||||
DBG("AXIS moving");
|
||||
*nstopped = 0;
|
||||
}
|
||||
*prev = cur;
|
||||
return stat;
|
||||
}
|
||||
// check for stopped/pointing states
|
||||
static void ChkStopped(const SSstat *s, mountdata_t *m){
|
||||
static int32_t Xmot_prev = INT32_MAX, Ymot_prev = INT32_MAX; // previous coordinates
|
||||
static int Xnstopped = 0, Ynstopped = 0; // counters to get STOPPED state
|
||||
axis_status_t Xstat, Ystat;
|
||||
Xstat = chkstopstat(&Xmot_prev, s->Xmot, m->Xtarget, &Xnstopped, m->Xstate);
|
||||
Ystat = chkstopstat(&Ymot_prev, s->Ymot, m->Ytarget, &Ynstopped, m->Ystate);
|
||||
if(Xstat != m->Xstate || Ystat != m->Ystate){
|
||||
DBG("Status changed");
|
||||
setStat(Xstat, Ystat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SSconvstat - convert stat from SSII format to human
|
||||
* @param s (i) - just read data
|
||||
@@ -87,7 +54,6 @@ void SSconvstat(const SSstat *s, mountdata_t *m, struct timespec *t){
|
||||
if(!s || !m || !t) return;
|
||||
m->motXposition.val = X_MOT2RAD(s->Xmot);
|
||||
m->motYposition.val = Y_MOT2RAD(s->Ymot);
|
||||
ChkStopped(s, m);
|
||||
m->motXposition.t = m->motYposition.t = *t;
|
||||
// fill encoder data from here, as there's no separate enc thread
|
||||
if(!Conf.SepEncoder){
|
||||
|
||||
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")
|
||||
|
||||
@@ -3,7 +3,7 @@ LDFLAGS = -lerfa -pthread -lusefull_macros
|
||||
SRCS = $(wildcard *.c)
|
||||
CC = gcc
|
||||
DEFINES = -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_XOPEN_SOURCE=1111
|
||||
DEFINES += -DEBUG
|
||||
#DEFINES += -DEBUG
|
||||
CXX = gcc
|
||||
CFLAGS = -Wall -Werror -Wextra -Wno-trampolines $(DEFINES)
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
|
||||
@@ -47,16 +47,21 @@ extern void check4running(char *self, char *pidfilename, void (*iffound)(pid_t p
|
||||
// pause for incoming message waiting (out coordinates sent after that timeout)
|
||||
#define SOCK_TMOUT (1)
|
||||
|
||||
static pid_t childpid = 1; // PID of child process
|
||||
static pid_t childpid = 0; // PID of child process
|
||||
volatile int global_quit = 0;
|
||||
// quit by signal
|
||||
void signals(int sig){
|
||||
signal(sig, SIG_IGN);
|
||||
if(!childpid){ // child process
|
||||
DBG("STOP tel");
|
||||
stop_telescope();
|
||||
DBG("Disconn tel");
|
||||
disconnect_telescope();
|
||||
unlink(GP->pidfile); // and remove pidfile
|
||||
DBG("Disconn weat");
|
||||
weatherserver_disconnect();
|
||||
}else{
|
||||
DBG("Unlink PID");
|
||||
unlink(GP->pidfile); // and remove pidfile
|
||||
}
|
||||
DBG("Get signal %d, quit.\n", sig);
|
||||
global_quit = 1;
|
||||
|
||||
@@ -113,6 +113,10 @@ static char *read_string(){
|
||||
*/
|
||||
static char *write_cmd(const char *cmd, char *buff){
|
||||
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
if(!TTY || TTY->comfd < 0){
|
||||
DBG("TTY destroyed");
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_lock(&mutex);
|
||||
DBG("Write %s", cmd);
|
||||
if(sl_tty_write(TTY->comfd, cmd, strlen(cmd))) return NULL;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE QtCreatorProject>
|
||||
<!-- Written by QtCreator 19.0.0, 2026-03-24T22:16:27. -->
|
||||
<!-- Written by QtCreator 18.0.0, 2026-04-01T11:06:44. -->
|
||||
<qtcreator>
|
||||
<data>
|
||||
<variable>EnvironmentId</variable>
|
||||
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
|
||||
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
|
||||
</data>
|
||||
<data>
|
||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||
@@ -32,7 +32,7 @@
|
||||
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
|
||||
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
||||
<value type="int" key="EditorConfiguration.IndentSize">4</value>
|
||||
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
|
||||
<value type="bool" key="EditorConfiguration.KeyboardTooltips">true</value>
|
||||
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
||||
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
|
||||
@@ -40,9 +40,9 @@
|
||||
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
||||
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
|
||||
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
|
||||
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
|
||||
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
|
||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
|
||||
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
|
||||
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
|
||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
||||
@@ -51,10 +51,10 @@
|
||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
|
||||
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
|
||||
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
|
||||
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
|
||||
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
|
||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||
</valuemap>
|
||||
@@ -79,7 +79,7 @@
|
||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
|
||||
<value type="int" key="ClangTools.ParallelJobs">8</value>
|
||||
<value type="int" key="ClangTools.ParallelJobs">4</value>
|
||||
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||
@@ -96,12 +96,12 @@
|
||||
<value type="bool" key="HasPerBcDcs">true</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
|
||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Big/Data/00__Small_tel/C-sources/domedaemon_baader</value>
|
||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/p/git/small_tel/Daemons/domedaemon_baader</value>
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
|
||||
@@ -155,7 +155,6 @@
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
|
||||
<value type="QList<int>" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
@@ -192,7 +191,6 @@
|
||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
|
||||
<value type="QList<int>" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
|
||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||
|
||||
@@ -5,7 +5,7 @@ LDFLAGS += -lusefull_macros
|
||||
SRCS := $(wildcard *.c)
|
||||
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
|
||||
OBJDIR := mk
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines -std=gnu23
|
||||
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines -Wno-format-truncation -std=gnu23
|
||||
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
|
||||
DEPS := $(OBJS:.o=.d)
|
||||
TARGFILE := $(OBJDIR)/TARGET
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
commands.h
|
||||
header.c
|
||||
header.h
|
||||
main.c
|
||||
socket.c
|
||||
socket.h
|
||||
|
||||
107
Daemons/domedaemon_baader/header.c
Normal file
107
Daemons/domedaemon_baader/header.c
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* This file is part of the baader_dome project.
|
||||
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "header.h"
|
||||
#include "socket.h"
|
||||
|
||||
static char *headername = NULL;
|
||||
static char *dome_name = NULL;
|
||||
|
||||
static int printhdr(int fd, const char *key, const char *val, const char *cmnt){
|
||||
char tmp[81];
|
||||
char tk[9];
|
||||
if(strlen(key) > 8){
|
||||
snprintf(tk, 8, "%s", key);
|
||||
key = tk;
|
||||
}
|
||||
if(cmnt){
|
||||
snprintf(tmp, 81, "%-8s= %-21s / %s", key, val, cmnt);
|
||||
}else{
|
||||
snprintf(tmp, 81, "%-8s= %s", key, val);
|
||||
}
|
||||
size_t l = strlen(tmp);
|
||||
tmp[l] = '\n';
|
||||
++l;
|
||||
if(write(fd, tmp, l) != (ssize_t)l){
|
||||
WARN("write()");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// return TRUE if can write to given header file
|
||||
int header_create(const char *file){
|
||||
if(!file) return FALSE;
|
||||
FILE *hf = fopen(file, "w");
|
||||
if(!hf) return FALSE;
|
||||
unlink(file);
|
||||
if(headername) FREE(headername);
|
||||
headername = strdup(file);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// set given `name` for telescope name
|
||||
void domename(const char *name){
|
||||
if(dome_name) FREE(dome_name);
|
||||
if(name){
|
||||
int l = strlen(name) + 3;
|
||||
dome_name = MALLOC(char, l);
|
||||
snprintf(dome_name, l-1, "'%s'", name);
|
||||
}
|
||||
}
|
||||
|
||||
void write_header(){
|
||||
if(!headername) return;
|
||||
char aname[PATH_MAX];
|
||||
char val[22];
|
||||
#define WRHDR(k, v, c) do{if(printhdr(fd, k, v, c)){goto returning;}}while(0)
|
||||
snprintf(aname, PATH_MAX-1, "%sXXXXXX", headername);
|
||||
int fd = mkstemp(aname);
|
||||
if(fd < 0){
|
||||
LOGWARN("Can't write header file: mkstemp()");
|
||||
return;
|
||||
}
|
||||
fchmod(fd, 0644);
|
||||
|
||||
dome_data_t st;
|
||||
if(get_dome_data(&st)) WRHDR("OPERATIO", "'FORBIDDEN'", "Observations are forbidden");
|
||||
if(dome_name) WRHDR("DOME", dome_name, "Dome manufacturer/name");
|
||||
WRHDR("DOMESTAT", st.status, "Dome status");
|
||||
WRHDR("DOMEWEAT", st.weather, "Dome weather sensor status");
|
||||
snprintf(val, 21, "%d", st.errcode);
|
||||
WRHDR("DOMEECOD", val, "Dome error code: Rain|Watchdog|Power");
|
||||
snprintf(val, 21, "%.3f", st.stattime);
|
||||
char timebuf[BUFSIZ];
|
||||
time_t t = (time_t) st.stattime;
|
||||
struct tm *tmp;
|
||||
tmp = localtime(&t);
|
||||
if(!tmp || 0 == strftime(timebuf, BUFSIZ, "Measurement time: %F %T", tmp)){
|
||||
LOGERR("localtime() returned NULL");
|
||||
goto returning;
|
||||
}
|
||||
WRHDR("TDOMMEAS", val, timebuf);
|
||||
#undef WRHDR
|
||||
returning:
|
||||
close(fd);
|
||||
rename(aname, headername);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* This file is part of the weatherdaemon project.
|
||||
* This file is part of the baader_dome project.
|
||||
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@@ -18,4 +18,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
int getFD(char *path);
|
||||
void write_header();
|
||||
int header_create(const char *file);
|
||||
void domename(const char *name);
|
||||
@@ -20,12 +20,22 @@
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <linux/prctl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/socket.h>
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "header.h"
|
||||
#include "socket.h"
|
||||
#include "term.h"
|
||||
|
||||
#define DEFAULT_PIDFILE "/tmp/domedaemon.pid"
|
||||
#define DEFAULT_HEADERFILE "/tmp/dome.fits"
|
||||
#define DEFAULT_DOMENAME "Baader"
|
||||
|
||||
static pid_t childpid = 0;
|
||||
|
||||
typedef struct{
|
||||
int help;
|
||||
int verbose;
|
||||
@@ -37,12 +47,17 @@ typedef struct{
|
||||
char *node;
|
||||
char *termpath;
|
||||
char *pidfile;
|
||||
char *headerfile;
|
||||
char *dome_name;
|
||||
} parameters;
|
||||
|
||||
static parameters G = {
|
||||
.maxclients = 2,
|
||||
.serspeed = 9600,
|
||||
.sertmout = 5000
|
||||
.sertmout = 5000,
|
||||
.pidfile = DEFAULT_PIDFILE,
|
||||
.headerfile = DEFAULT_HEADERFILE,
|
||||
.dome_name = DEFAULT_DOMENAME,
|
||||
};
|
||||
|
||||
static sl_option_t cmdlnopts[] = {
|
||||
@@ -52,34 +67,56 @@ static sl_option_t cmdlnopts[] = {
|
||||
{"node", NEED_ARG, NULL, 'n', arg_string, APTR(&G.node), "node \"IP\", \"name:IP\" or path (could be \"\\0path\" for anonymous UNIX-socket)"},
|
||||
{"unixsock", NO_ARGS, NULL, 'u', arg_int, APTR(&G.isunix), "UNIX socket instead of INET"},
|
||||
{"maxclients", NEED_ARG, NULL, 'm', arg_int, APTR(&G.maxclients),"max amount of clients connected to server (default: 2)"},
|
||||
{"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), "PID-file"},
|
||||
{"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), "full path to PID-file (default: " DEFAULT_PIDFILE ")"},
|
||||
{"serialdev", NEED_ARG, NULL, 'd', arg_string, APTR(&G.termpath), "full path to serial device"},
|
||||
{"baudrate", NEED_ARG, NULL, 'b', arg_int, APTR(&G.serspeed), "serial device speed (baud)"},
|
||||
{"sertmout", NEED_ARG, NULL, 'T', arg_double, APTR(&G.sertmout), "serial device timeout (us)"},
|
||||
{"baudrate", NEED_ARG, NULL, 'b', arg_int, APTR(&G.serspeed), "serial device speed (baud, default: 9600)"},
|
||||
{"sertmout", NEED_ARG, NULL, 'T', arg_double, APTR(&G.sertmout), "serial device timeout (us, default: 5000)"},
|
||||
{"headerfile", NEED_ARG, NULL, 'H', arg_string, APTR(&G.headerfile),"full path to output FITS-header (default: " DEFAULT_HEADERFILE ")"},
|
||||
{"domename", NEED_ARG, NULL, 'N', arg_string, APTR(&G.dome_name), "dome name in FITS-header (default: " DEFAULT_DOMENAME ")"},
|
||||
end_option
|
||||
};
|
||||
|
||||
void sl_iffound_deflt(pid_t pid){
|
||||
WARNX("Another copy of this process found, pid=%d. Exit.", pid);
|
||||
exit(1); // don't run `signals` to protect foreign PID-file from removal
|
||||
}
|
||||
|
||||
// SIGUSR1 - FORBID observations
|
||||
// SIGUSR2 - allow
|
||||
void signals(int sig){
|
||||
if(sig){
|
||||
if(sig == SIGUSR1){
|
||||
forbid_observations(1);
|
||||
return;
|
||||
}else if(sig == SIGUSR2){
|
||||
forbid_observations(0);
|
||||
return;
|
||||
if(signals != signal(sig, SIG_IGN)) exit(sig); // function called "as is", before sig registration
|
||||
if(childpid == 0){ // child -> test USR1/USR2
|
||||
LOGDBG("Child gotta signal %d", sig);
|
||||
if(sig == SIGUSR1){
|
||||
forbid_observations(1);
|
||||
LOGMSG("Got signal `observations forbidden`");
|
||||
signal(sig, signals);
|
||||
return;
|
||||
}else if(sig == SIGUSR2){
|
||||
forbid_observations(0);
|
||||
LOGMSG("Got signal `observations permitted`");
|
||||
signal(sig, signals);
|
||||
return;
|
||||
}
|
||||
}
|
||||
signal(sig, SIG_IGN);
|
||||
DBG("Get signal %d, quit.\n", sig);
|
||||
LOGERR("Exit with status %d", sig);
|
||||
}else LOGERR("Exit");
|
||||
DBG("Stop server");
|
||||
stopserver();
|
||||
DBG("Close terminal");
|
||||
term_close();
|
||||
DBG("Exit");
|
||||
LOGDBG("Get signal %d, quit.\n", sig);
|
||||
}
|
||||
if(childpid == 0){
|
||||
DBG("Stop server");
|
||||
LOGMSG("Stop server");
|
||||
stopserver();
|
||||
DBG("Close terminal");
|
||||
LOGMSG("Close terminal");
|
||||
term_close();
|
||||
}else{
|
||||
if(G.pidfile){
|
||||
LOGMSG("Unlink %s", G.pidfile);
|
||||
usleep(10000);
|
||||
unlink(G.pidfile);
|
||||
}
|
||||
}
|
||||
LOGERR("Exit with status %d", sig);
|
||||
exit(sig);
|
||||
}
|
||||
|
||||
@@ -88,9 +125,13 @@ int main(int argc, char **argv){
|
||||
sl_init();
|
||||
sl_parseargs(&argc, &argv, cmdlnopts);
|
||||
if(G.help) sl_showhelp(-1, cmdlnopts);
|
||||
if(!G.node) ERRX("Point node");
|
||||
if(!G.node) ERRX("Point communication node");
|
||||
if(!G.termpath) ERRX("Point serial device path");
|
||||
if(!header_create(G.headerfile))
|
||||
ERRX("Cannot write into '%s'", G.headerfile);
|
||||
domename(G.dome_name);
|
||||
sl_check4running((char*)__progname, G.pidfile);
|
||||
if(sl_daemonize()) ERR("Can't daemonize!");
|
||||
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
|
||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||
if(G.logfile) OPENLOG(G.logfile, lvl, 1);
|
||||
@@ -103,11 +144,26 @@ int main(int argc, char **argv){
|
||||
signal(SIGINT, signals);
|
||||
signal(SIGQUIT, signals);
|
||||
signal(SIGTSTP, SIG_IGN);
|
||||
signal(SIGHUP, signals);
|
||||
signal(SIGUSR1, SIG_IGN);
|
||||
signal(SIGUSR2, SIG_IGN);
|
||||
while(1){ // guard for dead processes
|
||||
childpid = fork();
|
||||
if(childpid){
|
||||
LOGMSG("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
|
||||
}
|
||||
}
|
||||
// react for USRx only in child
|
||||
signal(SIGUSR1, signals);
|
||||
signal(SIGUSR2, signals);
|
||||
runserver(G.isunix, G.node, G.maxclients);
|
||||
LOGMSG("Ended");
|
||||
DBG("Close");
|
||||
LOGERR("Server error -> exit");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "commands.h"
|
||||
#include "header.h"
|
||||
#include "socket.h"
|
||||
#include "term.h"
|
||||
|
||||
@@ -33,15 +34,12 @@ typedef enum{
|
||||
} dome_commands_t;
|
||||
|
||||
typedef struct{
|
||||
int errcode; // error code
|
||||
char status[STATBUF_SZ]; // device status
|
||||
double stattime;// time of last status
|
||||
char weather[STATBUF_SZ]; // data from weather sensor
|
||||
dome_commands_t cmd;
|
||||
dome_commands_t erroredcmd; // command didn't run - waiting or stalled
|
||||
int errcode; // error code
|
||||
char *status; // device status
|
||||
int statlen; // size of `status` buffer
|
||||
double stattime;// time of last status
|
||||
char *weather; // data from weather sensor
|
||||
int weathlen; // length of `weather` buffer
|
||||
double weathtime;// time of last weather
|
||||
pthread_mutex_t mutex;
|
||||
} dome_t;
|
||||
|
||||
@@ -53,6 +51,14 @@ static atomic_bool ForbidObservations = 0; // ==1 if all is forbidden -> close d
|
||||
static sl_sock_t *locksock = NULL; // local server socket
|
||||
static sl_ringbuffer_t *rb = NULL; // incoming serial data
|
||||
|
||||
int get_dome_data(dome_data_t *d){
|
||||
if(!d) return FALSE;
|
||||
pthread_mutex_lock(&Dome.mutex);
|
||||
*d = *((dome_data_t*)&Dome);
|
||||
pthread_mutex_unlock(&Dome.mutex);
|
||||
return ForbidObservations;
|
||||
}
|
||||
|
||||
void stopserver(){
|
||||
if(locksock) sl_sock_delete(&locksock);
|
||||
if(rb) sl_RB_delete(&rb);
|
||||
@@ -150,10 +156,10 @@ static sl_sock_hresult_e weathh(sl_sock_t *client, sl_sock_hitem_t *item, _U_ co
|
||||
char buf[256];
|
||||
double t = NAN;
|
||||
pthread_mutex_lock(&Dome.mutex);
|
||||
if(!*Dome.weather || sl_dtime() - Dome.weathtime > 3.*T_INTERVAL) snprintf(buf, 255, "%s=unknown\n", item->key);
|
||||
if(sl_dtime() - Dome.stattime > 3.*T_INTERVAL) snprintf(buf, 255, "%s=unknown\n", item->key);
|
||||
else{
|
||||
snprintf(buf, 255, "%s=%s\n", item->key, Dome.weather);
|
||||
t = Dome.weathtime;
|
||||
t = Dome.stattime;
|
||||
}
|
||||
pthread_mutex_unlock(&Dome.mutex);
|
||||
sl_sock_sendstrmessage(client, buf);
|
||||
@@ -227,11 +233,11 @@ static int poll_device(){
|
||||
if(sscanf(data, "%d", &I) == 1){
|
||||
pthread_mutex_lock(&Dome.mutex);
|
||||
if(I == 1111)
|
||||
snprintf(Dome.status, Dome.statlen, "opened");
|
||||
snprintf(Dome.status, STATBUF_SZ-1, "opened");
|
||||
else if(I == 2222)
|
||||
snprintf(Dome.status, Dome.statlen, "closed");
|
||||
snprintf(Dome.status, STATBUF_SZ-1, "closed");
|
||||
else
|
||||
snprintf(Dome.status, Dome.statlen, "intermediate");
|
||||
snprintf(Dome.status, STATBUF_SZ-1, "intermediate");
|
||||
Dome.stattime = sl_dtime();
|
||||
pthread_mutex_unlock(&Dome.mutex);
|
||||
}
|
||||
@@ -240,12 +246,11 @@ static int poll_device(){
|
||||
if(sscanf(data, "%d", &I) == 1){
|
||||
pthread_mutex_lock(&Dome.mutex);
|
||||
if(I == 0)
|
||||
snprintf(Dome.weather, Dome.weathlen, "good");
|
||||
snprintf(Dome.weather, STATBUF_SZ-1, "good");
|
||||
else if (I == 1)
|
||||
snprintf(Dome.weather, Dome.weathlen, "rain/clouds");
|
||||
snprintf(Dome.weather, STATBUF_SZ-1, "rain/clouds");
|
||||
else
|
||||
snprintf(Dome.weather, Dome.weathlen, "unknown");
|
||||
Dome.weathtime = sl_dtime();
|
||||
snprintf(Dome.weather, STATBUF_SZ-1, "unknown");
|
||||
pthread_mutex_unlock(&Dome.mutex);
|
||||
}
|
||||
return TRUE;
|
||||
@@ -258,12 +263,6 @@ void runserver(int isunix, const char *node, int maxclients){
|
||||
if(rb) sl_RB_delete(&rb);
|
||||
rb = sl_RB_new(BUFSIZ);
|
||||
Dome.cmd = CMD_NONE;
|
||||
FREE(Dome.status);
|
||||
Dome.statlen = STATBUF_SZ;
|
||||
Dome.status = MALLOC(char, STATBUF_SZ);
|
||||
FREE(Dome.weather);
|
||||
Dome.weathlen = STATBUF_SZ;
|
||||
Dome.weather = MALLOC(char, STATBUF_SZ);
|
||||
pthread_mutex_init(&Dome.mutex, NULL);
|
||||
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
|
||||
locksock = sl_sock_run_server(type, node, -1, handlers);
|
||||
@@ -293,7 +292,10 @@ void runserver(int isunix, const char *node, int maxclients){
|
||||
}
|
||||
double tnow = sl_dtime();
|
||||
if(tnow - tgot > T_INTERVAL){
|
||||
if(poll_device()) tgot = tnow;
|
||||
if(poll_device()){
|
||||
tgot = tnow;
|
||||
write_header();
|
||||
}
|
||||
}
|
||||
if(ForbidObservations) continue;
|
||||
pthread_mutex_lock(&Dome.mutex);
|
||||
|
||||
@@ -25,6 +25,14 @@
|
||||
// dome polling interval (clear watchdog & get status)
|
||||
#define T_INTERVAL (5.0)
|
||||
|
||||
typedef struct{
|
||||
int errcode; // error code
|
||||
char status[STATBUF_SZ]; // device status
|
||||
double stattime;// time of last status
|
||||
char weather[STATBUF_SZ]; // data from weather sensor
|
||||
} dome_data_t;
|
||||
|
||||
int get_dome_data(dome_data_t *d);
|
||||
void runserver(int isunix, const char *node, int maxclients);
|
||||
void stopserver();
|
||||
void forbid_observations(int forbid);
|
||||
|
||||
@@ -101,17 +101,18 @@ void write_header(){
|
||||
|
||||
if(get_telescope_data(&st)) WRHDR("OPERATIO", "'FORBIDDEN'", "Observations are forbidden");
|
||||
if(header_mask.telname && telescope_name) WRHDR("TELESCOP", telescope_name, "Telescope name");
|
||||
WRHDR("TELSTAT", st.status, "Telescope shutters' status");
|
||||
if(header_mask.fosuser){
|
||||
snprintf(val, 21, "%d", st.focuserpos);
|
||||
WRHDR("FOCUS", val, "Current focuser position");
|
||||
}
|
||||
if(header_mask.cooler){
|
||||
snprintf(val, 21, "%d", st.cooler);
|
||||
WRHDR("COOLER", val, "Primary mirror cooler status: 0/1 (off/on)");
|
||||
WRHDR("TELCOOLR", val, "Primary mirror cooler status: 0/1 (off/on)");
|
||||
}
|
||||
if(header_mask.heater){
|
||||
snprintf(val, 21, "%d", st.heater);
|
||||
WRHDR("HEATER", val, "Secondary mirror heater status: 0/1 (off/on)");
|
||||
WRHDR("TELHEATR", val, "Secondary mirror heater status: 0/1 (off/on)");
|
||||
}
|
||||
if(header_mask.exttemp){
|
||||
snprintf(val, 21, "%.1f", st.ambienttemp);
|
||||
@@ -127,11 +128,11 @@ void write_header(){
|
||||
time_t t = (time_t) st.stattime;
|
||||
struct tm *tmp;
|
||||
tmp = localtime(&t);
|
||||
if(!tmp || 0 == strftime(timebuf, BUFSIZ, "Measurement time: %F %T", tmp)){
|
||||
if(!tmp || 0 == strftime(timebuf, BUFSIZ, "Measurement time (telescope): %F %T", tmp)){
|
||||
LOGERR("localtime() returned NULL");
|
||||
goto returning;
|
||||
}
|
||||
WRHDR("TMEAS", val, timebuf);
|
||||
WRHDR("TTELMEAS", val, timebuf);
|
||||
|
||||
}
|
||||
#undef WRHDR
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
#include "term.h"
|
||||
|
||||
#define DEFAULT_PIDFILE "/tmp/teldaemon.pid"
|
||||
#define DEFAULT_HEADERFILE "/tmp/telescope.pid"
|
||||
#define DEFAULT_HEADERFILE "/tmp/telescope.fits"
|
||||
#define DEFAULT_TELNAME "Astro-M (1)"
|
||||
|
||||
static pid_t childpid = 0;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -fPIC
|
||||
LDFLAGS = -lrt -pthread
|
||||
CFLAGS = -Wall -Wextra -fPIC -DEBUG
|
||||
LDFLAGS = -lrt -pthread -lusefull_macros
|
||||
|
||||
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 <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -9,8 +26,7 @@
|
||||
#include <semaphore.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define SHM_NAME "/weather_shm"
|
||||
#define SEM_NAME "/weather_sem"
|
||||
#include "weather_data.h"
|
||||
|
||||
int get_weather_data(weather_data_t *data) {
|
||||
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 <stdio.h>
|
||||
|
||||
int main() {
|
||||
weather_data_t wd;
|
||||
if (get_weather_data(&wd) == 0) {
|
||||
printf("Weather: %d, Max wind: %.1f, Wind: %.1f, Temp: %.1f; updated @%.1f\n",
|
||||
wd.weather, wd.windmax, wd.wind, wd.exttemp, wd.last_update);
|
||||
} else {
|
||||
if(get_weather_data(&wd) == 0){
|
||||
char strt[64];
|
||||
struct tm *T = localtime(&wd.last_update);
|
||||
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");
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -1,284 +1,367 @@
|
||||
#include "weather_data.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.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 <fcntl.h>
|
||||
#include <semaphore.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 <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 STAT_TMOUT 60
|
||||
#define WEAT_TMOUT 5
|
||||
// don't ask new data less than `WEAT_TMOUT` seconds
|
||||
#define WEAT_TMOUT 1
|
||||
|
||||
#define SHM_NAME "/weather_shm"
|
||||
#define SEM_NAME "/weather_sem"
|
||||
typedef struct{
|
||||
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 forbidden = 0;
|
||||
static sem_t *sem = NULL;
|
||||
static weather_data_t *shared_data = NULL;
|
||||
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)
|
||||
#define log_error(...) do{printf("error: "); printf(__VA_ARGS__); printf("\n");}while(0)
|
||||
static sl_option_t opts[] = {
|
||||
{"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) {
|
||||
if (sig == SIGTERM || sig == SIGINT) {
|
||||
running = 0;
|
||||
log_message("Received signal %d, shutting down", sig);
|
||||
void signals(int signo){
|
||||
if(signo){
|
||||
if(signals != signal(signo, SIG_IGN)) exit(signo); // function called "as is", before sig registration
|
||||
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
|
||||
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");
|
||||
// no - create new
|
||||
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0644);
|
||||
if (shm_fd == -1) {
|
||||
log_error("shm_open (create) failed: %s", strerror(errno));
|
||||
if(shm_fd == -1){
|
||||
LOGERR("shm_open (create) failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (ftruncate(shm_fd, sizeof(weather_data_t)) == -1) {
|
||||
log_error("ftruncate failed: %s", strerror(errno));
|
||||
if(ftruncate(shm_fd, sizeof(weather_data_t)) == -1){
|
||||
LOGERR("ftruncate failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, shm_fd, 0);
|
||||
if (shared_data == MAP_FAILED) {
|
||||
log_error("mmap failed: %s", strerror(errno));
|
||||
if(shared_data == MAP_FAILED){
|
||||
LOGERR("mmap failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
// default values to data
|
||||
} else {
|
||||
printf("Use existant SHM\n");
|
||||
// use existant SHM
|
||||
}else{
|
||||
DBG("Use existant SHM\n");
|
||||
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, shm_fd, 0);
|
||||
if (shared_data == MAP_FAILED) {
|
||||
log_error("mmap failed: %s", strerror(errno));
|
||||
if(shared_data == MAP_FAILED){
|
||||
LOGERR("mmap failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
memset(shared_data, 0, sizeof(weather_data_t));
|
||||
|
||||
// create samaphore if no
|
||||
sem = sem_open(SEM_NAME, O_CREAT, 0666, 1);
|
||||
if (sem == SEM_FAILED) {
|
||||
log_error("sem_open failed: %s", strerror(errno));
|
||||
if(sem == SEM_FAILED){
|
||||
LOGERR("sem_open failed: %s", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// free IPC
|
||||
static void cleanup_ipc(void) {
|
||||
static void cleanup_ipc(void){
|
||||
if (sem != NULL) {
|
||||
sem_close(sem);
|
||||
printf("semaphore closed\n");
|
||||
if(-1 == sem_unlink(SEM_NAME)) perror("Can't delete semaphore");
|
||||
DBG("semaphore closed\n");
|
||||
if(-1 == sem_unlink(SEM_NAME)) LOGERR("Can't delete semaphore");
|
||||
}
|
||||
if (shared_data != NULL) {
|
||||
printf("memory unmapped\n");
|
||||
if(shared_data != NULL){
|
||||
DBG("memory unmapped\n");
|
||||
munmap(shared_data, sizeof(weather_data_t));
|
||||
}
|
||||
if (shm_fd != -1) {
|
||||
if(shm_fd != -1){
|
||||
close(shm_fd);
|
||||
printf("close shared mem\n");
|
||||
if (shm_unlink(SHM_NAME) == -1) {
|
||||
perror("can't unlink SHM");
|
||||
DBG("close shared mem\n");
|
||||
if(shm_unlink(SHM_NAME) == -1){
|
||||
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) {
|
||||
char key[64];
|
||||
char value[256];
|
||||
if (sscanf(line, "%63[^=]=%255s", key, value) == 2) {
|
||||
if (strcmp(key, "weather") == 0) {
|
||||
if (strcmp(value, "good") == 0)
|
||||
data->weather = WEATHER_GOOD;
|
||||
else if (strcmp(value, "bad") == 0)
|
||||
data->weather = WEATHER_BAD;
|
||||
else if (strcmp(value, "terrible") == 0)
|
||||
data->weather = WEATHER_TERRIBLE;
|
||||
char key[SL_KEY_LEN];
|
||||
char value[SL_VAL_LEN];
|
||||
|
||||
int update = 0; // 0 for updating, 1 for finishing, -1 for error
|
||||
|
||||
if(sl_get_keyval(line, key, value)){
|
||||
if(strcmp(key, "WEATHER") == 0){
|
||||
data->weather = (weather_condition_t) atoi(value);
|
||||
printf("got weather: %d\n", data->weather);
|
||||
} else if (strcmp(key, "Windmax") == 0) {
|
||||
}else if (strcmp(key, "WINDMAX1") == 0){
|
||||
data->windmax = atof(value);
|
||||
printf("got windmax: %g\n", data->windmax);
|
||||
} else if (strcmp(key, "Rain") == 0) {
|
||||
}else if (strcmp(key, "PRECIP") == 0){
|
||||
data->rain = atoi(value);
|
||||
printf("got rain: %d\n", data->rain);
|
||||
} else if (strcmp(key, "Clouds") == 0) {
|
||||
}else if (strcmp(key, "CLOUDS") == 0){
|
||||
data->clouds = atof(value);
|
||||
printf("got clouds: %g\n", data->clouds);
|
||||
} else if (strcmp(key, "Wind") == 0) {
|
||||
}else if (strcmp(key, "WIND") == 0){
|
||||
data->wind = atof(value);
|
||||
printf("got wind: %g\n", data->wind);
|
||||
} else if (strcmp(key, "Temperature") == 0) {
|
||||
}else if (strcmp(key, "EXTTEMP") == 0){
|
||||
data->exttemp = atof(value);
|
||||
printf("got temp: %g\n", data->exttemp);
|
||||
} else if (strcmp(key, "Pressure") == 0) {
|
||||
}else if (strcmp(key, "PRESSURE") == 0){
|
||||
data->pressure = atof(value);
|
||||
printf("got pressure: %g\n", data->pressure);
|
||||
} else if (strcmp(key, "Humidity") == 0) {
|
||||
}else if (strcmp(key, "HUMIDITY") == 0){
|
||||
data->humidity = atof(value);
|
||||
printf("got humidity: %g\n", data->humidity);
|
||||
} else if (strcmp(key, "prohibited") == 0) {
|
||||
}else if (strcmp(key, "PROHIBIT") == 0){
|
||||
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);
|
||||
if(data->weather == WEATHER_PROHIBITED || forbidden) data->prohibited = 1;
|
||||
else if(data->weather < WEATHER_PROHIBITED) data->prohibited = 0;
|
||||
// update all
|
||||
if (sem_wait(sem) == -1) {
|
||||
log_error("sem_wait failed: %s", strerror(errno));
|
||||
} else {
|
||||
memcpy(shared_data, data, sizeof(weather_data_t));
|
||||
sem_post(sem);
|
||||
log_message("Weather data updated");
|
||||
}
|
||||
update_shm(data);
|
||||
update = 1;
|
||||
}else update = -1;
|
||||
if(update > -1 && G.fitsheader){
|
||||
FITS_update(line, update);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int sock = -1;
|
||||
static FILE *sock_file = NULL;
|
||||
|
||||
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";
|
||||
static int request_weather_data(sl_sock_t *sock){
|
||||
static time_t tcur = 0;
|
||||
char *request = "get\n";
|
||||
time_t tnow = time(NULL);
|
||||
if(tnow - tcur >= WEAT_TMOUT){
|
||||
tcur = tnow;
|
||||
}else if(tnow - tstat >= STAT_TMOUT){
|
||||
tstat = tnow;
|
||||
request = "stat60\n";
|
||||
}else return 1; // not now
|
||||
|
||||
printf("try to send request: '%s", request);
|
||||
if (send(sock, request, strlen(request), 0) < 0) {
|
||||
log_error("send failed: %s", strerror(errno));
|
||||
close(sock);
|
||||
sock = -1;
|
||||
DBG("try to send request: '%s", request);
|
||||
if(sl_sock_sendstrmessage(sock, request) < 1){
|
||||
LOGWARN("Can't poll new data");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_daemon(const char *server_ip, int port) {
|
||||
static void run_daemon(){
|
||||
char line[256];
|
||||
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));
|
||||
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)) {
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
printf("parse '%s'\n", line);
|
||||
while(running){
|
||||
time_t tnow = time(NULL);
|
||||
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);
|
||||
}
|
||||
usleep(500000);
|
||||
}
|
||||
close(sock);
|
||||
sl_sock_delete(&sock); // disconnect and clear memory
|
||||
DBG("run_daemon() exited");
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s <server_ip> <port>\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
int main(int argc, char *argv[]){
|
||||
sl_init();
|
||||
sl_parseargs(&argc, &argv, opts);
|
||||
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);
|
||||
}
|
||||
|
||||
const char *server_ip = argv[1];
|
||||
int port = atoi(argv[2]);
|
||||
if (port <= 0 || port > 65535) {
|
||||
fprintf(stderr, "Invalid port\n");
|
||||
exit(EXIT_FAILURE);
|
||||
sl_check4running(NULL, G.pidfile);
|
||||
if(G.logfile){
|
||||
sl_loglevel_e lvl = LOGLEVEL_ERR + G.verb;
|
||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||
DBG("Loglevel: %d", lvl);
|
||||
if(!OPENLOG(G.logfile, lvl, 1)) ERRX("Can't open log file %s", G.logfile);
|
||||
LOGMSG("Started");
|
||||
}
|
||||
|
||||
//daemonize();
|
||||
|
||||
log_message("Starting weather daemon, server %s:%d", server_ip, port);
|
||||
|
||||
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(SIGTERM, signals);
|
||||
signal(SIGINT, signals);
|
||||
signal(SIGUSR1, SIG_IGN);
|
||||
signal(SIGUSR2, 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) {
|
||||
log_error("IPC initialization failed");
|
||||
if(init_ipc() != 0){
|
||||
LOGERR("IPC initialization failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
run_daemon(server_ip, port);
|
||||
|
||||
run_daemon();
|
||||
LOGDBG("Daemon is dead");
|
||||
cleanup_ipc();
|
||||
log_message("Weather daemon stopped");
|
||||
LOGDBG("IPC cleaned");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3,23 +3,27 @@
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
#define SHM_NAME "/weather_shm"
|
||||
#define SEM_NAME "/weather_sem"
|
||||
|
||||
typedef enum {
|
||||
WEATHER_GOOD = 0,
|
||||
WEATHER_BAD = 1,
|
||||
WEATHER_TERRIBLE = 2
|
||||
WEATHER_GOOD = 0, // may start observations
|
||||
WEATHER_BAD = 1, // cannot start but can continue if want
|
||||
WEATHER_TERRIBLE = 2, // close & park: wind, precipitation, humidity etc.
|
||||
WEATHER_PROHIBITED = 3, // force closing & parking; power off equipment, ready to power off computer
|
||||
} weather_condition_t;
|
||||
|
||||
typedef struct {
|
||||
weather_condition_t weather;
|
||||
float windmax;
|
||||
int rain;
|
||||
float clouds;
|
||||
float wind;
|
||||
float exttemp;
|
||||
float pressure;
|
||||
float humidity;
|
||||
int prohibited;
|
||||
double last_update;
|
||||
weather_condition_t weather; // conditions: field "WEATHER"
|
||||
float windmax; // maximal wind for last hour, m/s: "WINDMAX1"
|
||||
float wind; // current wind speed, m/s: "WIND"
|
||||
float clouds; // sky "quality" (>2500 - OK): "CLOUDS"
|
||||
float exttemp; // external temperature, degC: "EXTTEMP"
|
||||
float pressure; // atm. pressure, mmHg: "PRESSURE"
|
||||
float humidity; // humidity, percents: "HUMIDITY"
|
||||
int rain; // ==1 when rainy: "PRECIP"
|
||||
int prohibited; // ==1 if "weather == prohibited" or got `prohibited` signal -> ready to power off
|
||||
time_t last_update; // value of "TMEAS"
|
||||
} weather_data_t;
|
||||
|
||||
int get_weather_data(weather_data_t *data);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
cmake_minimum_required(VERSION 4.0)
|
||||
set(PROJ weatherdaemon)
|
||||
set(PROJ superweatherdaemon)
|
||||
set(PROJLIB senslib)
|
||||
set(MAJOR_VERSION "0")
|
||||
set(MID_VERSION "0")
|
||||
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}")
|
||||
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
|
||||
@@ -15,15 +15,21 @@ message("VER: ${VERSION}")
|
||||
option(DEBUG "Compile in debug mode" OFF)
|
||||
option(DUMMY "Dummy device plugin" ON)
|
||||
option(FDEXAMPLE "Example of file descriptor plugin" ON)
|
||||
option(HYDREON "Hydreon rain sensor plugin" ON)
|
||||
option(BTAMETEO "BTA main meteostation plugin" ON)
|
||||
option(REINHARDT "Old Reinhardt 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
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -pedantic-errors -fPIC")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -fPIC")
|
||||
|
||||
message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}")
|
||||
|
||||
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
||||
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\"
|
||||
-DMAJOR_VERSION=\"${MAJOR_VESION}\")
|
||||
-DMAJOR_VERSION=\"${MAJOR_VERSION}\")
|
||||
|
||||
set(CMAKE_COLOR_MAKEFILE ON)
|
||||
|
||||
@@ -51,8 +57,8 @@ pkg_check_modules(${PROJ} REQUIRED usefull_macros>=0.3.5)
|
||||
#endif()
|
||||
|
||||
# static lib for sensors
|
||||
set(LIBSRC "weathlib.c")
|
||||
set(LIBHEADER "weathlib.h")
|
||||
set(LIBSRC fd.c weathlib.c)
|
||||
set(LIBHEADER weathlib.h)
|
||||
add_library(${PROJLIB} STATIC ${LIBSRC})
|
||||
set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION})
|
||||
|
||||
|
||||
@@ -1,22 +1,324 @@
|
||||
Weather daemon for several different weather stations
|
||||
=====================================================
|
||||
# superweatherdaemon Documentation
|
||||
|
||||
## Usage:
|
||||
## Overview
|
||||
|
||||
```
|
||||
Usage: weatherdaemon [args]
|
||||
Be careful: command line options have priority over config
|
||||
Where args are:
|
||||
**superweatherdaemon** is a weather monitoring daemon designed for astronomical observatories. It
|
||||
collects data from multiple heterogeneous weather stations (meteostations) via a plugin
|
||||
architecture, computes a unified weather status, and provides control interfaces over TCP and local
|
||||
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)
|
||||
-c, --conffile=arg configuration file name (consists all or a part of long-named parameters and their values (e.g. plugin=liboldweather.so)
|
||||
-h, --help show this help
|
||||
-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)
|
||||
-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
|
||||
--sockpath=arg UNIX socket path (starting from '\0' for anonimous) of command socket
|
||||
The project is written in C, uses CMake as its build system, and relies on the
|
||||
[usefull_macros](https://github.com/eddyem/snippets_library) library for utilities and socket
|
||||
management.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Build tools**: CMake >= 4.0, C compiler with C11 support, `pkg-config`.
|
||||
- **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>.*
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "cmdlnopts.h"
|
||||
#include "mainweather.h"
|
||||
|
||||
/*
|
||||
* here are global parameters initialisation
|
||||
@@ -30,7 +32,7 @@ int help;
|
||||
|
||||
// default values for Gdefault & help
|
||||
#define DEFAULT_PORT "12345"
|
||||
#define DEFAULT_PID "/tmp/weatherdaemon.pid"
|
||||
#define DEFAULT_PID "/tmp/superweatherdaemon.pid"
|
||||
|
||||
// DEFAULTS
|
||||
// default global parameters
|
||||
@@ -38,12 +40,35 @@ static glob_pars defpars = {
|
||||
.port = DEFAULT_PORT,
|
||||
.logfile = NULL,
|
||||
.verb = 0,
|
||||
.pidfile = DEFAULT_PID
|
||||
.pidfile = DEFAULT_PID,
|
||||
};
|
||||
// default config: all values should be wrong or empty to understand than user change them
|
||||
static glob_pars defconf = {
|
||||
.verb = -1,
|
||||
};
|
||||
// only for config
|
||||
weather_conf_t WeatherConf = {
|
||||
.ahtung_delay = 30*60, // 30 minutes
|
||||
.reinit_delay = 60, // each 1 minute
|
||||
.wind.good = 5., // < 5m/s - good weather
|
||||
.wind.bad = 10., // > 10m/s - bad weather
|
||||
.wind.terrible = 15., // > 15m/s - terrible weather
|
||||
.humidity.good = 65.,
|
||||
.humidity.bad = 87.,
|
||||
.humidity.terrible = 94.,
|
||||
.clouds.good = 2500.,
|
||||
.clouds.bad = 2000.,
|
||||
.clouds.terrible = 500.,
|
||||
.clouds.negflag = 1, // the higher values is the better
|
||||
.sky.good = -40.,
|
||||
.sky.bad = -10.,
|
||||
.sky.terrible = 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;
|
||||
|
||||
@@ -55,30 +80,47 @@ 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"}, \
|
||||
{"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 ")"}, \
|
||||
{"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"}, \
|
||||
{"pollt", NEED_ARG, NULL, 'T', arg_int, APTR(&G.pollt), "set maximal polling interval (seconds, integer)"},
|
||||
|
||||
sl_option_t cmdlnopts[] = {
|
||||
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
|
||||
{"conffile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.conffile), "configuration file name (consists all or a part of long-named parameters and their values (e.g. plugin=liboldweather.so:D:/dev/ttyS0:115200)"},
|
||||
{"verb", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), "logfile verbocity level (each -v increased)"}, \
|
||||
{"conffile",NEED_ARG, NULL, 'c', arg_string, APTR(&G.conffile), "configuration file name (or non-existant file for help)"},
|
||||
{"verb", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), "logfile verbocity level (each -v increased)"},
|
||||
COMMON_OPTS
|
||||
end_option
|
||||
};
|
||||
|
||||
sl_option_t confopts[] = {
|
||||
{"verbose", NEED_ARG, NULL, 'v', 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, 0, arg_int, APTR(&WeatherConf.ahtung_delay), "delay in seconds after bad weather to change to good"},
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"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"},
|
||||
{"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 greater than this, clouds are bad"},
|
||||
{"terrible_clouds",NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.clouds.terrible), "if greater, clouds are terrible"},
|
||||
{"clouds_negflag",NEED_ARG, NULL, 0, arg_int, APTR(&WeatherConf.clouds.negflag), "==1 to invert sign (lesser value is worst)"},
|
||||
{"good_sky", NEED_ARG, NULL, 0, arg_double, APTR(&WeatherConf.sky.good), "sky-ambient less than this is good"},
|
||||
{"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
|
||||
end_option
|
||||
};
|
||||
|
||||
#if 0
|
||||
static int sortstrings(const void *v1, const void *v2){
|
||||
const char **s1 = (const char **)v1, **s2 = (const char **)v2;
|
||||
return strcmp(*s1, *s2);
|
||||
}
|
||||
#endif
|
||||
|
||||
// compare plugins from configuration and command line; add to command line plugins all new
|
||||
// to use similar stations several times you should point for them different settings
|
||||
static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
||||
if(!cmdline) return;
|
||||
char **p;
|
||||
@@ -101,10 +143,30 @@ static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
||||
for(int i = 0; i < nconf; ++i){ newarray[i+ncmd] = conf->plugins[i]; }
|
||||
FREE(conf->plugins);
|
||||
}
|
||||
qsort(newarray, newsize, sizeof(char*), sortstrings);
|
||||
// don't sort: we need leave priority as user pointed
|
||||
//qsort(newarray, newsize, sizeof(char*), sortstrings);
|
||||
#ifdef EBUG
|
||||
DBG("NOW together:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
||||
p = newarray;
|
||||
int nondobuleidx = 0;
|
||||
#endif
|
||||
for(int i = 0; i < newsize-1; ++i){
|
||||
if(NULL == newarray[i]) continue;
|
||||
for(int j = i+1; j < newsize; ++j){
|
||||
if(NULL == newarray[j]) continue;
|
||||
if(0 == strcmp(newarray[i], newarray[j])) FREE(newarray[j]);
|
||||
}
|
||||
}
|
||||
// now collect them in order
|
||||
int nondoubleidx = 0;
|
||||
for(int i = 0; i < newsize; ++i){
|
||||
if(newarray[i]){
|
||||
if(i != nondoubleidx){
|
||||
newarray[nondoubleidx] = newarray[i];
|
||||
newarray[i] = NULL;
|
||||
}
|
||||
++nondoubleidx;
|
||||
}
|
||||
}
|
||||
#if 0
|
||||
for(int i = 0; i < newsize;){
|
||||
int j = i + 1;
|
||||
for(; j < newsize; ++j){
|
||||
@@ -118,9 +180,12 @@ static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
||||
++nondobuleidx;
|
||||
i = j;
|
||||
}
|
||||
#endif
|
||||
#ifdef EBUG
|
||||
DBG("Result:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
||||
#endif
|
||||
cmdline->plugins = newarray;
|
||||
cmdline->nplugins = nondobuleidx;
|
||||
cmdline->nplugins = nondoubleidx;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -146,11 +211,16 @@ glob_pars *parse_args(int argc, char **argv){
|
||||
if(G.conffile){ // read conffile and fix parameters (cmdline args are in advantage)
|
||||
glob_pars oldpars = G; // save cmdline opts
|
||||
G = defconf;
|
||||
if(!sl_conf_readopts(oldpars.conffile, confopts)) ERRX("Can't get options from %s", G.conffile);
|
||||
if(!sl_conf_readopts(oldpars.conffile, confopts)){
|
||||
fprintf(stderr, "\nDefault options:\n%s\n", sl_print_opts(confopts, 1));
|
||||
sl_conf_showhelp(-1, confopts);
|
||||
return NULL;
|
||||
}
|
||||
DBG("CONF: \n-------------\n%s-------------\n\n", sl_print_opts(confopts, 1));
|
||||
if((0 == strcmp(oldpars.port, DEFAULT_PORT)) && G.port) oldpars.port = G.port;
|
||||
if(!oldpars.logfile && G.logfile) oldpars.logfile = G.logfile;
|
||||
if(!oldpars.verb && G.verb > -1) oldpars.verb = G.verb;
|
||||
if(G.pollt > 0 && oldpars.pollt == 0) oldpars.pollt = G.pollt;
|
||||
if((0 == strcmp(oldpars.pidfile, DEFAULT_PID)) && G.pidfile) oldpars.pidfile = G.pidfile;
|
||||
if(!oldpars.sockname && G.sockname) oldpars.sockname = G.sockname;
|
||||
// now check plugins
|
||||
|
||||
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,104 +29,39 @@
|
||||
#include <sys/un.h> // unix socket
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "fd.h"
|
||||
|
||||
/**
|
||||
* @brief openserial - try to open serial device
|
||||
* @param path - path to device and speed, colon-separated (without given speed assume 9600)
|
||||
* @return -1 if failed or opened FD
|
||||
* WARNING!!! Memory leakage danger. Don't call this function too much times!
|
||||
*/
|
||||
static int openserial(char *path){
|
||||
static int openserial(const char *path){
|
||||
FNAME();
|
||||
int speed = 9600; // default speed
|
||||
char *colon = strchr(path, ':');
|
||||
char *str = strdup(path);
|
||||
char *colon = strchr(str, ':');
|
||||
if(colon){
|
||||
*colon++ = 0;
|
||||
if(!sl_str2i(&speed, colon)){
|
||||
WARNX("Wrong speed settings: '%s'", colon);
|
||||
FREE(str);
|
||||
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)){
|
||||
WARN("Can't open %s @ speed %d", path, speed);
|
||||
return -1;
|
||||
}
|
||||
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);
|
||||
WARN("Can't open %s @ speed %d", str, speed);
|
||||
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;
|
||||
}
|
||||
DBG("FD: %d", sock);
|
||||
return sock;
|
||||
DBG("Opened %s @ %d", str, speed);
|
||||
FREE(str);
|
||||
int comfd = serial->comfd;
|
||||
FREE(serial->portname);
|
||||
FREE(serial->buf);
|
||||
FREE(serial->format);
|
||||
FREE(serial);
|
||||
return comfd;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,17 +70,21 @@ static int opensocket(char *path, sl_socktype_e type){
|
||||
* WARNING!!! Contents of `path` would be modified in this function!
|
||||
* @return opened file descriptor or -1 in case of error
|
||||
*/
|
||||
int getFD(char *path){
|
||||
if(!path || !*path) return -1;
|
||||
int getFD(const char *path){
|
||||
if(!path || !*path || strlen(path) < 2) return -1;
|
||||
char type = *path;
|
||||
if(path[1] != ':') return -1; // after protocol letter should be delimeter
|
||||
path += 2;
|
||||
if(!*path) return -1; // empty path
|
||||
switch(type){
|
||||
case 'D': // serial device
|
||||
return openserial(path);
|
||||
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
|
||||
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);
|
||||
return -1;
|
||||
|
||||
@@ -24,42 +24,57 @@
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "cmdlnopts.h"
|
||||
#include "mainweather.h"
|
||||
#include "sensors.h"
|
||||
#include "server.h"
|
||||
|
||||
static pid_t childpid = 0;
|
||||
static glob_pars *GP = NULL;
|
||||
|
||||
// SIGUSR1 - FORBID observations
|
||||
// SIGUSR2 - allow
|
||||
void signals(int signo){
|
||||
if(childpid){
|
||||
if(signo){
|
||||
if(signals != signal(signo, SIG_IGN)) exit(signo); // function called "as is", before sig registration
|
||||
if(childpid == 0){ // child -> test USR1/USR2
|
||||
LOGDBG("Child gotta signal %d", signo);
|
||||
if(signo == SIGUSR1){
|
||||
forbid_observations(1);
|
||||
LOGWARN("Got signal `observations forbidden`, set FORBIDDEN");
|
||||
signal(signo, signals);
|
||||
return;
|
||||
}else if(signo == SIGUSR2){
|
||||
forbid_observations(0);
|
||||
LOGWARN("Got signal `observations permitted`, clear FORBIDDEN");
|
||||
signal(signo, signals);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(childpid){ // master
|
||||
LOGERR("Main process exits with status %d", signo);
|
||||
if(GP && GP->pidfile) unlink(GP->pidfile);
|
||||
}else{ // child
|
||||
LOGERR("Killed with status %d", signo);
|
||||
closeplugins();
|
||||
kill_servers();
|
||||
usleep(1000); // let child close everything before dead
|
||||
}else{
|
||||
LOGERR("Main process exits with status %d", signo);
|
||||
}
|
||||
usleep(1000); // let child close everything before dead
|
||||
exit(signo);
|
||||
}
|
||||
|
||||
static void getpipe(int _U_ signo){
|
||||
WARNX("Get sigpipe!");
|
||||
LOGWARN("SIGPIPE: something disconnected?");
|
||||
// TODO: check all sensors for disconnected one
|
||||
signal(SIGPIPE, getpipe);
|
||||
}
|
||||
|
||||
extern const char *__progname;
|
||||
|
||||
int main(int argc, char **argv){
|
||||
glob_pars *GP = NULL;
|
||||
sl_init();
|
||||
signal(SIGTERM, signals); // kill (-15) - quit
|
||||
signal(SIGHUP, SIG_IGN); // hup - ignore
|
||||
signal(SIGINT, signals); // ctrl+C - quit
|
||||
signal(SIGQUIT, signals); // ctrl+\ - quit
|
||||
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
|
||||
signal(SIGPIPE, getpipe); // socket disconnected
|
||||
GP = parse_args(argc, argv);
|
||||
if(!GP) ERRX("Error parsing args");
|
||||
sl_check4running((char*)__progname, GP->pidfile);
|
||||
if(!GP->sockname) ERRX("Point command socket name");
|
||||
if(GP->logfile){
|
||||
sl_loglevel_e lvl = LOGLEVEL_ERR + GP->verb;
|
||||
@@ -71,6 +86,14 @@ int main(int argc, char **argv){
|
||||
if(GP->pollt > 0){
|
||||
if(!set_pollT((time_t)GP->pollt)) ERRX("Can't set polling time to %d seconds", GP->pollt);
|
||||
}
|
||||
signal(SIGTERM, signals); // kill (-15) - quit
|
||||
signal(SIGHUP, SIG_IGN); // hup - ignore
|
||||
signal(SIGINT, signals); // ctrl+C - quit
|
||||
signal(SIGQUIT, signals); // ctrl+\ - quit
|
||||
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
|
||||
signal(SIGPIPE, getpipe); // socket disconnected
|
||||
signal(SIGUSR1, SIG_IGN);
|
||||
signal(SIGUSR2, SIG_IGN);
|
||||
int nopened = openplugins(GP->plugins, GP->nplugins);
|
||||
if(nopened < 1){
|
||||
LOGERR("No plugins found; exit!");
|
||||
@@ -78,7 +101,7 @@ int main(int argc, char **argv){
|
||||
}
|
||||
if(GP->nplugins && GP->nplugins != nopened) LOGWARN("Work without some plugins");
|
||||
#ifndef EBUG
|
||||
sl_check4running((char*)__progname, GP->pidfile);
|
||||
sl_daemonize();
|
||||
while(1){ // guard for dead processes
|
||||
childpid = fork();
|
||||
if(childpid){
|
||||
@@ -94,8 +117,11 @@ int main(int argc, char **argv){
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// react for USRx only in child
|
||||
signal(SIGUSR1, signals);
|
||||
signal(SIGUSR2, signals);
|
||||
if(!start_servers(GP->port, GP->sockname)) ERRX("Can't run server's threads");
|
||||
while(1);
|
||||
//while(1) pause();
|
||||
//WARNX("TEST ends");
|
||||
//signals(0);
|
||||
return 0; // never reached
|
||||
|
||||
586
Daemons/weatherdaemon_multimeteo/mainweather.c
Normal file
586
Daemons/weatherdaemon_multimeteo/mainweather.c
Normal file
@@ -0,0 +1,586 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// collect here weather from all weatherstations sorted by importance
|
||||
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "mainweather.h"
|
||||
#include "sensors.h"
|
||||
#include "weathlib.h"
|
||||
|
||||
// wind speed history array size (not less than for one hour)
|
||||
#define MAX_HISTORY 3600
|
||||
// throw out data older than 24 hours
|
||||
#define TOO_OLD_DATA 86400
|
||||
// one hour
|
||||
#define T_ONE_HOUR 3600
|
||||
|
||||
static pthread_mutex_t datamutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
static int Forbidden = 0;
|
||||
|
||||
// index of meteodata in array
|
||||
enum{
|
||||
NWIND,
|
||||
NWINDMAX,
|
||||
NWINDMAX1,
|
||||
NWINDDIR,
|
||||
NWINDDIR1,
|
||||
NWINDDIR2,
|
||||
NHUMIDITY,
|
||||
NAMB_TEMP,
|
||||
NPRESSURE,
|
||||
NPRECIP,
|
||||
NPRECIP_LEVEL,
|
||||
NMIST,
|
||||
NCLOUDS,
|
||||
NSKYTEMP,
|
||||
NCOMMWEATH,
|
||||
NLASTAHTUNG,
|
||||
NAHTUNGRSN,
|
||||
// NLIGHTDIST,
|
||||
NBADWEATH,
|
||||
NTERRWEATH,
|
||||
NFORCEDSHTDN,
|
||||
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] = {
|
||||
[NWIND] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||
[NWINDMAX] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX", .comment = "Maximal wind speed for last 24 hours"},
|
||||
[NWINDMAX1] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX1", .comment = "Maximal wind speed for last hour"},
|
||||
[NWINDDIR] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
||||
[NWINDDIR1] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR1", .comment = "Mean wind speed 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_BROKEN, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||
[NAMB_TEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||
[NPRESSURE] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||
[NPRECIP] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||
[NPRECIP_LEVEL] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||
[NMIST] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_MIST},
|
||||
[NCLOUDS] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_CLOUDS},
|
||||
[NSKYTEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_SKYTEMP},
|
||||
// [NLIGHTDIST] = {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
|
||||
// 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},
|
||||
};
|
||||
|
||||
// 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{
|
||||
double array[MAX_HISTORY];
|
||||
double sum;
|
||||
} sumval_t;
|
||||
|
||||
typedef struct{
|
||||
int write_idx;
|
||||
sumval_t C; // (speed * cos(dir))
|
||||
sumval_t C2; // (speed^2 * cos(dir))
|
||||
sumval_t S; // (speed * sin(dir))
|
||||
sumval_t S2; // (speed^2 * sin(dir))
|
||||
} meanwinddir_t;
|
||||
|
||||
static meanwinddir_t winddirs = {0};
|
||||
|
||||
static double update_sum(sumval_t *sv, double newval, int oldidx){
|
||||
double old = sv->array[oldidx];
|
||||
sv->array[oldidx] = newval;
|
||||
return (sv->sum += newval - old);
|
||||
}
|
||||
|
||||
// add current value into floating array and recalculate mean speeds
|
||||
// data mutex should be locked outside this function
|
||||
static void wind_dir_add(double curspeed, double curdir, double *dir, double *dir2){
|
||||
double C, S;
|
||||
sincos(curdir * M_PI / 180., &S, &C);
|
||||
double vS = curspeed * S, vC = curspeed * C, v2S = curspeed * vS, v2C = curspeed * vC;
|
||||
int idx = winddirs.write_idx;
|
||||
winddirs.write_idx = (idx + 1) % MAX_HISTORY;
|
||||
// calculate sums
|
||||
vS = update_sum(&winddirs.S, vS, idx);
|
||||
vC = update_sum(&winddirs.C, vC, idx);
|
||||
v2S = update_sum(&winddirs.S2, v2S, idx);
|
||||
v2C = update_sum(&winddirs.C2, v2C, idx);
|
||||
*dir = atan2(vS, vC) * 180. / M_PI;
|
||||
*dir2 = atan2(v2S, v2C) * 180. / M_PI;
|
||||
if(*dir < 0.) *dir += 360.;
|
||||
if(*dir2 < 0.) *dir2 += 360.;
|
||||
|
||||
}
|
||||
|
||||
typedef struct{
|
||||
double speeds[MAX_HISTORY];
|
||||
time_t timestamps[MAX_HISTORY];
|
||||
int write_idx; // index in `speeds` and `timestamps` for new value
|
||||
|
||||
int deq[MAX_HISTORY]; // array of indexes in queue
|
||||
int deq_head, deq_tail; // queue's head and tail
|
||||
} sliding_max_t;
|
||||
|
||||
static sliding_max_t windspeeds = {0};
|
||||
|
||||
static void add_windspeed(sliding_max_t *sm, double speed, time_t now) {
|
||||
// Write new data portion into queue
|
||||
int idx = sm->write_idx;
|
||||
sm->speeds[idx] = speed;
|
||||
sm->timestamps[idx] = now;
|
||||
sm->write_idx = (idx + 1) % MAX_HISTORY;
|
||||
|
||||
// Remove values older than `TOO_OLD_DATA`
|
||||
time_t cutoff = now - TOO_OLD_DATA;
|
||||
while(sm->deq_head != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < cutoff){
|
||||
sm->deq_head = (sm->deq_head + 1) % MAX_HISTORY;
|
||||
}
|
||||
|
||||
// Remove small values less than current
|
||||
while(sm->deq_head != sm->deq_tail && sm->speeds[sm->deq[(sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY]] <= speed){
|
||||
sm->deq_tail = (sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY;
|
||||
}
|
||||
|
||||
// Add new index into queue
|
||||
sm->deq[sm->deq_tail] = idx;
|
||||
sm->deq_tail = (sm->deq_tail + 1) % MAX_HISTORY;
|
||||
}
|
||||
|
||||
static double get_current_max(sliding_max_t *sm){
|
||||
if(sm->deq_head == sm->deq_tail) return 0.0; // No data
|
||||
return sm->speeds[sm->deq[sm->deq_head]];
|
||||
}
|
||||
|
||||
static double get_max_forT(sliding_max_t *sm, time_t tcutoff){
|
||||
if(sm->deq_head == sm->deq_tail) return 0.0; // No data
|
||||
int idx = sm->deq_head;
|
||||
while(idx != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < tcutoff){
|
||||
idx = (idx + 1) % MAX_HISTORY;
|
||||
}
|
||||
if(idx == sm->deq_tail) return 0.0; // No fresh data
|
||||
return sm->speeds[sm->deq[idx]];
|
||||
}
|
||||
|
||||
int collected_amount(){
|
||||
return NAMOUNT_OF_DATA + Nadditional;
|
||||
}
|
||||
|
||||
int get_collected(val_t *val, int N){
|
||||
if(!val || N < 0 || N >= NAMOUNT_OF_DATA + Nadditional){
|
||||
DBG("Wrong number (%d) requested or no place for data", N);
|
||||
return FALSE;
|
||||
}
|
||||
pthread_mutex_lock(&datamutex);
|
||||
val_t *dptr = (N < NAMOUNT_OF_DATA) ? &collected_data[N] : &additional_data[N-NAMOUNT_OF_DATA];
|
||||
#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);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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->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;
|
||||
if(collected->type == fresh->type){ // good case
|
||||
memcpy(&collected->value, &fresh->value, sizeof(num_t));
|
||||
//DBG("Types are the same");
|
||||
return;
|
||||
}
|
||||
// bad case: have different types
|
||||
// DON'T convert between string and number types!
|
||||
switch(collected->type){
|
||||
case VALT_UINT:
|
||||
switch(fresh->type){
|
||||
case VALT_INT:
|
||||
collected->value.u = (uint32_t) fresh->value.i;
|
||||
//DBG("i->u");
|
||||
break;
|
||||
case VALT_FLOAT:
|
||||
collected->value.u = (uint32_t) fresh->value.f;
|
||||
//DBG("f->u");
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case VALT_INT:
|
||||
switch(fresh->type){
|
||||
case VALT_UINT:
|
||||
collected->value.i = (int32_t) fresh->value.u;
|
||||
//DBG("u->i");
|
||||
break;
|
||||
case VALT_FLOAT:
|
||||
collected->value.i = (int32_t) fresh->value.f;
|
||||
//DBG("f->i");
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
case VALT_FLOAT:
|
||||
switch(fresh->type){
|
||||
case VALT_UINT:
|
||||
collected->value.f = (float) fresh->value.u;
|
||||
//DBG("u->f");
|
||||
break;
|
||||
case VALT_INT:
|
||||
collected->value.f = (float) fresh->value.i;
|
||||
//DBG("i->f");
|
||||
default: break;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// find value.name in `additional_data`, if need - allocate new memory and update
|
||||
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
|
||||
curvalue = -curvalue;
|
||||
good = -good;
|
||||
bad = -bad;
|
||||
terrible = -terrible;
|
||||
prohibited = -prohibited;
|
||||
}
|
||||
int newlevel = -1;
|
||||
if(haveproh && curvalue > prohibited){
|
||||
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;
|
||||
if(newlevel == -1) return 0;
|
||||
time_t curt = time(NULL);
|
||||
if(curcond->shtdnflag && newlevel >= WEATHER_TERRIBLE){
|
||||
DBG("Forced shutdown flag is set, curvalue: %g", (curcond->negflag) ? -curvalue : curvalue);
|
||||
// set to one collected data flag and its time
|
||||
val_t *f = &collected_data[NFORCEDSHTDN];
|
||||
f->value.u = 1;
|
||||
f->time = (int) curt;
|
||||
DBG("forced = %u", collected_data[NFORCEDSHTDN].value.u);
|
||||
// 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){
|
||||
//FNAME();
|
||||
//static time_t poll_time = 0;
|
||||
static char reason[KEY_LEN+1] = {0}; // reason of weather level increasing
|
||||
val_t value;
|
||||
if(!s || !s->get_value) return;
|
||||
//if(poll_time == 0) poll_time = get_pollT();
|
||||
static uint32_t curlevel = 0; // this is worse weather leavel, start from best (collect by all sensors through 3*tpoll)
|
||||
static time_t lasttupdate = 0; // last update time of weather level
|
||||
time_t curtime = time(NULL);
|
||||
time_t tpoll = get_pollT(), _3tpoll = 3*tpoll;
|
||||
double dir = -100., dir2 = -100.; // mean wind directions
|
||||
//DBG("%d meteo values", s->Nvalues);
|
||||
for(int i = 0; i < s->Nvalues; ++i){
|
||||
//DBG("\nTry to get %dth value", i);
|
||||
if(!s->get_value(s, &value, i) || value.sense > VAL_RECOMMENDED) continue;
|
||||
//DBG("got value");
|
||||
int idx = -1;
|
||||
double curvalue = val2d(&value);
|
||||
const weather_cond_t *curcond = NULL;
|
||||
switch(value.meaning){
|
||||
case IS_WIND:
|
||||
idx = NWIND;
|
||||
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);
|
||||
pthread_mutex_unlock(&datamutex);
|
||||
break;
|
||||
case IS_WINDDIR:
|
||||
idx = NWINDDIR;
|
||||
pthread_mutex_lock(&datamutex);
|
||||
wind_dir_add(collected_data[NWIND].value.f, value.value.f, &dir, &dir2);
|
||||
pthread_mutex_unlock(&datamutex);
|
||||
break;
|
||||
case IS_HUMIDITY:
|
||||
idx = NHUMIDITY;
|
||||
curcond = &WeatherConf.humidity;
|
||||
break;
|
||||
case IS_AMB_TEMP:
|
||||
idx = NAMB_TEMP;
|
||||
break;
|
||||
case IS_PRESSURE:
|
||||
idx = NPRESSURE;
|
||||
break;
|
||||
case IS_PRECIP:
|
||||
idx = NPRECIP;
|
||||
curcond = &prohibweathflag;
|
||||
if(curvalue > 0.) DBG("IS_PRECIP == 1 !!!");
|
||||
break;
|
||||
case IS_PRECIP_LEVEL:
|
||||
idx = NPRECIP_LEVEL;
|
||||
curcond = &terrweathflag;
|
||||
break;
|
||||
case IS_MIST:
|
||||
idx = NMIST;
|
||||
curcond = &terrweathflag;
|
||||
break;
|
||||
case IS_CLOUDS:
|
||||
idx = NCLOUDS;
|
||||
curcond = &WeatherConf.clouds;
|
||||
break;
|
||||
case IS_SKYTEMP:
|
||||
idx = NSKYTEMP;
|
||||
curcond = &WeatherConf.sky;
|
||||
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;
|
||||
}
|
||||
if(value.meaning == IS_OTHER){ // check for new or existant field in `additional_data`
|
||||
update_additional(&value);
|
||||
continue;
|
||||
}
|
||||
if(idx < 0 || idx >= NAMOUNT_OF_DATA) continue;
|
||||
//DBG("IDX=%d", idx);
|
||||
pthread_mutex_lock(&datamutex);
|
||||
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_lock(&datamutex);
|
||||
// refresh max
|
||||
if(dir >= 0.){
|
||||
collected_data[NWINDDIR1].value.f = (float) dir;
|
||||
collected_data[NWINDDIR1].time = curtime;
|
||||
}
|
||||
if(dir2 >= 0.){
|
||||
collected_data[NWINDDIR2].value.f = (float) dir2;
|
||||
collected_data[NWINDDIR2].time = curtime;
|
||||
}
|
||||
if(curtime - collected_data[NWIND].time < tpoll + 1){
|
||||
collected_data[NWINDMAX].value.f = (float) get_current_max(&windspeeds);
|
||||
collected_data[NWINDMAX].time = curtime;
|
||||
collected_data[NWINDMAX1].value.f = (float) get_max_forT(&windspeeds, curtime - T_ONE_HOUR);
|
||||
collected_data[NWINDMAX1].time = curtime;
|
||||
}
|
||||
//DBG("check ahtung");
|
||||
time_t _2update = lasttupdate + _3tpoll;
|
||||
if(Forbidden){
|
||||
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);
|
||||
//DBG("Refreshed");
|
||||
}
|
||||
|
||||
// set/clear `forbid` flag (by signals USR1 and USR2)
|
||||
void forbid_observations(int f){
|
||||
pthread_mutex_lock(&datamutex);
|
||||
if(f) Forbidden = 1;
|
||||
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);
|
||||
}
|
||||
|
||||
// `forbid` flag getter
|
||||
int is_forbidden(){ return Forbidden; }
|
||||
69
Daemons/weatherdaemon_multimeteo/mainweather.h
Normal file
69
Daemons/weatherdaemon_multimeteo/mainweather.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
#include "weathlib.h"
|
||||
|
||||
// weather conditions
|
||||
enum{
|
||||
WEATHER_GOOD, // good to start observations
|
||||
WEATHER_BAD, // bad for start, but can run
|
||||
WEATHER_TERRIBLE, // need close the dome
|
||||
WEATHER_PROHIBITED, // need close all, park and power off equipment <-- by SIGUSR1/SIGUSR2 + by FORCEOFF
|
||||
};
|
||||
|
||||
typedef struct{
|
||||
double good; // if value less than this, weather is good
|
||||
double bad; // if value greater than this, weather is bad
|
||||
double terrible; // if value greater than this, weather is terrible
|
||||
double prohibited; // ...
|
||||
int negflag; // reversal flag (good if > val, etc)
|
||||
int shtdnflag; // ==1 to shut down if `terrible`
|
||||
} weather_cond_t;
|
||||
|
||||
typedef struct{
|
||||
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
|
||||
weather_cond_t wind;
|
||||
// humidity, %%
|
||||
weather_cond_t humidity;
|
||||
// "clouds", > 2500 - OK -> should be negated when check!!!
|
||||
weather_cond_t clouds;
|
||||
// sky temperature minus ambient temperature, degC
|
||||
weather_cond_t sky;
|
||||
// distance to lightning
|
||||
weather_cond_t ligtdist;
|
||||
} weather_conf_t;
|
||||
|
||||
// defined in cmdlnopts.c
|
||||
extern weather_conf_t WeatherConf;
|
||||
|
||||
int collected_amount();
|
||||
int get_collected(val_t *val, int N);
|
||||
|
||||
void forbid_observations(int f);
|
||||
int is_forbidden();
|
||||
|
||||
void refresh_sensval(sensordata_t *s);
|
||||
|
||||
int force_off(int flag);
|
||||
int weather_level(int new);
|
||||
|
||||
//void run_mainweather();
|
||||
@@ -21,4 +21,46 @@ if(FDEXAMPLE)
|
||||
list(APPEND LIBS fdex)
|
||||
endif()
|
||||
|
||||
if(HYDREON)
|
||||
add_library(hydreon SHARED hydreon.c)
|
||||
list(APPEND LIBS hydreon)
|
||||
endif()
|
||||
|
||||
if(BTAMETEO)
|
||||
add_library(btameteo SHARED btameteo.c bta_shdata.c)
|
||||
target_link_libraries(btameteo -lcrypt)
|
||||
list(APPEND LIBS btameteo)
|
||||
endif()
|
||||
|
||||
if(REINHARDT)
|
||||
add_library(reinhardt SHARED reinhardt.c)
|
||||
list(APPEND LIBS reinhardt)
|
||||
endif()
|
||||
|
||||
if(WXA100)
|
||||
add_library(wxa100 SHARED wxa100.c)
|
||||
list(APPEND LIBS wxa100)
|
||||
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})
|
||||
|
||||
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."
|
||||
|
351
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.c
Normal file
351
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.c
Normal file
@@ -0,0 +1,351 @@
|
||||
// Copyright: V.S. Shergin, vsher@sao.ru
|
||||
// fixed for x86_64 E.V. Emelianov, edward.emelianoff@gmail.com
|
||||
#include "bta_shdata.h"
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include <crypt.h>
|
||||
|
||||
#pragma pack(push, 4)
|
||||
// Main command channel (level 5)
|
||||
struct CMD_Queue mcmd = {{"Mcmd"}, 0200,0,-1,0};
|
||||
// Operator command channel (level 4)
|
||||
struct CMD_Queue ocmd = {{"Ocmd"}, 0200,0,-1,0};
|
||||
// User command channel (level 2/3)
|
||||
struct CMD_Queue ucmd = {{"Ucmd"}, 0200,0,-1,0};
|
||||
|
||||
#define MSGLEN (80)
|
||||
static char msg[MSGLEN];
|
||||
#define PERR(...) do{snprintf(msg, MSGLEN, __VA_ARGS__); perror(msg);} while(0)
|
||||
|
||||
#ifndef BTA_MODULE
|
||||
volatile struct BTA_Data *sdt = NULL;
|
||||
volatile struct BTA_Local *sdtl = NULL;
|
||||
|
||||
volatile struct SHM_Block sdat = {
|
||||
{"Sdat"},
|
||||
sizeof(struct BTA_Data),
|
||||
2048,0444,
|
||||
SHM_RDONLY,
|
||||
bta_data_init,
|
||||
bta_data_check,
|
||||
bta_data_close,
|
||||
ClientSide,-1,NULL
|
||||
};
|
||||
|
||||
int snd_id = -1; // client sender ID
|
||||
int cmd_src_pid = 0; // next command source PID
|
||||
uint32_t cmd_src_ip = 0;// next command source IP
|
||||
|
||||
/**
|
||||
* Init data
|
||||
*/
|
||||
void bta_data_init() {
|
||||
sdt = (struct BTA_Data *)sdat.addr;
|
||||
sdtl = (struct BTA_Local *)(sdat.addr+sizeof(struct BTA_Data));
|
||||
if(sdat.side == ClientSide) {
|
||||
if(sdt->magic != sdat.key.code) {
|
||||
WARN("Wrong shared data (maybe server turned off)");
|
||||
}
|
||||
if(sdt->version == 0) {
|
||||
WARN("Null shared data version (maybe server turned off)");
|
||||
}
|
||||
else if(sdt->version != BTA_Data_Ver) {
|
||||
WARN("Wrong shared data version: I'am - %d, but server - %d ...",
|
||||
BTA_Data_Ver, sdt->version );
|
||||
}
|
||||
if(sdt->size != sdat.size) {
|
||||
if(sdt->size > sdat.size) {
|
||||
WARN("Wrong shared area size: I needs - %d, but server - %d ...",
|
||||
sdat.size, sdt->size );
|
||||
} else {
|
||||
WARN("Attention! Too little shared data structure!");
|
||||
WARN("I needs - %d, but server gives only %d ...",
|
||||
sdat.size, sdt->size );
|
||||
WARN("May be server's version too old!?");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* ServerSide */
|
||||
if(sdt->magic == sdat.key.code &&
|
||||
sdt->version == BTA_Data_Ver &&
|
||||
sdt->size == sdat.size)
|
||||
return;
|
||||
memset(sdat.addr, 0, sdat.maxsize);
|
||||
sdt->magic = sdat.key.code;
|
||||
sdt->version = BTA_Data_Ver;
|
||||
sdt->size = sdat.size;
|
||||
Tel_Hardware = Hard_On;
|
||||
Pos_Corr = PC_On;
|
||||
TrkOk_Mode = UseDiffVel | UseDiffAZ ;
|
||||
inp_B = 591.;
|
||||
Pressure = 595.;
|
||||
PEP_code_A = 0x002aaa;
|
||||
PEP_code_Z = 0x002aaa;
|
||||
PEP_code_P = 0x002aaa;
|
||||
PEP_code_F = 0x002aaa;
|
||||
PEP_code_D = 0x002aaa;
|
||||
DomeSEW_N = 1;
|
||||
}
|
||||
|
||||
int bta_data_check() {
|
||||
if(!sdt) return 0;
|
||||
return( (sdt->magic == sdat.key.code) && (sdt->version == BTA_Data_Ver) );
|
||||
}
|
||||
|
||||
void bta_data_close() {
|
||||
if(!sdt) return;
|
||||
if(sdat.side == ServerSide) {
|
||||
sdt->magic = 0;
|
||||
sdt->version = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate shared memory segment
|
||||
*/
|
||||
int get_shm_block(volatile struct SHM_Block *sb, int server) {
|
||||
int getsize = (server)? sb->maxsize : sb->size;
|
||||
// first try to find existing one
|
||||
sb->id = shmget(sb->key.code, getsize, sb->mode);
|
||||
if(sb->id < 0 && errno == ENOENT && server){
|
||||
// if no - try to create a new one
|
||||
int cresize = sb->maxsize;
|
||||
if(sb->size > cresize){
|
||||
WARN("Wrong shm maxsize(%d) < realsize(%d)",sb->maxsize,sb->size);
|
||||
cresize = sb->size;
|
||||
}
|
||||
sb->id = shmget(sb->key.code, cresize, IPC_CREAT|IPC_EXCL|sb->mode);
|
||||
}
|
||||
if(sb->id < 0){
|
||||
if(server)
|
||||
PERR("Can't create shared memory segment '%s'",sb->key.name);
|
||||
else
|
||||
PERR("Can't find shared segment '%s' (maybe no server process) ",sb->key.name);
|
||||
return 0;
|
||||
}
|
||||
// attach it to our memory space
|
||||
sb->addr = (unsigned char *) shmat(sb->id, NULL, sb->atflag);
|
||||
if((long)sb->addr == -1){
|
||||
PERR("Can't attach shared memory segment '%s'",sb->key.name);
|
||||
return 0;
|
||||
}
|
||||
if(server && (shmctl(sb->id, SHM_LOCK, NULL) < 0)){
|
||||
PERR("Can't prevents swapping of shared memory segment '%s'",sb->key.name);
|
||||
return 0;
|
||||
}
|
||||
DBG("Create & attach shared memory segment '%s' %dbytes", sb->key.name, sb->size);
|
||||
sb->side = server;
|
||||
if(sb->init != NULL)
|
||||
sb->init();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int close_shm_block(volatile struct SHM_Block *sb){
|
||||
int ret;
|
||||
if(sb->close != NULL)
|
||||
sb->close();
|
||||
if(sb->side == ServerSide) {
|
||||
// ret = shmctl(sb->id, SHM_UNLOCK, NULL);
|
||||
ret = shmctl(sb->id, IPC_RMID, NULL);
|
||||
}
|
||||
ret = shmdt (sb->addr);
|
||||
return(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create|Find command queue
|
||||
*/
|
||||
void get_cmd_queue(struct CMD_Queue *cq, int server){
|
||||
if (!server && cq->id >= 0) { //if already in use set current
|
||||
snd_id = cq->id;
|
||||
return;
|
||||
}
|
||||
// first try to find existing one
|
||||
cq->id = msgget(cq->key.code, cq->mode);
|
||||
// if no - try to create a new one
|
||||
if(cq->id<0 && errno == ENOENT && server)
|
||||
cq->id = msgget(cq->key.code, IPC_CREAT|IPC_EXCL|cq->mode);
|
||||
if(cq->id<0){
|
||||
if(server)
|
||||
PERR("Can't create comand queue '%s'",cq->key.name);
|
||||
else
|
||||
PERR("Can't find comand queue '%s' (maybe no server process) ",cq->key.name);
|
||||
return;
|
||||
}
|
||||
cq->side = server;
|
||||
if(server){
|
||||
char buf[120]; /* выбросить все команды из очереди */
|
||||
while(msgrcv(cq->id, (struct msgbuf *)buf, 112, 0, IPC_NOWAIT) > 0);
|
||||
}else
|
||||
snd_id = cq->id;
|
||||
cq->acckey = 0;
|
||||
}
|
||||
|
||||
#endif // BTA_MODULE
|
||||
|
||||
|
||||
int check_shm_block(volatile struct SHM_Block *sb){
|
||||
if(sb->check){
|
||||
return(sb->check());
|
||||
}
|
||||
else return(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set access key in current channel
|
||||
*/
|
||||
void set_acckey(uint32_t newkey){
|
||||
if(snd_id < 0) return;
|
||||
if(ucmd.id == snd_id) ucmd.acckey = newkey;
|
||||
else if(ocmd.id == snd_id) ocmd.acckey = newkey;
|
||||
else if(mcmd.id == snd_id) mcmd.acckey = newkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup source data for one following command if default values
|
||||
* (IP == 0 - local, PID = current) not suits
|
||||
*/
|
||||
void set_cmd_src(uint32_t ip, int pid) {
|
||||
cmd_src_pid = pid;
|
||||
cmd_src_ip = ip;
|
||||
}
|
||||
|
||||
#pragma pack(push, 4)
|
||||
/**
|
||||
* Send client commands to server
|
||||
*/
|
||||
void send_cmd(int cmd_code, char *buf, int size) {
|
||||
struct my_msgbuf mbuf;
|
||||
if(snd_id < 0) return;
|
||||
if(size > 100) size = 100;
|
||||
if(cmd_code > 0)
|
||||
mbuf.mtype = cmd_code;
|
||||
else
|
||||
return;
|
||||
if(ucmd.id == snd_id) mbuf.acckey = ucmd.acckey;
|
||||
else if(ocmd.id == snd_id) mbuf.acckey = ocmd.acckey;
|
||||
else if(mcmd.id == snd_id) mbuf.acckey = mcmd.acckey;
|
||||
|
||||
mbuf.src_pid = cmd_src_pid ? cmd_src_pid : getpid();
|
||||
mbuf.src_ip = cmd_src_ip;
|
||||
cmd_src_pid = cmd_src_ip = 0;
|
||||
|
||||
if(size > 0)
|
||||
memcpy(mbuf.mtext, buf, size);
|
||||
else {
|
||||
mbuf.mtext[0] = 0;
|
||||
size = 1;
|
||||
}
|
||||
msgsnd(snd_id, (struct msgbuf *)&mbuf, size+12, IPC_NOWAIT);
|
||||
}
|
||||
|
||||
void send_cmd_noarg(int cmd_code) {
|
||||
send_cmd(cmd_code, NULL, 0);
|
||||
}
|
||||
void send_cmd_str(int cmd_code, char *arg) {
|
||||
send_cmd(cmd_code, arg, strlen(arg)+1);
|
||||
}
|
||||
void send_cmd_i1(int cmd_code, int32_t arg1) {
|
||||
send_cmd(cmd_code, (char *)&arg1, sizeof(int32_t));
|
||||
}
|
||||
void send_cmd_i2(int cmd_code, int32_t arg1, int32_t arg2) {
|
||||
int32_t ibuf[2];
|
||||
ibuf[0] = arg1;
|
||||
ibuf[1] = arg2;
|
||||
send_cmd(cmd_code, (char *)ibuf, 2*sizeof(int32_t));
|
||||
}
|
||||
void send_cmd_i3(int cmd_code, int32_t arg1, int32_t arg2, int32_t arg3) {
|
||||
int32_t ibuf[3];
|
||||
ibuf[0] = arg1;
|
||||
ibuf[1] = arg2;
|
||||
ibuf[2] = arg3;
|
||||
send_cmd(cmd_code, (char *)ibuf, 3*sizeof(int32_t));
|
||||
}
|
||||
void send_cmd_i4(int cmd_code, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) {
|
||||
int32_t ibuf[4];
|
||||
ibuf[0] = arg1;
|
||||
ibuf[1] = arg2;
|
||||
ibuf[2] = arg3;
|
||||
ibuf[3] = arg4;
|
||||
send_cmd(cmd_code, (char *)ibuf, 4*sizeof(int32_t));
|
||||
}
|
||||
void send_cmd_d1(int32_t cmd_code, double arg1) {
|
||||
send_cmd(cmd_code, (char *)&arg1, sizeof(double));
|
||||
}
|
||||
void send_cmd_d2(int cmd_code, double arg1, double arg2) {
|
||||
double dbuf[2];
|
||||
dbuf[0] = arg1;
|
||||
dbuf[1] = arg2;
|
||||
send_cmd(cmd_code, (char *)dbuf, 2*sizeof(double));
|
||||
}
|
||||
void send_cmd_i1d1(int cmd_code, int32_t arg1, double arg2) {
|
||||
struct {
|
||||
int32_t ival;
|
||||
double dval;
|
||||
} buf;
|
||||
buf.ival = arg1;
|
||||
buf.dval = arg2;
|
||||
send_cmd(cmd_code, (char *)&buf, sizeof(buf));
|
||||
}
|
||||
void send_cmd_i2d1(int cmd_code, int32_t arg1, int32_t arg2, double arg3) {
|
||||
struct {
|
||||
int32_t ival[2];
|
||||
double dval;
|
||||
} buf;
|
||||
buf.ival[0] = arg1;
|
||||
buf.ival[1] = arg2;
|
||||
buf.dval = arg3;
|
||||
send_cmd(cmd_code, (char *)&buf, sizeof(buf));
|
||||
}
|
||||
void send_cmd_i3d1(int cmd_code, int32_t arg1, int32_t arg2, int32_t arg3, double arg4) {
|
||||
struct {
|
||||
int32_t ival[3];
|
||||
double dval;
|
||||
} buf;
|
||||
buf.ival[0] = arg1;
|
||||
buf.ival[1] = arg2;
|
||||
buf.ival[2] = arg3;
|
||||
buf.dval = arg4;
|
||||
send_cmd(cmd_code, (char *)&buf, sizeof(buf));
|
||||
}
|
||||
|
||||
void encode_lev_passwd(char *passwd, int nlev, uint32_t *keylev, uint32_t *codlev){
|
||||
char salt[4];
|
||||
char *encr;
|
||||
union {
|
||||
uint32_t ui;
|
||||
char c[4];
|
||||
} key, cod;
|
||||
sprintf(salt,"L%1d",nlev);
|
||||
encr = (char *)crypt(passwd, salt);
|
||||
cod.c[0] = encr[2];
|
||||
key.c[0] = encr[3];
|
||||
cod.c[1] = encr[4];
|
||||
key.c[1] = encr[5];
|
||||
cod.c[2] = encr[6];
|
||||
key.c[2] = encr[7];
|
||||
cod.c[3] = encr[8];
|
||||
key.c[3] = encr[9];
|
||||
*keylev = key.ui;
|
||||
*codlev = cod.ui;
|
||||
}
|
||||
|
||||
int find_lev_passwd(char *passwd, uint32_t *keylev, uint32_t *codlev){
|
||||
int nlev;
|
||||
for(nlev = 5; nlev > 0; --nlev){
|
||||
encode_lev_passwd(passwd, nlev, keylev, codlev);
|
||||
if(*codlev == code_Lev(nlev)) break;
|
||||
}
|
||||
return(nlev);
|
||||
}
|
||||
|
||||
int check_lev_passwd(char *passwd){
|
||||
uint32_t keylev,codlev;
|
||||
int nlev;
|
||||
nlev = find_lev_passwd(passwd, &keylev, &codlev);
|
||||
if(nlev > 0) set_acckey(keylev);
|
||||
return(nlev);
|
||||
}
|
||||
|
||||
#pragma pack(pop)
|
||||
850
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.h
Normal file
850
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.h
Normal file
@@ -0,0 +1,850 @@
|
||||
// Copyright: V.S. Shergin, vsher@sao.ru
|
||||
// fixed for x86_64 E.V. Emelianov, edward.emelianoff@gmail.com
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#include <sys/msg.h>
|
||||
#include <errno.h>
|
||||
|
||||
#pragma pack(push, 4)
|
||||
/*
|
||||
* Shared memory block
|
||||
*/
|
||||
struct SHM_Block {
|
||||
union {
|
||||
char name[5]; // memory segment identificator
|
||||
key_t code;
|
||||
} key;
|
||||
int32_t size; // size of memory used
|
||||
int32_t maxsize; // size when created
|
||||
int32_t mode; // access mode (rwxrwxrwx)
|
||||
int32_t atflag; // connection mode (SHM_RDONLY or 0)
|
||||
void (*init)(); // init function
|
||||
int32_t (*check)(); // test function
|
||||
void (*close)(); // deinit function
|
||||
int32_t side; // connection type: client/server
|
||||
int32_t id; // connection identificator
|
||||
uint8_t *addr; // connection address
|
||||
};
|
||||
|
||||
extern volatile struct SHM_Block sdat;
|
||||
|
||||
/*
|
||||
* Command queue descriptor
|
||||
*/
|
||||
struct CMD_Queue {
|
||||
union {
|
||||
char name[5]; // queue key
|
||||
key_t code;
|
||||
} key;
|
||||
int32_t mode; // access mode (rwxrwxrwx)
|
||||
int32_t side; // connection type (Sender/Receiver - server/client)
|
||||
int32_t id; // connection identificator
|
||||
uint32_t acckey; // access key (for transmission from client to server)
|
||||
};
|
||||
|
||||
extern struct CMD_Queue mcmd;
|
||||
extern struct CMD_Queue ocmd;
|
||||
extern struct CMD_Queue ucmd;
|
||||
|
||||
void send_cmd_noarg(int);
|
||||
void send_cmd_str(int, char *);
|
||||
void send_cmd_i1(int, int32_t);
|
||||
void send_cmd_i2(int, int32_t, int32_t);
|
||||
void send_cmd_i3(int, int32_t, int32_t, int32_t);
|
||||
void send_cmd_i4(int, int32_t, int32_t, int32_t, int32_t);
|
||||
void send_cmd_d1(int, double);
|
||||
void send_cmd_d2(int, double, double);
|
||||
void send_cmd_i1d1(int, int32_t, double);
|
||||
void send_cmd_i2d1(int, int32_t, int32_t, double);
|
||||
void send_cmd_i3d1(int, int32_t, int32_t, int32_t, double);
|
||||
|
||||
/*******************************************************************************
|
||||
* Command list *
|
||||
*******************************************************************************/
|
||||
/* name code args type */
|
||||
// Stop telescope
|
||||
#define StopTel 1
|
||||
#define StopTeleskope() send_cmd_noarg( 1 )
|
||||
// High/low speed
|
||||
#define StartHS 2
|
||||
#define StartHighSpeed() send_cmd_noarg( 2 )
|
||||
#define StartLS 3
|
||||
#define StartLowSpeed() send_cmd_noarg( 3 )
|
||||
// Timer setup (Ch7_15 or SysTimer)
|
||||
#define SetTmr 4
|
||||
#define SetTimerMode(T) send_cmd_i1 ( 4, (int)(T))
|
||||
// Simulation (modeling) mode
|
||||
#define SetModMod 5
|
||||
#define SetModelMode(M) send_cmd_i1 ( 5, (int)(M))
|
||||
// Azimuth speed code
|
||||
#define SetCodA 6
|
||||
#define SetPKN_A(iA,sA) send_cmd_i2 ( 6, (int)(iA),(int)(sA))
|
||||
// Zenith speed code
|
||||
#define SetCodZ 7
|
||||
#define SetPKN_Z(iZ) send_cmd_i1 ( 7, (int)(iZ))
|
||||
// Parangle speed code
|
||||
#define SetCodP 8
|
||||
#define SetPKN_P(iP) send_cmd_i1 ( 8, (int)(iP))
|
||||
// Set Az velocity
|
||||
#define SetVA 9
|
||||
#define SetSpeedA(vA) send_cmd_d1 ( 9, (double)(vA))
|
||||
// Set Z velocity
|
||||
#define SetVZ 10
|
||||
#define SetSpeedZ(vZ) send_cmd_d1 (10, (double)(vZ))
|
||||
// Set P velocity
|
||||
#define SetVP 11
|
||||
#define SetSpeedP(vP) send_cmd_d1 (11, (double)(vP))
|
||||
// Set new polar coordinates
|
||||
#define SetAD 12
|
||||
#define SetRADec(Alp,Del) send_cmd_d2 (12, (double)(Alp),(double)(Del))
|
||||
// Set new azimutal coordinates
|
||||
#define SetAZ 13
|
||||
#define SetAzimZ(A,Z) send_cmd_d2 (13, (double)(A),(double)(Z))
|
||||
// Goto new object by polar coords
|
||||
#define GoToAD 14
|
||||
#define GoToObject() send_cmd_noarg(14 )
|
||||
// Start steering to object by polar coords
|
||||
#define MoveToAD 15
|
||||
#define MoveToObject() send_cmd_noarg(15 )
|
||||
// Go to object by azimutal coords
|
||||
#define GoToAZ 16
|
||||
#define GoToAzimZ() send_cmd_noarg(16 )
|
||||
// Set A&Z for simulation
|
||||
#define WriteAZ 17
|
||||
#define WriteModelAZ() send_cmd_noarg(17 )
|
||||
// Set P2 mode
|
||||
#define SetModP 18
|
||||
#define SetPMode(pmod) send_cmd_i1 (18, (int)(pmod))
|
||||
// Move(+-1)/Stop(0) P2
|
||||
#define P2Move 19
|
||||
#define MoveP2(dir) send_cmd_i1 (19, (int)(dir))
|
||||
// Move(+-2,+-1)/Stop(0) focus
|
||||
#define FocMove 20
|
||||
#define MoveFocus(speed,time) send_cmd_i1d1(20,(int)(speed),(double)(time))
|
||||
// Use/don't use pointing correction system
|
||||
#define UsePCorr 21
|
||||
#define SwitchPosCorr(pc_flag) send_cmd_i1 (21, (int)(pc_flag))
|
||||
// Tracking flags
|
||||
#define SetTrkFlags 22
|
||||
#define SetTrkOkMode(trk_flags) send_cmd_i1 (22, (int)(trk_flags))
|
||||
// Set focus (0 - primary, 1 - N1, 2 - N2)
|
||||
#define SetTFoc 23
|
||||
#define SetTelFocus(N) send_cmd_i1 ( 23, (int)(N))
|
||||
// Set intrinsic move parameters by RA/Decl
|
||||
#define SetVAD 24
|
||||
#define SetVelAD(VAlp,VDel) send_cmd_d2 (24, (double)(VAlp),(double)(VDel))
|
||||
// Reverse Azimuth direction when pointing
|
||||
#define SetRevA 25
|
||||
#define SetAzRevers(amod) send_cmd_i1 (25, (int)(amod))
|
||||
// Set P2 velocity
|
||||
#define SetVP2 26
|
||||
#define SetVelP2(vP2) send_cmd_d1 (26, (double)(vP2))
|
||||
// Set pointing target
|
||||
#define SetTarg 27
|
||||
#define SetSysTarg(Targ) send_cmd_i1 (27, (int)(Targ))
|
||||
// Send message to all clients (+write into protocol)
|
||||
#define SendMsg 28
|
||||
#define SendMessage(Mesg) send_cmd_str (28, (char *)(Mesg))
|
||||
// RA/Decl user correction
|
||||
#define CorrAD 29
|
||||
#define DoADcorr(dAlp,dDel) send_cmd_d2 (29, (double)(dAlp),(double)(dDel))
|
||||
// A/Z user correction
|
||||
#define CorrAZ 30
|
||||
#define DoAZcorr(dA,dZ) send_cmd_d2 (30, (double)(dA),(double)(dZ))
|
||||
// sec A/Z user correction speed
|
||||
#define SetVCAZ 31
|
||||
#define SetVCorr(vA,vZ) send_cmd_d2 (31, (double)(vA),(double)(vZ))
|
||||
// move P2 with given velocity for a given time
|
||||
#define P2MoveTo 32
|
||||
#define MoveP2To(vP2,time) send_cmd_d2 (32, (double)(vP2),(double)(time))
|
||||
// Go to t/Decl position
|
||||
#define GoToTD 33
|
||||
#define GoToSat() send_cmd_noarg (33 )
|
||||
// Move to t/Decl
|
||||
#define MoveToTD 34
|
||||
#define MoveToSat() send_cmd_noarg (34 )
|
||||
// Empty command for synchronisation
|
||||
#define NullCom 35
|
||||
#define SyncCom() send_cmd_noarg (35 )
|
||||
// Button "Start"
|
||||
#define StartTel 36
|
||||
#define StartTeleskope() send_cmd_noarg(36 )
|
||||
// Set telescope mode
|
||||
#define SetTMod 37
|
||||
#define SetTelMode(M) send_cmd_i1 ( 37, (int)(M))
|
||||
// Turn telescope on (oil etc)
|
||||
#define TelOn 38
|
||||
#define TeleskopeOn() send_cmd_noarg(38 )
|
||||
// Dome mode
|
||||
#define SetModD 39
|
||||
#define SetDomeMode(dmod) send_cmd_i1 (39, (int)(dmod))
|
||||
// Move(+-3,+-2,+-1)/Stop(0) dome
|
||||
#define DomeMove 40
|
||||
#define MoveDome(speed,time) send_cmd_i1d1(40,(int)(speed),(double)(time))
|
||||
// Set account password
|
||||
#define SetPass 41
|
||||
#define SetPasswd(LPass) send_cmd_str (41, (char *)(LPass))
|
||||
// Set code of access level
|
||||
#define SetLevC 42
|
||||
#define SetLevCode(Nlev,Cod) send_cmd_i2(42, (int)(Nlev),(int)(Cod))
|
||||
// Set key for access level
|
||||
#define SetLevK 43
|
||||
#define SetLevKey(Nlev,Key) send_cmd_i2(43, (int)(Nlev),(int)(Key))
|
||||
// Setup network
|
||||
#define SetNet 44
|
||||
#define SetNetAcc(Mask,Addr) send_cmd_i2(44, (int)(Mask),(int)(Addr))
|
||||
// Input meteo data
|
||||
#define SetMet 45
|
||||
#define SetMeteo(m_id,m_val) send_cmd_i1d1(45,(int)(m_id),(double)(m_val))
|
||||
// Cancel meteo data
|
||||
#define TurnMetOff 46
|
||||
#define TurnMeteoOff(m_id) send_cmd_i1 (46, (int)(m_id))
|
||||
// Set time correction (IERS DUT1=UT1-UTC)
|
||||
#define SetDUT1 47
|
||||
#define SetDtime(dT) send_cmd_d1 (47, (double)(dT))
|
||||
// Set polar motion (IERS polar motion)
|
||||
#define SetPM 48
|
||||
#define SetPolMot(Xp,Yp) send_cmd_d2 (48, (double)(Xp),(double)(Yp))
|
||||
// Get SEW parameter
|
||||
#define GetSEW 49
|
||||
#define GetSEWparam(Ndrv,Indx,Cnt) send_cmd_i3(49,(int)(Ndrv),(int)(Indx),(int)(Cnt))
|
||||
// Set SEW parameter
|
||||
#define PutSEW 50
|
||||
#define PutSEWparam(Ndrv,Indx,Key,Val) send_cmd_i4(50,(int)(Ndrv),(int)(Indx),(int)(Key),(int)(Val))
|
||||
// Set lock flags
|
||||
#define SetLocks 51
|
||||
#define SetLockFlags(f) send_cmd_i1 (SetLocks, (int)(f))
|
||||
// Clear lock flags
|
||||
#define ClearLocks 52
|
||||
#define ClearLockFlags(f) send_cmd_i1 (ClearLocks, (int)(f))
|
||||
// Set PEP-RK bits
|
||||
#define SetRKbits 53
|
||||
#define AddRKbits(f) send_cmd_i1 (SetRKbits, (int)(f))
|
||||
// Clear PEP-RK bits
|
||||
#define ClrRKbits 54
|
||||
#define ClearRKbits(f) send_cmd_i1 (ClrRKbits, (int)(f))
|
||||
// Set SEW dome motor number (for indication)
|
||||
#define SetSEWnd 55
|
||||
#define SetDomeDrive(ND) send_cmd_i1 (SetSEWnd, (int)(ND))
|
||||
// Turn SEW controllers of dome on/off
|
||||
#define SEWsDome 56
|
||||
#define DomeSEW(OnOff) send_cmd_i1 (SEWsDome, (int)(OnOff))
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* BTA data structure definitions *
|
||||
*******************************************************************************/
|
||||
|
||||
#define ServPID (sdt->pid) // PID of main program
|
||||
// model
|
||||
#define UseModel (sdt->model) // model variants
|
||||
enum{
|
||||
NoModel = 0 // OFF
|
||||
,CheckModel // control motors by model
|
||||
,DriveModel // "blind" management without real sensors
|
||||
,FullModel // full model without telescope
|
||||
};
|
||||
// timer
|
||||
#define ClockType (sdt->timer) // which timer to use
|
||||
enum{
|
||||
Ch7_15 = 0 // Inner timer with synchronisation by CH7_15
|
||||
,SysTimer // System timer (synchronisation unknown)
|
||||
,ExtSynchro // External synchronisation (bta_time or xntpd)
|
||||
};
|
||||
// system
|
||||
#define Sys_Mode (sdt->system) // main system mode
|
||||
enum{
|
||||
SysStop = 0 // Stop
|
||||
,SysWait // Wait for start (pointing)
|
||||
,SysPointAZ // Pointing by A/Z
|
||||
,SysPointAD // Pointing by RA/Decl
|
||||
,SysTrkStop // Tracking stop
|
||||
,SysTrkStart // Start tracking (acceleration to nominal velocity)
|
||||
,SysTrkMove // Tracking move to object
|
||||
,SysTrkSeek // Tracking in seeking mode
|
||||
,SysTrkOk // Tracking OK
|
||||
,SysTrkCorr // Correction of tracking position
|
||||
,SysTest // Test
|
||||
};
|
||||
// sys_target
|
||||
#define Sys_Target (sdt->sys_target) // system pointing target
|
||||
enum{
|
||||
TagPosition = 0 // point by A/Z
|
||||
,TagObject // point by RA/Decl
|
||||
,TagNest // point to "nest"
|
||||
,TagZenith // point to zenith
|
||||
,TagHorizon // point to horizon
|
||||
,TagStatObj // point to statinary object (t/Decl)
|
||||
};
|
||||
// tel_focus
|
||||
#define Tel_Focus (sdt->tel_focus) // telescope focus type
|
||||
enum{
|
||||
Prime = 0
|
||||
,Nasmyth1
|
||||
,Nasmyth2
|
||||
};
|
||||
// PCS
|
||||
#define PosCor_Coeff (sdt->pc_coeff) // pointing correction system coefficients
|
||||
// tel_state
|
||||
#define Tel_State (sdt->tel_state) // telescope state
|
||||
#define Req_State (sdt->req_state) // required state
|
||||
enum{
|
||||
Stopping = 0
|
||||
,Pointing
|
||||
,Tracking
|
||||
};
|
||||
// tel_hard_state
|
||||
#define Tel_Hardware (sdt->tel_hard_state) // Power state
|
||||
enum{
|
||||
Hard_Off = 0
|
||||
,Hard_On
|
||||
};
|
||||
// tel_mode
|
||||
#define Tel_Mode (sdt->tel_mode) // telescope mode
|
||||
enum{
|
||||
Automatic = 0 // Automatic (normal) mode
|
||||
,Manual = 1 // manual mode
|
||||
,ZenHor = 2 // work when Z<5 || Z>80
|
||||
,A_Move = 4 // hand move by A
|
||||
,Z_Move = 8 // hand move by Z
|
||||
,Balance =0x10// balancing
|
||||
};
|
||||
// az_mode
|
||||
#define Az_Mode (sdt->az_mode) // azimuth reverce
|
||||
enum{
|
||||
Rev_Off = 0 // move by nearest way
|
||||
,Rev_On // move by longest way
|
||||
};
|
||||
// p2_state
|
||||
#define P2_State (sdt->p2_state) // P2 motor state
|
||||
#define P2_Mode (sdt->p2_req_mode)
|
||||
enum{
|
||||
P2_Off = 0 // Stop
|
||||
,P2_On // Guiding
|
||||
,P2_Plus // Move to +
|
||||
,P2_Minus = -2 // Move to -
|
||||
};
|
||||
// focus_state
|
||||
#define Foc_State (sdt->focus_state) // focus motor state
|
||||
enum{
|
||||
Foc_Hminus = -2// fast "-" move
|
||||
,Foc_Lminus // slow "-" move
|
||||
,Foc_Off // Off
|
||||
,Foc_Lplus // slow "+" move
|
||||
,Foc_Hplus // fast "+" move
|
||||
};
|
||||
// dome_state
|
||||
#define Dome_State (sdt->dome_state) // dome motors state
|
||||
enum{
|
||||
D_Hminus = -3 // speeds: low, medium, high
|
||||
,D_Mminus
|
||||
,D_Lminus
|
||||
,D_Off // off
|
||||
,D_Lplus
|
||||
,D_Mplus
|
||||
,D_Hplus
|
||||
,D_On = 7 // auto
|
||||
};
|
||||
// pcor_mode
|
||||
#define Pos_Corr (sdt->pcor_mode) // pointing correction mode
|
||||
enum{
|
||||
PC_Off = 0
|
||||
,PC_On
|
||||
};
|
||||
// trkok_mode
|
||||
#define TrkOk_Mode (sdt->trkok_mode) // tracking mode
|
||||
enum{
|
||||
UseDiffVel = 1 // Isodrome (correction by real motors speed)
|
||||
,UseDiffAZ = 2 // Tracking by coordinate difference
|
||||
,UseDFlt = 4 // Turn on digital filter
|
||||
};
|
||||
// input RA/Decl values
|
||||
#define InpAlpha (sdt->i_alpha)
|
||||
#define InpDelta (sdt->i_delta)
|
||||
// current source RA/Decl values
|
||||
#define SrcAlpha (sdt->s_alpha)
|
||||
#define SrcDelta (sdt->s_delta)
|
||||
// intrinsic object velocity
|
||||
#define VelAlpha (sdt->v_alpha)
|
||||
#define VelDelta (sdt->v_delta)
|
||||
// input A/Z values
|
||||
#define InpAzim (sdt->i_azim)
|
||||
#define InpZdist (sdt->i_zdist)
|
||||
// calculated values
|
||||
#define CurAlpha (sdt->c_alpha)
|
||||
#define CurDelta (sdt->c_delta)
|
||||
// current values (from sensors)
|
||||
#define tag_A (sdt->tag_a)
|
||||
#define tag_Z (sdt->tag_z)
|
||||
#define tag_P (sdt->tag_p)
|
||||
// calculated corrections
|
||||
#define pos_cor_A (sdt->pcor_a)
|
||||
#define pos_cor_Z (sdt->pcor_z)
|
||||
#define refract_Z (sdt->refr_z)
|
||||
// reverse calculation corr.
|
||||
#define tel_cor_A (sdt->tcor_a)
|
||||
#define tel_cor_Z (sdt->tcor_z)
|
||||
#define tel_ref_Z (sdt->tref_z)
|
||||
// coords difference
|
||||
#define Diff_A (sdt->diff_a)
|
||||
#define Diff_Z (sdt->diff_z)
|
||||
#define Diff_P (sdt->diff_p)
|
||||
// base object velocity
|
||||
#define vel_objA (sdt->vbasea)
|
||||
#define vel_objZ (sdt->vbasez)
|
||||
#define vel_objP (sdt->vbasep)
|
||||
// correction by real speed
|
||||
#define diff_vA (sdt->diffva)
|
||||
#define diff_vZ (sdt->diffvz)
|
||||
#define diff_vP (sdt->diffvp)
|
||||
// motor speed
|
||||
#define speedA (sdt->speeda)
|
||||
#define speedZ (sdt->speedz)
|
||||
#define speedP (sdt->speedp)
|
||||
// last precipitation time
|
||||
#define Precip_time (sdt->m_time_precip)
|
||||
// reserved
|
||||
#define Reserve (sdt->reserve)
|
||||
// real motor speed (''/sec)
|
||||
#define req_speedA (sdt->rspeeda)
|
||||
#define req_speedZ (sdt->rspeedz)
|
||||
#define req_speedP (sdt->rspeedp)
|
||||
// model speed
|
||||
#define mod_vel_A (sdt->simvela)
|
||||
#define mod_vel_Z (sdt->simvelz)
|
||||
#define mod_vel_P (sdt->simvelp)
|
||||
#define mod_vel_F (sdt->simvelf)
|
||||
#define mod_vel_D (sdt->simvelf)
|
||||
// telescope & hand correction state
|
||||
/*
|
||||
* 0x8000 - ÁÚÉÍÕÔ ÐÏÌÏÖÉÔÅÌØÎÙÊ
|
||||
* 0x4000 - ÏÔÒÁÂÏÔËÁ ×ËÌ.
|
||||
* 0x2000 - ÒÅÖÉÍ ×ÅÄÅÎÉÑ
|
||||
* 0x1000 - ÏÔÒÁÂÏÔËÁ P2 ×ËÌ.
|
||||
* 0x01F0 - ÓË.ËÏÒÒ. 0.2 0.4 1.0 2.0 5.0("/ÓÅË)
|
||||
* 0x000F - ÎÁÐÒ.ËÏÒÒ. +Z -Z +A -A
|
||||
*/
|
||||
#define code_KOST (sdt->kost)
|
||||
// different time (UTC, stellar, local)
|
||||
#define M_time (sdt->m_time)
|
||||
#define S_time (sdt->s_time)
|
||||
#define L_time (sdt->l_time)
|
||||
// PPNDD sensor (rough) code
|
||||
#define ppndd_A (sdt->ppndd_a)
|
||||
#define ppndd_Z (sdt->ppndd_z)
|
||||
#define ppndd_P (sdt->ppndd_p)
|
||||
#define ppndd_B (sdt->ppndd_b) // atm. pressure
|
||||
// DUP sensor (precise) code (Gray code)
|
||||
#define dup_A (sdt->dup_a)
|
||||
#define dup_Z (sdt->dup_z)
|
||||
#define dup_P (sdt->dup_p)
|
||||
#define dup_F (sdt->dup_f)
|
||||
#define dup_D (sdt->dup_d)
|
||||
// binary 14-digit precise code
|
||||
#define low_A (sdt->low_a)
|
||||
#define low_Z (sdt->low_z)
|
||||
#define low_P (sdt->low_p)
|
||||
#define low_F (sdt->low_f)
|
||||
#define low_D (sdt->low_d)
|
||||
// binary 23-digit rough code
|
||||
#define code_A (sdt->code_a)
|
||||
#define code_Z (sdt->code_z)
|
||||
#define code_P (sdt->code_p)
|
||||
#define code_B (sdt->code_b)
|
||||
#define code_F (sdt->code_f)
|
||||
#define code_D (sdt->code_d)
|
||||
// ADC PCL818 (8-channel) codes
|
||||
#define ADC(N) (sdt->adc[(N)])
|
||||
#define code_T1 ADC(0) // External temperature code
|
||||
#define code_T2 ADC(1) // In-dome temperature code
|
||||
#define code_T3 ADC(2) // Mirror temperature code
|
||||
#define code_Wnd ADC(3) // Wind speed code
|
||||
// calculated values
|
||||
#define val_A (sdt->val_a) // A, ''
|
||||
#define val_Z (sdt->val_z) // Z, ''
|
||||
#define val_P (sdt->val_p) // P, ''
|
||||
#define val_B (sdt->val_b) // atm. pressure, mm.hg.
|
||||
#define val_F (sdt->val_f) // focus, mm
|
||||
#define val_D (sdt->val_d) // Dome Az, ''
|
||||
#define val_T1 (sdt->val_t1) // ext. T, degrC
|
||||
#define val_T2 (sdt->val_t2) // in-dome T, degrC
|
||||
#define val_T3 (sdt->val_t3) // mirror T, degrC
|
||||
#define val_Wnd (sdt->val_wnd) // wind speed, m/s
|
||||
// RA/Decl calculated by A/Z
|
||||
#define val_Alp (sdt->val_alp)
|
||||
#define val_Del (sdt->val_del)
|
||||
// measured speed
|
||||
#define vel_A (sdt->vel_a)
|
||||
#define vel_Z (sdt->vel_z)
|
||||
#define vel_P (sdt->vel_p)
|
||||
#define vel_F (sdt->vel_f)
|
||||
#define vel_D (sdt->vel_d)
|
||||
// system messages queue
|
||||
#define MesgNum 3
|
||||
#define MesgLen 39
|
||||
// message type
|
||||
enum{
|
||||
MesgEmpty = 0
|
||||
,MesgInfor
|
||||
,MesgWarn
|
||||
,MesgFault
|
||||
,MesgLog
|
||||
};
|
||||
#define Sys_Mesg(N) (sdt->sys_msg_buf[N])
|
||||
// access levels
|
||||
#define code_Lev1 (sdt->code_lev[0]) // remote observer - only information
|
||||
#define code_Lev2 (sdt->code_lev[1]) // local observer - input coordinates
|
||||
#define code_Lev3 (sdt->code_lev[2]) // main observer - correction by A/Z, P2/F management
|
||||
#define code_Lev4 (sdt->code_lev[3]) // operator - start/stop telescope, testing
|
||||
#define code_Lev5 (sdt->code_lev[4]) // main operator - full access
|
||||
#define code_Lev(x) (sdt->code_lev[(x-1)])
|
||||
// network settings
|
||||
#define NetMask (sdt->netmask) // subnet mask (usually 255.255.255.0)
|
||||
#define NetWork (sdt->netaddr) // subnet address (for ex.: 192.168.3.0)
|
||||
#define ACSMask (sdt->acsmask) // ACS network mask (for ex.: 255.255.255.0)
|
||||
#define ACSNet (sdt->acsaddr) // ACS subnet address (for ex.: 192.168.13.0)
|
||||
// meteo data
|
||||
#define MeteoMode (sdt->meteo_stat)
|
||||
enum{
|
||||
INPUT_B = 1 // pressure
|
||||
,INPUT_T1 = 2 // external T
|
||||
,INPUT_T2 = 4 // in-dome T
|
||||
,INPUT_T3 = 8 // mirror T
|
||||
,INPUT_WND = 0x10 // wind speed
|
||||
,INPUT_HMD = 0x20 // humidity
|
||||
};
|
||||
#define SENSOR_B (INPUT_B <<8) // external data flags
|
||||
#define SENSOR_T1 (INPUT_T1 <<8)
|
||||
#define SENSOR_T2 (INPUT_T2 <<8)
|
||||
#define SENSOR_T3 (INPUT_T3 <<8)
|
||||
#define SENSOR_WND (INPUT_WND<<8)
|
||||
#define SENSOR_HMD (INPUT_HMD<<8)
|
||||
#define ADC_B (INPUT_B <<16) // reading from ADC flags
|
||||
#define ADC_T1 (INPUT_T1 <<16)
|
||||
#define ADC_T2 (INPUT_T2 <<16)
|
||||
#define ADC_T3 (INPUT_T3 <<16)
|
||||
#define ADC_WND (INPUT_WND<<16)
|
||||
#define ADC_HMD (INPUT_HMD<<16)
|
||||
#define NET_B (INPUT_B <<24) // got by network flags
|
||||
#define NET_T1 (INPUT_T1 <<24)
|
||||
#define NET_T3 (INPUT_T3 <<24)
|
||||
#define NET_WND (INPUT_WND<<24)
|
||||
#define NET_HMD (INPUT_HMD<<24)
|
||||
// input meteo values
|
||||
#define inp_B (sdt->inp_b) // atm.pressure (mm.hg)
|
||||
#define inp_T1 (sdt->inp_t1) // ext T
|
||||
#define inp_T2 (sdt->inp_t2) // in-dome T
|
||||
#define inp_T3 (sdt->inp_t3) // mirror T
|
||||
#define inp_Wnd (sdt->inp_wnd) // wind
|
||||
// values used for refraction calculation
|
||||
#define Temper (sdt->temper)
|
||||
#define Pressure (sdt->press)
|
||||
// last wind gust time
|
||||
#define Wnd10_time (sdt->m_time10)
|
||||
#define Wnd15_time (sdt->m_time15)
|
||||
// IERS DUT1
|
||||
#define DUT1 (sdt->dut1)
|
||||
// sensors reading time
|
||||
#define A_time (sdt->a_time)
|
||||
#define Z_time (sdt->z_time)
|
||||
#define P_time (sdt->p_time)
|
||||
// input speeds
|
||||
#define speedAin (sdt->speedain)
|
||||
#define speedZin (sdt->speedzin)
|
||||
#define speedPin (sdt->speedpin)
|
||||
// acceleration (''/sec^2)
|
||||
#define acc_A (sdt->acc_a)
|
||||
#define acc_Z (sdt->acc_z)
|
||||
#define acc_P (sdt->acc_p)
|
||||
#define acc_F (sdt->acc_f)
|
||||
#define acc_D (sdt->acc_d)
|
||||
// SEW code
|
||||
#define code_SEW (sdt->code_sew)
|
||||
// sew data
|
||||
#define statusSEW(Drv) (sdt->sewdrv[(Drv)-1].status)
|
||||
#define statusSEW1 (sdt->sewdrv[0].status)
|
||||
#define statusSEW2 (sdt->sewdrv[1].status)
|
||||
#define statusSEW3 (sdt->sewdrv[2].status)
|
||||
#define speedSEW(Drv) (sdt->sewdrv[(Drv)-1].set_speed)
|
||||
#define speedSEW1 (sdt->sewdrv[0].set_speed)
|
||||
#define speedSEW2 (sdt->sewdrv[1].set_speed)
|
||||
#define speedSEW3 (sdt->sewdrv[2].set_speed)
|
||||
#define vel_SEW(Drv) (sdt->sewdrv[(Drv)-1].mes_speed)
|
||||
#define vel_SEW1 (sdt->sewdrv[0].mes_speed)
|
||||
#define vel_SEW2 (sdt->sewdrv[1].mes_speed)
|
||||
#define vel_SEW3 (sdt->sewdrv[2].mes_speed)
|
||||
#define currentSEW(Drv) (sdt->sewdrv[(Drv)-1].current)
|
||||
#define currentSEW1 (sdt->sewdrv[0].current)
|
||||
#define currentSEW2 (sdt->sewdrv[1].current)
|
||||
#define currentSEW3 (sdt->sewdrv[2].current)
|
||||
#define indexSEW(Drv) (sdt->sewdrv[(Drv)-1].index)
|
||||
#define indexSEW1 (sdt->sewdrv[0].index)
|
||||
#define indexSEW2 (sdt->sewdrv[1].index)
|
||||
#define indexSEW3 (sdt->sewdrv[2].index)
|
||||
#define valueSEW(Drv) (sdt->sewdrv[(Drv)-1].value.l)
|
||||
#define valueSEW1 (sdt->sewdrv[0].value.l)
|
||||
#define valueSEW2 (sdt->sewdrv[1].value.l)
|
||||
#define valueSEW3 (sdt->sewdrv[2].value.l)
|
||||
#define bvalSEW(Drv,Nb) (sdt->sewdrv[(Drv)-1].value.b[Nb])
|
||||
// 23-digit PEP-controllers code
|
||||
#define PEP_code_A (sdt->pep_code_a)
|
||||
#define PEP_code_Z (sdt->pep_code_z)
|
||||
#define PEP_code_P (sdt->pep_code_p)
|
||||
// PEP end-switches code
|
||||
#define switch_A (sdt->pep_sw_a)
|
||||
enum{
|
||||
Sw_minus_A = 1 // negative A value
|
||||
,Sw_plus240_A = 2 // end switch +240degr
|
||||
,Sw_minus240_A = 4 // end switch -240degr
|
||||
,Sw_minus45_A = 8 // "horizon" end switch
|
||||
};
|
||||
#define switch_Z (sdt->pep_sw_z)
|
||||
enum{
|
||||
Sw_0_Z = 1
|
||||
,Sw_5_Z = 2
|
||||
,Sw_20_Z = 4
|
||||
,Sw_60_Z = 8
|
||||
,Sw_80_Z = 0x10
|
||||
,Sw_90_Z = 0x20
|
||||
};
|
||||
#define switch_P (sdt->pep_sw_p)
|
||||
enum{
|
||||
Sw_No_P = 0 // no switches
|
||||
,Sw_22_P = 1 // 22degr
|
||||
,Sw_89_P = 2 // 89degr
|
||||
,Sw_Sm_P = 0x80 // Primary focus smoke sensor
|
||||
};
|
||||
// PEP codes
|
||||
#define PEP_code_F (sdt->pep_code_f)
|
||||
#define PEP_code_D (sdt->pep_code_d)
|
||||
#define PEP_code_Rin (sdt->pep_code_ri)
|
||||
#define PEP_code_Rout (sdt->pep_code_ro)
|
||||
// PEP flags
|
||||
#define PEP_A_On (sdt->pep_on[0])
|
||||
#define PEP_A_Off (PEP_A_On==0)
|
||||
#define PEP_Z_On (sdt->pep_on[1])
|
||||
#define PEP_Z_Off (PEP_Z_On==0)
|
||||
#define PEP_P_On (sdt->pep_on[2])
|
||||
#define PEP_P_Off (PEP_P_On==0)
|
||||
#define PEP_F_On (sdt->pep_on[3])
|
||||
#define PEP_F_Off (PEP_F_On==0)
|
||||
#define PEP_D_On (sdt->pep_on[4])
|
||||
#define PEP_D_Off (PEP_D_On==0)
|
||||
#define PEP_R_On (sdt->pep_on[5])
|
||||
#define PEP_R_Off ((PEP_R_On&1)==0)
|
||||
#define PEP_R_Inp ((PEP_R_On&2)!=0)
|
||||
#define PEP_K_On (sdt->pep_on[6])
|
||||
#define PEP_K_Off ((PEP_K_On&1)==0)
|
||||
#define PEP_K_Inp ((PEP_K_On&2)!=0)
|
||||
// IERS polar motion
|
||||
#define polarX (sdt->xpol)
|
||||
#define polarY (sdt->ypol)
|
||||
// current Julian date, sidereal time correction by "Equation of the Equinoxes"
|
||||
#define JDate (sdt->jdate)
|
||||
#define EE_time (sdt->eetime)
|
||||
// humidity value (%%) & hand input
|
||||
#define val_Hmd (sdt->val_hmd)
|
||||
#define inp_Hmd (sdt->val_hmd)
|
||||
// worm position, mkm
|
||||
#define worm_A (sdt->worm_a)
|
||||
#define worm_Z (sdt->worm_z)
|
||||
// locking flags
|
||||
#define LockFlags (sdt->lock_flags)
|
||||
enum{
|
||||
Lock_A = 1
|
||||
,Lock_Z = 2
|
||||
,Lock_P = 4
|
||||
,Lock_F = 8
|
||||
,Lock_D = 0x10
|
||||
};
|
||||
#define A_Locked (LockFlags&Lock_A)
|
||||
#define Z_Locked (LockFlags&Lock_Z)
|
||||
#define P_Locked (LockFlags&Lock_P)
|
||||
#define F_Locked (LockFlags&Lock_F)
|
||||
#define D_Locked (LockFlags&Lock_D)
|
||||
// SEW dome divers speed
|
||||
#define Dome_Speed (sdt->sew_dome_speed)
|
||||
// SEW dome drive number (for indication)
|
||||
#define DomeSEW_N (sdt->sew_dome_num)
|
||||
// SEW dome driver parameters
|
||||
#define statusSEWD (sdt->sewdomedrv.status) // controller status
|
||||
#define speedSEWD (sdt->sewdomedrv.set_speed) // speed, rpm
|
||||
#define vel_SEWD (sdt->sewdomedrv.mes_speed) /*ÉÚÍÅÒÅÎÎÁÑ ÓËÏÒÏÓÔØ ÏÂ/ÍÉÎ (rpm)*/
|
||||
#define currentSEWD (sdt->sewdomedrv.current) // current, A
|
||||
#define indexSEWD (sdt->sewdomedrv.index) // parameter index
|
||||
#define valueSEWD (sdt->sewdomedrv.value.l) // parameter value
|
||||
// dome PEP codes
|
||||
#define PEP_code_Din (sdt->pep_code_di) // data in
|
||||
#define PEP_Dome_SEW_Ok 0x200
|
||||
#define PEP_Dome_Cable_Ok 0x100
|
||||
#define PEP_code_Dout (sdt->pep_code_do) // data out
|
||||
#define PEP_Dome_SEW_On 0x10
|
||||
#define PEP_Dome_SEW_Off 0x20
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* BTA data structure *
|
||||
*******************************************************************************/
|
||||
|
||||
#define BTA_Data_Ver 2
|
||||
struct BTA_Data {
|
||||
int32_t magic; // magic value
|
||||
int32_t version; // BTA_Data_Ver
|
||||
int32_t size; // sizeof(struct BTA_Data)
|
||||
int32_t pid; // main process PID
|
||||
int32_t model; // model modes
|
||||
int32_t timer; // timer selected
|
||||
int32_t system; // main system mode
|
||||
int32_t sys_target; // system pointing target
|
||||
int32_t tel_focus; // telescope focus type
|
||||
double pc_coeff[8]; // pointing correction system coefficients
|
||||
int32_t tel_state; // telescope state
|
||||
int32_t req_state; // new (required) state
|
||||
int32_t tel_hard_state; // Power state
|
||||
int32_t tel_mode; // telescope mode
|
||||
int32_t az_mode; // azimuth reverce
|
||||
int32_t p2_state; // P2 motor state
|
||||
int32_t p2_req_mode; // P2 required state
|
||||
int32_t focus_state; // focus motor state
|
||||
int32_t dome_state; // dome motors state
|
||||
int32_t pcor_mode; // pointing correction mode
|
||||
int32_t trkok_mode; // tracking mode
|
||||
double i_alpha, i_delta; // input values
|
||||
double s_alpha, s_delta; // source
|
||||
double v_alpha, v_delta; // intrinsic vel.
|
||||
double i_azim, i_zdist; // input A/Z
|
||||
double c_alpha, c_delta; // calculated values
|
||||
double tag_a, tag_z, tag_p; // current values (from sensors)
|
||||
double pcor_a, pcor_z, refr_z; // calculated corrections
|
||||
double tcor_a, tcor_z, tref_z; // reverse calculation corr.
|
||||
double diff_a, diff_z, diff_p; // coords difference
|
||||
double vbasea,vbasez,vbasep; // base object velocity
|
||||
double diffva,diffvz,diffvp; // correction by real speed
|
||||
double speeda,speedz,speedp; // motor speed
|
||||
double m_time_precip; // last precipitation time
|
||||
uint8_t reserve[16]; // reserved
|
||||
double rspeeda, rspeedz, rspeedp; // real motor speed (''/sec)
|
||||
double simvela, simvelz, simvelp, simvelf, simveld; // model speed
|
||||
uint32_t kost; // telescope & hand correction state
|
||||
double m_time, s_time, l_time; // different time (UTC, stellar, local)
|
||||
uint32_t ppndd_a, ppndd_z, ppndd_p, ppndd_b; // PPNDD sensor (rough) code
|
||||
uint32_t dup_a, dup_z, dup_p, dup_f, dup_d; // DUP sensor (precise) code (Gray code)
|
||||
uint32_t low_a, low_z, low_p, low_f, low_d; // binary 14-digit precise code
|
||||
uint32_t code_a, code_z, code_p, code_b, code_f, code_d; // binary 23-digit rough code
|
||||
uint32_t adc[8]; // ADC PCL818 (8-channel) codes
|
||||
double val_a, val_z, val_p, val_b, val_f, val_d;
|
||||
double val_t1, val_t2, val_t3, val_wnd; // calculated values
|
||||
double val_alp, val_del; // RA/Decl calculated by A/Z
|
||||
double vel_a, vel_z, vel_p, vel_f, vel_d; // measured speed
|
||||
// system messages queue
|
||||
struct SysMesg {
|
||||
int32_t seq_num;
|
||||
char type; // message type
|
||||
char text[MesgLen]; // message itself
|
||||
} sys_msg_buf[MesgNum];
|
||||
// access levels
|
||||
uint32_t code_lev[5];
|
||||
// network settings
|
||||
uint32_t netmask, netaddr, acsmask, acsaddr;
|
||||
int32_t meteo_stat; // meteo data
|
||||
double inp_b, inp_t1, inp_t2, inp_t3, inp_wnd; // input meteo values
|
||||
double temper, press; // values used for refraction calculation
|
||||
double m_time10, m_time15; // last wind gust time
|
||||
double dut1; // IERS DUT1 (src: ftp://maia.usno.navy.mil/ser7/ser7.dat), DUT1 = UT1-UTC
|
||||
double a_time, z_time, p_time; // sensors reading time
|
||||
double speedain, speedzin, speedpin; // input speeds
|
||||
double acc_a, acc_z, acc_p, acc_f, acc_d; // acceleration (''/sec^2)
|
||||
uint32_t code_sew; // SEW code
|
||||
struct SEWdata { // sew data
|
||||
int32_t status;
|
||||
double set_speed; // target speed, rpm
|
||||
double mes_speed; // measured speed, rpm
|
||||
double current; // measured current, A
|
||||
int32_t index; // parameter number
|
||||
union{ // parameter code
|
||||
uint8_t b[4];
|
||||
uint32_t l;
|
||||
} value;
|
||||
} sewdrv[3];
|
||||
uint32_t pep_code_a, pep_code_z, pep_code_p; // 23-digit PEP-controllers code
|
||||
uint32_t pep_sw_a, pep_sw_z, pep_sw_p; // PEP end-switches code
|
||||
uint32_t pep_code_f, pep_code_d, pep_code_ri, pep_code_ro; // PEP codes
|
||||
uint8_t pep_on[10]; // PEP flags
|
||||
double xpol, ypol; // IERS polar motion (src: ftp://maia.usno.navy.mil/ser7/ser7.dat)
|
||||
double jdate, eetime; // current Julian date, sidereal time correction by "Equation of the Equinoxes"
|
||||
double val_hmd, inp_hmd; // humidity value (%%) & hand input
|
||||
double worm_a, worm_z; // worm position, mkm
|
||||
/* ÆÌÁÇÉ ÂÌÏËÉÒÏ×ËÉ ÕÐÒÁ×ÌÅÎÉÑ ÕÚÌÁÍÉ */
|
||||
uint32_t lock_flags; // locking flags
|
||||
int32_t sew_dome_speed; // SEW dome divers speed: D_Lplus, D_Hminus etc
|
||||
int32_t sew_dome_num; // SEW dome drive number (for indication)
|
||||
struct SEWdata sewdomedrv; // SEW dome driver parameters
|
||||
uint32_t pep_code_di, pep_code_do; // dome PEP codes
|
||||
};
|
||||
|
||||
extern volatile struct BTA_Data *sdt;
|
||||
|
||||
/*******************************************************************************
|
||||
* Local data structure *
|
||||
*******************************************************************************/
|
||||
// Oil pressure, MPa
|
||||
#define PressOilA (sdtl->pr_oil_a)
|
||||
#define PressOilZ (sdtl->pr_oil_z)
|
||||
#define PressOilTank (sdtl->pr_oil_t)
|
||||
// Oil themperature, degrC
|
||||
#define OilTemper1 (sdtl->t_oil_1) // oil
|
||||
#define OilTemper2 (sdtl->t_oil_2) // water
|
||||
|
||||
// Local data structure
|
||||
struct BTA_Local {
|
||||
uint8_t reserve[120]; // reserved data
|
||||
double pr_oil_a,pr_oil_z,pr_oil_t; // Oil pressure
|
||||
double t_oil_1,t_oil_2; // Oil themperature
|
||||
};
|
||||
|
||||
/**
|
||||
* Message buffer structure
|
||||
*/
|
||||
struct my_msgbuf {
|
||||
int32_t mtype; // message type
|
||||
uint32_t acckey; // client access key
|
||||
uint32_t src_pid; // source PID
|
||||
uint32_t src_ip; // IP of command source or 0 for local
|
||||
char mtext[100]; // message itself
|
||||
};
|
||||
|
||||
extern volatile struct BTA_Local *sdtl;
|
||||
extern int snd_id;
|
||||
extern int cmd_src_pid;
|
||||
extern uint32_t cmd_src_ip;
|
||||
|
||||
#define ClientSide 0
|
||||
#define ServerSide 1
|
||||
|
||||
#ifndef BTA_MODULE
|
||||
void bta_data_init();
|
||||
int bta_data_check();
|
||||
void bta_data_close();
|
||||
int get_shm_block(volatile struct SHM_Block *sb, int server);
|
||||
int close_shm_block(volatile struct SHM_Block *sb);
|
||||
void get_cmd_queue(struct CMD_Queue *cq, int server);
|
||||
#endif
|
||||
|
||||
int check_shm_block(volatile struct SHM_Block *sb);
|
||||
|
||||
void encode_lev_passwd(char *passwd, int nlev, uint32_t *keylev, uint32_t *codlev);
|
||||
int find_lev_passwd(char *passwd, uint32_t *keylev, uint32_t *codlev);
|
||||
int check_lev_passwd(char *passwd);
|
||||
void set_acckey(uint32_t newkey);
|
||||
|
||||
// restore packing
|
||||
#pragma pack(pop)
|
||||
//#pragma GCC diagnostic pop
|
||||
|
||||
87
Daemons/weatherdaemon_multimeteo/plugins/btameteo.c
Normal file
87
Daemons/weatherdaemon_multimeteo/plugins/btameteo.c
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 "bta_shdata.h"
|
||||
#include "weathlib.h"
|
||||
|
||||
#define SENSOR_NAME "BTA 6-m telescope main meteostation"
|
||||
|
||||
enum{
|
||||
NWIND,
|
||||
NHUMIDITY,
|
||||
NAMB_TEMP,
|
||||
NPRESSURE,
|
||||
NPRECIP,
|
||||
NAMOUNT
|
||||
};
|
||||
|
||||
static const val_t values[NAMOUNT] = {
|
||||
[NWIND] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||
[NHUMIDITY] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||
[NAMB_TEMP] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||
[NPRESSURE] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||
[NPRECIP] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||
};
|
||||
|
||||
static void *mainthread(void *s){
|
||||
FNAME();
|
||||
sensordata_t *sensor = (sensordata_t *)s;
|
||||
while(sensor->fdes > -1){
|
||||
if(check_shm_block(&sdat)){
|
||||
//DBG("Got next");
|
||||
time_t tnow = time(NULL);
|
||||
pthread_mutex_lock(&sensor->valmutex);
|
||||
for(int i = 0; i < NAMOUNT; ++i)
|
||||
sensor->values[i].time = tnow;
|
||||
sensor->values[NWIND].value.f = val_Wnd;
|
||||
sensor->values[NPRESSURE].value.f = val_B;
|
||||
sensor->values[NAMB_TEMP].value.f = val_T1;
|
||||
sensor->values[NHUMIDITY].value.f = val_Hmd;
|
||||
//DBG("Tprecip=%.1f, tnow=%.1f", Precip_time, sl_dtime());
|
||||
sensor->values[NPRECIP].value.u = (tnow - (time_t)Precip_time < 60) ? 1 : 0;
|
||||
pthread_mutex_unlock(&sensor->valmutex);
|
||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||
}else break; // no connection?
|
||||
sleep(1);
|
||||
}
|
||||
DBG("Lost connection -> suicide");
|
||||
sensor->kill(sensor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int sensor_init(sensordata_t *s){
|
||||
FNAME();
|
||||
if(!s) return FALSE;
|
||||
if(!get_shm_block(&sdat, ClientSide)){
|
||||
WARNX("Can't get BTA shared memory block or create main thread");
|
||||
s->kill(s);
|
||||
return FALSE;
|
||||
}
|
||||
s->values = MALLOC(val_t, NAMOUNT);
|
||||
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||
s->Nvalues = NAMOUNT;
|
||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
||||
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||
WARN("Can't create main thread");
|
||||
s->kill(s);
|
||||
return FALSE;
|
||||
}
|
||||
s->fdes = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
|
||||
#include "weathlib.h"
|
||||
|
||||
#define NS (6)
|
||||
#define SENSOR_NAME "Dummy weatherstation"
|
||||
|
||||
extern sensordata_t sensor;
|
||||
#define NS (6)
|
||||
|
||||
static const val_t values[NS] = { // fields `name` and `comment` have no sense until value meaning is `IS_OTHER`
|
||||
{.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||
@@ -31,65 +31,62 @@ 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_HUMIDITY},
|
||||
{.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||
//{.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
|
||||
};
|
||||
|
||||
static void *mainthread(void _U_ *U){
|
||||
static void *mainthread(void *s){
|
||||
FNAME();
|
||||
double t0 = sl_dtime();
|
||||
while(1){
|
||||
float f = sensor.values[0].value.f + (drand48() - 0.5) / 2.;
|
||||
if(f >= 0.) sensor.values[0].value.f = f;
|
||||
f = sensor.values[1].value.f + (drand48() - 0.5) * 4.;
|
||||
if(f > 160. && f < 200.) sensor.values[1].value.f = f;
|
||||
f = sensor.values[2].value.f + (drand48() - 0.5) / 20.;
|
||||
if(f > 13. && f < 21.) sensor.values[2].value.f = f;
|
||||
f = sensor.values[3].value.f + (drand48() - 0.5) / 100.;
|
||||
if(f > 585. && f < 615.) sensor.values[3].value.f = f;
|
||||
f = sensor.values[4].value.f + (drand48() - 0.5) / 10.;
|
||||
if(f > 60. && f <= 100.) sensor.values[4].value.f = f;
|
||||
sensor.values[5].value.u = (f > 98.) ? 1 : 0;
|
||||
sensordata_t *sensor = (sensordata_t *)s;
|
||||
while(sensor->fdes > -1){
|
||||
//DBG("locked");
|
||||
pthread_mutex_lock(&sensor->valmutex);
|
||||
float f = sensor->values[0].value.f + (drand48() - 0.5) / 2.;
|
||||
if(f >= 0.) sensor->values[0].value.f = f;
|
||||
f = sensor->values[1].value.f + (drand48() - 0.5) * 4.;
|
||||
if(f > 160. && f < 200.) sensor->values[1].value.f = f;
|
||||
f = sensor->values[2].value.f + (drand48() - 0.5) / 2.;
|
||||
if(f > 13. && f < 21.) sensor->values[2].value.f = f;
|
||||
f = sensor->values[3].value.f + (drand48() - 0.5) / 100.;
|
||||
if(f > 585. && f < 615.) sensor->values[3].value.f = f;
|
||||
f = sensor->values[4].value.f + (drand48() - 0.5) * 10.;
|
||||
if(f > 60. && f <= 100.) sensor->values[4].value.f = f;
|
||||
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);
|
||||
for(int i = 0; i < NS; ++i) sensor.values[i].time = cur;
|
||||
if(sensor.freshdatahandler) sensor.freshdatahandler(&sensor);
|
||||
while(sl_dtime() - t0 < sensor.tpoll) usleep(500);
|
||||
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);
|
||||
//DBG("unlocked");
|
||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||
while(sl_dtime() - t0 < sensor->tpoll) usleep(500);
|
||||
t0 = sl_dtime();
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int init(struct sensordata_t* s, int N, time_t pollt, int _U_ fd){
|
||||
int sensor_init(sensordata_t *s){
|
||||
FNAME();
|
||||
if(pthread_create(&s->thread, NULL, mainthread, NULL)) return 0;
|
||||
if(pollt) s->tpoll = pollt;
|
||||
if(!s) return FALSE;
|
||||
s->Nvalues = NS;
|
||||
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
||||
s->values = MALLOC(val_t, NS);
|
||||
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
||||
sensor.values[0].value.f = 1.;
|
||||
sensor.values[1].value.f = 180.;
|
||||
sensor.values[2].value.f = 17.;
|
||||
sensor.values[3].value.f = 600.;
|
||||
sensor.values[4].value.f = 80.;
|
||||
sensor.values[5].value.u = 0;
|
||||
sensor.PluginNo = N;
|
||||
return NS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief getval - value's getter
|
||||
* @param o (o) - value
|
||||
* @param N - it's index
|
||||
* @return FALSE if failed
|
||||
*/
|
||||
static int getval(struct sensordata_t* s, val_t *o, int N){
|
||||
if(N < 0 || N >= NS) return FALSE;
|
||||
if(o) *o = s->values[N];
|
||||
s->values[0].value.f = 1.;
|
||||
s->values[1].value.f = 180.;
|
||||
s->values[2].value.f = 17.;
|
||||
s->values[3].value.f = 600.;
|
||||
s->values[4].value.f = 89.;
|
||||
s->values[5].value.u = 0;
|
||||
//s->values[6].value.f = 4.5;
|
||||
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||
s->kill(s);
|
||||
return FALSE;
|
||||
}
|
||||
s->fdes = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
sensordata_t sensor = {
|
||||
.name = "Dummy weatherstation",
|
||||
.Nvalues = NS,
|
||||
.init = init,
|
||||
.onrefresh = common_onrefresh,
|
||||
.get_value = getval,
|
||||
.kill = common_kill,
|
||||
};
|
||||
|
||||
@@ -23,10 +23,9 @@
|
||||
|
||||
// dummy example of file descriptors usage
|
||||
|
||||
#define SENSOR_NAME "Dummy socket or serial device weatherstation"
|
||||
#define NS (4)
|
||||
|
||||
extern sensordata_t sensor;
|
||||
|
||||
static const val_t values[NS] = { // fields `name` and `comment` have no sense until value meaning is `IS_OTHER`
|
||||
{.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||
{.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||
@@ -34,21 +33,23 @@ static const val_t values[NS] = { // fields `name` and `comment` have no sense u
|
||||
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||
};
|
||||
|
||||
static int format_values(char *buf){
|
||||
static int format_values(sensordata_t *sensor, char *buf){
|
||||
int gotvals = 0;
|
||||
char *token = strtok(buf, ",");
|
||||
time_t tnow = time(NULL);
|
||||
pthread_mutex_lock(&sensor->valmutex);
|
||||
while(token && gotvals < NS){
|
||||
double v;
|
||||
DBG("TOKEN: %s", token);
|
||||
if(sl_str2d(&v, token)){
|
||||
DBG("next value: %g", v);
|
||||
sensor.values[gotvals].value.f = (float) v;
|
||||
sensor.values[gotvals].time = tnow;
|
||||
sensor->values[gotvals].value.f = (float) v;
|
||||
sensor->values[gotvals].time = tnow;
|
||||
++gotvals;
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
pthread_mutex_unlock(&sensor->valmutex);
|
||||
DBG("GOT: %d", gotvals);
|
||||
return gotvals;
|
||||
}
|
||||
@@ -77,75 +78,66 @@ static ssize_t writedata(int fd, const char *str, size_t size){
|
||||
return sent;
|
||||
}
|
||||
|
||||
static void *mainthread(void _U_ *U){
|
||||
static void *mainthread(void *s){
|
||||
FNAME();
|
||||
time_t task = 0;
|
||||
const char begging[] = "Enter comma-separated data: wind, exttemp, pressure, humidity\n";
|
||||
char buf[128];
|
||||
while(sensor.fdes > -1){
|
||||
sensordata_t *sensor = (sensordata_t *)s;
|
||||
while(sensor->fdes > -1){
|
||||
time_t tnow = time(NULL);
|
||||
int canread = sl_canread(sensor.fdes);
|
||||
int canread = sl_canread(sensor->fdes);
|
||||
if(canread < 0){
|
||||
WARNX("Disconnected fd %d", sensor.fdes);
|
||||
WARNX("Disconnected fd %d", sensor->fdes);
|
||||
break;
|
||||
}else if(canread == 1){
|
||||
ssize_t got = read(sensor.fdes, buf, 128);
|
||||
ssize_t got = read(sensor->fdes, buf, 128);
|
||||
if(got > 0){
|
||||
sl_RB_write(sensor.ringbuffer, (uint8_t*)buf, got);
|
||||
sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got);
|
||||
}else if(got < 0){
|
||||
DBG("Disconnected?");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(sl_RB_readline(sensor.ringbuffer, buf, 127) > 0){
|
||||
if(NS == format_values(buf) && sensor.freshdatahandler)
|
||||
sensor.freshdatahandler(&sensor);
|
||||
if(sl_RB_readline(sensor->ringbuffer, buf, 127) > 0){
|
||||
if(NS == format_values(sensor, buf) && sensor->freshdatahandler)
|
||||
sensor->freshdatahandler(sensor);
|
||||
}
|
||||
if(sensor.tpoll){
|
||||
if(sensor->tpoll){
|
||||
if(tnow >= task){
|
||||
DBG("write %s", begging);
|
||||
ssize_t got = writedata(sensor.fdes, begging, sizeof(begging)-1);
|
||||
if(got > 0) task = tnow + sensor.tpoll;
|
||||
ssize_t got = writedata(sensor->fdes, begging, sizeof(begging)-1);
|
||||
if(got > 0) task = tnow + sensor->tpoll;
|
||||
else if(got < 0){
|
||||
close(sensor.fdes);
|
||||
sensor.fdes = -1;
|
||||
close(sensor->fdes);
|
||||
sensor->fdes = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DBG("OOOOps!");
|
||||
sensor->kill(sensor);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int init(struct sensordata_t *s, int N, time_t pollt, int fd){
|
||||
int sensor_init(sensordata_t *s){
|
||||
FNAME();
|
||||
if(!s) return -1;
|
||||
if(!s) return FALSE;
|
||||
int fd = getFD(s->path);
|
||||
if(fd < 0) return FALSE;
|
||||
s->fdes = fd;
|
||||
if(s->fdes < 0) return -1;
|
||||
sensor.PluginNo = N;
|
||||
if(pollt) s->tpoll = pollt;
|
||||
if(pthread_create(&s->thread, NULL, mainthread, NULL)) return -1;
|
||||
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||
s->values = MALLOC(val_t, NS);
|
||||
// don't use memcpy, as `values` could be aligned
|
||||
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
||||
if(!(s->ringbuffer = sl_RB_new(BUFSIZ))){
|
||||
WARNX("Can't init ringbuffer!");
|
||||
return -1;
|
||||
s->kill(s);
|
||||
return FALSE;
|
||||
}
|
||||
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||
s->kill(s);
|
||||
return FALSE;
|
||||
}
|
||||
return NS;
|
||||
}
|
||||
|
||||
static int getval(struct sensordata_t *s, val_t *o, int N){
|
||||
if(!s || N < 0 || N >= NS) return FALSE;
|
||||
if(o) *o = s->values[N];
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
sensordata_t sensor = {
|
||||
.name = "Dummy socket or serial device weatherstation",
|
||||
.Nvalues = NS,
|
||||
.init = init,
|
||||
.onrefresh = common_onrefresh,
|
||||
.get_value = getval,
|
||||
.kill = common_kill,
|
||||
};
|
||||
|
||||
214
Daemons/weatherdaemon_multimeteo/plugins/hydreon.c
Normal file
214
Daemons/weatherdaemon_multimeteo/plugins/hydreon.c
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "weathlib.h"
|
||||
|
||||
// HYDREON rain sensor
|
||||
|
||||
#define SENSOR_NAME "Hydreon RG-11 rain sensor"
|
||||
|
||||
// amount of datafields
|
||||
#define RREGNUM 6
|
||||
#define RGBITNUM 8
|
||||
#define SREGNUM 16
|
||||
|
||||
// RGBits values:
|
||||
// PeakRS overflow (>255)
|
||||
#define PkOverThr (1<<0)
|
||||
// is raining (after several PKOverThr by fixed time)
|
||||
#define Raining (1<<1)
|
||||
// outern relay is on (after bucket overflows from 18 to 0)
|
||||
#define Out1On (1<<2)
|
||||
// heater is on
|
||||
#define HtrOn (1<<3)
|
||||
// ambient light @0 (murky, twilight)
|
||||
#define IsDark (1<<4)
|
||||
// ???
|
||||
#define Cndnstn (1<<5)
|
||||
// ???
|
||||
#define Freeze (1<<6)
|
||||
// ???
|
||||
#define Storm (1<<7)
|
||||
|
||||
// minimal packet length (without slow registers)
|
||||
#define REGMINLEN (14)
|
||||
// standard packet length
|
||||
#define REGLEN (18)
|
||||
#define BUFLEN (32)
|
||||
|
||||
typedef struct{
|
||||
uint8_t PeakRS; // water intensity (255 - continuous)
|
||||
uint8_t SPeakRS; // most time == PeakRS
|
||||
uint8_t RainAD8; // (???)
|
||||
uint8_t LRA; // average rain activity (~envelope of PeakRS)
|
||||
uint8_t TransRat; // amount of measurements per second (???)
|
||||
uint8_t AmbLNoise; // ambient noise RMS (???)
|
||||
uint8_t RGBits; // flags
|
||||
uint8_t SlowRegIngex; // slow register index
|
||||
uint8_t SlowRegValue; // slow register value
|
||||
} rg11;
|
||||
|
||||
typedef struct{
|
||||
uint8_t RevLevel; // (??? == 14)
|
||||
uint8_t EmLevel; // (???) seems correlated with RainAD8
|
||||
uint8_t RecEmStr; // (???) seems correlated with RainAD8
|
||||
uint8_t ABLevel; // (??? == 7..12)
|
||||
uint8_t TmprtrF; // (inner T)
|
||||
uint8_t PUGain; // (??? == 37)
|
||||
uint8_t ClearTR; // (??? almost constant == 121..149)
|
||||
uint8_t AmbLight; // ambient light
|
||||
uint8_t Bucket; // Intergal PeakRS. When no rain, decreased near 4 hours per 1 unit
|
||||
uint8_t Barrel; // Integral Bucket (increases when Bucket goes through 12->14 after last overflow). Decreased near 2 hours per 1 unit
|
||||
uint8_t RGConfig; // (??? == 0)
|
||||
uint8_t DwellT; // 100 - no rain, 50 - low, 5 - max rain (like exponental function)
|
||||
uint8_t SinceRn; // (0..20) increases every minute after rain is over
|
||||
uint8_t MonoStb; // when Raining==1, MonoStb=15, then decrements when no rain (1 unit per ~1minute)
|
||||
uint8_t LightAD; // (???) seems correlated with RainAD8
|
||||
uint8_t RainThr; // (??? == 12)
|
||||
} slowregs;
|
||||
|
||||
enum{
|
||||
NPRECIP = 0,
|
||||
NPRECIP_LEVEL,
|
||||
NSINCERN,
|
||||
NPOW,
|
||||
NAVG,
|
||||
NAMBL,
|
||||
NFREEZ,
|
||||
NAMOUNT
|
||||
};
|
||||
|
||||
static const val_t values[NAMOUNT] = { // fields `name` and `comment` have no sense until value meaning is `IS_OTHER`
|
||||
[NPRECIP] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||
[NPRECIP_LEVEL] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||
[NSINCERN] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "TSINCERN", .comment = "Minutes since rain (20 means a lot of)"},
|
||||
[NPOW] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RAINPOW", .comment = "Rain strength, 0..255"},
|
||||
[NAVG] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RAINAVG", .comment = "Average rain strength, 0..255"},
|
||||
[NAMBL] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RSAMBL", .comment = "Ambient light by rain sensor, 0..255"},
|
||||
[NFREEZ] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RSFREEZ", .comment = "Rain sensor is freezed"},
|
||||
};
|
||||
|
||||
static int getv(char s, uint8_t *v){
|
||||
if(s >= '0' && s <= '9'){
|
||||
*v = s - '0';
|
||||
return 1;
|
||||
}else if(s >= 'a' && s <= 'f'){
|
||||
*v = 10 + s - 'a';
|
||||
return 1;
|
||||
}
|
||||
DBG("'%c' not a HEX", s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int encodepacket(const char *buf, int len, rg11 *Rregs, slowregs *Sregs){
|
||||
DBG("got buffer: %s[%d]", buf, len);
|
||||
uint8_t databuf[REGLEN/2] = {0};
|
||||
static slowregs slow = {0};
|
||||
if(len != REGMINLEN && len != REGLEN){
|
||||
DBG("Wrong buffer len!");
|
||||
return FALSE;
|
||||
}
|
||||
for(int i = 0; i < len; ++i){
|
||||
int l = i&1; // low part
|
||||
int idx = i/2; // data index
|
||||
uint8_t v;
|
||||
if(!getv(buf[i], &v)) return FALSE;
|
||||
if(l) databuf[idx] |= v;
|
||||
else databuf[idx] |= v << 4;
|
||||
}
|
||||
if(Rregs) memcpy(Rregs, databuf, sizeof(rg11));
|
||||
rg11 r = *((rg11*)databuf);
|
||||
uint8_t *s = (uint8_t*) &slow;
|
||||
if(len == REGLEN){
|
||||
if(r.SlowRegIngex < 16){
|
||||
s[r.SlowRegIngex] = r.SlowRegValue;
|
||||
}
|
||||
}
|
||||
if(Sregs) memcpy(Sregs, &slow, sizeof(slowregs));
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void *mainthread(void *s){
|
||||
FNAME();
|
||||
char buf[128];
|
||||
rg11 Rregs;
|
||||
slowregs Sregs;
|
||||
sensordata_t *sensor = (sensordata_t *)s;
|
||||
while(sensor->fdes > -1){
|
||||
time_t tnow = time(NULL);
|
||||
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, 128);
|
||||
if(got > 0){
|
||||
//DBG("write into buffer: %s[%zd]", buf, got);
|
||||
sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got);
|
||||
}else if(got < 0){
|
||||
DBG("Disconnected?");
|
||||
break;
|
||||
}
|
||||
}
|
||||
int got = sl_RB_readto(sensor->ringbuffer, 's', (uint8_t*)buf, 127);
|
||||
if(got > 0){
|
||||
buf[--got] = 0;
|
||||
if(encodepacket(buf, got, &Rregs, &Sregs)){
|
||||
DBG("refresh...");
|
||||
pthread_mutex_lock(&sensor->valmutex);
|
||||
for(int i = 0; i < NAMOUNT; ++i)
|
||||
sensor->values[i].time = tnow;
|
||||
sensor->values[NPRECIP].value.u = (Rregs.RGBits & (Raining | Storm)) ? 1 : 0;
|
||||
float f = Sregs.Barrel * 256.f + Sregs.Bucket - 14.f;
|
||||
sensor->values[NPRECIP_LEVEL].value.f = (f > 0.f) ? f : 0.f;
|
||||
sensor->values[NSINCERN].value.u = Sregs.SinceRn;
|
||||
sensor->values[NPOW].value.u = Rregs.PeakRS;
|
||||
sensor->values[NAVG].value.u = Rregs.LRA;
|
||||
sensor->values[NAMBL].value.u = Sregs.AmbLight;
|
||||
sensor->values[NFREEZ].value.u = (Rregs.RGBits & Freeze) ? 1 : 0;
|
||||
pthread_mutex_unlock(&sensor->valmutex);
|
||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||
}
|
||||
}
|
||||
}
|
||||
DBG("OOOOps!");
|
||||
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->values = MALLOC(val_t, NAMOUNT);
|
||||
// don't use memcpy, as `values` could be aligned
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
185
Daemons/weatherdaemon_multimeteo/plugins/reinhardt.c
Normal file
185
Daemons/weatherdaemon_multimeteo/plugins/reinhardt.c
Normal file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
|
||||
#include "weathlib.h"
|
||||
|
||||
#define SENSOR_NAME "Old Reinhard meteostation"
|
||||
|
||||
//static const char *emultemplate = "<?U> 06:50:36, 20.01.00, TE-2.20, DR1405.50, WU2057.68, RT0.00, WK1.00, WR177.80, WT-2.20, FE0.69, RE0.00, WG7.36, WV260.03, TI0.00, FI0.00,";
|
||||
|
||||
enum{
|
||||
NWIND,
|
||||
NWINDDIR,
|
||||
NHUMIDITY,
|
||||
NAMB_TEMP,
|
||||
NPRESSURE,
|
||||
NCLOUDS,
|
||||
NPRECIP,
|
||||
NPRECIPLVL,
|
||||
NAMOUNT
|
||||
};
|
||||
|
||||
static const val_t values[NAMOUNT] = {
|
||||
[NWIND] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||
[NWINDDIR] = {.sense = VAL_RECOMMENDED,.type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
||||
[NHUMIDITY] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||
[NAMB_TEMP] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||
[NPRESSURE] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||
[NCLOUDS] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_CLOUDS},
|
||||
[NPRECIP] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||
[NPRECIPLVL]= {.sense = VAL_UNNECESSARY,.type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief getpar - get parameter value
|
||||
* @param string (i) - string where to search
|
||||
* @param Val (o) - value found
|
||||
* @param Name - parameter name
|
||||
* @return TRUE if found
|
||||
*/
|
||||
static int getpar(char *string, double *Val, char *Name){
|
||||
if(!string || !Val || !Name) return FALSE;
|
||||
char *p = strstr(string, Name);
|
||||
if(!p) return FALSE;
|
||||
p += strlen(Name);
|
||||
//DBG("search %s", Name);
|
||||
char *endptr;
|
||||
*Val = strtod(p, &endptr);
|
||||
//DBG("eptr=%s, val=%g", endptr, *Val);
|
||||
if(endptr == string){
|
||||
WARNX("Double value not found");
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
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){
|
||||
if(4 != write(sensor->fdes, "?U\r\n", 4)){
|
||||
WARN("Can't ask new data");
|
||||
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){
|
||||
DBG("Disconnected?");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(sl_RB_datalen(sensor->ringbuffer) > BUFSIZ-1){
|
||||
WARNX("Overfull? Clear data from ring buffer");
|
||||
sl_RB_clearbuf(sensor->ringbuffer);
|
||||
}
|
||||
if(sl_RB_readto(sensor->ringbuffer, '\n', (uint8_t*)buf, BUFSIZ-1) > 0){
|
||||
tnow = time(NULL);
|
||||
DBG("Got next: %s", buf);
|
||||
pthread_mutex_lock(&sensor->valmutex);
|
||||
double d;
|
||||
//int Ngot = 0;
|
||||
if(getpar(buf, &d, "RE")){
|
||||
//++Ngot;
|
||||
sensor->values[NPRECIPLVL].value.f = (float) d;
|
||||
sensor->values[NPRECIPLVL].time = tnow;
|
||||
DBG("Got precip. lvl: %g", d);
|
||||
}
|
||||
if(getpar(buf, &d, "RT")){
|
||||
//++Ngot;
|
||||
sensor->values[NPRECIP].value.u = (d > 0.) ? 1 : 0;
|
||||
sensor->values[NPRECIP].time = tnow;
|
||||
DBG("Got precip.: %g", d);
|
||||
}
|
||||
if(getpar(buf, &d, "WU")){
|
||||
//++Ngot;
|
||||
sensor->values[NCLOUDS].value.f = (float) d;
|
||||
sensor->values[NCLOUDS].time = tnow;
|
||||
DBG("Got clouds.: %g", d);
|
||||
}
|
||||
if(getpar(buf, &d, "TE")){
|
||||
//++Ngot;
|
||||
sensor->values[NAMB_TEMP].value.f = (float) d;
|
||||
sensor->values[NAMB_TEMP].time = tnow;
|
||||
DBG("Got ext. T: %g", d);
|
||||
}
|
||||
if(getpar(buf, &d, "WG")){
|
||||
//++Ngot;
|
||||
d /= 3.6;
|
||||
DBG("Wind: %g", d);
|
||||
sensor->values[NWIND].value.f = (float) d;
|
||||
sensor->values[NWIND].time = tnow;
|
||||
}
|
||||
if(getpar(buf, &d, "WR")){
|
||||
//++Ngot;
|
||||
sensor->values[NWINDDIR].value.f = (float) d;
|
||||
sensor->values[NWINDDIR].time = tnow;
|
||||
DBG("Winddir: %g", d);
|
||||
}
|
||||
if(getpar(buf, &d, "DR")){
|
||||
//++Ngot;
|
||||
sensor->values[NPRESSURE].value.f = (float) (d * 0.7500616);
|
||||
sensor->values[NPRESSURE].time = tnow;
|
||||
DBG("Pressure: %g", d);
|
||||
}
|
||||
if(getpar(buf, &d, "FE")){
|
||||
//++Ngot;
|
||||
sensor->values[NHUMIDITY].value.f = (float) d;
|
||||
sensor->values[NHUMIDITY].time = tnow;
|
||||
DBG("Humidity: %g", d);
|
||||
}
|
||||
pthread_mutex_unlock(&sensor->valmutex);
|
||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||
}
|
||||
}
|
||||
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;
|
||||
s->Nvalues = NAMOUNT;
|
||||
s->fdes = fd;
|
||||
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
214
Daemons/weatherdaemon_multimeteo/plugins/wxa100.c
Normal file
214
Daemons/weatherdaemon_multimeteo/plugins/wxa100.c
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* 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 <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "weathlib.h"
|
||||
|
||||
#define SENSOR_NAME "WXA100-06 ultrasonic meteostation"
|
||||
|
||||
// static const char *emultemplate = "0R0,S=1.9,D=217.2,P=787.7,T=10.8,H=69.0,R=31.0,Ri=0.0,Rs=Y";
|
||||
|
||||
enum{
|
||||
NWIND,
|
||||
NWINDDIR,
|
||||
NHUMIDITY,
|
||||
NAMB_TEMP,
|
||||
NPRESSURE,
|
||||
NPRECIP,
|
||||
NPRECIPLVL,
|
||||
NPRECIPINT,
|
||||
NAMOUNT
|
||||
};
|
||||
|
||||
static const val_t values[NAMOUNT] = {
|
||||
[NWIND] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||
[NWINDDIR] = {.sense = VAL_RECOMMENDED,.type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
||||
[NHUMIDITY] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||
[NAMB_TEMP] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||
[NPRESSURE] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||
[NPRECIP] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||
[NPRECIPLVL]= {.sense = VAL_RECOMMENDED,.type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||
[NPRECIPINT]= {.sense = VAL_RECOMMENDED,.type = VALT_FLOAT, .meaning = IS_OTHER, .name = "PRECRATE", .comment = "Precipitation rate, mm/h"},
|
||||
};
|
||||
|
||||
typedef struct{
|
||||
double windspeed; // speed in m/s
|
||||
double winddir; // direction from north
|
||||
double pressure; // pressure in hPa
|
||||
double temperature; // outern temperature in degC
|
||||
double humidity; // humidity in percents
|
||||
double rainfall; // cumulative rain level (mm)
|
||||
double rainrate; // rain rate, mm/h
|
||||
double israin; // ==1 if it's raining
|
||||
} weather_t;
|
||||
|
||||
typedef struct{
|
||||
const char *parname; // parameter started value
|
||||
int isboolean; // ==1 if answer Y/N
|
||||
int parlen; // parameter length in bytes
|
||||
double *weatherpar; // data target
|
||||
} wpair_t;
|
||||
|
||||
static weather_t lastweather;
|
||||
|
||||
static const wpair_t wpairs[] = {
|
||||
{"S=", 0, 2, &lastweather.windspeed},
|
||||
{"D=", 0, 2, &lastweather.winddir},
|
||||
{"P=", 0, 2, &lastweather.pressure},
|
||||
{"T=", 0, 2, &lastweather.temperature},
|
||||
{"H=", 0, 2, &lastweather.humidity},
|
||||
{"R=", 0, 2, &lastweather.rainfall},
|
||||
{"Ri=",0, 3, &lastweather.rainrate},
|
||||
{"Rs=",1, 3, &lastweather.israin},
|
||||
{NULL, 0, 0, NULL}
|
||||
};
|
||||
|
||||
static int parseans(char *str){
|
||||
int ncollected = 0;
|
||||
if(strncmp(str, "0R0,", 4)){
|
||||
WARNX("Wrong answer");
|
||||
LOGWARN("poll_device() get wrong answer: %s", str);
|
||||
return 0;
|
||||
}
|
||||
// init with NaNs
|
||||
const wpair_t *el = wpairs;
|
||||
while(el->parname){
|
||||
*el->weatherpar = NAN;
|
||||
++el;
|
||||
}
|
||||
char *token = strtok(str, ",");
|
||||
while(token){
|
||||
el = wpairs;
|
||||
while(el->parname){
|
||||
if(strncmp(token, el->parname, el->parlen) == 0){ // found next parameter
|
||||
token += el->parlen;
|
||||
char *endptr;
|
||||
if(el->isboolean){
|
||||
*el->weatherpar = (*token == 'Y') ? 1. : 0.;
|
||||
++ncollected;
|
||||
}else{
|
||||
*el->weatherpar = strtod(token, &endptr);
|
||||
if(endptr == token){
|
||||
DBG("Wrong double value %s", token);
|
||||
}else ++ncollected;
|
||||
}
|
||||
break;
|
||||
}
|
||||
++el;
|
||||
}
|
||||
token = strtok(NULL, ",");
|
||||
}
|
||||
DBG("Got %d values", ncollected);
|
||||
return ncollected;
|
||||
}
|
||||
|
||||
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){
|
||||
if(6 != write(sensor->fdes, "!0R0\r\n", 6)){
|
||||
WARN("Can't ask new data");
|
||||
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){
|
||||
DBG("Disconnected?");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(sl_RB_datalen(sensor->ringbuffer) > BUFSIZ-1){
|
||||
WARNX("Overfull? Clear data from ring buffer");
|
||||
sl_RB_clearbuf(sensor->ringbuffer);
|
||||
}
|
||||
if(sl_RB_readline(sensor->ringbuffer, buf, BUFSIZ-1) > 0 && parseans(buf) > 0){
|
||||
tnow = time(NULL);
|
||||
pthread_mutex_lock(&sensor->valmutex);
|
||||
if(!isnan(lastweather.rainfall)){
|
||||
sensor->values[NPRECIPLVL].value.f = (float) lastweather.rainfall;
|
||||
sensor->values[NPRECIPLVL].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.rainrate)){
|
||||
sensor->values[NPRECIPINT].value.f = (float) lastweather.rainrate;
|
||||
sensor->values[NPRECIPINT].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.israin)){
|
||||
sensor->values[NPRECIP].value.u = (lastweather.israin > 0.) ? 1 : 0;
|
||||
sensor->values[NPRECIP].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.temperature)){
|
||||
sensor->values[NAMB_TEMP].value.f = (float) lastweather.temperature;
|
||||
sensor->values[NAMB_TEMP].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.windspeed)){
|
||||
sensor->values[NWIND].value.f = (float) lastweather.windspeed;
|
||||
sensor->values[NWIND].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.winddir)){
|
||||
sensor->values[NWINDDIR].value.f = (float) lastweather.winddir;
|
||||
sensor->values[NWINDDIR].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.pressure)){
|
||||
sensor->values[NPRESSURE].value.f = (float) (lastweather.pressure * 0.7500616); // mmHg instead of hPa!
|
||||
sensor->values[NPRESSURE].time = tnow;
|
||||
}
|
||||
if(!isnan(lastweather.humidity)){
|
||||
sensor->values[NHUMIDITY].value.f = (float) lastweather.humidity;
|
||||
sensor->values[NHUMIDITY].time = tnow;
|
||||
}
|
||||
pthread_mutex_unlock(&sensor->valmutex);
|
||||
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||
}
|
||||
}
|
||||
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->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;
|
||||
}
|
||||
|
||||
@@ -20,8 +20,9 @@
|
||||
#include <string.h>
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "fd.h"
|
||||
#include "mainweather.h"
|
||||
#include "sensors.h"
|
||||
#include "weathlib.h"
|
||||
|
||||
#define WARNXL(...) do{ LOGWARN(__VA_ARGS__); WARNX(__VA_ARGS__); } while(0)
|
||||
#define WARNL(...) do{ LOGWARN(__VA_ARGS__); WARN(__VA_ARGS__); } while(0)
|
||||
@@ -45,6 +46,8 @@ int set_pollT(time_t t){
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
time_t get_pollT(){ return poll_interval;}
|
||||
|
||||
/**
|
||||
* @brief get_plugin - get link to opened plugin
|
||||
* @param o (o) - plugin with given index
|
||||
@@ -56,12 +59,16 @@ sensordata_t *get_plugin(int N){
|
||||
return allplugins[N];
|
||||
}
|
||||
|
||||
// TODO: fix for usage with several identical meteostations
|
||||
void *open_plugin(const char *name){
|
||||
DBG("try to open lib %s", name);
|
||||
void* dlh = dlopen(name, RTLD_NOLOAD); // library may be already opened
|
||||
void* dlh = dlopen(name, RTLD_NOW | RTLD_NOLOAD); // library may be already opened
|
||||
if(!dlh){
|
||||
DBG("Not loaded - load");
|
||||
dlh = dlopen(name, RTLD_NOW);
|
||||
}else{
|
||||
WARNX("Library %s already opened", name);
|
||||
//return NULL;
|
||||
}
|
||||
if(!dlh){
|
||||
char *e = dlerror();
|
||||
@@ -71,28 +78,40 @@ void *open_plugin(const char *name){
|
||||
return dlh;
|
||||
}
|
||||
|
||||
#ifdef EBUG
|
||||
// in release this function can be used for meteo logging
|
||||
/**
|
||||
* @brief dumpsensors - this function called each time some `station` got new data
|
||||
*
|
||||
* @param station - pointer to N'th station opened
|
||||
*/
|
||||
static void dumpsensors(struct sensordata_t* station){
|
||||
FNAME();
|
||||
if(!station || !station->get_value || station->Nvalues < 1) return;
|
||||
//FNAME();
|
||||
if(!sensor_alive(station) || !station->get_value || station->Nvalues < 1 || station->IsMuted) return;
|
||||
refresh_sensval(station);
|
||||
#if 0
|
||||
DBG("New values...");
|
||||
#ifdef EBUG
|
||||
char buf[FULL_LEN+1];
|
||||
uint64_t Tsum = 0; int nsum = 0;
|
||||
int N = (nplugins > 1) ? station->PluginNo : -1;
|
||||
time_t oldest = time(NULL) - 100;
|
||||
for(int i = 0; i < station->Nvalues; ++i){
|
||||
val_t v;
|
||||
if(!station->get_value(station, &v, i)) continue;
|
||||
if(v.time < oldest) continue;
|
||||
if(0 < format_sensval(&v, buf, FULL_LEN+1, N)){
|
||||
printf("%s\n", buf);
|
||||
++nsum; Tsum += v.time;
|
||||
}
|
||||
}
|
||||
time_t last = (time_t)(Tsum / nsum);
|
||||
if(0 < format_msrmttm(last, buf, FULL_LEN+1)){
|
||||
printf("%s\n\n", buf);
|
||||
if(nsum > 0){
|
||||
time_t last = (time_t)(Tsum / nsum);
|
||||
if(0 < format_msrmttm(last, buf, FULL_LEN+1)){
|
||||
printf("%s\n\n", buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief openplugins - open sensors' plugin and init it
|
||||
@@ -117,26 +136,22 @@ int openplugins(char **paths, int N){
|
||||
void* dlh = open_plugin(buf);
|
||||
if(!dlh) continue;
|
||||
DBG("OPENED");
|
||||
void *s = dlsym(dlh, "sensor");
|
||||
if(s){
|
||||
sensordata_t *S = (sensordata_t*) s;
|
||||
if(!S->get_value) WARNXL("Sensor library %s have no values' getter!", paths[i]);
|
||||
if(!S->init){
|
||||
WARNXL("Sensor library %s have no init funtion");
|
||||
continue;
|
||||
}
|
||||
int fd = -1;
|
||||
if(colon) fd = getFD(colon);
|
||||
int ns = S->init(S, nplugins, poll_interval, fd); // here nplugins is index in array
|
||||
if(ns < 1) WARNXL("Can't init plugin %s", paths[i]);
|
||||
sensor_init_t sensinit = (sensor_init_t) dlsym(dlh, "sensor_init");
|
||||
if(sensinit){
|
||||
sensordata_t *S = sensor_new(nplugins, colon);
|
||||
if(!S) WARNXL("Can't allocate memory for 'sensor' structure");
|
||||
else{
|
||||
#ifdef EBUG
|
||||
if(!S->onrefresh(S, dumpsensors)) WARNXL("Can't init refresh funtion");
|
||||
#endif
|
||||
LOGMSG("Plugin %s nave %d sensors", paths[i], ns);
|
||||
S->init = sensinit;
|
||||
S->tpoll = poll_interval;
|
||||
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 field `sensor` in plugin %s: %s", paths[i], dlerror());
|
||||
}else WARNXL("Can't find initing function in plugin %s: %s", paths[i], dlerror());
|
||||
}
|
||||
return nplugins;
|
||||
}
|
||||
@@ -149,11 +164,52 @@ void closeplugins(){
|
||||
if(!allplugins || nplugins < 1) return;
|
||||
for(int i = 0; i < nplugins; ++i){
|
||||
if(allplugins[i]->kill) allplugins[i]->kill(allplugins[i]);
|
||||
FREE(allplugins[i]);
|
||||
}
|
||||
FREE(allplugins);
|
||||
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
|
||||
* @param v - value to get
|
||||
@@ -163,30 +219,17 @@ void closeplugins(){
|
||||
* @return amount of symbols printed or -1 if error
|
||||
*/
|
||||
int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
||||
--buflen; // for trailing zero
|
||||
if(!v || !buf || buflen < FULL_LEN) return -1;
|
||||
char strval[VAL_LEN+1];
|
||||
if(!v || !buf || buflen < 1) return -1;
|
||||
char strval[VAL_LEN];
|
||||
int fieldlen = 20; // minimal distance between '=' and '/' is 22 bytes
|
||||
switch(v->type){
|
||||
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_FLOAT: snprintf(strval, VAL_LEN, "%g", 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'");
|
||||
}
|
||||
const char* const NM[] = { // 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[] = { // comments for standard fields
|
||||
const char* const CMT[IS_OTHER] = { // comments for standard fields
|
||||
[IS_WIND] = "Wind, m/s",
|
||||
[IS_WINDDIR] = "Wind direction, degr (CW from north to FROM)",
|
||||
[IS_HUMIDITY] = "Humidity, percent",
|
||||
@@ -195,10 +238,11 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
||||
[IS_HW_TEMP] = "Hardware (mirror?) termperature, degC",
|
||||
[IS_PRESSURE] = "Atmospheric pressure, mmHg",
|
||||
[IS_PRECIP] = "Precipitation (1 - yes, 0 - no)",
|
||||
[IS_PRECIP_LEVEL]="Precipitation level (mm)",
|
||||
[IS_PRECIP_LEVEL]="Cumulative precipitation level (mm)",
|
||||
[IS_MIST] = "Mist (1 - yes, 0 - no)",
|
||||
[IS_CLOUDS] = "Integral clouds value (bigger - better)",
|
||||
[IS_SKYTEMP] = "Mean sky temperatyre"
|
||||
[IS_CLOUDS] = "Integral sky quality value (bigger - better)",
|
||||
[IS_SKYTEMP] = "Mean sky temperatyre",
|
||||
//[IS_LIGTDIST] = "Distance to last lightning, km",
|
||||
};
|
||||
const char *name = NULL, *comment = NULL;
|
||||
int idx = v->meaning;
|
||||
@@ -209,18 +253,82 @@ int format_sensval(const val_t *v, char *buf, int buflen, int Np){
|
||||
name = v->name;
|
||||
comment = v->comment;
|
||||
}
|
||||
if(!name) return 0; // no name pointed - don't show this value
|
||||
if(!comment) comment = "-";
|
||||
int got;
|
||||
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);
|
||||
return got;
|
||||
if(Np > -1) got = snprintf(buf, buflen, "%s[%d] = %s / %s", name, Np, strval, comment);
|
||||
else got = snprintf(buf, buflen, "%-*s= %*s / %s", KEY_LEN, name, fieldlen, strval, comment);
|
||||
return (got < buflen) ? got : buflen; // full or truncated
|
||||
}
|
||||
|
||||
// the same for measurement time formatting
|
||||
int format_msrmttm(time_t t, char *buf, int buflen){
|
||||
--buflen; // for trailing zero
|
||||
if(!buf || buflen < FULL_LEN) return -1;
|
||||
int format_msrmttm(time_t t, char *buf, int buflen, int Np){
|
||||
if(!buf || buflen < 1) return -1;
|
||||
char cmt[COMMENT_LEN+1];
|
||||
struct tm *T = localtime(&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,6 +27,21 @@ int openplugins(char **paths, int N);
|
||||
void closeplugins();
|
||||
sensordata_t *get_plugin(int N);
|
||||
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_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);
|
||||
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,16 +16,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <ctype.h>
|
||||
#include <signal.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <usefull_macros.h>
|
||||
|
||||
#include "mainweather.h"
|
||||
#include "sensors.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)
|
||||
static sl_sock_t *netsocket = NULL, *localsocket;
|
||||
//static pthread_t netthread, locthread;
|
||||
@@ -39,86 +39,290 @@ static sl_sock_hresult_e timehandler(sl_sock_t *client, _U_ sl_sock_hitem_t *ite
|
||||
return RESULT_SILENCE;
|
||||
}
|
||||
|
||||
#define SSZ_ (PATH_MAX + 256)
|
||||
// show all connected libraries
|
||||
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;
|
||||
char buf[256];
|
||||
char buf[SSZ_];
|
||||
int N = get_nplugins();
|
||||
if(N < 1) return RESULT_FAIL;
|
||||
sensordata_t *d = NULL;
|
||||
for(int i = 0; i < N; ++i){
|
||||
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);
|
||||
}
|
||||
return RESULT_SILENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief showdata - send to user meteodata
|
||||
* @param client - client data
|
||||
* @param N - index of station
|
||||
* @param showidx - == TRUE to show index in square brackets
|
||||
*/
|
||||
static void showdata(sl_sock_t *client, int N, int showidx){
|
||||
char buf[FULL_LEN+1];
|
||||
val_t v;
|
||||
// get N'th plugin or send error message
|
||||
sensordata_t *get_plugin_w_message(sl_sock_t *client, int N){
|
||||
char buf[FULL_LEN];
|
||||
sensordata_t *s = NULL;
|
||||
if(!(s = get_plugin(N)) || (s->Nvalues < 1)){
|
||||
snprintf(buf, FULL_LEN, "Can't get plugin[%d]\n", N);
|
||||
sl_sock_sendstrmessage(client, buf);
|
||||
return;
|
||||
return NULL;
|
||||
}
|
||||
if(!showidx || get_nplugins() == 1) N = -1; // only one -> don't show indexes
|
||||
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);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief showdata - send to client sensor's data
|
||||
* @param client - client data
|
||||
* @param N - -1 for common data or station index for specific meteo
|
||||
*/
|
||||
static void showdata(sl_sock_t *client, int N){
|
||||
char buf[FULL_LEN];
|
||||
val_t v;
|
||||
int Ncoll = 0;
|
||||
sensordata_t *s = NULL;
|
||||
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){
|
||||
int ans = 0;
|
||||
if(N < 0) ans = get_collected(&v, i);
|
||||
else ans = s->get_value(s, &v, i);
|
||||
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_sendbyte(client, '\n');
|
||||
++nsum; Tsum += v.time;
|
||||
if(v.time > mstm) mstm = v.time;
|
||||
}
|
||||
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');
|
||||
// also send FORCE flag if have
|
||||
if(N < 0 && is_forbidden()) sl_sock_sendstrmessage(client, "FORBID = 1 / Observations are forbidden by operator\n");
|
||||
if(mstm){
|
||||
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_sendbyte(client, '\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// get meteo data
|
||||
static sl_sock_hresult_e gethandler(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) for(int i = 0; i < N; ++i) showdata(client, i, TRUE);
|
||||
if(!req) showdata(client, -1);
|
||||
else{
|
||||
int n;
|
||||
if(!sl_str2i(&n, req) || n < 0 || n >= N) return RESULT_BADVAL;
|
||||
showdata(client, n, FALSE);
|
||||
showdata(client, n);
|
||||
}
|
||||
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
|
||||
static void toomuch(int fd){
|
||||
const char *m = "Try later: too much clients connected\n";
|
||||
send(fd, m, sizeof(m)-1, MSG_NOSIGNAL);
|
||||
shutdown(fd, SHUT_WR);
|
||||
DBG("shutdown, wait");
|
||||
double t0 = sl_dtime();
|
||||
uint8_t buf[8];
|
||||
while(sl_dtime() - t0 < 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);
|
||||
DBG("shutdown");
|
||||
LOGWARN("Client fd=%d tried to connect after MAX reached", fd);
|
||||
}
|
||||
// new connections handler (return FALSE to reject client)
|
||||
@@ -140,36 +344,29 @@ static sl_sock_hresult_e defhandler(struct sl_sock *s, const char *str){
|
||||
return RESULT_SILENCE;
|
||||
}
|
||||
|
||||
#define COMMONHANDLERS \
|
||||
{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}, \
|
||||
{timehandler, "time", "get server's UNIX time", NULL},
|
||||
|
||||
// handlers for network and local (UNIX) sockets
|
||||
static sl_sock_hitem_t nethandlers[] = { // net - only getters and client-only setters
|
||||
{gethandler, "get", "get all meteo or only for given plugin number", NULL},
|
||||
{listhandler, "list", "show all opened plugins", NULL},
|
||||
{timehandler, "time", "get server's UNIX time", NULL},
|
||||
COMMONHANDLERS
|
||||
{NULL, NULL, NULL, NULL}
|
||||
};
|
||||
static sl_sock_hitem_t localhandlers[] = { // local - full amount of setters/getters
|
||||
{gethandler, "get", "get all meteo or only for given plugin number", NULL},
|
||||
{listhandler, "list", "show all opened plugins", NULL},
|
||||
{timehandler, "time", "get server's UNIX time", NULL},
|
||||
{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
|
||||
{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){
|
||||
if(!netnode || !sockpath){
|
||||
LOGERR("start_servers(): need arguments");
|
||||
@@ -180,11 +377,13 @@ int start_servers(const char *netnode, const char *sockpath){
|
||||
LOGERR("start_servers(): can't run network socket");
|
||||
return FALSE;
|
||||
}
|
||||
DBG("Network server started");
|
||||
localsocket = sl_sock_run_server(SOCKT_UNIX, sockpath, BUFSIZ, localhandlers);
|
||||
if(!localsocket){
|
||||
LOGERR("start_servers(): can't run local socket");
|
||||
return FALSE;
|
||||
}
|
||||
DBG("Local server started");
|
||||
sl_sock_changemaxclients(netsocket, MAX_CLIENTS);
|
||||
sl_sock_changemaxclients(localsocket, 1);
|
||||
sl_sock_maxclhandler(netsocket, toomuch);
|
||||
@@ -195,35 +394,30 @@ int start_servers(const char *netnode, const char *sockpath){
|
||||
sl_sock_dischandler(localsocket, disconnected);
|
||||
sl_sock_defmsghandler(netsocket, defhandler);
|
||||
sl_sock_defmsghandler(localsocket, defhandler);
|
||||
#if 0
|
||||
if(pthread_create(&netthread, NULL, cmdparser, (void*)netsocket)){
|
||||
LOGERR("Can't run server's net thread");
|
||||
goto errs;
|
||||
// now run checking cycle
|
||||
int Nplugins = get_nplugins();
|
||||
time_t tstart = time(NULL), tcur;
|
||||
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)){
|
||||
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
|
||||
return TRUE; // should be never reached
|
||||
}
|
||||
|
||||
void kill_servers(){
|
||||
//pthread_cancel(locthread);
|
||||
//pthread_cancel(netthread);
|
||||
//LOGMSG("Server threads canceled");
|
||||
//usleep(500);
|
||||
sl_sock_delete(&localsocket);
|
||||
sl_sock_delete(&netsocket);
|
||||
LOGMSG("Server sockets destroyed");
|
||||
//usleep(500);
|
||||
//pthread_kill(locthread, 9);
|
||||
//pthread_kill(netthread, 9);
|
||||
//LOGMSG("Server threads killed");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
CMakeLists.txt
|
||||
Readme.md
|
||||
cmdlnopts.c
|
||||
cmdlnopts.h
|
||||
fd.c
|
||||
fd.h
|
||||
main.c
|
||||
mainweather.c
|
||||
mainweather.h
|
||||
plugins/bta_shdata.c
|
||||
plugins/bta_shdata.h
|
||||
plugins/btameteo.c
|
||||
plugins/dummy.c
|
||||
plugins/fdexample.c
|
||||
plugins/hydreon.c
|
||||
plugins/lightning.c
|
||||
plugins/reinhardt.c
|
||||
plugins/snmp.c
|
||||
plugins/wxa100.c
|
||||
sensors.c
|
||||
sensors.h
|
||||
server.c
|
||||
|
||||
@@ -18,15 +18,40 @@
|
||||
|
||||
// Some common functions and handlers for sensors
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#include "weathlib.h"
|
||||
|
||||
// private functions (for plugins usage only)
|
||||
//static int common_onrefresh(sensordata_t*, void (*handler)(sensordata_t*));
|
||||
//static void common_kill(sensordata_t *);
|
||||
//static int common_getval(sensordata_t*, val_t*, int);
|
||||
//static int common_init(sensordata_t*, int, time_t, int);
|
||||
|
||||
/**
|
||||
* @brief sensor_new - call this function before calling `sensor_init`
|
||||
* @param N - plugin number in array
|
||||
* @return pointer to allocated sensor's structure
|
||||
*/
|
||||
sensordata_t *sensor_new(int N, const char *descr){
|
||||
sensordata_t *s = MALLOC(sensordata_t, 1);
|
||||
s->fdes = -1; // not inited
|
||||
s->onrefresh = common_onrefresh; // `init` function can redefine basic stubs
|
||||
s->get_value = common_getval;
|
||||
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);
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief sensor_alive - test if sensor's thread isn't dead
|
||||
* @param s - sensor
|
||||
* @return FALSE if thread is dead
|
||||
*/
|
||||
int sensor_alive(sensordata_t *s){
|
||||
if(!s) return FALSE;
|
||||
if(!s || s->fdes < 0) return FALSE;
|
||||
if(pthread_kill(s->thread, 0)) return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
@@ -44,23 +69,51 @@ 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
|
||||
*/
|
||||
void common_kill(sensordata_t *s){
|
||||
FNAME();
|
||||
if(!s) return;
|
||||
if(0 == pthread_kill(s->thread, -9)){
|
||||
DBG("%s main thread killed, join", s->name);
|
||||
pthread_join(s->thread, NULL);
|
||||
DBG("Done");
|
||||
if(s->fdes > -1){ // inited and maybe have opened file/socket
|
||||
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");
|
||||
sl_RB_delete(&s->ringbuffer);
|
||||
if(s->fdes > -1){
|
||||
close(s->fdes);
|
||||
DBG("FD closed");
|
||||
}
|
||||
if(s->ringbuffer) sl_RB_delete(&s->ringbuffer);
|
||||
FREE(s->values);
|
||||
DBG("Sensor %s killed", s->name);
|
||||
if(s->privdatafree) s->privdatafree(s->privdata);
|
||||
else FREE(s->privdata);
|
||||
DBG("Sensor '%s' killed", s->name);
|
||||
if(s->path[0]) LOGERR("Sensor '%s' @ '%s' killed", s->name, s->path);
|
||||
else LOGERR("Sensor '%s' killed", s->name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief common_getval - common value getter
|
||||
* @param s (i) - station
|
||||
* @param o (o) - value or NULL (if you just wants test N)
|
||||
* @param N - number of sensor
|
||||
* @return FALSE if failed
|
||||
*/
|
||||
int common_getval(struct sensordata_t *s, val_t *o, int N){
|
||||
if(!s || N < 0 || N >= s->Nvalues) return FALSE;
|
||||
if(o){
|
||||
pthread_mutex_lock(&s->valmutex);
|
||||
*o = s->values[N];
|
||||
pthread_mutex_unlock(&s->valmutex);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -21,11 +21,14 @@
|
||||
#include <pthread.h>
|
||||
#include <signal.h> // pthread_kill
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <usefull_macros.h>
|
||||
|
||||
// length (in symbols) of key, value and comment
|
||||
#define KEY_LEN (8)
|
||||
// length of STR type (without terminal zero)
|
||||
#define STRT_LEN (11)
|
||||
#define VAL_LEN (31)
|
||||
#define COMMENT_LEN (63)
|
||||
// maximal full length of "KEY=val / comment" (as for sfitsio)
|
||||
@@ -35,10 +38,12 @@
|
||||
|
||||
// importance of values
|
||||
typedef enum{
|
||||
VAL_FORCEDSHTDN, // if this value is `terrible`, `forced sthudtown` flag will be set
|
||||
VAL_OBLIGATORY, // can't be omitted
|
||||
VAL_RECOMMENDED, // recommended to show
|
||||
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;
|
||||
|
||||
// meaning of values
|
||||
@@ -55,13 +60,19 @@ typedef enum{
|
||||
IS_MIST, // mist (1 - yes, 0 - no)
|
||||
IS_CLOUDS, // integral clouds value (bigger - better)
|
||||
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;
|
||||
|
||||
typedef union{
|
||||
uint32_t u;
|
||||
int32_t i;
|
||||
float f;
|
||||
char str[STRT_LEN+1];// up to 8 symbols (with terminating zero)
|
||||
} num_t;
|
||||
|
||||
// type of value
|
||||
@@ -69,7 +80,7 @@ typedef enum{
|
||||
VALT_UINT,
|
||||
VALT_INT,
|
||||
VALT_FLOAT,
|
||||
//VALT_STRING,
|
||||
VALT_STRING,
|
||||
} valtype_t;
|
||||
|
||||
// value
|
||||
@@ -86,23 +97,39 @@ typedef struct{
|
||||
// all sensor's data
|
||||
// all functions have `this` as first arg
|
||||
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 PluginNo; // plugin number in array (if several)
|
||||
int (*init)(struct sensordata_t*, int, time_t, int); // init meteostation with given PluginNo, poll_interval and fd; return amount of parameters found or -1 if error
|
||||
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 (*get_value)(struct sensordata_t*, val_t*, int); // getter of Nth value
|
||||
void (*kill)(struct sensordata_t*); // close everything and remove sensor
|
||||
// private members:
|
||||
val_t *values; // array of values
|
||||
pthread_t thread; // main thread
|
||||
void (*freshdatahandler)(struct sensordata_t*); // handler of fresh data
|
||||
int fdes; // file descriptor of device/socket
|
||||
pthread_mutex_t valmutex;// value getter/setter mutex
|
||||
// !!! if your plugin don't use file descriptor, you should set fdes to any non-negative value after running main thread
|
||||
int fdes; // file descriptor of device/socket or "init" flag (should be > -1)
|
||||
sl_ringbuffer_t *ringbuffer; // ringbuffer for device reading
|
||||
time_t tpoll; // forced polling time for sensor
|
||||
void (*freshdatahandler)(struct sensordata_t*); // handler of fresh data
|
||||
void (*privdatafree)(void*); // free private data (if don't wanna write own `kill` instead of `common kill`
|
||||
void *privdata; // some private data like struct
|
||||
} sensordata_t;
|
||||
|
||||
// library functions and other
|
||||
int common_onrefresh(sensordata_t*, void (*handler)(sensordata_t*));
|
||||
void common_kill(sensordata_t *s);
|
||||
// type for function extraction
|
||||
typedef int (*sensor_init_t)(sensordata_t *);
|
||||
|
||||
// init meteostation with given PluginNo, poll_interval and descriptor
|
||||
//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);
|
||||
sensordata_t *sensor_new(int N, const char *descr);
|
||||
|
||||
// private function (for plugins usage only)
|
||||
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