Compare commits

...

24 Commits

Author SHA1 Message Date
46de782019 Add readme, make some fixes 2026-05-07 10:56:21 +03:00
63f87d2283 add dead sensors' reinit, gathering all fresh data, fixed some little bugs 2026-05-06 17:13:00 +03:00
c5b9d43797 add lightning module 2026-05-05 17:57:42 +03:00
2413661e19 add UPS monitoring over SNMP 2026-04-30 16:16:12 +03:00
05e57ef012 .. 2026-04-29 17:47:23 +03:00
27cfe60fe8 some fixes; add forced shutdown 2026-04-28 18:14:34 +03:00
347d02e748 . 2026-04-27 17:45:29 +03:00
68febd02c4 add config example 2026-04-27 09:45:48 +03:00
Edward Emelianov
7b2d93299d add fits-header to weather_proxy, fixed something 2026-04-24 23:12:29 +03:00
5acd1cd97d some fixes 2026-04-10 15:52:55 +03:00
af33a036c8 some fixes 2026-04-10 14:51:40 +03:00
987cf022fe added 'level' changing 2026-04-10 14:21:25 +03:00
5be6876f9e seems like it works 2026-04-09 18:35:06 +03:00
e551b94499 add BTA, reinhard and Hydreon sensors 2026-04-08 18:09:36 +03:00
39d4e22061 add mean wind directions and max wind speed 2026-04-08 11:37:27 +03:00
05a42b0a10 tests works, need real meteo modules 2026-04-03 13:14:45 +03:00
cc870491f5 fixed deadlock 2026-04-03 10:39:14 +03:00
9d58bf1694 fixed bug 2026-04-02 12:00:43 +03:00
a324302404 fix dome daemon to print fits-header 2026-04-01 11:08:22 +03:00
7318307d4d add forgotten "status" 2026-04-01 09:43:49 +03:00
0f0c87ee2f add heater commands and fits-header creation 2026-03-31 17:54:09 +03:00
cae7a6e213 temporarily fixed stellarium daemon 2026-03-30 17:45:07 +03:00
Edward Emelianov
df4a597aa8 fixed IPC rules 2026-03-26 23:00:30 +03:00
Edward Emelianov
c74d1e6026 simplest local weather proxy over SHM 2026-03-26 20:29:29 +03:00
88 changed files with 8789 additions and 588 deletions

13
.gitignore vendored
View File

@@ -2,6 +2,15 @@
*
!*.*
!*/
!Makefile
![Rr][Ee][Aa][Dd][Mm][Ee]
# build dirs
mk/
build/
.qtc_clangd
.qtcreator
# Prerequisites
*.d
@@ -28,11 +37,9 @@
*.so
*.so.*
# build dirs
mk/
build/
# diff
.qtcreator/
*.log
*.tgz
*.tar.gz

View File

@@ -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);

View File

@@ -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>

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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
View 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")

View File

@@ -2,11 +2,17 @@ Different daemons & tools
=========================
- *10micron_stellarium* - simple daemon for 10-micron mount management from stellarium interface
- *domedaemon* - open/close Baaden dome by network query
- *astrosib* - some scripts used during observations
- *deprecated* - deprecated code
- *domedaemon-astrosib* - deprecated astrosib daemon
- *domedaemon_baader* - open/close Baaden dome by network query
- *domedaemon-astrosib* - open/close Astrosib dome by network query
- *netdaemon* - template for net-daemons
- *netsocket* - scripts for management of network 220V-socket
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon
- *teldaemon* - open/close Astrosib-500 scope covers by network query
- *weatherdaemon* - weather daemon for old meteostation
- *weatherdaemon_newmeteo* - daemon for new (chinese) meteostation
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon (almost deprecated)
- *teldaemon_astrosib* - open/close Astrosib-500 scope covers by network query
- *weatherdaemon* - weather daemon for old meteostation (almost deprecated)
- *weatherdaemon_multimeteo* - (pre-developed) version of weather daemon for ALL sensors on "Astro-M" complex
- *weatherdaemon_newmeteo* - daemon for new (chinese) meteostation (almost deprecated)
- *weather_database* - make database by data of almost deprecated weather daemons
- *weather_proxy* - (pre-developed) daemon gathering meteo data and sharing it on localhost over SHM

View File

@@ -0,0 +1,58 @@
# - очистить принимающий буфер
:shutdown# - выключить монтировку
:U2# - установить наивысшую точность
Состояние гидирования (ACK): команда 0x06, ответ L/P - вкл/выкл
:AL# - выключить ведение (без ответа)
:AP# - включить
GPS:
:gtg# - возвращает 1, если часы синхронизированы с GPS
наведение на цель
:MS# - двигаться на цель, возврат: 0, если все в порядке, либо текст с ошибкой
:SrHH:MM:SS.SS# - установить RA цели (возврат 1 если ОК)
:SdsDD*MM:SS.S# - установить DECL цели (возврат 1 если ОК)
:D# - статус наведения, возврат: 0x7f, если в процессе, либо #
:NUDGEsXXX,sYYY# - скорректировать положение по RA/DEC на N угловых секунд
:STOP# - останов (:AP# - для возобновления гидирования)
:Gstat# - состояние монтировки
:GT# - частота для гидирования (60.1643377745 по звездам)
:AL# - выкл. гидирование
:AP# - вкл
:Gpgc# - состояние гидирования
:GSC# - коррекция скорости гидирования на косинус h
:GTTRK# - возможно ли наведение на текущий объект
:pS# - с какой стороны монтировки телескоп (для перекладывания?)
:FLIP# - переложить монтировку
:GVP# - название монтировки
:GTMP@# (@ - номер датчика) - температура датчика
:GREF# - состояние коррекции на рефракцию
:GA# - высота телескопа над горизонтом
:GZ# - азимут
:GC# - дата
:GD# - склонение
:GR# - восхождение
:GJD# - юлианская дата (GJD1 - с наивысшей точностью)
:GLDT# - местные дата и время
:GS# - звездное время
парковка: стр. 20
ехать:
:Q# - прекратить наведение (а еще :Qe# - east и пр.)
:D# - состояние наведения
:NUDGEsXXXX,sYYYY# - коррекция положения на XXX/YYY секунд (RA/Dec)
:MA# - по введенным горизонтальным координатам цели
:MS# - ехать на цель
:Me$=#, :Mw#, :Mn#, :Ms# - движение в данную сторону
:MeXXX#, :MnXXX# и др. стороны - коррекция на XXX миллисекунд скорости гида
модель наведения - стр. 44
после определения центра изображения ввести его командами :Sr# и :Sd# и добавить точку командой :CMS#

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
[General]

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
-std=c++17

View File

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

View File

@@ -0,0 +1 @@
.

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,106 @@
/*
* geany_encoding=koi8-r
* emulation.c
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include <usefull_macros.h>
#include "math.h"
#include "emulation.h"
// emulation speed over RA & DEC (0.5 degr per sec)
#define RA_SPEED (0.033)
#define DECL_SPEED (0.5)
// current coordinates
static double RA = 0., DECL = 0.;
// target coordinates
static double RAtarg = 0., DECLtarg = 0.;
// coordinates @ guiding start
static double RA0 = 0., DECL0 = 0.;
static double raspeed = 0.;
// ==1 if pointing
static int pointing = 0;
// pointing start time
static double tstart = -1.;
/**
* send coordinates to telescope emulation
* @param ra - right ascention (hours)
* @param decl - declination (degrees)
* @return 1 if all OK
*/
int point_emulation(double ra, double decl){
DBG("(emul) Send ra=%g, decl=%g", ra, decl);
LOGMSG("(emul) Send ra=%g, decl=%g", ra, decl);
RAtarg = ra; DECLtarg = decl;
RA0 = RA; DECL0 = DECL;
raspeed = (RAtarg > RA) ? RA_SPEED : -RA_SPEED;
if(fabs(RAtarg - RA) > 12.){ // go to opposite direction
raspeed = -raspeed;
}
tstart = sl_dtime();
pointing = 1;
return 0;
}
static double getradiff(){
double diff = RAtarg - RA;
if(raspeed < 0.) diff = -diff;
if(diff > 12.) diff -= 24.;
else if(diff < -12.) diff += 24.;
return fabs(diff);
}
/**
* get coordinates (emulation)
* @return 1 if all OK
*/
int get_emul_coords(double *ra, double *decl){
if(pointing){
DBG("RA/DEC: targ: %g/%g, cur: %g/%g, start: %g/%g", RAtarg, DECLtarg, RA, DECL, RA0, DECL0);
// diff < speed? stop
if((fabs(RAtarg - RA) < RA_SPEED && fabs(DECLtarg - DECL) < DECL_SPEED)){
RA = RAtarg;
DECL = DECLtarg;
pointing = 0; // guiding
DBG("@ target");
}else{ // calculate new coordinates
double radiff = getradiff(), decldiff = fabs(DECLtarg - DECL);
double tdiff = sl_dtime() - tstart;
RA = RA0 + raspeed * tdiff;
DBG("RA=%g", RA);
if(getradiff() > radiff) RA = RAtarg;
DBG("RA=%g", RA);
if(RA < 0.) RA += 24.;
else if(RA > 24.) RA -= 24.;
DBG("RA=%g", RA);
double sign = (DECLtarg > DECL) ? 1. : -1.;
DECL = DECL0 + sign * DECL_SPEED * tdiff;
if(fabs(DECLtarg - DECL) > decldiff) DECL = DECLtarg;
DBG("RA/DEC: targ: %g/%g, cur: %g/%g, start: %g/%g", RAtarg, DECLtarg, RA, DECL, RA0, DECL0);
}
}
if(ra) *ra = RA;
if(decl) *decl = DECL;
return 1;
}

View File

@@ -0,0 +1,28 @@
/*
* geany_encoding=koi8-r
* emulation.h
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#pragma once
int point_emulation(double ra, double decl);
int get_emul_coords(double *ra, double *decl);

View File

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

View File

@@ -0,0 +1,84 @@
/*
* This file is part of the StelD project.
* Copyright 2020 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <erfa.h>
#include <erfam.h>
#include <sys/time.h>
// JD2451544.5 == 2000.0
#define MJD2000 (51544)
typedef struct{
double utc1; double utc2; // UTC JD, commonly used MJD = utc1+utc2-2400000.5
double MJD;
double tai1; double tai2; // TAI JD
double tt1; double tt2; // TT JD
} sMJD;
// polar coordinates & equation of origins (all in radians)
typedef struct{
double ha; // hour angle
double dec; // declination
double ra; // right ascension
double eo; // equation of origins
} polarCrds;
// horizontal coordinates (all in radians)
typedef struct{
double az; // azimuth, 0 @ south, positive clockwise
double zd; // zenith distance
} horizCrds;
// observational place coordinates and altitude; all coordinates are in radians!
typedef struct{
double slong; // longitude
double slat; // lattitude
double salt; // altitude, m
} placeData;
// place weather data
typedef struct{
double relhum; // rel. humidity, 0..100%
double pres; // atm. pressure (mmHg)
double tc; // temperature, degrC
double rain; // rain value (0..1)
double clouds; // clouds (0 - bad, >2500 - good)
double wind; // wind speed, m/s
double time; // measurements time
} localWeather;
// DUT/polar almanach data
typedef struct{
double DUT1; // UT1-UTC, sec
double px; // polar coordinates, arcsec
double py;
} almDut;
void r2sHMS(double radians, char *hms, int len);
void r2sDMS(double radians, char *hms, int len);
void hor2eq(horizCrds *h, polarCrds *pc, double sidTime);
void eq2horH(polarCrds *pc, horizCrds *h);
void eq2hor(polarCrds *pc, horizCrds *h, double sidTime);
int get_MJDt(struct timeval *tval, sMJD *MJD);
int get_LST(sMJD *mjd, double dUT1, double slong, double *LST);
int get_ObsPlace(struct timeval *tval, polarCrds *p2000, localWeather *weath, polarCrds *pnow, horizCrds *hnow);
almDut *getDUT();
localWeather *getWeath();
placeData *getPlace();

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,25 @@
/*
* This file is part of the StelD project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
void weatherserver_disconnect();
int weatherserver_connect();
int getparval(const char *par, const char *ansbuf, double *val);
char *getweathbuffer();

View File

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

View File

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

View File

@@ -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&lt;int&gt;" 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&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>

View File

@@ -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

View File

@@ -1,4 +1,6 @@
commands.h
header.c
header.h
main.c
socket.c
socket.h

View 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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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

View File

@@ -31,8 +31,12 @@
#define TXT_COOLEROFF "COOLEROFF?\r"
#define TXT_COOLERT "COOLERT?\r"
#define TXT_COOLERSTAT "COOLERSTATUS?\r"
#define TXT_HEATON "HEATON?100\r"
#define TXT_HEATOFF "HEATOFF?\r"
#define TXT_HEATSTAT "HEATSTATUS?\r"
#define TXT_PING "PING?\r"
#define TXT_ANS_OK "OK"
#define TXT_ANS_HEATSTAT "OK\rHEATERSTATUS?"
#define TXT_ANS_COOLERSTAT "OK\rCOOLERPWM?"
#define TXT_ANS_COOLERT "OK\rCOOLERT?"
#define TXT_ANS_STATUS "OK\rSHUTTERS?"

View File

@@ -0,0 +1,142 @@
/*
* This file is part of the teldaemon 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 *telescope_name = NULL;
static header_mask_t header_mask = {0};
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, int flags){
if(!file || flags < 0 || flags > 0xff) return FALSE;
FILE *hf = fopen(file, "w");
if(!hf) return FALSE;
unlink(file);
if(headername) FREE(headername);
headername = strdup(file);
header_mask.flags = (uint8_t)flags;
return TRUE;
}
// set given `name` for telescope name
void telname(const char *name){
if(telescope_name) FREE(telescope_name);
if(name){
int l = strlen(name) + 3;
telescope_name = MALLOC(char, l);
snprintf(telescope_name, l-1, "'%s'", name);
}
}
// return static buffer with help
const char *getheadermaskhelp(){
return
"0 - telescope name\n"
"1 - focuser status\n"
"2 - cooler status\n"
"3 - heater status\n"
"4 - external temperature\n"
"5 - mirror temperature\n"
"6 - measured time\n"
;
}
void write_header(){
if(!headername || !header_mask.flags) return;
char aname[PATH_MAX];
char val[22];
telstatus_t st;
#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);
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("TELCOOLR", val, "Primary mirror cooler status: 0/1 (off/on)");
}
if(header_mask.heater){
snprintf(val, 21, "%d", st.heater);
WRHDR("TELHEATR", val, "Secondary mirror heater status: 0/1 (off/on)");
}
if(header_mask.exttemp){
snprintf(val, 21, "%.1f", st.ambienttemp);
WRHDR("TDOME", val, "In-dome temperature, degC");
}
if(header_mask.mirtemp){
snprintf(val, 21, "%.1f", st.mirrortemp);
WRHDR("TMIRROR", val, "Mirror temperature, degC");
}
if(header_mask.meastime){
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 (telescope): %F %T", tmp)){
LOGERR("localtime() returned NULL");
goto returning;
}
WRHDR("TTELMEAS", val, timebuf);
}
#undef WRHDR
returning:
close(fd);
rename(aname, headername);
}

View File

@@ -0,0 +1,39 @@
/*
* This file is part of the teldaemon 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 <stdint.h>
typedef union{
struct{
uint8_t telname : 1; // show telescope name
uint8_t fosuser : 1; // show focuser status
uint8_t cooler : 1; // show cooler status
uint8_t heater : 1; // show heater status
uint8_t exttemp : 1; // show external temperature
uint8_t mirtemp : 1; // show mirror temperature
uint8_t meastime: 1; // show measurement time
};
uint8_t flags; // alltogether as single flags
} header_mask_t;
const char *getheadermaskhelp();
void write_header();
int header_create(const char *file, int flags);
void telname(const char *name);

View File

@@ -20,77 +20,125 @@
#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/teldaemon.pid"
#define DEFAULT_HEADERFILE "/tmp/telescope.fits"
#define DEFAULT_TELNAME "Astro-M (1)"
static pid_t childpid = 0;
typedef struct{
int help;
int verbose;
int isunix;
int maxclients;
int serspeed;
int headermask;
double sertmout;
char *logfile;
char *node;
char *termpath;
char *pidfile;
char *headerfile;
char *telescope_name;
} parameters;
static parameters G = {
.maxclients = 2,
.serspeed = 9600,
.sertmout = 1000.,
.pidfile = DEFAULT_PIDFILE,
.headerfile = DEFAULT_HEADERFILE,
.telescope_name = DEFAULT_TELNAME,
.headermask = 0xff,
};
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "verbose level (each -v adds 1)"},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "log file name"},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "log file name (FULL path)"},
{"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), "PID-file (FULL path, 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)"},
{"sertmout", NEED_ARG, NULL, 't', arg_double, APTR(&G.sertmout), "serial device timeout (us)"},
{"headerfile", NEED_ARG, NULL, 'H', arg_string, APTR(&G.headerfile),"full path to output FITS-header"},
{"telname", NEED_ARG, NULL, 'N', arg_string, APTR(&G.telescope_name), "telescope name in FITS-header"},
{"headermask", NEED_ARG, NULL, 'M', arg_int, APTR(&G.headermask), "mask of header file (try -1 for help; default: show all)"},
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);
}
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.headermask < 0){
green("Header mask bits (set to show info, clear to hide):\n");
printf("%s\n", getheadermaskhelp());
return 0;
}
if(!G.node) ERRX("Point communication node");
if(!G.termpath) ERRX("Point serial device path");
sl_check4running((char*)__progname, G.pidfile);
if(!header_create(G.headerfile, G.headermask))
ERRX("Cannot write into '%s'", G.headerfile);
telname(G.telescope_name);
sl_check4running(NULL, 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 +151,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;
}

View File

@@ -22,6 +22,7 @@
#include <string.h>
#include "commands.h"
#include "header.h"
#include "socket.h"
#include "term.h"
@@ -31,22 +32,21 @@ typedef enum{
CMD_FOCSTOP,
CMD_COOLERON,
CMD_COOLEROFF,
CMD_HEATERON,
CMD_HEATEROFF,
CMD_NONE
} tel_commands_t;
typedef struct{
tel_commands_t cmd; // deferred command
tel_commands_t errcmd; // command ended with error
int focuserpos; // focuser position
char *status; // device status
int statlen; // size of `status` buffer
char status[STATBUF_SZ]; // device status
double stattime; // time of last status
int cooler; // cooler's status
int heater; // heater's status
double mirrortemp; // T mirror, degC
double ambienttemp; // T ambient, degC
double temptime; // measurement time
tel_commands_t cmd; // deferred command
tel_commands_t errcmd; // command ended with error
pthread_mutex_t mutex;
} tel_t;
@@ -65,6 +65,19 @@ void stopserver(){
if(locksock) sl_sock_delete(&locksock);
}
/**
* @brief get_telescope_data - copy local `telescope` to `t`
* @param t (i) - pointer to your struct
* @return true if observations are permitted and false if forbidden
*/
bool get_telescope_data(telstatus_t *t){
if(!t) return false;
pthread_mutex_lock(&telescope.mutex);
*t = *((telstatus_t*)&telescope);
pthread_mutex_unlock(&telescope.mutex);
return ForbidObservations;
}
// send "measuret=..."
static void sendtmeasured(sl_sock_t *client, double t){
char buf[256];
@@ -160,6 +173,26 @@ static sl_sock_hresult_e coolerh(sl_sock_t *client, sl_sock_hitem_t *item, const
return RESULT_SILENCE;
}
static sl_sock_hresult_e heaterh(sl_sock_t *client, sl_sock_hitem_t *item, const char *req){
char buf[256];
if(req){ // getter
int onoff;
if(!sl_str2i(&onoff, req)) return RESULT_BADVAL;
pthread_mutex_lock(&telescope.mutex);
if(onoff) telescope.cmd = CMD_HEATERON;
else telescope.cmd = CMD_HEATEROFF;
pthread_mutex_unlock(&telescope.mutex);
return RESULT_OK;
}
// getter
pthread_mutex_lock(&telescope.mutex);
snprintf(buf, 255, "%s=%d\n", item->key, telescope.heater);
pthread_mutex_unlock(&telescope.mutex);
sl_sock_sendstrmessage(client, buf);
return RESULT_SILENCE;
}
// focuser getter
static sl_sock_hresult_e foch(sl_sock_t *client, sl_sock_hitem_t *item, _U_ const char *req){
char buf[256];
@@ -217,6 +250,7 @@ static sl_sock_hitem_t handlers[] = {
{statush, "status", "get shutters' status and temperatures", NULL},
{fstoph, "focstop", "stop focuser moving", NULL},
{coolerh, "cooler", "get/set cooler status", NULL},
{heaterh, "heater", "get/set heater status", NULL},
{dtimeh, "dtime", "get server's UNIX time for all clients connected", NULL},
{NULL, NULL, NULL, NULL}
};
@@ -239,9 +273,9 @@ static int poll_device(){
DBG("\nGot status");
pthread_mutex_lock(&telescope.mutex);
telescope.stattime = sl_dtime();
if(strncmp(data, "0,0,0,0,0", 9) == 0) snprintf(telescope.status, telescope.statlen, "closed");
else if(strncmp(data, "1,1,1,1,1", 9) == 0) snprintf(telescope.status, telescope.statlen, "opened");
else snprintf(telescope.status, telescope.statlen, "intermediate");
if(strncmp(data, "0,0,0,0,0", 9) == 0) snprintf(telescope.status, STATBUF_SZ-1, "closed");
else if(strncmp(data, "1,1,1,1,1", 9) == 0) snprintf(telescope.status, STATBUF_SZ-1, "opened");
else snprintf(telescope.status, STATBUF_SZ-1, "intermediate");
pthread_mutex_unlock(&telescope.mutex);
if(!(data = term_cmdwans(TXT_COOLERT, TXT_ANS_COOLERT, ans))) return FALSE;
@@ -253,7 +287,6 @@ static int poll_device(){
telescope.mirrortemp = m;
pthread_mutex_unlock(&telescope.mutex);
}
if(!(data = term_cmdwans(TXT_COOLERSTAT, TXT_ANS_COOLERSTAT, ans))) return FALSE;
DBG("\nGot cooler status");
if(sscanf(data, "%d", &I) == 1){
@@ -261,6 +294,13 @@ static int poll_device(){
telescope.cooler = I;
pthread_mutex_unlock(&telescope.mutex);
}
if(!(data = term_cmdwans(TXT_HEATSTAT, TXT_ANS_HEATSTAT, ans))) return FALSE;
DBG("\nGot heater status");
if(sscanf(data, "%d", &I) == 1){
pthread_mutex_lock(&telescope.mutex);
telescope.heater = I;
pthread_mutex_unlock(&telescope.mutex);
}
return TRUE;
}
@@ -269,9 +309,6 @@ void runserver(int isunix, const char *node, int maxclients){
int forbidden = 0;
if(locksock) sl_sock_delete(&locksock);
telescope.errcmd = telescope.cmd = CMD_NONE;
FREE(telescope.status);
telescope.statlen = STATBUF_SZ;
telescope.status = MALLOC(char, STATBUF_SZ);
pthread_mutex_init(&telescope.mutex, NULL);
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
locksock = sl_sock_run_server(type, node, -1, handlers);
@@ -298,7 +335,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(&telescope.mutex);
@@ -308,23 +348,50 @@ void runserver(int isunix, const char *node, int maxclients){
switch(tcmd){
case CMD_OPEN:
DBG("received command: open");
if(term_cmdwans(TXT_OPEN, TXT_ANS_OK, ans)) tcmd = CMD_NONE;
if(term_cmdwans(TXT_OPEN, TXT_ANS_OK, ans)){
LOGMSG("Open dome");
tcmd = CMD_NONE;
}
break;
case CMD_CLOSE:
DBG("received command: close");
if(term_cmdwans(TXT_CLOSE, TXT_ANS_OK, ans)) tcmd = CMD_NONE;
if(term_cmdwans(TXT_CLOSE, TXT_ANS_OK, ans)){
LOGMSG("Close dome");
tcmd = CMD_NONE;
}
break;
case CMD_FOCSTOP:
DBG("received command: stop focus");
LOGMSG("Stop focus");
term_write(TXT_FOCSTOP, ans); tcmd = CMD_NONE; // erroreous thing: no answer for this command
break;
case CMD_COOLEROFF:
DBG("received command: cooler off");
if(term_cmdwans(TXT_COOLEROFF, TXT_ANS_OK, ans)) tcmd = CMD_NONE;
if(term_cmdwans(TXT_COOLEROFF, TXT_ANS_OK, ans)){
LOGMSG("Cooler off");
tcmd = CMD_NONE;
}
break;
case CMD_COOLERON:
DBG("received command: cooler on");
if(term_cmdwans(TXT_COOLERON, TXT_ANS_OK, ans)) tcmd = CMD_NONE;
if(term_cmdwans(TXT_COOLERON, TXT_ANS_OK, ans)){
LOGMSG("Cooler on");
tcmd = CMD_NONE;
}
break;
case CMD_HEATERON:
DBG("received command: heater on");
if(term_cmdwans(TXT_HEATON, TXT_ANS_OK, ans)){
LOGMSG("Heater on");
tcmd = CMD_NONE;
}
break;
case CMD_HEATEROFF:
DBG("received command: heater off");
if(term_cmdwans(TXT_HEATOFF, TXT_ANS_OK, ans)){
LOGMSG("Heater off");
tcmd = CMD_NONE;
}
break;
default:
DBG("WTF?");

View File

@@ -25,6 +25,17 @@
// dome polling interval (clear watchdog & get status)
#define T_INTERVAL (2.0)
typedef struct{
int focuserpos; // focuser position
char status[STATBUF_SZ]; // device status
double stattime; // time of last status
int cooler; // cooler's status
int heater; // heater's status
double mirrortemp; // T mirror, degC
double ambienttemp; // T ambient, degC
} telstatus_t;
void runserver(int isunix, const char *node, int maxclients);
void stopserver();
void forbid_observations(int forbid);
bool get_telescope_data(telstatus_t *t);

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
commands.h
header.c
header.h
main.c
socket.c
socket.h

View File

@@ -0,0 +1,33 @@
CC = gcc
CFLAGS = -Wall -Wextra -fPIC -DEBUG
LDFLAGS = -lrt -pthread -lusefull_macros
all: weather_daemon libweather.so weather_clt_example
weather_daemon: weather_daemon.o
$(CC) -o $@ $^ $(LDFLAGS)
weather_clt_example: weather_clt_example.o
$(CC) -o $@ $^ $(LDFLAGS) -l weather -L.
weather_daemon.o: weather_daemon.c weather_data.h
$(CC) $(CFLAGS) -c $<
libweather.so: weather_client.o
$(CC) -shared -o $@ $^ $(LDFLAGS)
#libweather.a: weather_client.o
# ar rcs $@ $^
weather_client.o: weather_client.c weather_data.h
$(CC) $(CFLAGS) -c $<
weather_clt_example.o: weather_clt_example.c libweather.so
$(CC) $(CFLAGS) -c $<
clean:
rm -f *.o weather_daemon libweather.so libweather.a weather_clt_example
install:
cp libweather.so /usr/local/lib/
cp weather_data.h /usr/local/include/

View File

@@ -0,0 +1,73 @@
/*
* 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>
#include <errno.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <semaphore.h>
#include <unistd.h>
#include "weather_data.h"
int get_weather_data(weather_data_t *data) {
int shm_fd;
sem_t *sem;
weather_data_t *shared_data;
int ret = 0;
shm_fd = shm_open(SHM_NAME, O_RDONLY, 0600);
if (shm_fd == -1) {
perror("shm_open");
return -1;
}
shared_data = mmap(NULL, sizeof(weather_data_t), PROT_READ, MAP_SHARED, shm_fd, 0);
if (shared_data == MAP_FAILED) {
perror("mmap");
close(shm_fd);
return -1;
}
sem = sem_open(SEM_NAME, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
munmap(shared_data, sizeof(weather_data_t));
close(shm_fd);
return -1;
}
if (sem_wait(sem) == -1) {
perror("sem_wait");
ret = -1;
goto cleanup;
}
memcpy(data, shared_data, sizeof(weather_data_t));
sem_post(sem);
cleanup:
sem_close(sem);
munmap(shared_data, sizeof(weather_data_t));
close(shm_fd);
return ret;
}

View File

@@ -0,0 +1,35 @@
/*
* 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){
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;
}

View File

@@ -0,0 +1,367 @@
/*
* 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 <unistd.h>
#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
// don't ask new data less than `WEAT_TMOUT` seconds
#define WEAT_TMOUT 1
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};
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
};
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){
umask(0); // for read-write semaphore
shm_fd = shm_open(SHM_NAME, O_RDWR, 0644); // try to open existant SHM
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){
LOGERR("shm_open (create) failed: %s", strerror(errno));
return -1;
}
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){
LOGERR("mmap failed: %s", strerror(errno));
return -1;
}
// default values to data
}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){
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){
LOGERR("sem_open failed: %s", strerror(errno));
return -1;
}
return 0;
}
// free IPC
static void cleanup_ipc(void){
if (sem != NULL) {
sem_close(sem);
DBG("semaphore closed\n");
if(-1 == sem_unlink(SEM_NAME)) LOGERR("Can't delete semaphore");
}
if(shared_data != NULL){
DBG("memory unmapped\n");
munmap(shared_data, sizeof(weather_data_t));
}
if(shm_fd != -1){
close(shm_fd);
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[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, "WINDMAX1") == 0){
data->windmax = atof(value);
printf("got windmax: %g\n", data->windmax);
}else if (strcmp(key, "PRECIP") == 0){
data->rain = atoi(value);
printf("got rain: %d\n", data->rain);
}else if (strcmp(key, "CLOUDS") == 0){
data->clouds = atof(value);
printf("got clouds: %g\n", data->clouds);
}else if (strcmp(key, "WIND") == 0){
data->wind = atof(value);
printf("got wind: %g\n", data->wind);
}else if (strcmp(key, "EXTTEMP") == 0){
data->exttemp = atof(value);
printf("got temp: %g\n", data->exttemp);
}else if (strcmp(key, "PRESSURE") == 0){
data->pressure = atof(value);
printf("got pressure: %g\n", data->pressure);
}else if (strcmp(key, "HUMIDITY") == 0){
data->humidity = atof(value);
printf("got humidity: %g\n", data->humidity);
}else if (strcmp(key, "PROHIBIT") == 0){
data->prohibited = atoi(value);
}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
update_shm(data);
update = 1;
}else update = -1;
if(update > -1 && G.fitsheader){
FITS_update(line, update);
}
}
}
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 return 1; // not now
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(){
char line[256];
weather_data_t new_data;
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);
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);
}
sl_sock_delete(&sock); // disconnect and clear memory
DBG("run_daemon() exited");
}
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);
}
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");
}
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){
LOGERR("IPC initialization failed");
exit(EXIT_FAILURE);
}
run_daemon();
LOGDBG("Daemon is dead");
cleanup_ipc();
LOGDBG("IPC cleaned");
return 0;
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <stdint.h>
#include <time.h>
#define SHM_NAME "/weather_shm"
#define SEM_NAME "/weather_sem"
typedef enum {
WEATHER_GOOD = 0, // may start observations
WEATHER_BAD = 1, // cannot start but can continue if want
WEATHER_TERRIBLE = 2, // close & park: wind, precipitation, humidity etc.
WEATHER_PROHIBITED = 3, // force closing & parking; power off equipment, ready to power off computer
} weather_condition_t;
typedef struct {
weather_condition_t weather; // conditions: field "WEATHER"
float windmax; // maximal wind for last hour, m/s: "WINDMAX1"
float wind; // current wind speed, m/s: "WIND"
float clouds; // sky "quality" (>2500 - OK): "CLOUDS"
float exttemp; // external temperature, degC: "EXTTEMP"
float pressure; // atm. pressure, mmHg: "PRESSURE"
float humidity; // humidity, percents: "HUMIDITY"
int rain; // ==1 when rainy: "PRECIP"
int 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);

View File

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

View File

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

View File

@@ -0,0 +1 @@
[General]

View File

@@ -0,0 +1 @@
-std=c++17

View File

@@ -0,0 +1,4 @@
weather_client.c
weather_clt_example.c
weather_daemon.c
weather_data.h

View File

@@ -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})

View File

@@ -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>.*

View File

@@ -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

View 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

View File

@@ -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;

View File

@@ -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

View 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; }

View 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();

View File

@@ -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})

View 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."
1 OBJECT_NAME OBJECT_IDENTIFIER OBJECT_DATA_TYPE OBJECT_PREMISSIONS OBJECT_CLASS OBJECT_NODE_TYPE OBJECT_DESCRIPTION
2 upsMIB 1.3.6.1.2.1.33 moduleidentity The MIB module to describe Uninterruptible Power Supplies.
3 upsObjects 1.3.6.1.2.1.33.1 objectidentity
4 upsIdent 1.3.6.1.2.1.33.1.1 objectidentity
5 upsIdentManufacturer 1.3.6.1.2.1.33.1.1.1 displaystring read-only objecttype scalar The name of the UPS manufacturer.
6 upsIdentModel 1.3.6.1.2.1.33.1.1.2 displaystring read-only objecttype scalar The UPS Model designation.
7 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.
8 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.
9 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.
10 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.
11 upsBattery 1.3.6.1.2.1.33.1.2 objectidentity
12 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.
13 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.
14 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.
15 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.
16 upsBatteryVoltage 1.3.6.1.2.1.33.1.2.5 nonnegativeinteger read-only objecttype scalar The magnitude of the present battery voltage.
17 upsBatteryCurrent 1.3.6.1.2.1.33.1.2.6 integer32 read-only objecttype scalar The present battery current.
18 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.
19 upsInput 1.3.6.1.2.1.33.1.3 objectidentity
20 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.
21 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.
22 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.
23 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.
24 upsInputLineIndex 1.3.6.1.2.1.33.1.3.3.1.1 positiveinteger no-access objecttype column The input line identifier.
25 upsInputFrequency 1.3.6.1.2.1.33.1.3.3.1.2 nonnegativeinteger read-only objecttype column The present input frequency.
26 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.
27 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.
28 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.
29 upsOutput 1.3.6.1.2.1.33.1.4 objectidentity
30 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.
31 upsOutputFrequency 1.3.6.1.2.1.33.1.4.2 nonnegativeinteger read-only objecttype scalar The present output frequency.
32 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.
33 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.
34 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.
35 upsOutputLineIndex 1.3.6.1.2.1.33.1.4.4.1.1 positiveinteger no-access objecttype column The output line identifier.
36 upsOutputVoltage 1.3.6.1.2.1.33.1.4.4.1.2 nonnegativeinteger read-only objecttype column The present output voltage.
37 upsOutputCurrent 1.3.6.1.2.1.33.1.4.4.1.3 nonnegativeinteger read-only objecttype column The present output current.
38 upsOutputPower 1.3.6.1.2.1.33.1.4.4.1.4 nonnegativeinteger read-only objecttype column The present output true power.
39 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.
40 upsBypass 1.3.6.1.2.1.33.1.5 objectidentity
41 upsBypassFrequency 1.3.6.1.2.1.33.1.5.1 nonnegativeinteger read-only objecttype scalar The present bypass frequency.
42 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.
43 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.
44 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.
45 upsBypassLineIndex 1.3.6.1.2.1.33.1.5.3.1.1 positiveinteger no-access objecttype column The bypass line identifier.
46 upsBypassVoltage 1.3.6.1.2.1.33.1.5.3.1.2 nonnegativeinteger read-only objecttype column The present bypass voltage.
47 upsBypassCurrent 1.3.6.1.2.1.33.1.5.3.1.3 nonnegativeinteger read-only objecttype column The present bypass current.
48 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.
49 upsAlarm 1.3.6.1.2.1.33.1.6 objectidentity
50 upsAlarmsPresent 1.3.6.1.2.1.33.1.6.1 gauge32 read-only objecttype scalar The present number of active alarm conditions.
51 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.
52 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.
53 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.
54 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.
55 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.
56 upsWellKnownAlarms 1.3.6.1.2.1.33.1.6.3 objectidentity
57 upsAlarmBatteryBad 1.3.6.1.2.1.33.1.6.3.1 objectidentity One or more batteries have been determined to require replacement.
58 upsAlarmOnBattery 1.3.6.1.2.1.33.1.6.3.2 objectidentity The UPS is drawing power from the batteries.
59 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.
60 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.
61 upsAlarmTempBad 1.3.6.1.2.1.33.1.6.3.5 objectidentity A temperature is out of tolerance.
62 upsAlarmInputBad 1.3.6.1.2.1.33.1.6.3.6 objectidentity An input condition is out of tolerance.
63 upsAlarmOutputBad 1.3.6.1.2.1.33.1.6.3.7 objectidentity An output condition (other than OutputOverload) is out of tolerance.
64 upsAlarmOutputOverload 1.3.6.1.2.1.33.1.6.3.8 objectidentity The output load exceeds the UPS output capacity.
65 upsAlarmOnBypass 1.3.6.1.2.1.33.1.6.3.9 objectidentity The Bypass is presently engaged on the UPS.
66 upsAlarmBypassBad 1.3.6.1.2.1.33.1.6.3.10 objectidentity The Bypass is out of tolerance.
67 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.
68 upsAlarmUpsOffAsRequested 1.3.6.1.2.1.33.1.6.3.12 objectidentity The entire UPS has shutdown as commanded.
69 upsAlarmChargerFailed 1.3.6.1.2.1.33.1.6.3.13 objectidentity An uncorrected problem has been detected within the UPS charger subsystem.
70 upsAlarmUpsOutputOff 1.3.6.1.2.1.33.1.6.3.14 objectidentity The output of the UPS is in the off state.
71 upsAlarmUpsSystemOff 1.3.6.1.2.1.33.1.6.3.15 objectidentity The UPS system is in the off state.
72 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.
73 upsAlarmFuseFailure 1.3.6.1.2.1.33.1.6.3.17 objectidentity The failure of one or more fuses has been detected.
74 upsAlarmGeneralFault 1.3.6.1.2.1.33.1.6.3.18 objectidentity A general fault in the UPS has been detected.
75 upsAlarmDiagnosticTestFailed 1.3.6.1.2.1.33.1.6.3.19 objectidentity The result of the last diagnostic test indicates a failure.
76 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.
77 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.
78 upsAlarmShutdownPending 1.3.6.1.2.1.33.1.6.3.22 objectidentity A upsShutdownAfterDelay countdown is underway.
79 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.
80 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.
81 upsTest 1.3.6.1.2.1.33.1.7 objectidentity
82 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.
83 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.
84 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.
85 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.
86 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.
87 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.
88 upsWellKnownTests 1.3.6.1.2.1.33.1.7.7 objectidentity
89 upsTestNoTestsInitiated 1.3.6.1.2.1.33.1.7.7.1 objectidentity No tests have been initiated and no test is in progress.
90 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.
91 upsTestGeneralSystemsTest 1.3.6.1.2.1.33.1.7.7.3 objectidentity The manufacturer's standard test of UPS device systems.
92 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.
93 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.
94 upsControl 1.3.6.1.2.1.33.1.8 objectidentity
95 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.
96 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.
97 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.
98 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.
99 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.
100 upsConfig 1.3.6.1.2.1.33.1.9 objectidentity
101 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.
102 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.
103 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.
104 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.
105 upsConfigOutputVA 1.3.6.1.2.1.33.1.9.5 nonnegativeinteger read-only objecttype scalar The magnitude of the nominal Volt-Amp rating.
106 upsConfigOutputPower 1.3.6.1.2.1.33.1.9.6 nonnegativeinteger read-only objecttype scalar The magnitude of the nominal true power rating.
107 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.
108 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.
109 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.
110 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.
111 upsTraps 1.3.6.1.2.1.33.2 objectidentity
112 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.
113 upsTrapTestCompleted 1.3.6.1.2.1.33.2.2 notificationtype This trap is sent upon completion of a UPS diagnostic test.
114 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.
115 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.
116 upsConformance 1.3.6.1.2.1.33.3 objectidentity
117 upsCompliances 1.3.6.1.2.1.33.3.1 objectidentity
118 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.
119 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.
120 upsFullCompliance 1.3.6.1.2.1.33.3.1.3 modulecompliance The compliance statement for UPSs that support advanced full-featured functions.
121 upsGroups 1.3.6.1.2.1.33.3.2 objectidentity
122 upsSubsetGroups 1.3.6.1.2.1.33.3.2.1 objectidentity
123 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).
124 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.
125 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.
126 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.
127 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.
128 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.
129 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.
130 upsBasicGroups 1.3.6.1.2.1.33.3.2.2 objectidentity
131 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.
132 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.
133 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.
134 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.
135 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.
136 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.
137 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.
138 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.
139 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.
140 upsFullGroups 1.3.6.1.2.1.33.3.2.3 objectidentity
141 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.
142 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.
143 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.
144 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.
145 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.
146 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.
147 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.
148 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.
149 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.

View 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)

View 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

View 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;
}

View File

@@ -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,
};

View File

@@ -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,
};

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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);