mirror of
https://github.com/eddyem/small_tel.git
synced 2026-05-08 22:06:56 +03:00
Compare commits
27 Commits
6ea6bad008
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e05bdd807 | |||
| 46de782019 | |||
| 63f87d2283 | |||
| c5b9d43797 | |||
| 2413661e19 | |||
| 05e57ef012 | |||
| 27cfe60fe8 | |||
| 347d02e748 | |||
| 68febd02c4 | |||
|
|
7b2d93299d | ||
| 5acd1cd97d | |||
| af33a036c8 | |||
| 987cf022fe | |||
| 5be6876f9e | |||
| e551b94499 | |||
| 39d4e22061 | |||
| 05a42b0a10 | |||
| cc870491f5 | |||
| 9d58bf1694 | |||
| a324302404 | |||
| 7318307d4d | |||
| 0f0c87ee2f | |||
| cae7a6e213 | |||
|
|
df4a597aa8 | ||
|
|
c74d1e6026 | ||
| 734e36a85d | |||
| 9f2e893f1a |
25
.gitignore
vendored
25
.gitignore
vendored
@@ -1,3 +1,17 @@
|
|||||||
|
# executables
|
||||||
|
*
|
||||||
|
!*.*
|
||||||
|
!*/
|
||||||
|
!Makefile
|
||||||
|
![Rr][Ee][Aa][Dd][Mm][Ee]
|
||||||
|
|
||||||
|
# build dirs
|
||||||
|
mk/
|
||||||
|
build/
|
||||||
|
.qtc_clangd
|
||||||
|
.qtcreator
|
||||||
|
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
*.d
|
*.d
|
||||||
|
|
||||||
@@ -23,6 +37,11 @@
|
|||||||
*.so
|
*.so
|
||||||
*.so.*
|
*.so.*
|
||||||
|
|
||||||
# build dirs
|
|
||||||
mk/
|
# diff
|
||||||
build/
|
.qtcreator/
|
||||||
|
*.log
|
||||||
|
*.tgz
|
||||||
|
*.tar.gz
|
||||||
|
*.7z
|
||||||
|
*.zip
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ set(CMAKE_COLOR_MAKEFILE ON)
|
|||||||
|
|
||||||
option(DEBUG "Compile in debug mode" OFF)
|
option(DEBUG "Compile in debug mode" OFF)
|
||||||
option(EXAMPLES "Compile also some examples" ON)
|
option(EXAMPLES "Compile also some examples" ON)
|
||||||
|
option(BUILD_SHARED "Build shared libarary" OFF)
|
||||||
|
|
||||||
|
|
||||||
# cmake -DDEBUG=on -> debugging
|
# cmake -DDEBUG=on -> debugging
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
@@ -52,7 +54,13 @@ aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
|
|||||||
#list(APPEND ${PROJ}_LIBRARIES "-lfftw3_threads")
|
#list(APPEND ${PROJ}_LIBRARIES "-lfftw3_threads")
|
||||||
|
|
||||||
# library
|
# library
|
||||||
|
|
||||||
|
if(BUILD_SHARED)
|
||||||
add_library(${PROJ} SHARED ${SOURCES})
|
add_library(${PROJ} SHARED ${SOURCES})
|
||||||
|
else()
|
||||||
|
add_library(${PROJ} STATIC ${SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
# library header files
|
# library header files
|
||||||
set(LIBHEADER "sidservo.h")
|
set(LIBHEADER "sidservo.h")
|
||||||
# -I
|
# -I
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <float.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -25,34 +26,70 @@
|
|||||||
#include "PID.h"
|
#include "PID.h"
|
||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
|
|
||||||
PIDController_t *pid_create(const PIDpar_t *gain, size_t Iarrsz){
|
typedef struct {
|
||||||
|
PIDpar_t gain; // PID gains
|
||||||
|
double prev_error; // Previous error
|
||||||
|
double prev_tagpos; // previous target position
|
||||||
|
double integral; // Integral term
|
||||||
|
double *pidIarray; // array for Integral
|
||||||
|
struct timespec prevT; // time of previous correction
|
||||||
|
size_t pidIarrSize; // it's size
|
||||||
|
size_t curIidx; // and index of current element
|
||||||
|
} PIDController_t;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
axis_status_t state;
|
||||||
|
coordval_t position;
|
||||||
|
coordval_t speed;
|
||||||
|
} axisdata_t;
|
||||||
|
|
||||||
|
static PIDController_t *pid_create(const PIDpar_t *gain, size_t Iarrsz){
|
||||||
if(!gain || Iarrsz < 3) return NULL;
|
if(!gain || Iarrsz < 3) return NULL;
|
||||||
PIDController_t *pid = (PIDController_t*)calloc(1, sizeof(PIDController_t));
|
PIDController_t *pid = (PIDController_t*)calloc(1, sizeof(PIDController_t));
|
||||||
pid->gain = *gain;
|
pid->gain = *gain;
|
||||||
DBG("Created PID with P=%g, I=%g, D=%g\n", gain->P, gain->I, gain->D);
|
DBG("Created PID with P=%g, I=%g, D=%g\n", gain->P, gain->I, gain->D);
|
||||||
pid->pidIarrSize = Iarrsz;
|
pid->pidIarrSize = Iarrsz;
|
||||||
pid->pidIarray = (double*)calloc(Iarrsz, sizeof(double));
|
pid->pidIarray = (double*)calloc(Iarrsz, sizeof(double));
|
||||||
|
curtime(&pid->prevT);
|
||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't clear lastT!
|
// don't clear lastT!
|
||||||
void pid_clear(PIDController_t *pid){
|
static void pid_clear(PIDController_t *pid){
|
||||||
if(!pid) return;
|
if(!pid) return;
|
||||||
DBG("CLEAR PID PARAMETERS");
|
DBG("CLEAR PID PARAMETERS");
|
||||||
bzero(pid->pidIarray, sizeof(double) * pid->pidIarrSize);
|
bzero(pid->pidIarray, sizeof(double) * pid->pidIarrSize);
|
||||||
pid->integral = 0.;
|
pid->integral = 0.;
|
||||||
pid->prev_error = 0.;
|
pid->prev_error = 0.;
|
||||||
pid->curIidx = 0;
|
pid->curIidx = 0;
|
||||||
|
curtime(&pid->prevT);
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
void pid_delete(PIDController_t **pid){
|
static void pid_delete(PIDController_t **pid){
|
||||||
if(!pid || !*pid) return;
|
if(!pid || !*pid) return;
|
||||||
if((*pid)->pidIarray) free((*pid)->pidIarray);
|
if((*pid)->pidIarray) free((*pid)->pidIarray);
|
||||||
free(*pid);
|
free(*pid);
|
||||||
*pid = NULL;
|
*pid = NULL;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
double pid_calculate(PIDController_t *pid, double error, double dt){
|
// calculate new motor speed
|
||||||
|
static double pid_calculate(PIDController_t *pid, double axispos, const coordval_t *target){
|
||||||
|
double dtpid = timediff(&target->t, &pid->prevT);
|
||||||
|
if(dtpid < 0 || dtpid > Conf.PIDMaxDt){
|
||||||
|
DBG("time diff too big: clear PID");
|
||||||
|
pid_clear(pid);
|
||||||
|
pid->prev_tagpos = target->val;
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
double dt = timediff(&target->t, &pid->prevT);
|
||||||
|
if(dt < FLT_EPSILON){
|
||||||
|
DBG("Target time in past");
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
pid->prevT = target->t;
|
||||||
|
double error = target->val - axispos;
|
||||||
|
double tagspeed = (target->val - pid->prev_tagpos) / dt;
|
||||||
|
pid->prev_tagpos = target->val;
|
||||||
// calculate flowing integral
|
// calculate flowing integral
|
||||||
double oldi = pid->pidIarray[pid->curIidx], newi = error * dt;
|
double oldi = pid->pidIarray[pid->curIidx], newi = error * dt;
|
||||||
//DBG("oldi/new: %g, %g", oldi, newi);
|
//DBG("oldi/new: %g, %g", oldi, newi);
|
||||||
@@ -61,43 +98,33 @@ double pid_calculate(PIDController_t *pid, double error, double dt){
|
|||||||
pid->integral += newi - oldi;
|
pid->integral += newi - oldi;
|
||||||
double derivative = (error - pid->prev_error) / dt;
|
double derivative = (error - pid->prev_error) / dt;
|
||||||
pid->prev_error = error;
|
pid->prev_error = error;
|
||||||
double sum = pid->gain.P * error + pid->gain.I * pid->integral + pid->gain.D * derivative;
|
DBG("pid pars: P=%g, I=%g, D=%f", pid->gain.P, pid->gain.I, pid->gain.D);
|
||||||
DBG("P=%g, I=%g, D=%g; sum=%g", pid->gain.P * error, pid->gain.I * pid->integral, pid->gain.D * derivative, sum);
|
double sum = pid->gain.P * error + pid->gain.I * pid->integral + pid->gain.D * derivative + tagspeed;
|
||||||
|
DBG("tagspeed=%g, P=%g, I=%g, D=%g; sum=%g", tagspeed, pid->gain.P * error,
|
||||||
|
pid->gain.I * pid->integral, pid->gain.D * derivative, sum);
|
||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct{
|
|
||||||
PIDController_t *PIDC;
|
|
||||||
PIDController_t *PIDV;
|
|
||||||
} PIDpair_t;
|
|
||||||
|
|
||||||
typedef struct{
|
|
||||||
axis_status_t state;
|
|
||||||
coordval_t position;
|
|
||||||
coordval_t speed;
|
|
||||||
} axisdata_t;
|
|
||||||
/**
|
/**
|
||||||
* @brief process - Process PID for given axis
|
* @brief process - Process PID for given axis
|
||||||
* @param tagpos - given coordinate of target position
|
* @param tagpos - given coordinate of target position
|
||||||
* @param endpoint - endpoint for this coordinate
|
* @param endpoint - endpoint for this coordinate
|
||||||
* @param pid - pid itself
|
* @param pid - pid itself
|
||||||
* @return calculated new speed or -1 for max speed
|
* @return calculated NEW SPEED or NAN for max speed
|
||||||
*/
|
*/
|
||||||
static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t *axis){
|
static double getspeed(const coordval_t *tagpos, PIDController_t *pid, axisdata_t *axis){
|
||||||
double dt = timediff(&tagpos->t, &axis->position.t);
|
double dt = timediff(&tagpos->t, &axis->position.t);
|
||||||
if(dt < 0 || dt > Conf.PIDMaxDt){
|
if(dt < 0 || dt > Conf.PIDMaxDt){
|
||||||
DBG("target time: %ld, axis time: %ld - too big! (tag-ax=%g)", tagpos->t.tv_sec, axis->position.t.tv_sec, dt);
|
DBG("target time: %ld, axis time: %ld - too big! (tag-ax=%g)", tagpos->t.tv_sec, axis->position.t.tv_sec, dt);
|
||||||
return axis->speed.val; // data is too old or wrong
|
return axis->speed.val; // data is too old or wrong
|
||||||
}
|
}
|
||||||
double error = tagpos->val - axis->position.val, fe = fabs(error);
|
double error = tagpos->val - axis->position.val, fe = fabs(error);
|
||||||
DBG("error: %g", error);
|
DBG("error: %g'', cur speed: %g (deg/s)", error * 180. * 3600. / M_PI, axis->speed.val*180./M_PI);
|
||||||
PIDController_t *pid = NULL;
|
|
||||||
switch(axis->state){
|
switch(axis->state){
|
||||||
case AXIS_SLEWING:
|
case AXIS_SLEWING:
|
||||||
if(fe < Conf.MaxPointingErr){
|
if(fe < Conf.MaxFinePointingErr){
|
||||||
axis->state = AXIS_POINTING;
|
axis->state = AXIS_POINTING;
|
||||||
DBG("--> Pointing");
|
DBG("--> Pointing");
|
||||||
pid = pidpair->PIDC;
|
|
||||||
}else{
|
}else{
|
||||||
DBG("Slewing...");
|
DBG("Slewing...");
|
||||||
return NAN; // max speed for given axis
|
return NAN; // max speed for given axis
|
||||||
@@ -107,28 +134,26 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
|
|||||||
if(fe < Conf.MaxFinePointingErr){
|
if(fe < Conf.MaxFinePointingErr){
|
||||||
axis->state = AXIS_GUIDING;
|
axis->state = AXIS_GUIDING;
|
||||||
DBG("--> Guiding");
|
DBG("--> Guiding");
|
||||||
pid = pidpair->PIDV;
|
|
||||||
}else if(fe > Conf.MaxPointingErr){
|
}else if(fe > Conf.MaxPointingErr){
|
||||||
DBG("--> Slewing");
|
DBG("--> Slewing");
|
||||||
axis->state = AXIS_SLEWING;
|
axis->state = AXIS_SLEWING;
|
||||||
return NAN;
|
return NAN;
|
||||||
} else pid = pidpair->PIDC;
|
}
|
||||||
break;
|
break;
|
||||||
case AXIS_GUIDING:
|
case AXIS_GUIDING:
|
||||||
pid = pidpair->PIDV;
|
|
||||||
if(fe > Conf.MaxFinePointingErr){
|
if(fe > Conf.MaxFinePointingErr){
|
||||||
DBG("--> Pointing");
|
DBG("--> Pointing");
|
||||||
axis->state = AXIS_POINTING;
|
axis->state = AXIS_POINTING;
|
||||||
pid = pidpair->PIDC;
|
|
||||||
}else if(fe < Conf.MaxGuidingErr){
|
}else if(fe < Conf.MaxGuidingErr){
|
||||||
DBG("At target");
|
DBG("At target");
|
||||||
// TODO: we can point somehow that we are at target or introduce new axis state
|
// TODO: we can point somehow that we are at target or introduce new axis state
|
||||||
}else DBG("Current error: %g", fe);
|
}else DBG("Current abs error: %g", fe);
|
||||||
break;
|
break;
|
||||||
|
case AXIS_GONNASTOP:
|
||||||
case AXIS_STOPPED: // start pointing to target; will change speed next time
|
case AXIS_STOPPED: // start pointing to target; will change speed next time
|
||||||
DBG("AXIS STOPPED!!!! --> Slewing");
|
DBG("AXIS STOPPED!!!! --> Slewing");
|
||||||
axis->state = AXIS_SLEWING;
|
axis->state = AXIS_SLEWING;
|
||||||
return getspeed(tagpos, pidpair, axis);
|
return getspeed(tagpos, pid, axis);
|
||||||
case AXIS_ERROR:
|
case AXIS_ERROR:
|
||||||
DBG("Can't move from erroneous state");
|
DBG("Can't move from erroneous state");
|
||||||
return 0.;
|
return 0.;
|
||||||
@@ -137,17 +162,7 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
|
|||||||
DBG("WTF? Where is a PID?");
|
DBG("WTF? Where is a PID?");
|
||||||
return axis->speed.val;
|
return axis->speed.val;
|
||||||
}
|
}
|
||||||
double dtpid = timediff(&tagpos->t, &pid->prevT);
|
return pid_calculate(pid, axis->position.val, tagpos);
|
||||||
if(dtpid < 0 || dtpid > Conf.PIDMaxDt){
|
|
||||||
DBG("time diff too big: clear PID");
|
|
||||||
pid_clear(pid);
|
|
||||||
}
|
|
||||||
if(dtpid > Conf.PIDMaxDt) dtpid = Conf.PIDCycleDt;
|
|
||||||
pid->prevT = tagpos->t;
|
|
||||||
DBG("CALC PID (er=%g, dt=%g), state=%d", error, dtpid, axis->state);
|
|
||||||
double tagspeed = pid_calculate(pid, error, dtpid);
|
|
||||||
if(axis->state == AXIS_GUIDING) return axis->speed.val + tagspeed / dtpid; // velocity-based
|
|
||||||
return tagspeed; // coordinate-based
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,18 +172,14 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
|
|||||||
* @return error code
|
* @return error code
|
||||||
*/
|
*/
|
||||||
mcc_errcodes_t correct2(const coordval_pair_t *target){
|
mcc_errcodes_t correct2(const coordval_pair_t *target){
|
||||||
static PIDpair_t pidX = {0}, pidY = {0};
|
static PIDController_t *pidX = NULL, *pidY = NULL;
|
||||||
if(!pidX.PIDC){
|
if(!pidX){
|
||||||
pidX.PIDC = pid_create(&Conf.XPIDC, Conf.PIDCycleDt / Conf.PIDRefreshDt);
|
pidX = pid_create(&Conf.XPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
|
||||||
if(!pidX.PIDC) return MCC_E_FATAL;
|
if(!pidX) return MCC_E_FATAL;
|
||||||
pidX.PIDV = pid_create(&Conf.XPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
|
|
||||||
if(!pidX.PIDV) return MCC_E_FATAL;
|
|
||||||
}
|
}
|
||||||
if(!pidY.PIDC){
|
if(!pidY){
|
||||||
pidY.PIDC = pid_create(&Conf.YPIDC, Conf.PIDCycleDt / Conf.PIDRefreshDt);
|
pidY = pid_create(&Conf.YPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
|
||||||
if(!pidY.PIDC) return MCC_E_FATAL;
|
if(!pidY) return MCC_E_FATAL;
|
||||||
pidY.PIDV = pid_create(&Conf.YPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
|
|
||||||
if(!pidY.PIDV) return MCC_E_FATAL;
|
|
||||||
}
|
}
|
||||||
mountdata_t m;
|
mountdata_t m;
|
||||||
coordpair_t tagspeed; // absolute value of speed
|
coordpair_t tagspeed; // absolute value of speed
|
||||||
@@ -179,7 +190,7 @@ mcc_errcodes_t correct2(const coordval_pair_t *target){
|
|||||||
axis.state = m.Xstate;
|
axis.state = m.Xstate;
|
||||||
axis.position = m.encXposition;
|
axis.position = m.encXposition;
|
||||||
axis.speed = m.encXspeed;
|
axis.speed = m.encXspeed;
|
||||||
tagspeed.X = getspeed(&target->X, &pidX, &axis);
|
tagspeed.X = getspeed(&target->X, pidX, &axis);
|
||||||
if(isnan(tagspeed.X)){ // max speed
|
if(isnan(tagspeed.X)){ // max speed
|
||||||
if(target->X.val < axis.position.val) Xsign = -1.;
|
if(target->X.val < axis.position.val) Xsign = -1.;
|
||||||
tagspeed.X = Xlimits.max.speed;
|
tagspeed.X = Xlimits.max.speed;
|
||||||
@@ -191,7 +202,7 @@ mcc_errcodes_t correct2(const coordval_pair_t *target){
|
|||||||
axis.state = m.Ystate;
|
axis.state = m.Ystate;
|
||||||
axis.position = m.encYposition;
|
axis.position = m.encYposition;
|
||||||
axis.speed = m.encYspeed;
|
axis.speed = m.encYspeed;
|
||||||
tagspeed.Y = getspeed(&target->Y, &pidY, &axis);
|
tagspeed.Y = getspeed(&target->Y, pidY, &axis);
|
||||||
if(isnan(tagspeed.Y)){ // max speed
|
if(isnan(tagspeed.Y)){ // max speed
|
||||||
if(target->Y.val < axis.position.val) Ysign = -1.;
|
if(target->Y.val < axis.position.val) Ysign = -1.;
|
||||||
tagspeed.Y = Ylimits.max.speed;
|
tagspeed.Y = Ylimits.max.speed;
|
||||||
@@ -205,6 +216,7 @@ mcc_errcodes_t correct2(const coordval_pair_t *target){
|
|||||||
setStat(xstate, ystate);
|
setStat(xstate, ystate);
|
||||||
}
|
}
|
||||||
coordpair_t endpoint;
|
coordpair_t endpoint;
|
||||||
|
#if 0
|
||||||
// allow at least PIDMaxDt moving with target speed
|
// allow at least PIDMaxDt moving with target speed
|
||||||
double dv = fabs(tagspeed.X - m.encXspeed.val);
|
double dv = fabs(tagspeed.X - m.encXspeed.val);
|
||||||
double adder = dv/Xlimits.max.accel * (m.encXspeed.val + dv / 2.) // distanse with changing speed
|
double adder = dv/Xlimits.max.accel * (m.encXspeed.val + dv / 2.) // distanse with changing speed
|
||||||
@@ -216,6 +228,16 @@ mcc_errcodes_t correct2(const coordval_pair_t *target){
|
|||||||
+ Conf.PIDMaxDt * tagspeed.Y
|
+ Conf.PIDMaxDt * tagspeed.Y
|
||||||
+ tagspeed.Y * tagspeed.Y / Ylimits.max.accel / 2.;
|
+ tagspeed.Y * tagspeed.Y / Ylimits.max.accel / 2.;
|
||||||
endpoint.Y = m.encYposition.val + Ysign * adder;
|
endpoint.Y = m.encYposition.val + Ysign * adder;
|
||||||
|
#endif
|
||||||
|
// allow 10s moving but not more than 10deg and not less than 1deg
|
||||||
|
double adder = fabs(tagspeed.X) * 10.;
|
||||||
|
if(adder > 0.17453) adder = 0.17453;
|
||||||
|
else if(adder < 0.017453) adder = 0.017453;
|
||||||
|
endpoint.X = m.encXposition.val + Xsign * adder;
|
||||||
|
adder = fabs(tagspeed.Y) * 10.;
|
||||||
|
if(adder > 0.17453) adder = 0.17453;
|
||||||
|
else if(adder < 0.017453) adder = 0.017453;
|
||||||
|
endpoint.Y = m.encYposition.val + Ysign * adder;
|
||||||
DBG("TAG speeds: %g/%g (deg/s); TAG pos: %g/%g (deg)", tagspeed.X/M_PI*180., tagspeed.Y/M_PI*180., endpoint.X/M_PI*180., endpoint.Y/M_PI*180.);
|
DBG("TAG speeds: %g/%g (deg/s); TAG pos: %g/%g (deg)", tagspeed.X/M_PI*180., tagspeed.Y/M_PI*180., endpoint.X/M_PI*180., endpoint.Y/M_PI*180.);
|
||||||
return Mount.moveWspeed(&endpoint, &tagspeed);
|
return Mount.moveWspeed(&endpoint, &tagspeed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,19 +22,10 @@
|
|||||||
|
|
||||||
#include "sidservo.h"
|
#include "sidservo.h"
|
||||||
|
|
||||||
typedef struct {
|
/*
|
||||||
PIDpar_t gain; // PID gains
|
|
||||||
double prev_error; // Previous error
|
|
||||||
double integral; // Integral term
|
|
||||||
double *pidIarray; // array for Integral
|
|
||||||
struct timespec prevT; // time of previous correction
|
|
||||||
size_t pidIarrSize; // it's size
|
|
||||||
size_t curIidx; // and index of current element
|
|
||||||
} PIDController_t;
|
|
||||||
|
|
||||||
PIDController_t *pid_create(const PIDpar_t *gain, size_t Iarrsz);
|
PIDController_t *pid_create(const PIDpar_t *gain, size_t Iarrsz);
|
||||||
void pid_clear(PIDController_t *pid);
|
void pid_clear(PIDController_t *pid);
|
||||||
void pid_delete(PIDController_t **pid);
|
void pid_delete(PIDController_t **pid);
|
||||||
double pid_calculate(PIDController_t *pid, double error, double dt);
|
double pid_calculate(PIDController_t *pid, double error, double dt);
|
||||||
|
*/
|
||||||
mcc_errcodes_t correct2(const coordval_pair_t *target);
|
mcc_errcodes_t correct2(const coordval_pair_t *target);
|
||||||
|
|||||||
@@ -65,8 +65,12 @@ void signals(int sig){
|
|||||||
signal(sig, SIG_IGN);
|
signal(sig, SIG_IGN);
|
||||||
DBG("Get signal %d, quit.\n", sig);
|
DBG("Get signal %d, quit.\n", sig);
|
||||||
}
|
}
|
||||||
|
DBG("Stop!");
|
||||||
|
Mount.stop();
|
||||||
|
usleep(10000);
|
||||||
DBG("Quit");
|
DBG("Quit");
|
||||||
Mount.quit();
|
Mount.quit();
|
||||||
|
usleep(10000);
|
||||||
DBG("close");
|
DBG("close");
|
||||||
if(fcoords) fclose(fcoords);
|
if(fcoords) fclose(fcoords);
|
||||||
exit(sig);
|
exit(sig);
|
||||||
|
|||||||
@@ -109,17 +109,27 @@ static void runtraectory(traectory_fn tfn){
|
|||||||
}
|
}
|
||||||
if(!Mount.currentT(&tcur)) continue;
|
if(!Mount.currentT(&tcur)) continue;
|
||||||
if(telXY.X.t.tv_nsec == tlastXnsec && telXY.Y.t.tv_nsec == tlastYnsec) continue; // last measure - don't mind
|
if(telXY.X.t.tv_nsec == tlastXnsec && telXY.Y.t.tv_nsec == tlastYnsec) continue; // last measure - don't mind
|
||||||
DBG("\n\nTELPOS: %g'/%g' (%.6f/%.6f)", RAD2AMIN(telXY.X.val), RAD2AMIN(telXY.Y.val), RAD2DEG(telXY.X.val), RAD2DEG(telXY.Y.val));
|
|
||||||
tlastXnsec = telXY.X.t.tv_nsec; tlastYnsec = telXY.Y.t.tv_nsec;
|
tlastXnsec = telXY.X.t.tv_nsec; tlastYnsec = telXY.Y.t.tv_nsec;
|
||||||
double t = Mount.timeFromStart();
|
double t = Mount.timeFromStart();
|
||||||
if(fabs(telXY.X.val) > G.Xmax || fabs(telXY.Y.val) > G.Ymax || t - tstart > G.tmax) break;
|
if(fabs(telXY.X.val) > G.Xmax || fabs(telXY.Y.val) > G.Ymax || t - tstart > G.tmax){
|
||||||
if(!traectory_point(&traectXY, t)) break;
|
if(fabs(telXY.X.val) > G.Xmax) DBG("X over maximal limit!");
|
||||||
|
if(fabs(telXY.Y.val) > G.Ymax) DBG("Y over maximal limit!");
|
||||||
|
if(t - tstart > G.tmax) DBG("Time over...");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(!traectory_point(&traectXY, t)){
|
||||||
|
DBG("Error in 'traectory_point', time from start=%g", t);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DBG("\n\nTELPOS: %g'/%g' (%.6f/%.6f); traectory: %g'/%g' (%.6f/%.6f)",
|
||||||
|
RAD2AMIN(telXY.X.val), RAD2AMIN(telXY.Y.val), RAD2DEG(telXY.X.val), RAD2DEG(telXY.Y.val),
|
||||||
|
RAD2AMIN(traectXY.X), RAD2AMIN(traectXY.Y), RAD2DEG(traectXY.X), RAD2DEG(traectXY.Y));
|
||||||
target.X.val = traectXY.X; target.Y.val = traectXY.Y;
|
target.X.val = traectXY.X; target.Y.val = traectXY.Y;
|
||||||
target.X.t = target.Y.t = tcur;
|
target.X.t = target.Y.t = tcur;
|
||||||
if(t0.tv_nsec == 0 && t0.tv_sec == 0) dumpt0(&t0);
|
if(t0.tv_nsec == 0 && t0.tv_sec == 0) dumpt0(&t0);
|
||||||
else{
|
else{
|
||||||
//DBG("target: %g'/%g'", RAD2AMIN(traectXY.X), RAD2AMIN(traectXY.Y));
|
//DBG("target: %g'/%g'", RAD2AMIN(traectXY.X), RAD2AMIN(traectXY.Y));
|
||||||
DBG("%g: dX=%.4f'', dY=%.4f''", t-tstart, RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val));
|
DBG("%g, target-telescope: dX=%.4f'', dY=%.4f''", t-tstart, RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val));
|
||||||
//DBG("Correct to: %g/%g with EP %g/%g", RAD2DEG(target.X.val), RAD2DEG(target.Y.val), RAD2DEG(endpoint.X), RAD2DEG(endpoint.Y));
|
//DBG("Correct to: %g/%g with EP %g/%g", RAD2DEG(target.X.val), RAD2DEG(target.Y.val), RAD2DEG(endpoint.X), RAD2DEG(endpoint.Y));
|
||||||
if(errlog)
|
if(errlog)
|
||||||
fprintf(errlog, "%10.4f %10.4f %10.4f\n", Mount.timeDiff(&telXY.X.t, &t0), RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val));
|
fprintf(errlog, "%10.4f %10.4f %10.4f\n", Mount.timeDiff(&telXY.X.t, &t0), RAD2ASEC(traectXY.X-telXY.X.val), RAD2ASEC(traectXY.Y-telXY.Y.val));
|
||||||
@@ -146,10 +156,12 @@ int main(int argc, char **argv){
|
|||||||
ERRX("Can't open error log %s", G.errlog);
|
ERRX("Can't open error log %s", G.errlog);
|
||||||
else
|
else
|
||||||
fprintf(errlog, "# time Xerr'' Yerr'' // target - real\n");
|
fprintf(errlog, "# time Xerr'' Yerr'' // target - real\n");
|
||||||
|
setbuf(errlog, NULL);
|
||||||
}
|
}
|
||||||
if(G.coordsoutput){
|
if(G.coordsoutput){
|
||||||
if(!(fcoords = fopen(G.coordsoutput, "w")))
|
if(!(fcoords = fopen(G.coordsoutput, "w")))
|
||||||
ERRX("Can't open %s", G.coordsoutput);
|
ERRX("Can't open %s", G.coordsoutput);
|
||||||
|
setbuf(fcoords, NULL);
|
||||||
}else fcoords = stdout;
|
}else fcoords = stdout;
|
||||||
Config = readServoConf(G.conffile);
|
Config = readServoConf(G.conffile);
|
||||||
if(!Config || G.dumpconf){
|
if(!Config || G.dumpconf){
|
||||||
@@ -164,21 +176,22 @@ int main(int argc, char **argv){
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
coordpair_t c = {.X = DEG2RAD(G.X0), .Y = DEG2RAD(G.Y0)};
|
coordpair_t c = {.X = DEG2RAD(G.X0), .Y = DEG2RAD(G.Y0)};
|
||||||
if(!init_traectory(tfn, &c)){
|
|
||||||
ERRX("Can't init traectory");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
mcc_errcodes_t e = Mount.init(Config);
|
mcc_errcodes_t e = Mount.init(Config);
|
||||||
if(e != MCC_E_OK){
|
if(e != MCC_E_OK){
|
||||||
WARNX("Can't init devices");
|
WARNX("Can't init devices");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
// run this function only after mount inited!
|
||||||
|
if(!init_traectory(tfn, &c)){
|
||||||
|
ERRX("Can't init traectory");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
signal(SIGTERM, signals); // kill (-15) - quit
|
signal(SIGTERM, signals); // kill (-15) - quit
|
||||||
signal(SIGHUP, SIG_IGN); // hup - ignore
|
signal(SIGHUP, SIG_IGN); // hup - ignore
|
||||||
signal(SIGINT, signals); // ctrl+C - quit
|
signal(SIGINT, signals); // ctrl+C - quit
|
||||||
signal(SIGQUIT, signals); // ctrl+\ - quit
|
signal(SIGQUIT, signals); // ctrl+\ - quit
|
||||||
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
|
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
|
||||||
chk0(G.Ncycles);
|
// chk0(G.Ncycles);
|
||||||
logmnt(fcoords, NULL);
|
logmnt(fcoords, NULL);
|
||||||
if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread");
|
if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread");
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ int init_traectory(traectory_fn f, coordpair_t *XY0){
|
|||||||
cur_traectory = f;
|
cur_traectory = f;
|
||||||
XYstart = *XY0;
|
XYstart = *XY0;
|
||||||
tstart = Mount.timeFromStart();
|
tstart = Mount.timeFromStart();
|
||||||
|
DBG("inited @ %gs from start", tstart);
|
||||||
mountdata_t mdata;
|
mountdata_t mdata;
|
||||||
int ntries = 0;
|
int ntries = 0;
|
||||||
for(; ntries < 10; ++ntries){
|
for(; ntries < 10; ++ntries){
|
||||||
@@ -58,11 +59,21 @@ int init_traectory(traectory_fn f, coordpair_t *XY0){
|
|||||||
* @return FALSE if something wrong (e.g. X not in -90..90 or Y not in -180..180)
|
* @return FALSE if something wrong (e.g. X not in -90..90 or Y not in -180..180)
|
||||||
*/
|
*/
|
||||||
int traectory_point(coordpair_t *nextpt, double t){
|
int traectory_point(coordpair_t *nextpt, double t){
|
||||||
if(t < 0. || !cur_traectory) return FALSE;
|
if(t < 0. || !cur_traectory){
|
||||||
|
if(t < 0.) DBG("time in past!");
|
||||||
|
else DBG("no current traectory selected!");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
coordpair_t pt;
|
coordpair_t pt;
|
||||||
if(!cur_traectory(&pt, t)) return FALSE;
|
if(!cur_traectory(&pt, t)){
|
||||||
|
DBG("error in cur_traectory");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
if(nextpt) *nextpt = pt;
|
if(nextpt) *nextpt = pt;
|
||||||
if(pt.X < -M_PI_2 || pt.X > M_PI_2 || pt.Y < -M_PI || pt.Y > M_PI) return FALSE;
|
if(pt.X < -M_PI_2 || pt.X > M_PI_2 || pt.Y < -M_PI || pt.Y > M_PI){
|
||||||
|
DBG("some points is over limits, pt.x=%g, pt.y=%g degrees", RAD2DEG(pt.X), RAD2DEG(pt.Y));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +99,7 @@ int telpos(coordval_pair_t *curpos){
|
|||||||
// X=X0+1'/s, Y=Y0+15''/s
|
// X=X0+1'/s, Y=Y0+15''/s
|
||||||
int Linear(coordpair_t *nextpt, double t){
|
int Linear(coordpair_t *nextpt, double t){
|
||||||
coordpair_t pt;
|
coordpair_t pt;
|
||||||
|
DBG("t=%g, tfromstart=%g, dt=%g", t, tstart, t-tstart);
|
||||||
pt.X = XYstart.X + ASEC2RAD(0.1) * (t - tstart);
|
pt.X = XYstart.X + ASEC2RAD(0.1) * (t - tstart);
|
||||||
pt.Y = XYstart.Y + ASEC2RAD(15.)* (t - tstart);
|
pt.Y = XYstart.Y + ASEC2RAD(15.)* (t - tstart);
|
||||||
if(nextpt) *nextpt = pt;
|
if(nextpt) *nextpt = pt;
|
||||||
@@ -98,7 +110,7 @@ int Linear(coordpair_t *nextpt, double t){
|
|||||||
int SinCos(coordpair_t *nextpt, double t){
|
int SinCos(coordpair_t *nextpt, double t){
|
||||||
coordpair_t pt;
|
coordpair_t pt;
|
||||||
pt.X = XYstart.X + ASEC2RAD(5.) * sin((t-tstart)/30.*2*M_PI);
|
pt.X = XYstart.X + ASEC2RAD(5.) * sin((t-tstart)/30.*2*M_PI);
|
||||||
pt.Y = XYstart.Y + AMIN2RAD(10.)* cos((t-tstart)/200.*2*M_PI);
|
pt.Y = XYstart.Y + AMIN2RAD(1.)* cos((t-tstart)/10.*2*M_PI);
|
||||||
if(nextpt) *nextpt = pt;
|
if(nextpt) *nextpt = pt;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
@@ -111,7 +123,7 @@ typedef struct{
|
|||||||
|
|
||||||
static tr_names names[] = {
|
static tr_names names[] = {
|
||||||
{Linear, "linear", "X=X0+0.1''/s, Y=Y0+15''/s"},
|
{Linear, "linear", "X=X0+0.1''/s, Y=Y0+15''/s"},
|
||||||
{SinCos, "sincos", "X=X0+5''*sin(t/30*2pi), Y=Y0+10'*cos(t/200*2pi)"},
|
{SinCos, "sincos", "X=X0+5''*sin(t/30*2pi), Y=Y0+1'*cos(t/10*2pi)"},
|
||||||
{NULL, NULL, NULL}
|
{NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
169
Auxiliary_utils/LibSidServo/kalman.c
Normal file
169
Auxiliary_utils/LibSidServo/kalman.c
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the libsidservo 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 "kalman.h"
|
||||||
|
|
||||||
|
void kalman3_init(Kalman3 *kf, double dt, double enc_var){
|
||||||
|
kf->dt = dt;
|
||||||
|
|
||||||
|
kf->x[0] = 0;
|
||||||
|
kf->x[1] = 0;
|
||||||
|
kf->x[2] = 0;
|
||||||
|
|
||||||
|
for(int i=0;i<3;i++)
|
||||||
|
for(int j=0;j<3;j++)
|
||||||
|
kf->P[i][j] = 0;
|
||||||
|
|
||||||
|
kf->P[0][0] = 1;
|
||||||
|
kf->P[1][1] = 1;
|
||||||
|
kf->P[2][2] = 1;
|
||||||
|
|
||||||
|
// process noise
|
||||||
|
kf->Q[0][0] = 1e-6;
|
||||||
|
kf->Q[1][1] = 1e-4;
|
||||||
|
kf->Q[2][2] = 1e-3;
|
||||||
|
|
||||||
|
kf->R = enc_var; // encoder noise variance
|
||||||
|
}
|
||||||
|
|
||||||
|
void kalman3_set_jerk_noise(Kalman3 *kf, double sigma_j){
|
||||||
|
double dt = kf->dt;
|
||||||
|
|
||||||
|
double dt2 = dt*dt;
|
||||||
|
double dt3 = dt2*dt;
|
||||||
|
double dt4 = dt3*dt;
|
||||||
|
double dt5 = dt4*dt;
|
||||||
|
|
||||||
|
double q = sigma_j * sigma_j;
|
||||||
|
|
||||||
|
kf->Q[0][0] = q * dt5 / 20.0;
|
||||||
|
kf->Q[0][1] = q * dt4 / 8.0;
|
||||||
|
kf->Q[0][2] = q * dt3 / 6.0;
|
||||||
|
|
||||||
|
kf->Q[1][0] = q * dt4 / 8.0;
|
||||||
|
kf->Q[1][1] = q * dt3 / 3.0;
|
||||||
|
kf->Q[1][2] = q * dt2 / 2.0;
|
||||||
|
|
||||||
|
kf->Q[2][0] = q * dt3 / 6.0;
|
||||||
|
kf->Q[2][1] = q * dt2 / 2.0;
|
||||||
|
kf->Q[2][2] = q * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kalman3_predict(Kalman3 *kf){
|
||||||
|
double dt = kf->dt;
|
||||||
|
double dt2 = 0.5 * dt * dt;
|
||||||
|
|
||||||
|
double theta = kf->x[0];
|
||||||
|
double omega = kf->x[1];
|
||||||
|
double alpha = kf->x[2];
|
||||||
|
|
||||||
|
// state prediction
|
||||||
|
kf->x[0] = theta + omega*dt + alpha*dt2;
|
||||||
|
kf->x[1] = omega + alpha*dt;
|
||||||
|
kf->x[2] = alpha;
|
||||||
|
|
||||||
|
// transition matrix
|
||||||
|
double F[3][3] =
|
||||||
|
{
|
||||||
|
{1, dt, dt2},
|
||||||
|
{0, 1, dt},
|
||||||
|
{0, 0, 1}
|
||||||
|
};
|
||||||
|
|
||||||
|
// P = FPF^T + Q
|
||||||
|
|
||||||
|
double FP[3][3];
|
||||||
|
|
||||||
|
for(int i=0;i<3;i++)
|
||||||
|
for(int j=0;j<3;j++){
|
||||||
|
FP[i][j] =
|
||||||
|
F[i][0]*kf->P[0][j] +
|
||||||
|
F[i][1]*kf->P[1][j] +
|
||||||
|
F[i][2]*kf->P[2][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
double Pnew[3][3];
|
||||||
|
|
||||||
|
for(int i=0;i<3;i++)
|
||||||
|
for(int j=0;j<3;j++){
|
||||||
|
Pnew[i][j] =
|
||||||
|
FP[i][0]*F[j][0] +
|
||||||
|
FP[i][1]*F[j][1] +
|
||||||
|
FP[i][2]*F[j][2] +
|
||||||
|
kf->Q[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0;i<3;i++)
|
||||||
|
for(int j=0;j<3;j++)
|
||||||
|
kf->P[i][j] = Pnew[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
void kalman3_update(Kalman3 *kf, double z){
|
||||||
|
// innovation
|
||||||
|
double y = z - kf->x[0];
|
||||||
|
|
||||||
|
// S = HPH^T + R
|
||||||
|
double S = kf->P[0][0] + kf->R;
|
||||||
|
|
||||||
|
// Kalman gain
|
||||||
|
double K[3];
|
||||||
|
|
||||||
|
K[0] = kf->P[0][0] / S;
|
||||||
|
K[1] = kf->P[1][0] / S;
|
||||||
|
K[2] = kf->P[2][0] / S;
|
||||||
|
|
||||||
|
// state update
|
||||||
|
kf->x[0] += K[0] * y;
|
||||||
|
kf->x[1] += K[1] * y;
|
||||||
|
kf->x[2] += K[2] * y;
|
||||||
|
|
||||||
|
// covariance update
|
||||||
|
double P00 = kf->P[0][0];
|
||||||
|
double P01 = kf->P[0][1];
|
||||||
|
double P02 = kf->P[0][2];
|
||||||
|
|
||||||
|
double P10 = kf->P[1][0];
|
||||||
|
double P11 = kf->P[1][1];
|
||||||
|
double P12 = kf->P[1][2];
|
||||||
|
|
||||||
|
double P20 = kf->P[2][0];
|
||||||
|
double P21 = kf->P[2][1];
|
||||||
|
double P22 = kf->P[2][2];
|
||||||
|
|
||||||
|
kf->P[0][0] = P00 - K[0]*P00;
|
||||||
|
kf->P[0][1] = P01 - K[0]*P01;
|
||||||
|
kf->P[0][2] = P02 - K[0]*P02;
|
||||||
|
|
||||||
|
kf->P[1][0] = P10 - K[1]*P00;
|
||||||
|
kf->P[1][1] = P11 - K[1]*P01;
|
||||||
|
kf->P[1][2] = P12 - K[1]*P02;
|
||||||
|
|
||||||
|
kf->P[2][0] = P20 - K[2]*P00;
|
||||||
|
kf->P[2][1] = P21 - K[2]*P01;
|
||||||
|
kf->P[2][2] = P22 - K[2]*P02;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// estimation of the R
|
||||||
|
double encoder_noise(int counts){
|
||||||
|
double d = 2.0*M_PI / counts;
|
||||||
|
return d*d / 12.0;
|
||||||
|
}
|
||||||
|
|
||||||
34
Auxiliary_utils/LibSidServo/kalman.h
Normal file
34
Auxiliary_utils/LibSidServo/kalman.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the libsidservo project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
double x[3]; // [theta, omega, alpha]
|
||||||
|
double P[3][3]; // covariance
|
||||||
|
double Q[3][3]; // process noise
|
||||||
|
double R; // measurement noise
|
||||||
|
double dt;
|
||||||
|
} Kalman3;
|
||||||
|
|
||||||
|
|
||||||
|
double encoder_noise(int counts);
|
||||||
|
void kalman3_update(Kalman3 *kf, double z);
|
||||||
|
void kalman3_predict(Kalman3 *kf);
|
||||||
|
void kalman3_set_jerk_noise(Kalman3 *kf, double sigma_j);
|
||||||
|
void kalman3_init(Kalman3 *kf, double dt, double enc_var);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE QtCreatorProject>
|
<!DOCTYPE QtCreatorProject>
|
||||||
<!-- Written by QtCreator 18.0.1, 2026-01-26T22:24:32. -->
|
<!-- Written by QtCreator 18.0.0, 2026-04-03T10:35:41. -->
|
||||||
<qtcreator>
|
<qtcreator>
|
||||||
<data>
|
<data>
|
||||||
<variable>EnvironmentId</variable>
|
<variable>EnvironmentId</variable>
|
||||||
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
|
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
|
||||||
</data>
|
</data>
|
||||||
<data>
|
<data>
|
||||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
|
||||||
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
||||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</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="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.SmartSelectionChanging">true</value>
|
||||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
||||||
@@ -51,10 +51,10 @@
|
|||||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</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="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</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.skipTrailingWhitespace">true</value>
|
||||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</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>
|
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||||
@@ -96,12 +96,12 @@
|
|||||||
<value type="bool" key="HasPerBcDcs">true</value>
|
<value type="bool" key="HasPerBcDcs">true</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{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.ActiveBuildConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
||||||
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/robo5/mountcontrol.git/LibSidServo</value>
|
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/eddy/Docs/SAO/10micron/C-sources/erfa_functions</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||||
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
|
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
|
||||||
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
|
||||||
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||||
@@ -168,7 +168,6 @@
|
|||||||
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">%{RunConfig:Executable:Path}</value>
|
|
||||||
</valuemap>
|
</valuemap>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
@@ -204,7 +203,6 @@
|
|||||||
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
|
||||||
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
|
||||||
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
|
||||||
<value type="QString" key="RunConfiguration.WorkingDirectory.default">%{RunConfig:Executable:Path}</value>
|
|
||||||
</valuemap>
|
</valuemap>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
|
|||||||
@@ -13,12 +13,14 @@ examples/dumpswing.c
|
|||||||
examples/goto.c
|
examples/goto.c
|
||||||
examples/scmd_traectory.c
|
examples/scmd_traectory.c
|
||||||
examples/simpleconv.h
|
examples/simpleconv.h
|
||||||
|
kalman.c
|
||||||
main.c
|
main.c
|
||||||
sidservo.h
|
sidservo.h
|
||||||
serial.c
|
serial.c
|
||||||
examples/CMakeLists.txt
|
examples/CMakeLists.txt
|
||||||
examples/traectories.c
|
examples/traectories.c
|
||||||
examples/traectories.h
|
examples/traectories.h
|
||||||
|
kalman.h
|
||||||
main.h
|
main.h
|
||||||
movingmodel.c
|
movingmodel.c
|
||||||
movingmodel.h
|
movingmodel.h
|
||||||
|
|||||||
@@ -161,6 +161,8 @@ double LS_calc_slope(less_square_t *l, double x, double t){
|
|||||||
if(!l) return 0.;
|
if(!l) return 0.;
|
||||||
size_t idx = l->idx;
|
size_t idx = l->idx;
|
||||||
double oldx = l->x[idx], oldt = l->t[idx], oldt2 = l->t2[idx], oldxt = l->xt[idx];
|
double oldx = l->x[idx], oldt = l->t[idx], oldt2 = l->t2[idx], oldxt = l->xt[idx];
|
||||||
|
/*DBG("old: x=%g, t=%g, t2=%g, xt=%g; sum: %g, t=%g, t2=%g, xt=%g", oldx, oldt, oldt2, oldxt,
|
||||||
|
l->xsum, l->tsum, l->t2sum, l->xtsum);*/
|
||||||
double t2 = t * t, xt = x * t;
|
double t2 = t * t, xt = x * t;
|
||||||
l->x[idx] = x; l->t2[idx] = t2;
|
l->x[idx] = x; l->t2[idx] = t2;
|
||||||
l->t[idx] = t; l->xt[idx] = xt;
|
l->t[idx] = t; l->xt[idx] = xt;
|
||||||
@@ -172,9 +174,9 @@ double LS_calc_slope(less_square_t *l, double x, double t){
|
|||||||
l->xtsum += xt - oldxt;
|
l->xtsum += xt - oldxt;
|
||||||
double n = (double)l->arraysz;
|
double n = (double)l->arraysz;
|
||||||
double denominator = n * l->t2sum - l->tsum * l->tsum;
|
double denominator = n * l->t2sum - l->tsum * l->tsum;
|
||||||
//DBG("idx=%zd, arrsz=%zd, den=%g", l->idx, l->arraysz, denominator);
|
|
||||||
if(fabs(denominator) < 1e-7) return 0.;
|
if(fabs(denominator) < 1e-7) return 0.;
|
||||||
double numerator = n * l->xtsum - l->xsum * l->tsum;
|
double numerator = n * l->xtsum - l->xsum * l->tsum;
|
||||||
|
//DBG("x=%g, t=%g; idx=%zd, arrsz=%zd, den=%g; xsum=%g, num=%g", x, t, l->idx, l->arraysz, denominator, l->xsum, numerator);
|
||||||
// point: (sum_x - slope * sum_t) / n;
|
// point: (sum_x - slope * sum_t) / n;
|
||||||
return (numerator / denominator);
|
return (numerator / denominator);
|
||||||
}
|
}
|
||||||
@@ -200,6 +202,7 @@ static mcc_errcodes_t init(conf_t *c){
|
|||||||
if(!Xmodel || !Ymodel || !openMount()) return MCC_E_FAILED;
|
if(!Xmodel || !Ymodel || !openMount()) return MCC_E_FAILED;
|
||||||
return MCC_E_OK;
|
return MCC_E_OK;
|
||||||
}
|
}
|
||||||
|
DBG("Try to open mount device");
|
||||||
if(!Conf.MountDevPath || Conf.MountDevSpeed < MOUNT_BAUDRATE_MIN){
|
if(!Conf.MountDevPath || Conf.MountDevSpeed < MOUNT_BAUDRATE_MIN){
|
||||||
DBG("Define mount device path and speed");
|
DBG("Define mount device path and speed");
|
||||||
ret = MCC_E_BADFORMAT;
|
ret = MCC_E_BADFORMAT;
|
||||||
@@ -207,33 +210,53 @@ static mcc_errcodes_t init(conf_t *c){
|
|||||||
DBG("Can't open %s with speed %d", Conf.MountDevPath, Conf.MountDevSpeed);
|
DBG("Can't open %s with speed %d", Conf.MountDevPath, Conf.MountDevSpeed);
|
||||||
ret = MCC_E_MOUNTDEV;
|
ret = MCC_E_MOUNTDEV;
|
||||||
}
|
}
|
||||||
if(Conf.SepEncoder){
|
|
||||||
if(!Conf.EncoderDevPath && !Conf.EncoderXDevPath){
|
|
||||||
DBG("Define encoder device path");
|
|
||||||
ret = MCC_E_BADFORMAT;
|
|
||||||
}else if(!openEncoder()){
|
|
||||||
DBG("Can't open encoder device");
|
|
||||||
ret = MCC_E_ENCODERDEV;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: read hardware configuration on init
|
// TODO: read hardware configuration on init
|
||||||
if(Conf.EncoderSpeedInterval < Conf.EncoderReqInterval * MCC_CONF_MIN_SPEEDC || Conf.EncoderSpeedInterval > MCC_CONF_MAX_SPEEDINT){
|
if(Conf.EncoderSpeedInterval < Conf.EncoderReqInterval * MCC_CONF_MIN_SPEEDC || Conf.EncoderSpeedInterval > MCC_CONF_MAX_SPEEDINT){
|
||||||
DBG("Wrong speed interval");
|
DBG("Wrong speed interval");
|
||||||
ret = MCC_E_BADFORMAT;
|
ret = MCC_E_BADFORMAT;
|
||||||
}
|
}
|
||||||
if(!SSrawcmd(CMD_EXITACM, NULL)) ret = MCC_E_FAILED;
|
|
||||||
if(ret != MCC_E_OK) return ret;
|
if(ret != MCC_E_OK) return ret;
|
||||||
|
DBG("Exit ACM, exit manual mode");
|
||||||
|
SSrawcmd(CMD_EXITACM, NULL);
|
||||||
|
SStextcmd(CMD_AUTOX, NULL);
|
||||||
|
SStextcmd(CMD_AUTOY, NULL);
|
||||||
// read HW config to update constants
|
// read HW config to update constants
|
||||||
hardware_configuration_t HW;
|
hardware_configuration_t HW;
|
||||||
if(MCC_E_OK != get_hwconf(&HW)) return MCC_E_FAILED;
|
DBG("Read hardware configuration");
|
||||||
|
ret = MCC_E_FAILED;
|
||||||
|
for(int i = 0; i < MAX_ERR_CTR; ++i){
|
||||||
|
DBG("TRY %d..", i);
|
||||||
|
ret = get_hwconf(&HW);
|
||||||
|
if(ret == MCC_E_OK) break;
|
||||||
|
}
|
||||||
|
if(MCC_E_OK != ret) return ret;
|
||||||
// make a pause for actual encoder's values
|
// make a pause for actual encoder's values
|
||||||
|
DBG("Check encoders");
|
||||||
|
if(Conf.SepEncoder){
|
||||||
|
if(!Conf.EncoderDevPath && !Conf.EncoderXDevPath){
|
||||||
|
DBG("Define encoder device path");
|
||||||
|
ret = MCC_E_BADFORMAT;
|
||||||
|
}else{
|
||||||
|
ret = MCC_E_ENCODERDEV;
|
||||||
|
for(int i = 0; i < MAX_ERR_CTR; ++i){
|
||||||
|
if(openEncoder()){
|
||||||
|
ret = MCC_E_OK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(MCC_E_OK != ret) return ret;
|
||||||
double t0 = timefromstart();
|
double t0 = timefromstart();
|
||||||
while(timefromstart() - t0 < Conf.EncoderReqInterval) usleep(1000);
|
DBG("Wait for first encoders' measurement");
|
||||||
|
while(timefromstart() - t0 < Conf.EncoderReqInterval * 15.) usleep(1000);
|
||||||
|
DBG("Update motor position");
|
||||||
mcc_errcodes_t e = updateMotorPos();
|
mcc_errcodes_t e = updateMotorPos();
|
||||||
// and refresh data after updating
|
// and refresh data after updating
|
||||||
DBG("Wait for next mount reading");
|
DBG("Wait for next mount reading");
|
||||||
t0 = timefromstart();
|
t0 = timefromstart();
|
||||||
while(timefromstart() - t0 < Conf.MountReqInterval * 3.) usleep(1000);
|
while(timefromstart() - t0 < Conf.MountReqInterval * 5.) usleep(1000);
|
||||||
|
DBG("ALL READY!");
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,10 +311,6 @@ static mcc_errcodes_t move2(const coordpair_t *target){
|
|||||||
cmd.Ymot = target->Y;
|
cmd.Ymot = target->Y;
|
||||||
cmd.Xspeed = Xlimits.max.speed;
|
cmd.Xspeed = Xlimits.max.speed;
|
||||||
cmd.Yspeed = Ylimits.max.speed;
|
cmd.Yspeed = Ylimits.max.speed;
|
||||||
/*mcc_errcodes_t r = shortcmd(&cmd);
|
|
||||||
if(r != MCC_E_OK) return r;
|
|
||||||
setslewingstate();
|
|
||||||
return MCC_E_OK;*/
|
|
||||||
return shortcmd(&cmd);
|
return shortcmd(&cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,10 +347,7 @@ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed
|
|||||||
cmd.Ymot = target->Y;
|
cmd.Ymot = target->Y;
|
||||||
cmd.Xspeed = speed->X;
|
cmd.Xspeed = speed->X;
|
||||||
cmd.Yspeed = speed->Y;
|
cmd.Yspeed = speed->Y;
|
||||||
mcc_errcodes_t r = shortcmd(&cmd);
|
return shortcmd(&cmd);
|
||||||
if(r != MCC_E_OK) return r;
|
|
||||||
setslewingstate();
|
|
||||||
return MCC_E_OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "kalman.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "movingmodel.h"
|
#include "movingmodel.h"
|
||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
@@ -49,7 +50,13 @@ static pthread_mutex_t mntmutex = PTHREAD_MUTEX_INITIALIZER,
|
|||||||
// encoders thread and mount thread
|
// encoders thread and mount thread
|
||||||
static pthread_t encthread, mntthread;
|
static pthread_t encthread, mntthread;
|
||||||
// max timeout for 1.5 bytes of encoder and 2 bytes of mount - for `select`
|
// max timeout for 1.5 bytes of encoder and 2 bytes of mount - for `select`
|
||||||
static struct timeval encRtmout = {.tv_sec = 0, .tv_usec = 100}, mntRtmout = {.tv_sec = 0, .tv_usec = 50000};
|
// this values will be modified later
|
||||||
|
static struct timeval encRtmout = {.tv_sec = 0, .tv_usec = 100}, // encoder reading timeout
|
||||||
|
mnt1Rtmout = {.tv_sec = 0, .tv_usec = 200000}, // first reading
|
||||||
|
mntRtmout = {.tv_sec = 0, .tv_usec = 50000}; // next readings
|
||||||
|
|
||||||
|
static volatile int GlobExit = 0;
|
||||||
|
|
||||||
// encoders raw data
|
// encoders raw data
|
||||||
typedef struct __attribute__((packed)){
|
typedef struct __attribute__((packed)){
|
||||||
uint8_t magick;
|
uint8_t magick;
|
||||||
@@ -71,7 +78,6 @@ void getXspeed(){
|
|||||||
mountdata.encXspeed.val = speed;
|
mountdata.encXspeed.val = speed;
|
||||||
mountdata.encXspeed.t = mountdata.encXposition.t;
|
mountdata.encXspeed.t = mountdata.encXposition.t;
|
||||||
}
|
}
|
||||||
//DBG("Xspeed=%g", mountdata.encXspeed.val);
|
|
||||||
}
|
}
|
||||||
void getYspeed(){
|
void getYspeed(){
|
||||||
static less_square_t *ls = NULL;
|
static less_square_t *ls = NULL;
|
||||||
@@ -138,77 +144,6 @@ static void parse_encbuf(uint8_t databuf[ENC_DATALEN], struct timespec *t){
|
|||||||
//DBG("time = %zd+%zd/1e6, X=%g deg, Y=%g deg", tv->tv_sec, tv->tv_usec, mountdata.encposition.X*180./M_PI, mountdata.encposition.Y*180./M_PI);
|
//DBG("time = %zd+%zd/1e6, X=%g deg, Y=%g deg", tv->tv_sec, tv->tv_usec, mountdata.encposition.X*180./M_PI, mountdata.encposition.Y*180./M_PI);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
/**
|
|
||||||
* @brief getencval - get uint64_t data from encoder
|
|
||||||
* @param fd - encoder fd
|
|
||||||
* @param val - value read
|
|
||||||
* @param t - measurement time
|
|
||||||
* @return amount of data read or 0 if problem
|
|
||||||
*/
|
|
||||||
static int getencval(int fd, double *val, struct timespec *t){
|
|
||||||
if(fd < 0){
|
|
||||||
DBG("Encoder fd < 0!");
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
char buf[128];
|
|
||||||
int got = 0, Lmax = 127;
|
|
||||||
double t0 = timefromstart();
|
|
||||||
//DBG("start: %.6g", t0);
|
|
||||||
do{
|
|
||||||
fd_set rfds;
|
|
||||||
FD_ZERO(&rfds);
|
|
||||||
FD_SET(fd, &rfds);
|
|
||||||
struct timeval tv = encRtmout;
|
|
||||||
int retval = select(fd + 1, &rfds, NULL, NULL, &tv);
|
|
||||||
if(!retval){
|
|
||||||
//DBG("select()==0 - timeout, %.6g", timefromstart());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if(retval < 0){
|
|
||||||
if(errno == EINTR){
|
|
||||||
DBG("EINTR");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
DBG("select() < 0");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if(FD_ISSET(fd, &rfds)){
|
|
||||||
ssize_t l = read(fd, &buf[got], Lmax);
|
|
||||||
if(l < 1){
|
|
||||||
DBG("read() < 0");
|
|
||||||
return 0; // disconnected ??
|
|
||||||
}
|
|
||||||
got += l; Lmax -= l;
|
|
||||||
buf[got] = 0;
|
|
||||||
} else continue;
|
|
||||||
if(buf[got-1] == '\n') break; // got EOL as last symbol
|
|
||||||
}while(Lmax && timefromstart() - t0 < Conf.EncoderReqInterval / 5.);
|
|
||||||
if(got == 0){
|
|
||||||
//DBG("No data from encoder, tfs=%.6g", timefromstart());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
char *estr = strrchr(buf, '\n');
|
|
||||||
if(!estr){
|
|
||||||
DBG("No EOL");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
*estr = 0;
|
|
||||||
char *bgn = strrchr(buf, '\n');
|
|
||||||
if(bgn) ++bgn;
|
|
||||||
else bgn = buf;
|
|
||||||
char *eptr;
|
|
||||||
long data = strtol(bgn, &eptr, 10);
|
|
||||||
if(eptr != estr){
|
|
||||||
DBG("NAN");
|
|
||||||
return 0; // wrong number
|
|
||||||
}
|
|
||||||
if(val) *val = (double) data;
|
|
||||||
if(t){ if(!curtime(t)){ DBG("Can't get time"); return 0; }}
|
|
||||||
return got;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// try to read 1 byte from encoder; return -1 if nothing to read or -2 if device seems to be disconnected
|
// try to read 1 byte from encoder; return -1 if nothing to read or -2 if device seems to be disconnected
|
||||||
static int getencbyte(){
|
static int getencbyte(){
|
||||||
if(encfd[0] < 0) return -1;
|
if(encfd[0] < 0) return -1;
|
||||||
@@ -232,43 +167,60 @@ static int getencbyte(){
|
|||||||
}while(1);
|
}while(1);
|
||||||
return (int)byte;
|
return (int)byte;
|
||||||
}
|
}
|
||||||
// read 1 byte from mount; return -1 if nothing to read, -2 if disconnected
|
|
||||||
static int getmntbyte(){
|
/**
|
||||||
if(mntfd < 0) return -1;
|
* @brief readmntdata - read data
|
||||||
uint8_t byte;
|
* @param buffer - input buffer
|
||||||
|
* @param maxlen - maximal buffer length
|
||||||
|
* @return amount of bytes read or -1 in case of error
|
||||||
|
*/
|
||||||
|
static int readmntdata(uint8_t *buffer, int maxlen){
|
||||||
|
if(mntfd < 0){
|
||||||
|
DBG("mntfd non opened");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(!buffer || maxlen < 1) return 0;
|
||||||
|
//DBG("ask for %d bytes", maxlen);
|
||||||
|
int got = 0;
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
/* ssize_t l = read(mntfd, &byte, 1);
|
struct timeval tv = mnt1Rtmout;
|
||||||
//DBG("MNT read=%zd byte=0x%X", l, byte);
|
|
||||||
if(l == 0) return -1;
|
|
||||||
if(l != 1) return -2; // disconnected ??
|
|
||||||
return (int) byte;*/
|
|
||||||
do{
|
do{
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
FD_SET(mntfd, &rfds);
|
FD_SET(mntfd, &rfds);
|
||||||
struct timeval tv = mntRtmout;
|
//DBG("select");
|
||||||
int retval = select(mntfd + 1, &rfds, NULL, NULL, &tv);
|
int retval = select(mntfd + 1, &rfds, NULL, NULL, &tv);
|
||||||
|
//DBG("returned %d", retval);
|
||||||
if(retval < 0){
|
if(retval < 0){
|
||||||
if(errno == EINTR) continue;
|
if(errno == EINTR) continue;
|
||||||
DBG("Error in select()");
|
DBG("Error in select()");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
//DBG("FD_ISSET = %d", FD_ISSET(mntfd, &rfds));
|
|
||||||
if(FD_ISSET(mntfd, &rfds)){
|
if(FD_ISSET(mntfd, &rfds)){
|
||||||
ssize_t l = read(mntfd, &byte, 1);
|
ssize_t l = read(mntfd, buffer, maxlen);
|
||||||
//DBG("MNT read=%zd byte=0x%X", l, byte);
|
if(l == 0){
|
||||||
if(l != 1){
|
DBG("read ZERO");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(l < 0){
|
||||||
DBG("Mount disconnected?");
|
DBG("Mount disconnected?");
|
||||||
return -2; // disconnected ??
|
return -2; // disconnected ??
|
||||||
}
|
}
|
||||||
|
buffer += l;
|
||||||
|
maxlen -= l;
|
||||||
|
got += l;
|
||||||
|
}else{
|
||||||
|
DBG("no new data after %d bytes (%s)", got, buffer - got);
|
||||||
break;
|
break;
|
||||||
} else return -1;
|
|
||||||
}while(1);
|
|
||||||
return (int)byte;
|
|
||||||
}
|
}
|
||||||
|
tv = mntRtmout;
|
||||||
|
}while(maxlen);
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
// clear data from input buffer
|
// clear data from input buffer
|
||||||
static void clrmntbuf(){
|
static void clrmntbuf(){
|
||||||
if(mntfd < 0) return;
|
if(mntfd < 0) return;
|
||||||
uint8_t byte;
|
uint8_t bytes[256];
|
||||||
fd_set rfds;
|
fd_set rfds;
|
||||||
do{
|
do{
|
||||||
FD_ZERO(&rfds);
|
FD_ZERO(&rfds);
|
||||||
@@ -281,8 +233,9 @@ static void clrmntbuf(){
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(FD_ISSET(mntfd, &rfds)){
|
if(FD_ISSET(mntfd, &rfds)){
|
||||||
ssize_t l = read(mntfd, &byte, 1);
|
ssize_t l = read(mntfd, &bytes, 256);
|
||||||
if(l != 1) break;
|
if(l < 1) break;
|
||||||
|
DBG("clr got %zd bytes: %s", l, bytes);
|
||||||
}else break;
|
}else break;
|
||||||
}while(1);
|
}while(1);
|
||||||
}
|
}
|
||||||
@@ -293,7 +246,7 @@ static void *encoderthread1(void _U_ *u){
|
|||||||
uint8_t databuf[ENC_DATALEN];
|
uint8_t databuf[ENC_DATALEN];
|
||||||
int wridx = 0, errctr = 0;
|
int wridx = 0, errctr = 0;
|
||||||
struct timespec tcur;
|
struct timespec tcur;
|
||||||
while(encfd[0] > -1 && errctr < MAX_ERR_CTR){
|
while(encfd[0] > -1 && errctr < MAX_ERR_CTR && !GlobExit){
|
||||||
int b = getencbyte();
|
int b = getencbyte();
|
||||||
if(b == -2) ++errctr;
|
if(b == -2) ++errctr;
|
||||||
if(b < 0) continue;
|
if(b < 0) continue;
|
||||||
@@ -328,9 +281,12 @@ typedef struct{
|
|||||||
|
|
||||||
// write to buffer next data portion; return FALSE in case of error
|
// write to buffer next data portion; return FALSE in case of error
|
||||||
static int readstrings(buf_t *buf, int fd){
|
static int readstrings(buf_t *buf, int fd){
|
||||||
FNAME();
|
|
||||||
if(!buf){DBG("Empty buffer"); return FALSE;}
|
if(!buf){DBG("Empty buffer"); return FALSE;}
|
||||||
int L = XYBUFSZ - buf->len;
|
int L = XYBUFSZ - buf->len;
|
||||||
|
if(L < 0){
|
||||||
|
DBG("buf not initialized!");
|
||||||
|
buf->len = 0;
|
||||||
|
}
|
||||||
if(L == 0){
|
if(L == 0){
|
||||||
DBG("buffer overfull: %d!", buf->len);
|
DBG("buffer overfull: %d!", buf->len);
|
||||||
char *lastn = strrchr(buf->buf, '\n');
|
char *lastn = strrchr(buf->buf, '\n');
|
||||||
@@ -344,6 +300,7 @@ static int readstrings(buf_t *buf, int fd){
|
|||||||
}else buf->len = 0;
|
}else buf->len = 0;
|
||||||
L = XYBUFSZ - buf->len;
|
L = XYBUFSZ - buf->len;
|
||||||
}
|
}
|
||||||
|
//DBG("read %d bytes from %d", L, fd);
|
||||||
int got = read(fd, &buf->buf[buf->len], L);
|
int got = read(fd, &buf->buf[buf->len], L);
|
||||||
if(got < 0){
|
if(got < 0){
|
||||||
DBG("read()");
|
DBG("read()");
|
||||||
@@ -351,13 +308,16 @@ static int readstrings(buf_t *buf, int fd){
|
|||||||
}else if(got == 0){ DBG("NO data"); return TRUE; }
|
}else if(got == 0){ DBG("NO data"); return TRUE; }
|
||||||
buf->len += got;
|
buf->len += got;
|
||||||
buf->buf[buf->len] = 0;
|
buf->buf[buf->len] = 0;
|
||||||
DBG("buf[%d]: %s", buf->len, buf->buf);
|
//DBG("buf[%d]: %s", buf->len, buf->buf);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// return TRUE if got, FALSE if no data found
|
// return TRUE if got, FALSE if no data found
|
||||||
static int getdata(buf_t *buf, long *out){
|
static int getdata(buf_t *buf, long *out){
|
||||||
if(!buf || buf->len < 1) return FALSE;
|
if(!buf || buf->len < 1 || buf->len > (XYBUFSZ+1)){
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
// DBG("got data");
|
||||||
// read record between last '\n' and previous (or start of string)
|
// read record between last '\n' and previous (or start of string)
|
||||||
char *last = &buf->buf[buf->len - 1];
|
char *last = &buf->buf[buf->len - 1];
|
||||||
//DBG("buf: _%s_", buf->buf);
|
//DBG("buf: _%s_", buf->buf);
|
||||||
@@ -378,7 +338,7 @@ static int getdata(buf_t *buf, long *out){
|
|||||||
|
|
||||||
// try to write '\n' asking new data portion; return FALSE if failed
|
// try to write '\n' asking new data portion; return FALSE if failed
|
||||||
static int asknext(int fd){
|
static int asknext(int fd){
|
||||||
FNAME();
|
//FNAME();
|
||||||
if(fd < 0) return FALSE;
|
if(fd < 0) return FALSE;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for(; i < 5; ++i){
|
for(; i < 5; ++i){
|
||||||
@@ -399,12 +359,24 @@ static void *encoderthread2(void _U_ *u){
|
|||||||
pfds[0].fd = encfd[0]; pfds[0].events = POLLIN;
|
pfds[0].fd = encfd[0]; pfds[0].events = POLLIN;
|
||||||
pfds[1].fd = encfd[1]; pfds[1].events = POLLIN;
|
pfds[1].fd = encfd[1]; pfds[1].events = POLLIN;
|
||||||
double t0[2], tstart;
|
double t0[2], tstart;
|
||||||
buf_t strbuf[2];
|
buf_t strbuf[2] = {0};
|
||||||
long msrlast[2]; // last encoder data
|
long msrlast[2]; // last encoder data
|
||||||
double mtlast[2]; // last measurement time
|
double mtlast[2]; // last measurement time
|
||||||
asknext(encfd[0]); asknext(encfd[1]);
|
asknext(encfd[0]); asknext(encfd[1]);
|
||||||
t0[0] = t0[1] = tstart = timefromstart();
|
t0[0] = t0[1] = tstart = timefromstart();
|
||||||
int errctr = 0;
|
int errctr = 0;
|
||||||
|
|
||||||
|
// init Kalman for both axes
|
||||||
|
Kalman3 kf[2];
|
||||||
|
double dt = Conf.EncoderReqInterval; // 1ms encoders step
|
||||||
|
double sigma_jx = 1e-6, sigma_jy = 1e-6; // "jerk" sigma
|
||||||
|
double xnoice = encoder_noise(X_ENC_STEPSPERREV);
|
||||||
|
double ynoice = encoder_noise(Y_ENC_STEPSPERREV);
|
||||||
|
kalman3_init(&kf[0], dt, xnoice);
|
||||||
|
kalman3_init(&kf[1], dt, ynoice);
|
||||||
|
kalman3_set_jerk_noise(&kf[0], sigma_jx);
|
||||||
|
kalman3_set_jerk_noise(&kf[1], sigma_jy);
|
||||||
|
|
||||||
do{ // main cycle
|
do{ // main cycle
|
||||||
if(poll(pfds, 2, 0) < 0){
|
if(poll(pfds, 2, 0) < 0){
|
||||||
DBG("poll()");
|
DBG("poll()");
|
||||||
@@ -414,6 +386,7 @@ static void *encoderthread2(void _U_ *u){
|
|||||||
for(int i = 0; i < 2; ++i){
|
for(int i = 0; i < 2; ++i){
|
||||||
if(pfds[i].revents && POLLIN){
|
if(pfds[i].revents && POLLIN){
|
||||||
if(!readstrings(&strbuf[i], encfd[i])){
|
if(!readstrings(&strbuf[i], encfd[i])){
|
||||||
|
DBG("ERR");
|
||||||
++errctr;
|
++errctr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -421,16 +394,37 @@ static void *encoderthread2(void _U_ *u){
|
|||||||
double curt = timefromstart();
|
double curt = timefromstart();
|
||||||
if(getdata(&strbuf[i], &msrlast[i])) mtlast[i] = curt;
|
if(getdata(&strbuf[i], &msrlast[i])) mtlast[i] = curt;
|
||||||
if(curt - t0[i] >= Conf.EncoderReqInterval){ // get last records
|
if(curt - t0[i] >= Conf.EncoderReqInterval){ // get last records
|
||||||
|
//DBG("last rec %d, curt=%g, t0=%g, mtlast=%g", i, curt, t0[i], mtlast[i]);
|
||||||
if(curt - mtlast[i] < 1.5*Conf.EncoderReqInterval){
|
if(curt - mtlast[i] < 1.5*Conf.EncoderReqInterval){
|
||||||
|
//DBG("time OK");
|
||||||
pthread_mutex_lock(&datamutex);
|
pthread_mutex_lock(&datamutex);
|
||||||
|
double pos = (double)msrlast[i];
|
||||||
if(i == 0){
|
if(i == 0){
|
||||||
mountdata.encXposition.val = Xenc2rad((double)msrlast[i]);
|
pos = Xenc2rad(pos);
|
||||||
|
// Kalman filtering
|
||||||
|
kalman3_predict(&kf[i]);
|
||||||
|
kalman3_update(&kf[i], pos);
|
||||||
|
//DBG("Got pos=%g, kalman: angle=%g, vel=%g, acc=%g",
|
||||||
|
// pos, kf[i].x[0], kf[i].x[1], kf[i].x[2]);
|
||||||
|
mountdata.encXposition.val = kf[i].x[0];
|
||||||
curtime(&mountdata.encXposition.t);
|
curtime(&mountdata.encXposition.t);
|
||||||
|
/*DBG("msrlast=%ld, Xpos.val=%g, t=%zd; XEzero=%d, SPR=%g",
|
||||||
|
msrlast[i], mountdata.encXposition.val, mountdata.encXposition.t.tv_sec,
|
||||||
|
X_ENC_ZERO, X_ENC_STEPSPERREV);*/
|
||||||
getXspeed();
|
getXspeed();
|
||||||
|
//mountdata.encXspeed.val = kf[i].x[1];
|
||||||
|
//mountdata.encXspeed.t = mountdata.encXposition.t;
|
||||||
}else{
|
}else{
|
||||||
mountdata.encYposition.val = Yenc2rad((double)msrlast[i]);
|
pos = Yenc2rad(pos);
|
||||||
|
kalman3_predict(&kf[i]);
|
||||||
|
kalman3_update(&kf[i], pos);
|
||||||
|
//DBG("Got pos=%g, kalman: angle=%g, vel=%g, acc=%g",
|
||||||
|
// pos, kf[i].x[0], kf[i].x[1], kf[i].x[2]);
|
||||||
|
mountdata.encYposition.val = kf[i].x[0];
|
||||||
curtime(&mountdata.encYposition.t);
|
curtime(&mountdata.encYposition.t);
|
||||||
getYspeed();
|
getYspeed();
|
||||||
|
//mountdata.encYspeed.val = kf[i].x[1];
|
||||||
|
//mountdata.encYspeed.t = mountdata.encYposition.t;
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&datamutex);
|
pthread_mutex_unlock(&datamutex);
|
||||||
}
|
}
|
||||||
@@ -443,7 +437,7 @@ static void *encoderthread2(void _U_ *u){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(got == 2) errctr = 0;
|
if(got == 2) errctr = 0;
|
||||||
}while(encfd[0] > -1 && encfd[1] > -1 && errctr < MAX_ERR_CTR);
|
}while(encfd[0] > -1 && encfd[1] > -1 && errctr < MAX_ERR_CTR && !GlobExit);
|
||||||
DBG("\n\nEXIT: ERRCTR=%d", errctr);
|
DBG("\n\nEXIT: ERRCTR=%d", errctr);
|
||||||
for(int i = 0; i < 2; ++i){
|
for(int i = 0; i < 2; ++i){
|
||||||
if(encfd[i] > -1){
|
if(encfd[i] > -1){
|
||||||
@@ -487,10 +481,45 @@ static void chkModStopped(double *prev, double cur, int *nstopped, axis_status_t
|
|||||||
*prev = cur;
|
*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
|
// main mount thread
|
||||||
static void *mountthread(void _U_ *u){
|
static void *mountthread(void _U_ *u){
|
||||||
int errctr = 0;
|
int errctr = 0;
|
||||||
uint8_t buf[2*sizeof(SSstat)];
|
uint8_t buf[sizeof(SSstat)];
|
||||||
SSstat *status = (SSstat*) buf;
|
SSstat *status = (SSstat*) buf;
|
||||||
bzero(&mountdata, sizeof(mountdata));
|
bzero(&mountdata, sizeof(mountdata));
|
||||||
double t0 = timefromstart(), tstart = t0, tcur = t0;
|
double t0 = timefromstart(), tstart = t0, tcur = t0;
|
||||||
@@ -499,7 +528,7 @@ static void *mountthread(void _U_ *u){
|
|||||||
if(Conf.RunModel){
|
if(Conf.RunModel){
|
||||||
double Xprev = NAN, Yprev = NAN; // previous coordinates
|
double Xprev = NAN, Yprev = NAN; // previous coordinates
|
||||||
int xcnt = 0, ycnt = 0;
|
int xcnt = 0, ycnt = 0;
|
||||||
while(1){
|
while(!GlobExit){
|
||||||
coordpair_t c;
|
coordpair_t c;
|
||||||
movestate_t xst, yst;
|
movestate_t xst, yst;
|
||||||
// now change data
|
// now change data
|
||||||
@@ -508,20 +537,20 @@ static void *mountthread(void _U_ *u){
|
|||||||
if(!curtime(&tnow) || (tcur = timefromstart()) < 0.) continue;
|
if(!curtime(&tnow) || (tcur = timefromstart()) < 0.) continue;
|
||||||
pthread_mutex_lock(&datamutex);
|
pthread_mutex_lock(&datamutex);
|
||||||
mountdata.encXposition.t = mountdata.encYposition.t = tnow;
|
mountdata.encXposition.t = mountdata.encYposition.t = tnow;
|
||||||
mountdata.encXposition.val = c.X;
|
mountdata.encXposition.val = c.X + (drand48() - 0.5)*1e-6; // .2arcsec error
|
||||||
mountdata.encYposition.val = c.Y;
|
mountdata.encYposition.val = c.Y + (drand48() - 0.5)*1e-6;
|
||||||
//DBG("t=%g, X=%g, Y=%g", tnow, c.X.val, c.Y.val);
|
//DBG("t=%g, X=%g, Y=%g", tnow, c.X.val, c.Y.val);
|
||||||
if(tcur - oldmt > Conf.MountReqInterval){
|
if(tcur - oldmt > Conf.MountReqInterval){
|
||||||
oldmillis = mountdata.millis = (uint32_t)((tcur - tstart) * 1e3);
|
oldmillis = mountdata.millis = (uint32_t)((tcur - tstart) * 1e3);
|
||||||
mountdata.motYposition.t = mountdata.motXposition.t = tnow;
|
mountdata.motYposition.t = mountdata.motXposition.t = tnow;
|
||||||
if(xst == ST_MOVE)
|
if(xst == ST_MOVE)
|
||||||
mountdata.motXposition.val = c.X + (c.X - mountdata.motXposition.val)*(drand48() - 0.5)/100.;
|
mountdata.motXposition.val = c.X + (c.X - mountdata.motXposition.val)*(drand48() - 0.5)/100.;
|
||||||
else
|
//else
|
||||||
mountdata.motXposition.val = c.X;
|
// mountdata.motXposition.val = c.X;
|
||||||
if(yst == ST_MOVE)
|
if(yst == ST_MOVE)
|
||||||
mountdata.motYposition.val = c.Y + (c.Y - mountdata.motYposition.val)*(drand48() - 0.5)/100.;
|
mountdata.motYposition.val = c.Y + (c.Y - mountdata.motYposition.val)*(drand48() - 0.5)/100.;
|
||||||
else
|
//else
|
||||||
mountdata.motYposition.val = c.Y;
|
// mountdata.motYposition.val = c.Y;
|
||||||
oldmt = tcur;
|
oldmt = tcur;
|
||||||
}else mountdata.millis = oldmillis;
|
}else mountdata.millis = oldmillis;
|
||||||
chkModStopped(&Xprev, c.X, &xcnt, &mountdata.Xstate);
|
chkModStopped(&Xprev, c.X, &xcnt, &mountdata.Xstate);
|
||||||
@@ -537,7 +566,7 @@ static void *mountthread(void _U_ *u){
|
|||||||
// cmd to send
|
// cmd to send
|
||||||
data_t *cmd_getstat = cmd2dat(CMD_GETSTAT);
|
data_t *cmd_getstat = cmd2dat(CMD_GETSTAT);
|
||||||
if(!cmd_getstat) goto failed;
|
if(!cmd_getstat) goto failed;
|
||||||
while(mntfd > -1 && errctr < MAX_ERR_CTR){
|
while(mntfd > -1 && errctr < MAX_ERR_CTR && !GlobExit){
|
||||||
// read data to status
|
// read data to status
|
||||||
struct timespec tcur;
|
struct timespec tcur;
|
||||||
if(!curtime(&tcur)) continue;
|
if(!curtime(&tcur)) continue;
|
||||||
@@ -558,6 +587,7 @@ static void *mountthread(void _U_ *u){
|
|||||||
pthread_mutex_lock(&datamutex);
|
pthread_mutex_lock(&datamutex);
|
||||||
// now change data
|
// now change data
|
||||||
SSconvstat(status, &mountdata, &tcur);
|
SSconvstat(status, &mountdata, &tcur);
|
||||||
|
ChkStopped(status, &mountdata);
|
||||||
pthread_mutex_unlock(&datamutex);
|
pthread_mutex_unlock(&datamutex);
|
||||||
// allow writing & getters
|
// allow writing & getters
|
||||||
do{
|
do{
|
||||||
@@ -591,11 +621,12 @@ static int ttyopen(const char *path, speed_t speed){
|
|||||||
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
|
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
|
||||||
tty.c_iflag = 0; // don't do any changes in input stream
|
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_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_ispeed = speed;
|
||||||
tty.c_ospeed = speed;
|
tty.c_ospeed = speed;
|
||||||
//tty.c_cc[VMIN] = 0; // non-canonical mode
|
tty.c_cc[VMIN] = 0; // non-canonical mode
|
||||||
//tty.c_cc[VTIME] = 5;
|
tty.c_cc[VTIME] = 0;
|
||||||
if(ioctl(fd, TCSETS2, &tty)){
|
if(ioctl(fd, TCSETS2, &tty)){
|
||||||
DBG("Can't set TTY settings");
|
DBG("Can't set TTY settings");
|
||||||
close(fd);
|
close(fd);
|
||||||
@@ -610,15 +641,18 @@ static int ttyopen(const char *path, speed_t speed){
|
|||||||
|
|
||||||
// return FALSE if failed
|
// return FALSE if failed
|
||||||
int openEncoder(){
|
int openEncoder(){
|
||||||
|
// TODO: open real devices in "model" mode too!
|
||||||
if(Conf.RunModel) return TRUE;
|
if(Conf.RunModel) return TRUE;
|
||||||
if(!Conf.SepEncoder) return FALSE; // try to open separate encoder when it's absent
|
if(!Conf.SepEncoder) return FALSE; // try to open separate encoder when it's absent
|
||||||
|
/*
|
||||||
|
encRtmout.tv_sec = 0;
|
||||||
|
encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed; // 10 bytes
|
||||||
|
*/
|
||||||
if(Conf.SepEncoder == 1){ // only one device
|
if(Conf.SepEncoder == 1){ // only one device
|
||||||
DBG("One device");
|
DBG("One device");
|
||||||
if(encfd[0] > -1) close(encfd[0]);
|
if(encfd[0] > -1) close(encfd[0]);
|
||||||
encfd[0] = ttyopen(Conf.EncoderDevPath, (speed_t) Conf.EncoderDevSpeed);
|
encfd[0] = ttyopen(Conf.EncoderDevPath, (speed_t) Conf.EncoderDevSpeed);
|
||||||
if(encfd[0] < 0) return FALSE;
|
if(encfd[0] < 0) return FALSE;
|
||||||
encRtmout.tv_sec = 0;
|
|
||||||
encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed; // 10 bytes
|
|
||||||
if(pthread_create(&encthread, NULL, encoderthread1, NULL)){
|
if(pthread_create(&encthread, NULL, encoderthread1, NULL)){
|
||||||
close(encfd[0]);
|
close(encfd[0]);
|
||||||
encfd[0] = -1;
|
encfd[0] = -1;
|
||||||
@@ -632,8 +666,6 @@ int openEncoder(){
|
|||||||
encfd[i] = ttyopen(paths[i], (speed_t) Conf.EncoderDevSpeed);
|
encfd[i] = ttyopen(paths[i], (speed_t) Conf.EncoderDevSpeed);
|
||||||
if(encfd[i] < 0) return FALSE;
|
if(encfd[i] < 0) return FALSE;
|
||||||
}
|
}
|
||||||
encRtmout.tv_sec = 0;
|
|
||||||
encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed;
|
|
||||||
if(pthread_create(&encthread, NULL, encoderthread2, NULL)){
|
if(pthread_create(&encthread, NULL, encoderthread2, NULL)){
|
||||||
for(int i = 0; i < 2; ++i){
|
for(int i = 0; i < 2; ++i){
|
||||||
close(encfd[i]);
|
close(encfd[i]);
|
||||||
@@ -648,6 +680,7 @@ int openEncoder(){
|
|||||||
|
|
||||||
// return FALSE if failed
|
// return FALSE if failed
|
||||||
int openMount(){
|
int openMount(){
|
||||||
|
// TODO: open real devices in "model" mode too!
|
||||||
if(Conf.RunModel) goto create_thread;
|
if(Conf.RunModel) goto create_thread;
|
||||||
if(mntfd > -1) close(mntfd);
|
if(mntfd > -1) close(mntfd);
|
||||||
DBG("Open mount %s @ %d", Conf.MountDevPath, Conf.MountDevSpeed);
|
DBG("Open mount %s @ %d", Conf.MountDevPath, Conf.MountDevSpeed);
|
||||||
@@ -655,16 +688,13 @@ int openMount(){
|
|||||||
if(mntfd < 0) return FALSE;
|
if(mntfd < 0) return FALSE;
|
||||||
DBG("mntfd=%d", mntfd);
|
DBG("mntfd=%d", mntfd);
|
||||||
// clear buffer
|
// clear buffer
|
||||||
while(getmntbyte() > -1);
|
clrmntbuf();
|
||||||
/*int g = write(mntfd, "XXS\r", 4);
|
/*
|
||||||
DBG("Written %d", g);
|
mnt1Rtmout.tv_sec = 0;
|
||||||
uint8_t buf[100];
|
mnt1Rtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes * 10bits / speed
|
||||||
do{
|
|
||||||
ssize_t l = read(mntfd, buf, 100);
|
|
||||||
DBG("got %zd", l);
|
|
||||||
}while(1);*/
|
|
||||||
mntRtmout.tv_sec = 0;
|
mntRtmout.tv_sec = 0;
|
||||||
mntRtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes * 10bits / speed
|
mntRtmout.tv_usec = mnt1Rtmout.tv_usec / 50;
|
||||||
|
*/
|
||||||
create_thread:
|
create_thread:
|
||||||
if(pthread_create(&mntthread, NULL, mountthread, NULL)){
|
if(pthread_create(&mntthread, NULL, mountthread, NULL)){
|
||||||
DBG("Can't create mount thread");
|
DBG("Can't create mount thread");
|
||||||
@@ -680,15 +710,18 @@ create_thread:
|
|||||||
|
|
||||||
// close all opened serial devices and quit threads
|
// close all opened serial devices and quit threads
|
||||||
void closeSerial(){
|
void closeSerial(){
|
||||||
// TODO: close devices in "model" mode too!
|
GlobExit = 1;
|
||||||
if(Conf.RunModel) return;
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
DBG("Give 100ms to proper close");
|
||||||
|
usleep(100000);
|
||||||
|
DBG("Force closed all devices");
|
||||||
if(mntfd > -1){
|
if(mntfd > -1){
|
||||||
DBG("Cancel mount thread");
|
DBG("Cancel mount thread");
|
||||||
pthread_cancel(mntthread);
|
pthread_cancel(mntthread);
|
||||||
DBG("join mount thread");
|
DBG("join mount thread");
|
||||||
pthread_join(mntthread, NULL);
|
pthread_join(mntthread, NULL);
|
||||||
DBG("close mount fd");
|
DBG("close mount fd");
|
||||||
close(mntfd);
|
if(mntfd > -1) close(mntfd);
|
||||||
mntfd = -1;
|
mntfd = -1;
|
||||||
}
|
}
|
||||||
if(encfd[0] > -1){
|
if(encfd[0] > -1){
|
||||||
@@ -697,13 +730,14 @@ void closeSerial(){
|
|||||||
DBG("join encoder thread");
|
DBG("join encoder thread");
|
||||||
pthread_join(encthread, NULL);
|
pthread_join(encthread, NULL);
|
||||||
DBG("close encoder's fd");
|
DBG("close encoder's fd");
|
||||||
close(encfd[0]);
|
if(encfd[0] > -1) close(encfd[0]);
|
||||||
encfd[0] = -1;
|
encfd[0] = -1;
|
||||||
if(Conf.SepEncoder == 2 && encfd[1] > -1){
|
if(Conf.SepEncoder == 2 && encfd[1] > -1){
|
||||||
close(encfd[1]);
|
close(encfd[1]);
|
||||||
encfd[1] = -1;
|
encfd[1] = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GlobExit = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get fresh encoder information
|
// get fresh encoder information
|
||||||
@@ -731,26 +765,29 @@ static int wr(const data_t *out, data_t *in, int needeol){
|
|||||||
DBG("Wrong arguments or no mount fd");
|
DBG("Wrong arguments or no mount fd");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
//DBG("clrbuf");
|
||||||
clrmntbuf();
|
clrmntbuf();
|
||||||
if(out){
|
if(out){
|
||||||
|
//DBG("write %zd bytes (%s)", out->len, out->buf);
|
||||||
if(out->len != (size_t)write(mntfd, out->buf, out->len)){
|
if(out->len != (size_t)write(mntfd, out->buf, out->len)){
|
||||||
DBG("written bytes not equal to need");
|
DBG("written bytes not equal to need");
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
//DBG("eol, mntfd=%d", mntfd);
|
||||||
if(needeol){
|
if(needeol){
|
||||||
int g = write(mntfd, "\r", 1); // add EOL
|
int g = write(mntfd, "\r", 1); // add EOL
|
||||||
(void) g;
|
(void) g;
|
||||||
}
|
}
|
||||||
usleep(50000); // add little pause so that the idiot has time to swallow
|
//usleep(50000); // add little pause so that the idiot has time to swallow
|
||||||
}
|
}
|
||||||
if(!in) return TRUE;
|
if(!in || in->maxlen < 1) return TRUE;
|
||||||
|
int got = readmntdata(in->buf, in->maxlen);
|
||||||
|
if(got < 0){
|
||||||
|
DBG("Error reading mount data!");
|
||||||
in->len = 0;
|
in->len = 0;
|
||||||
for(size_t i = 0; i < in->maxlen; ++i){
|
return FALSE;
|
||||||
int b = getmntbyte();
|
|
||||||
if(b < 0) break; // nothing to read -> go out
|
|
||||||
in->buf[in->len++] = (uint8_t) b;
|
|
||||||
}
|
}
|
||||||
while(getmntbyte() > -1);
|
in->len = got;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,22 +798,24 @@ static int wr(const data_t *out, data_t *in, int needeol){
|
|||||||
* @return FALSE if failed
|
* @return FALSE if failed
|
||||||
*/
|
*/
|
||||||
int MountWriteRead(const data_t *out, data_t *in){
|
int MountWriteRead(const data_t *out, data_t *in){
|
||||||
if(Conf.RunModel) return -1;
|
if(Conf.RunModel) return FALSE;
|
||||||
|
//double t0 = timefromstart();
|
||||||
pthread_mutex_lock(&mntmutex);
|
pthread_mutex_lock(&mntmutex);
|
||||||
int ret = wr(out, in, 1);
|
int ret = wr(out, in, 1);
|
||||||
pthread_mutex_unlock(&mntmutex);
|
pthread_mutex_unlock(&mntmutex);
|
||||||
|
//DBG("Got %gus", (timefromstart()-t0)*1e6);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
// send binary data - without EOL
|
// send binary data - without EOL
|
||||||
int MountWriteReadRaw(const data_t *out, data_t *in){
|
int MountWriteReadRaw(const data_t *out, data_t *in){
|
||||||
if(Conf.RunModel) return -1;
|
if(Conf.RunModel) return FALSE;
|
||||||
pthread_mutex_lock(&mntmutex);
|
pthread_mutex_lock(&mntmutex);
|
||||||
int ret = wr(out, in, 0);
|
int ret = wr(out, in, 0);
|
||||||
pthread_mutex_unlock(&mntmutex);
|
pthread_mutex_unlock(&mntmutex);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef EBUG
|
#if 0
|
||||||
static void logscmd(SSscmd *c){
|
static void logscmd(SSscmd *c){
|
||||||
printf("Xmot=%d, Ymot=%d, Xspeed=%d, Yspeed=%d\n", c->Xmot, c->Ymot, c->Xspeed, c->Yspeed);
|
printf("Xmot=%d, Ymot=%d, Xspeed=%d, Yspeed=%d\n", c->Xmot, c->Ymot, c->Xspeed, c->Yspeed);
|
||||||
printf("xychange=0x%02X, Xbits=0x%02X, Ybits=0x%02X\n", c->xychange, c->XBits, c->YBits);
|
printf("xychange=0x%02X, Xbits=0x%02X, Ybits=0x%02X\n", c->xychange, c->XBits, c->YBits);
|
||||||
@@ -799,31 +838,37 @@ static int bincmd(uint8_t *cmd, int len){
|
|||||||
if(!dlcmd) dlcmd = cmd2dat(CMD_LONGCMD);
|
if(!dlcmd) dlcmd = cmd2dat(CMD_LONGCMD);
|
||||||
int ret = FALSE;
|
int ret = FALSE;
|
||||||
pthread_mutex_lock(&mntmutex);
|
pthread_mutex_lock(&mntmutex);
|
||||||
// dummy buffer to clear trash in input
|
|
||||||
//char ans[300];
|
|
||||||
//data_t a = {.buf = (uint8_t*)ans, .maxlen=299};
|
|
||||||
if(len == sizeof(SSscmd)){
|
if(len == sizeof(SSscmd)){
|
||||||
((SSscmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
|
((SSscmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
|
||||||
DBG("Short command");
|
//DBG("Short command");
|
||||||
#ifdef EBUG
|
#if 0
|
||||||
logscmd((SSscmd*)cmd);
|
logscmd((SSscmd*)cmd);
|
||||||
#endif
|
#endif
|
||||||
if(!wr(dscmd, NULL, 1)) goto rtn;
|
if(!wr(dscmd, NULL, 1)) goto rtn;
|
||||||
}else if(len == sizeof(SSlcmd)){
|
}else if(len == sizeof(SSlcmd)){
|
||||||
((SSlcmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
|
((SSlcmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
|
||||||
DBG("Long command");
|
// DBG("Long command");
|
||||||
#ifdef EBUG
|
#if 0
|
||||||
loglcmd((SSlcmd*)cmd);
|
loglcmd((SSlcmd*)cmd);
|
||||||
#endif
|
#endif
|
||||||
if(!wr(dlcmd, NULL, 1)) goto rtn;
|
if(!wr(dlcmd, NULL, 1)) goto rtn;
|
||||||
}else{
|
}else{
|
||||||
goto rtn;
|
goto rtn;
|
||||||
}
|
}
|
||||||
data_t d;
|
SSstat ans;
|
||||||
|
data_t d, in;
|
||||||
d.buf = cmd;
|
d.buf = cmd;
|
||||||
d.len = d.maxlen = len;
|
d.len = d.maxlen = len;
|
||||||
ret = wr(&d, NULL, 0);
|
in.buf = (uint8_t*)&ans; in.maxlen = sizeof(SSstat);
|
||||||
|
ret = wr(&d, &in, 0);
|
||||||
DBG("%s", ret ? "SUCCESS" : "FAIL");
|
DBG("%s", ret ? "SUCCESS" : "FAIL");
|
||||||
|
if(ret){
|
||||||
|
SSscmd *sc = (SSscmd*)cmd;
|
||||||
|
mountdata.Xtarget = sc->Xmot;
|
||||||
|
mountdata.Ytarget = sc->Ymot;
|
||||||
|
DBG("ANS: Xmot/Ymot: %d/%d, Ylast/Ylast: %d/%d; Xtag/Ytag: %d/%d",
|
||||||
|
ans.Xmot, ans.Ymot, ans.XLast, ans.YLast, mountdata.Xtarget, mountdata.Ytarget);
|
||||||
|
}
|
||||||
rtn:
|
rtn:
|
||||||
pthread_mutex_unlock(&mntmutex);
|
pthread_mutex_unlock(&mntmutex);
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
23
Auxiliary_utils/LibSidServo/servo_real.conf
Normal file
23
Auxiliary_utils/LibSidServo/servo_real.conf
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
MountDevPath=/dev/ttyUSB0
|
||||||
|
MountDevSpeed=19200
|
||||||
|
EncoderDevSpeed=1000000
|
||||||
|
MountReqInterval=0.1
|
||||||
|
EncoderReqInterval=0.001
|
||||||
|
SepEncoder=2
|
||||||
|
EncoderXDevPath=/dev/encoder_X0
|
||||||
|
EncoderYDevPath=/dev/encoder_Y0
|
||||||
|
EncoderSpeedInterval=0.05
|
||||||
|
RunModel=0
|
||||||
|
# telescope is in "pointing state" when coordinate error less than MaxFinePointingErr and goes to "slewing state"
|
||||||
|
# when this error greater than MaxPointingErr
|
||||||
|
MaxPointingErr = 0.3490658504 # "pointing zone" - 20 degr
|
||||||
|
MaxFinePointingErr = 0.1745329252 # "guiding zone" - 10 degr
|
||||||
|
MaxGuidingErr = 4.8481368e-6 # "on target zone" - 1 arcsec
|
||||||
|
XPIDVP=0.9
|
||||||
|
XPIDVI=0.0005
|
||||||
|
XPIDVD=0.0
|
||||||
|
YPIDVP=0.5
|
||||||
|
YPIDVI=0.005
|
||||||
|
YPIDVD=0.
|
||||||
|
XEncZero=36627112
|
||||||
|
YEncZero=36067741
|
||||||
@@ -136,10 +136,11 @@ typedef struct{
|
|||||||
} extradata_t;
|
} extradata_t;
|
||||||
|
|
||||||
typedef enum{
|
typedef enum{
|
||||||
AXIS_STOPPED,
|
AXIS_STOPPED, // stop
|
||||||
AXIS_SLEWING,
|
AXIS_GONNASTOP, // stop command run
|
||||||
AXIS_POINTING,
|
AXIS_SLEWING, // go to target with maximal speed
|
||||||
AXIS_GUIDING,
|
AXIS_POINTING, // axis is in pointing zone, use PID
|
||||||
|
AXIS_GUIDING, // near target
|
||||||
AXIS_ERROR,
|
AXIS_ERROR,
|
||||||
} axis_status_t;
|
} axis_status_t;
|
||||||
|
|
||||||
@@ -157,6 +158,9 @@ typedef struct{
|
|||||||
uint32_t millis;
|
uint32_t millis;
|
||||||
double temperature;
|
double temperature;
|
||||||
double voltage;
|
double voltage;
|
||||||
|
// target X/Y position by last `short` or `long` command
|
||||||
|
int32_t Xtarget; // in SidServo's counts
|
||||||
|
int32_t Ytarget; // -//-
|
||||||
} mountdata_t;
|
} mountdata_t;
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
|
|||||||
@@ -26,8 +26,12 @@
|
|||||||
#include "serial.h"
|
#include "serial.h"
|
||||||
#include "ssii.h"
|
#include "ssii.h"
|
||||||
|
|
||||||
int X_ENC_ZERO, Y_ENC_ZERO;
|
int X_ENC_ZERO = 0, Y_ENC_ZERO = 0; // will be filled later from config
|
||||||
double X_MOT_STEPSPERREV = 1., Y_MOT_STEPSPERREV = 1., X_ENC_STEPSPERREV = 1., Y_ENC_STEPSPERREV = 1.;
|
// defaults until read from controller
|
||||||
|
double X_MOT_STEPSPERREV = 13312000.,
|
||||||
|
Y_MOT_STEPSPERREV = 17578668.,
|
||||||
|
X_ENC_STEPSPERREV = 67108864.,
|
||||||
|
Y_ENC_STEPSPERREV = 67108864.;
|
||||||
|
|
||||||
uint16_t SScalcChecksum(uint8_t *buf, int len){
|
uint16_t SScalcChecksum(uint8_t *buf, int len){
|
||||||
uint16_t checksum = 0;
|
uint16_t checksum = 0;
|
||||||
@@ -40,30 +44,6 @@ uint16_t SScalcChecksum(uint8_t *buf, int len){
|
|||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next three functions runs under locked mountdata_t mutex and shouldn't call locked it again!!
|
|
||||||
static void chkstopstat(int32_t *prev, int32_t cur, int *nstopped, axis_status_t *stat){
|
|
||||||
if(*prev == INT32_MAX){
|
|
||||||
*stat = AXIS_STOPPED;
|
|
||||||
DBG("START");
|
|
||||||
}else if(*stat != AXIS_STOPPED){
|
|
||||||
if(*prev == cur && ++(*nstopped) > MOTOR_STOPPED_CNT){
|
|
||||||
*stat = AXIS_STOPPED;
|
|
||||||
DBG("AXIS stopped");
|
|
||||||
}
|
|
||||||
}else if(*prev != cur){
|
|
||||||
DBG("AXIS moving");
|
|
||||||
*nstopped = 0;
|
|
||||||
}
|
|
||||||
*prev = cur;
|
|
||||||
}
|
|
||||||
// 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
|
|
||||||
chkstopstat(&Xmot_prev, s->Xmot, &Xnstopped, &m->Xstate);
|
|
||||||
chkstopstat(&Ymot_prev, s->Ymot, &Ynstopped, &m->Ystate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief SSconvstat - convert stat from SSII format to human
|
* @brief SSconvstat - convert stat from SSII format to human
|
||||||
* @param s (i) - just read data
|
* @param s (i) - just read data
|
||||||
@@ -74,10 +54,10 @@ void SSconvstat(const SSstat *s, mountdata_t *m, struct timespec *t){
|
|||||||
if(!s || !m || !t) return;
|
if(!s || !m || !t) return;
|
||||||
m->motXposition.val = X_MOT2RAD(s->Xmot);
|
m->motXposition.val = X_MOT2RAD(s->Xmot);
|
||||||
m->motYposition.val = Y_MOT2RAD(s->Ymot);
|
m->motYposition.val = Y_MOT2RAD(s->Ymot);
|
||||||
ChkStopped(s, m);
|
|
||||||
m->motXposition.t = m->motYposition.t = *t;
|
m->motXposition.t = m->motYposition.t = *t;
|
||||||
// fill encoder data from here, as there's no separate enc thread
|
// fill encoder data from here, as there's no separate enc thread
|
||||||
if(!Conf.SepEncoder){
|
if(!Conf.SepEncoder){
|
||||||
|
DBG("ENCODER from SSII");
|
||||||
m->encXposition.val = Xenc2rad(s->Xenc);
|
m->encXposition.val = Xenc2rad(s->Xenc);
|
||||||
DBG("encx: %g", m->encXposition.val);
|
DBG("encx: %g", m->encXposition.val);
|
||||||
m->encYposition.val = Yenc2rad(s->Yenc);
|
m->encYposition.val = Yenc2rad(s->Yenc);
|
||||||
@@ -165,14 +145,17 @@ int SSsetterI(const char *cmd, int32_t ival){
|
|||||||
}
|
}
|
||||||
|
|
||||||
int SSstop(int emerg){
|
int SSstop(int emerg){
|
||||||
|
FNAME();
|
||||||
int i = 0;
|
int i = 0;
|
||||||
const char *cmdx = (emerg) ? CMD_EMSTOPX : CMD_STOPX;
|
const char *cmdx = (emerg) ? CMD_EMSTOPX : CMD_STOPX;
|
||||||
const char *cmdy = (emerg) ? CMD_EMSTOPY : CMD_STOPY;
|
const char *cmdy = (emerg) ? CMD_EMSTOPY : CMD_STOPY;
|
||||||
|
setStat(AXIS_GONNASTOP, AXIS_GONNASTOP);
|
||||||
for(; i < 10; ++i){
|
for(; i < 10; ++i){
|
||||||
if(!SStextcmd(cmdx, NULL)) continue;
|
if(!SStextcmd(cmdx, NULL)) continue;
|
||||||
if(SStextcmd(cmdy, NULL)) break;
|
if(SStextcmd(cmdy, NULL)) break;
|
||||||
}
|
}
|
||||||
if(i == 10) return FALSE;
|
if(i == 10) return FALSE;
|
||||||
|
DBG("Stopped");
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -209,12 +209,14 @@ extern double X_MOT_STEPSPERREV, Y_MOT_STEPSPERREV, X_ENC_STEPSPERREV, Y_ENC_STE
|
|||||||
|
|
||||||
// convert angle in radians to +-pi
|
// convert angle in radians to +-pi
|
||||||
static inline __attribute__((always_inline)) double ang2half(double ang){
|
static inline __attribute__((always_inline)) double ang2half(double ang){
|
||||||
|
ang = fmod(ang, 2.*M_PI);
|
||||||
if(ang < -M_PI) ang += 2.*M_PI;
|
if(ang < -M_PI) ang += 2.*M_PI;
|
||||||
else if(ang > M_PI) ang -= 2.*M_PI;
|
else if(ang > M_PI) ang -= 2.*M_PI;
|
||||||
return ang;
|
return ang;
|
||||||
}
|
}
|
||||||
// convert to only positive: 0..2pi
|
// convert to only positive: 0..2pi
|
||||||
static inline __attribute__((always_inline)) double ang2full(double ang){
|
static inline __attribute__((always_inline)) double ang2full(double ang){
|
||||||
|
ang = fmod(ang, 2.*M_PI);
|
||||||
if(ang < 0.) ang += 2.*M_PI;
|
if(ang < 0.) ang += 2.*M_PI;
|
||||||
else if(ang > 2.*M_PI) ang -= 2.*M_PI;
|
else if(ang > 2.*M_PI) ang -= 2.*M_PI;
|
||||||
return ang;
|
return ang;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
// make datetime/pressure/temperature corrections each CORRECTIONS_TIMEDIFF seconds
|
// make datetime/pressure/temperature corrections each CORRECTIONS_TIMEDIFF seconds
|
||||||
#define CORRECTIONS_TIMEDIFF (3600)
|
#define CORRECTIONS_TIMEDIFF (3600)
|
||||||
|
|
||||||
#define TELESCOPE_NAME "'Astrosib-500 (1)'"
|
#define TELESCOPE_NAME "'Astrosib-500 (2)'"
|
||||||
|
|
||||||
// telescope statuses
|
// telescope statuses
|
||||||
typedef enum{
|
typedef enum{
|
||||||
|
|||||||
@@ -274,7 +274,7 @@ int mygetchar(){ // getchar() without need of pressing ENTER
|
|||||||
/******************************************************************************\
|
/******************************************************************************\
|
||||||
* TTY with select()
|
* TTY with select()
|
||||||
\******************************************************************************/
|
\******************************************************************************/
|
||||||
static struct termio oldtty, tty; // TTY flags
|
static struct termios oldtty, tty; // TTY flags
|
||||||
static int comfd = -1; // TTY fd
|
static int comfd = -1; // TTY fd
|
||||||
|
|
||||||
// run on exit:
|
// run on exit:
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
|
||||||
#if defined GETTEXT_PACKAGE && defined LOCALEDIR
|
#if defined GETTEXT_PACKAGE && defined LOCALEDIR
|
||||||
/*
|
/*
|
||||||
* GETTEXT
|
* GETTEXT
|
||||||
@@ -47,7 +49,6 @@
|
|||||||
#endif
|
#endif
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <termios.h>
|
#include <termios.h>
|
||||||
#include <termio.h>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|||||||
117
Daemons/2DO.txt
Normal file
117
Daemons/2DO.txt
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
(хорошо бы нигде не ориентироваться на DNS, везде писать IP)
|
||||||
|
|
||||||
|
### superweatherdaemon ###
|
||||||
|
|
||||||
|
Все "станции" (* - готово):
|
||||||
|
- старая метео *
|
||||||
|
- новая метео *
|
||||||
|
- датчик дождя *
|
||||||
|
- датчик грозы (молния <=5км -> FORCEOFF=1; молния >=10км - FORCEOFF=0)
|
||||||
|
поля: LIGTPWR, LIGTDIST, LIGTTIME
|
||||||
|
- ИБП в стойке (через минуту после выключения света FORCEOFF=1; через пять минут после включения - FORCEOFF=0)
|
||||||
|
поля: POWERED (0 - внешнее питание комплекса отключено, 1 - включено)
|
||||||
|
- ИК-allsky (как будет готов - получение "процента облачности" и "температуры неба")
|
||||||
|
поля: CLOUDS, SKYTEMP
|
||||||
|
|
||||||
|
В выдаваемую клиентам информацию добавить "стандартное" поле FORCEOFF (если ==1 - парковаться, закрываться и выключаться,
|
||||||
|
в т.ч. компьютер).
|
||||||
|
|
||||||
|
Только ИБП и датчик грозы влияют на поле FORCEOFF. FORCEOFF автоматом ставит WEATHER=3.
|
||||||
|
Флаг FORCEOFF - исключительная прерогатива "супердемона", как и поле WEATHER. Значение флага снимается лишь по устареванию,
|
||||||
|
как для прочих полей с проверкой на слишком старые данные. Лишь если снят FORCEOFF, поле WEATHER может начать понижать
|
||||||
|
уровень. Сразу со снятием флага FORCEOFF, снижаем уровень WEATHER до 2. Следовательно, запускать наблюдения можно будет
|
||||||
|
лишь через 2N секунд (кстати, добавить в конфиг время ожидания понижения уровня погоды) после подачи питания.
|
||||||
|
|
||||||
|
??? Добавить "стандартные" поля: LIGTPWR, LIGTDIST, LIGTTIME (например, если гроза еще дальше 5км, писать эти данные
|
||||||
|
в FITS-шапку).
|
||||||
|
|
||||||
|
Обязательно логгировать источники повышения и понижения уровня погоды, а также источник установки флага FORCEOFF.
|
||||||
|
|
||||||
|
В управляющий UNIX-сокет добавить комады:
|
||||||
|
- forceoff - чтобы можно было вручную ставить/снимать флаг
|
||||||
|
- weath - вручную сменять уровень погоды
|
||||||
|
- mute/unmute плагинов (по номеру из list); если плагин mute, то вся информация от него игнорируется
|
||||||
|
- ?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Демон общего питания (robometeo) ###
|
||||||
|
|
||||||
|
Мониторит FORCEOFF. При ==1 выжидает OFF_T_WAIT секунд, затем начинает проверять все 5 "роботелов": если купол закрыт и не пингуется,
|
||||||
|
отключаем питание оборудования и питание купола (кроме первого). Через OFF_T_FORCE секунд после сигнала даже если комп пингуется,
|
||||||
|
но купол закрыт, питание отключаем. Интервалы времени - в конфигурационный файл.
|
||||||
|
|
||||||
|
Если все отключено, ждет, пока не наступит 0. Потом выжидает еще ON_WAIT секунд, после чего последовательно подает питание
|
||||||
|
на все купола, выжидает ON_FORCE секунд и поочередно включает нагрузку всех ИБП куполов. Проверяет пингуемость компов и
|
||||||
|
пишет в лог, во сколько какой запинговался.
|
||||||
|
|
||||||
|
Аналогично с приборами в стойке: после OFF_R_WAIT секунд пинговать roboserv, robonas и robostorage; как не пингуются все -
|
||||||
|
выключать питание стойки. Либо же принудительно отключать после OFF_R_FORCE секунд. При снятии флага выжидаем ON_WAIT секунд,
|
||||||
|
затем включаем напряжение стойки и пингуем ее компы, логгируем.
|
||||||
|
|
||||||
|
В конфиг-файл: IP-адреса и номера релюшек оборудования телескопов и стойки, например,
|
||||||
|
|
||||||
|
telescopes=Astro-M1, Astro-M2...
|
||||||
|
Astro-M1=192.168.70.33:1:-1 # Название из telescopes = IP:номер реле оборудования:номер реле питания или -1
|
||||||
|
Astro-M2=192.168.70.35:2:22
|
||||||
|
...
|
||||||
|
equipment=roboserv,robonas,robostorage
|
||||||
|
roboserv=192.168.70.6:11:-1 # аналогично должен вести себя 192.168.70.6:-1:11
|
||||||
|
...
|
||||||
|
|
||||||
|
(при разборе конфига группировать по номеру реле оборудования и номеру реле питания; не отключать реле питания, пока
|
||||||
|
не отключены все компы, кроме выхода таймаута)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### weather proxy ###
|
||||||
|
|
||||||
|
Добавить флаг forceoff.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Clients ###
|
||||||
|
|
||||||
|
Все клиентские демоны используют weather_proxy и его библиотеку, чтобы получать из SHM погодные данные.
|
||||||
|
Демоны купола, телескопа и монтировки обязаны создавать в /tmp файлы с FITS-шапками (по ним "демон питания" узнает, что
|
||||||
|
можно выключать компьютер).
|
||||||
|
|
||||||
|
Клиенты реагируют на WEATHER:
|
||||||
|
0 - можно работать
|
||||||
|
1 - нельзя открываться, но можно продолжать работать
|
||||||
|
2 - форсированное закрывание купола, останов телескопа, остальное без изменений
|
||||||
|
3 - закрывание всего, парковка телескопа
|
||||||
|
|
||||||
|
|
||||||
|
## Демон купола ##
|
||||||
|
|
||||||
|
Форсированное закрывание при WEATHER>1 или FORCEOFF=1.
|
||||||
|
|
||||||
|
|
||||||
|
## Демон телескопа ##
|
||||||
|
|
||||||
|
Форсированное закрывание при WEATHER>2 или FORCEOFF=1.
|
||||||
|
|
||||||
|
|
||||||
|
## Демон монтировки ##
|
||||||
|
|
||||||
|
Останов при WEATHER=2 или FORCEOFF=1, парковка при WEATHER=3 (в случае FORCEOFF=1 ни в коем случае не парковать).
|
||||||
|
|
||||||
|
ДОБАВИТЬ ключ HDRTIME // UNIX-time of last activity -> в этом ключе содержится sl_dtime() обновления шапки (нужно для
|
||||||
|
демона питания, чтобы контролировать, когда отмерла монтировка).
|
||||||
|
|
||||||
|
|
||||||
|
## Демон питания оборудования и компьютера ##
|
||||||
|
|
||||||
|
Этот демон запускается из-под рута. Принимает от пользователя команды вклчения-выключения оборудования (сюда же можно
|
||||||
|
воткнуть управление плоским полем, пинание монтировки на включение, управление светом).
|
||||||
|
|
||||||
|
При получении FORCEOFF=1, сначала щелкается кнопка отключения монтировки. Далее
|
||||||
|
проверяются DOMFITSHDR (DOMESTAT= closed), TELFITSHDR (TELSTAT = closed) и MOUNTFITSHDR (HDRTIME должен быть минимум
|
||||||
|
на 30 секунд старше текущего времени).
|
||||||
|
Если все ОК, то выключается навесное оборудование и монтировка, а следом - и сам компьютер получает сигнал poweroff.
|
||||||
|
|
||||||
|
sync(); // Flush disk buffer to prevent data loss
|
||||||
|
reboot(RB_POWER_OFF);
|
||||||
|
или (правильней?): system("shutdown -P now")
|
||||||
|
|
||||||
@@ -2,11 +2,17 @@ Different daemons & tools
|
|||||||
=========================
|
=========================
|
||||||
|
|
||||||
- *10micron_stellarium* - simple daemon for 10-micron mount management from stellarium interface
|
- *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
|
- *domedaemon-astrosib* - open/close Astrosib dome by network query
|
||||||
- *netdaemon* - template for net-daemons
|
- *netdaemon* - template for net-daemons
|
||||||
- *netsocket* - scripts for management of network 220V-socket
|
- *netsocket* - scripts for management of network 220V-socket
|
||||||
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon
|
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon (almost deprecated)
|
||||||
- *teldaemon* - open/close Astrosib-500 scope covers by network query
|
- *teldaemon_astrosib* - open/close Astrosib-500 scope covers by network query
|
||||||
- *weatherdaemon* - weather daemon for old meteostation
|
- *weatherdaemon* - weather daemon for old meteostation (almost deprecated)
|
||||||
- *weatherdaemon_newmeteo* - daemon for new (chinese) meteostation
|
- *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
|
||||||
|
|||||||
23
Daemons/deprecated/10micron_stellarium.deprecated/Makefile
Normal file
23
Daemons/deprecated/10micron_stellarium.deprecated/Makefile
Normal 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)
|
||||||
6
Daemons/deprecated/10micron_stellarium.deprecated/Readme
Normal file
6
Daemons/deprecated/10micron_stellarium.deprecated/Readme
Normal 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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
-std=c17
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// Add predefined macros for your project here. For example:
|
||||||
|
// #define THE_ANSWER 42
|
||||||
|
#define EBUG 1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[General]
|
||||||
@@ -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<int>" 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<int>" 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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
-std=c++17
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.
|
||||||
110
Daemons/deprecated/10micron_stellarium.deprecated/cmdlnopts.c
Normal file
110
Daemons/deprecated/10micron_stellarium.deprecated/cmdlnopts.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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);
|
||||||
144
Daemons/deprecated/10micron_stellarium.deprecated/daemon.c
Normal file
144
Daemons/deprecated/10micron_stellarium.deprecated/daemon.c
Normal 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);
|
||||||
|
}
|
||||||
106
Daemons/deprecated/10micron_stellarium.deprecated/emulation.c
Normal file
106
Daemons/deprecated/10micron_stellarium.deprecated/emulation.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
383
Daemons/deprecated/10micron_stellarium.deprecated/libsofa.c
Normal file
383
Daemons/deprecated/10micron_stellarium.deprecated/libsofa.c
Normal 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(¤tTime, NULL);
|
||||||
|
gmtime_r(¤tTime.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
|
||||||
84
Daemons/deprecated/10micron_stellarium.deprecated/libsofa.h
Normal file
84
Daemons/deprecated/10micron_stellarium.deprecated/libsofa.h
Normal 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();
|
||||||
|
|
||||||
527
Daemons/deprecated/10micron_stellarium.deprecated/main.c
Normal file
527
Daemons/deprecated/10micron_stellarium.deprecated/main.c
Normal 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;
|
||||||
|
}
|
||||||
40
Daemons/deprecated/10micron_stellarium.deprecated/main.h
Normal file
40
Daemons/deprecated/10micron_stellarium.deprecated/main.h
Normal 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;
|
||||||
208
Daemons/deprecated/10micron_stellarium.deprecated/socket.c
Normal file
208
Daemons/deprecated/10micron_stellarium.deprecated/socket.c
Normal 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;
|
||||||
|
}
|
||||||
25
Daemons/deprecated/10micron_stellarium.deprecated/socket.h
Normal file
25
Daemons/deprecated/10micron_stellarium.deprecated/socket.h
Normal 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();
|
||||||
722
Daemons/deprecated/10micron_stellarium.deprecated/telescope.c
Normal file
722
Daemons/deprecated/10micron_stellarium.deprecated/telescope.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE QtCreatorProject>
|
<!DOCTYPE QtCreatorProject>
|
||||||
<!-- Written by QtCreator 19.0.0, 2026-03-22T21:14:01. -->
|
<!-- Written by QtCreator 18.0.0, 2026-04-01T11:06:44. -->
|
||||||
<qtcreator>
|
<qtcreator>
|
||||||
<data>
|
<data>
|
||||||
<variable>EnvironmentId</variable>
|
<variable>EnvironmentId</variable>
|
||||||
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
|
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
|
||||||
</data>
|
</data>
|
||||||
<data>
|
<data>
|
||||||
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
<variable>ProjectExplorer.Project.ActiveTarget</variable>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
|
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
|
||||||
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
|
||||||
<value type="int" key="EditorConfiguration.IndentSize">4</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.LineEndingBehavior">0</value>
|
||||||
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
|
||||||
<value type="bool" key="EditorConfiguration.MouseHiding">true</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.PaddingMode">1</value>
|
||||||
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
|
||||||
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</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="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.SmartSelectionChanging">true</value>
|
||||||
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
|
||||||
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
|
||||||
@@ -51,10 +51,10 @@
|
|||||||
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
|
||||||
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
|
||||||
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</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="bool" key="EditorConfiguration.cleanWhitespace">true</value>
|
||||||
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</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.skipTrailingWhitespace">true</value>
|
||||||
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
|
||||||
</valuemap>
|
</valuemap>
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
|
||||||
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
|
||||||
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</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>
|
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
|
||||||
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
|
||||||
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
|
||||||
@@ -96,12 +96,12 @@
|
|||||||
<value type="bool" key="HasPerBcDcs">true</value>
|
<value type="bool" key="HasPerBcDcs">true</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
|
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
|
||||||
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{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.ActiveBuildConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
|
||||||
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
|
<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.BuildConfiguration.BuildStepList.0">
|
||||||
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
|
||||||
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
|
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
|
||||||
@@ -155,7 +155,6 @@
|
|||||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
|
|
||||||
<value type="QList<int>" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
|
<value type="QList<int>" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
|
||||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
@@ -192,7 +191,6 @@
|
|||||||
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
|
||||||
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
|
||||||
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
|
||||||
<valuelist type="QVariantList" key="Analyzer.Valgrind.SuppressionFiles"/>
|
|
||||||
<value type="QList<int>" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
|
<value type="QList<int>" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
|
||||||
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
<valuelist type="QVariantList" key="CustomOutputParsers"/>
|
||||||
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ LDFLAGS += -lusefull_macros
|
|||||||
SRCS := $(wildcard *.c)
|
SRCS := $(wildcard *.c)
|
||||||
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
|
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
|
||||||
OBJDIR := mk
|
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))
|
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
|
||||||
DEPS := $(OBJS:.o=.d)
|
DEPS := $(OBJS:.o=.d)
|
||||||
TARGFILE := $(OBJDIR)/TARGET
|
TARGFILE := $(OBJDIR)/TARGET
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
commands.h
|
||||||
|
header.c
|
||||||
|
header.h
|
||||||
main.c
|
main.c
|
||||||
socket.c
|
socket.c
|
||||||
socket.h
|
socket.h
|
||||||
|
|||||||
31
Daemons/domedaemon_baader/commands.h
Normal file
31
Daemons/domedaemon_baader/commands.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// text commands and answers
|
||||||
|
#define TXT_GETWARN "d#warning"
|
||||||
|
#define TXT_OPENDOME "d#opendom"
|
||||||
|
#define TXT_CLOSEDOME "d#closdom"
|
||||||
|
#define TXT_STOPDOME "d#stopdom"
|
||||||
|
#define TXT_GETSTAT "d#get_dom"
|
||||||
|
#define TXT_GETWEAT "d#ask_wea"
|
||||||
|
#define TXT_ANS_MSGOK "d#gotmess"
|
||||||
|
#define TXT_ANS_WEAT "d#wea"
|
||||||
|
#define TXT_ANS_STAT "d#pos"
|
||||||
|
#define TXT_ANS_ERR "d#erro"
|
||||||
107
Daemons/domedaemon_baader/header.c
Normal file
107
Daemons/domedaemon_baader/header.c
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the baader_dome project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
#include "socket.h"
|
||||||
|
|
||||||
|
static char *headername = NULL;
|
||||||
|
static char *dome_name = NULL;
|
||||||
|
|
||||||
|
static int printhdr(int fd, const char *key, const char *val, const char *cmnt){
|
||||||
|
char tmp[81];
|
||||||
|
char tk[9];
|
||||||
|
if(strlen(key) > 8){
|
||||||
|
snprintf(tk, 8, "%s", key);
|
||||||
|
key = tk;
|
||||||
|
}
|
||||||
|
if(cmnt){
|
||||||
|
snprintf(tmp, 81, "%-8s= %-21s / %s", key, val, cmnt);
|
||||||
|
}else{
|
||||||
|
snprintf(tmp, 81, "%-8s= %s", key, val);
|
||||||
|
}
|
||||||
|
size_t l = strlen(tmp);
|
||||||
|
tmp[l] = '\n';
|
||||||
|
++l;
|
||||||
|
if(write(fd, tmp, l) != (ssize_t)l){
|
||||||
|
WARN("write()");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return TRUE if can write to given header file
|
||||||
|
int header_create(const char *file){
|
||||||
|
if(!file) return FALSE;
|
||||||
|
FILE *hf = fopen(file, "w");
|
||||||
|
if(!hf) return FALSE;
|
||||||
|
unlink(file);
|
||||||
|
if(headername) FREE(headername);
|
||||||
|
headername = strdup(file);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set given `name` for telescope name
|
||||||
|
void domename(const char *name){
|
||||||
|
if(dome_name) FREE(dome_name);
|
||||||
|
if(name){
|
||||||
|
int l = strlen(name) + 3;
|
||||||
|
dome_name = MALLOC(char, l);
|
||||||
|
snprintf(dome_name, l-1, "'%s'", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_header(){
|
||||||
|
if(!headername) return;
|
||||||
|
char aname[PATH_MAX];
|
||||||
|
char val[22];
|
||||||
|
#define WRHDR(k, v, c) do{if(printhdr(fd, k, v, c)){goto returning;}}while(0)
|
||||||
|
snprintf(aname, PATH_MAX-1, "%sXXXXXX", headername);
|
||||||
|
int fd = mkstemp(aname);
|
||||||
|
if(fd < 0){
|
||||||
|
LOGWARN("Can't write header file: mkstemp()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fchmod(fd, 0644);
|
||||||
|
|
||||||
|
dome_data_t st;
|
||||||
|
if(get_dome_data(&st)) WRHDR("OPERATIO", "'FORBIDDEN'", "Observations are forbidden");
|
||||||
|
if(dome_name) WRHDR("DOME", dome_name, "Dome manufacturer/name");
|
||||||
|
WRHDR("DOMESTAT", st.status, "Dome status");
|
||||||
|
WRHDR("DOMEWEAT", st.weather, "Dome weather sensor status");
|
||||||
|
snprintf(val, 21, "%d", st.errcode);
|
||||||
|
WRHDR("DOMEECOD", val, "Dome error code: Rain|Watchdog|Power");
|
||||||
|
snprintf(val, 21, "%.3f", st.stattime);
|
||||||
|
char timebuf[BUFSIZ];
|
||||||
|
time_t t = (time_t) st.stattime;
|
||||||
|
struct tm *tmp;
|
||||||
|
tmp = localtime(&t);
|
||||||
|
if(!tmp || 0 == strftime(timebuf, BUFSIZ, "Measurement time: %F %T", tmp)){
|
||||||
|
LOGERR("localtime() returned NULL");
|
||||||
|
goto returning;
|
||||||
|
}
|
||||||
|
WRHDR("TDOMMEAS", val, timebuf);
|
||||||
|
#undef WRHDR
|
||||||
|
returning:
|
||||||
|
close(fd);
|
||||||
|
rename(aname, headername);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of the weatherdaemon project.
|
* This file is part of the baader_dome project.
|
||||||
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@@ -18,4 +18,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
int getFD(char *path);
|
void write_header();
|
||||||
|
int header_create(const char *file);
|
||||||
|
void domename(const char *name);
|
||||||
@@ -20,12 +20,22 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <linux/prctl.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
#include "term.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{
|
typedef struct{
|
||||||
int help;
|
int help;
|
||||||
int verbose;
|
int verbose;
|
||||||
@@ -37,11 +47,17 @@ typedef struct{
|
|||||||
char *node;
|
char *node;
|
||||||
char *termpath;
|
char *termpath;
|
||||||
char *pidfile;
|
char *pidfile;
|
||||||
|
char *headerfile;
|
||||||
|
char *dome_name;
|
||||||
} parameters;
|
} parameters;
|
||||||
|
|
||||||
static parameters G = {
|
static parameters G = {
|
||||||
.maxclients = 2,
|
.maxclients = 2,
|
||||||
.serspeed = 9600
|
.serspeed = 9600,
|
||||||
|
.sertmout = 5000,
|
||||||
|
.pidfile = DEFAULT_PIDFILE,
|
||||||
|
.headerfile = DEFAULT_HEADERFILE,
|
||||||
|
.dome_name = DEFAULT_DOMENAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
static sl_option_t cmdlnopts[] = {
|
static sl_option_t cmdlnopts[] = {
|
||||||
@@ -51,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)"},
|
{"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"},
|
{"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)"},
|
{"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"},
|
{"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)"},
|
{"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)"},
|
{"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
|
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
|
// SIGUSR1 - FORBID observations
|
||||||
// SIGUSR2 - allow
|
// SIGUSR2 - allow
|
||||||
void signals(int sig){
|
void signals(int sig){
|
||||||
if(sig){
|
if(sig){
|
||||||
|
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){
|
if(sig == SIGUSR1){
|
||||||
forbid_observations(1);
|
forbid_observations(1);
|
||||||
|
LOGMSG("Got signal `observations forbidden`");
|
||||||
|
signal(sig, signals);
|
||||||
return;
|
return;
|
||||||
}else if(sig == SIGUSR2){
|
}else if(sig == SIGUSR2){
|
||||||
forbid_observations(0);
|
forbid_observations(0);
|
||||||
|
LOGMSG("Got signal `observations permitted`");
|
||||||
|
signal(sig, signals);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
signal(sig, SIG_IGN);
|
}
|
||||||
DBG("Get signal %d, quit.\n", sig);
|
LOGDBG("Get signal %d, quit.\n", sig);
|
||||||
LOGERR("Exit with status %d", sig);
|
}
|
||||||
}else LOGERR("Exit");
|
if(childpid == 0){
|
||||||
DBG("Stop server");
|
DBG("Stop server");
|
||||||
|
LOGMSG("Stop server");
|
||||||
stopserver();
|
stopserver();
|
||||||
DBG("Close terminal");
|
DBG("Close terminal");
|
||||||
close_term();
|
LOGMSG("Close terminal");
|
||||||
DBG("Exit");
|
term_close();
|
||||||
|
}else{
|
||||||
|
if(G.pidfile){
|
||||||
|
LOGMSG("Unlink %s", G.pidfile);
|
||||||
|
usleep(10000);
|
||||||
|
unlink(G.pidfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGERR("Exit with status %d", sig);
|
||||||
exit(sig);
|
exit(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,14 +125,18 @@ int main(int argc, char **argv){
|
|||||||
sl_init();
|
sl_init();
|
||||||
sl_parseargs(&argc, &argv, cmdlnopts);
|
sl_parseargs(&argc, &argv, cmdlnopts);
|
||||||
if(G.help) sl_showhelp(-1, 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(!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);
|
sl_check4running((char*)__progname, G.pidfile);
|
||||||
|
if(sl_daemonize()) ERR("Can't daemonize!");
|
||||||
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
|
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
|
||||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||||
if(G.logfile) OPENLOG(G.logfile, lvl, 1);
|
if(G.logfile) OPENLOG(G.logfile, lvl, 1);
|
||||||
LOGMSG("Started");
|
LOGMSG("Started");
|
||||||
if(!open_term(G.termpath, G.serspeed, G.sertmout)){
|
if(!term_open(G.termpath, G.serspeed, G.sertmout)){
|
||||||
LOGERR("Can't open %s", G.termpath);
|
LOGERR("Can't open %s", G.termpath);
|
||||||
ERRX("Fatal error");
|
ERRX("Fatal error");
|
||||||
}
|
}
|
||||||
@@ -102,11 +144,26 @@ int main(int argc, char **argv){
|
|||||||
signal(SIGINT, signals);
|
signal(SIGINT, signals);
|
||||||
signal(SIGQUIT, signals);
|
signal(SIGQUIT, signals);
|
||||||
signal(SIGTSTP, SIG_IGN);
|
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(SIGUSR1, signals);
|
||||||
signal(SIGUSR2, signals);
|
signal(SIGUSR2, signals);
|
||||||
runserver(G.isunix, G.node, G.maxclients);
|
runserver(G.isunix, G.node, G.maxclients);
|
||||||
LOGMSG("Ended");
|
LOGERR("Server error -> exit");
|
||||||
DBG("Close");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "commands.h"
|
||||||
|
#include "header.h"
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
#include "term.h"
|
#include "term.h"
|
||||||
|
|
||||||
@@ -32,14 +34,12 @@ typedef enum{
|
|||||||
} dome_commands_t;
|
} dome_commands_t;
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
dome_commands_t cmd;
|
|
||||||
int errcode; // error code
|
int errcode; // error code
|
||||||
char *status; // device status
|
char status[STATBUF_SZ]; // device status
|
||||||
int statlen; // size of `status` buffer
|
|
||||||
double stattime;// time of last status
|
double stattime;// time of last status
|
||||||
char *weather; // data from weather sensor
|
char weather[STATBUF_SZ]; // data from weather sensor
|
||||||
int weathlen; // length of `weather` buffer
|
dome_commands_t cmd;
|
||||||
double weathtime;// time of last weather
|
dome_commands_t erroredcmd; // command didn't run - waiting or stalled
|
||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
} dome_t;
|
} dome_t;
|
||||||
|
|
||||||
@@ -51,29 +51,19 @@ static atomic_bool ForbidObservations = 0; // ==1 if all is forbidden -> close d
|
|||||||
static sl_sock_t *locksock = NULL; // local server socket
|
static sl_sock_t *locksock = NULL; // local server socket
|
||||||
static sl_ringbuffer_t *rb = NULL; // incoming serial data
|
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(){
|
void stopserver(){
|
||||||
if(locksock) sl_sock_delete(&locksock);
|
if(locksock) sl_sock_delete(&locksock);
|
||||||
if(rb) sl_RB_delete(&rb);
|
if(rb) sl_RB_delete(&rb);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
|
||||||
// flags for standard handlers
|
|
||||||
static sl_sock_int_t iflag = {0};
|
|
||||||
static sl_sock_double_t dflag = {0};
|
|
||||||
static sl_sock_string_t sflag = {0};
|
|
||||||
static uint32_t bitflags = 0;
|
|
||||||
|
|
||||||
static sl_sock_hresult_e show(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
|
||||||
if(locksock && locksock->type != SOCKT_UNIX){
|
|
||||||
if(*client->IP){
|
|
||||||
printf("Client \"%s\" (fd=%d) ask for flags:\n", client->IP, client->fd);
|
|
||||||
}else printf("Can't get client's IP, flags:\n");
|
|
||||||
}else printf("Socket fd=%d asks for flags:\n", client->fd);
|
|
||||||
printf("\tiflag=%" PRId64 ", dflag=%g\n", iflag.val, dflag.val);
|
|
||||||
return RESULT_OK;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// send "measuret=..."
|
// send "measuret=..."
|
||||||
static void sendtmeasured(sl_sock_t *client, double t){
|
static void sendtmeasured(sl_sock_t *client, double t){
|
||||||
char buf[256];
|
char buf[256];
|
||||||
@@ -116,17 +106,18 @@ static sl_sock_hresult_e stoph(_U_ sl_sock_t *client, _U_ sl_sock_hitem_t *item,
|
|||||||
static sl_sock_hresult_e statush(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
static sl_sock_hresult_e statush(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
||||||
char buf[256];
|
char buf[256];
|
||||||
double t = NAN;
|
double t = NAN;
|
||||||
int ecode;
|
int ecode, lastecmd;
|
||||||
pthread_mutex_lock(&Dome.mutex);
|
pthread_mutex_lock(&Dome.mutex);
|
||||||
if(!*Dome.status || sl_dtime() - Dome.stattime > 3.*T_INTERVAL) snprintf(buf, 255, "%s=unknown\n", item->key);
|
if(!*Dome.status || sl_dtime() - Dome.stattime > 3.*T_INTERVAL) snprintf(buf, 255, "%s=unknown\n", item->key);
|
||||||
else{
|
else{
|
||||||
snprintf(buf, 255, "%s=%s\n", item->key, Dome.status);
|
snprintf(buf, 255, "%s=%s\n", item->key, Dome.status);
|
||||||
t = Dome.stattime;
|
t = Dome.stattime;
|
||||||
}
|
}
|
||||||
|
ecode = Dome.errcode;
|
||||||
|
lastecmd = Dome.erroredcmd;
|
||||||
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
if(!isnan(t)) sendtmeasured(client, t);
|
if(!isnan(t)) sendtmeasured(client, t);
|
||||||
ecode = Dome.errcode;
|
|
||||||
pthread_mutex_unlock(&Dome.mutex);
|
|
||||||
if(ecode){
|
if(ecode){
|
||||||
int l = snprintf(buf, 255, "error=closed");
|
int l = snprintf(buf, 255, "error=closed");
|
||||||
if(ecode > 99){
|
if(ecode > 99){
|
||||||
@@ -147,6 +138,17 @@ static sl_sock_hresult_e statush(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _
|
|||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
}
|
}
|
||||||
if(ForbidObservations) sl_sock_sendstrmessage(client, "FORBIDDEN\n");
|
if(ForbidObservations) sl_sock_sendstrmessage(client, "FORBIDDEN\n");
|
||||||
|
if(lastecmd != CMD_NONE){
|
||||||
|
const char *tcmd;
|
||||||
|
switch(lastecmd){
|
||||||
|
case CMD_OPEN: tcmd = "open"; break;
|
||||||
|
case CMD_CLOSE: tcmd = "close"; break;
|
||||||
|
case CMD_STOP: tcmd = "stop"; break;
|
||||||
|
default: tcmd = "unknown";
|
||||||
|
}
|
||||||
|
snprintf(buf, 255, "errored_command=%s\n", tcmd);
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
}
|
||||||
return RESULT_SILENCE;
|
return RESULT_SILENCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,10 +156,10 @@ static sl_sock_hresult_e weathh(sl_sock_t *client, sl_sock_hitem_t *item, _U_ co
|
|||||||
char buf[256];
|
char buf[256];
|
||||||
double t = NAN;
|
double t = NAN;
|
||||||
pthread_mutex_lock(&Dome.mutex);
|
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{
|
else{
|
||||||
snprintf(buf, 255, "%s=%s\n", item->key, Dome.weather);
|
snprintf(buf, 255, "%s=%s\n", item->key, Dome.weather);
|
||||||
t = Dome.weathtime;
|
t = Dome.stattime;
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&Dome.mutex);
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
@@ -202,44 +204,8 @@ static sl_sock_hresult_e defhandler(struct sl_sock *s, const char *str){
|
|||||||
sl_sock_sendstrmessage(s, "\n```\nTry \"help\"\n");
|
sl_sock_sendstrmessage(s, "\n```\nTry \"help\"\n");
|
||||||
return RESULT_SILENCE;
|
return RESULT_SILENCE;
|
||||||
}
|
}
|
||||||
#if 0
|
|
||||||
// if we use this macro, there's no need to run `sl_sock_keyno_init` later
|
|
||||||
static sl_sock_keyno_t kph_number = SL_SOCK_KEYNO_DEFAULT;
|
|
||||||
// handler for key with optional parameter number
|
|
||||||
static sl_sock_hresult_e keyparhandler(struct sl_sock *s, sl_sock_hitem_t *item, const char *req){
|
|
||||||
if(!item->data) return RESULT_FAIL;
|
|
||||||
char buf[1024];
|
|
||||||
int no = sl_sock_keyno_check((sl_sock_keyno_t*)item->data);
|
|
||||||
long long newval = -1;
|
|
||||||
if(req){
|
|
||||||
if(!sl_str2ll(&newval, req) || newval < 0 || newval > 0xffffffff) return RESULT_BADVAL;
|
|
||||||
}
|
|
||||||
printf("no = %d\n", no);
|
|
||||||
if(no < 0){ // flags as a whole
|
|
||||||
if(req) bitflags = (uint32_t)newval;
|
|
||||||
snprintf(buf, 1023, "flags = 0x%08X\n", bitflags);
|
|
||||||
sl_sock_sendstrmessage(s, buf);
|
|
||||||
}else if(no < 32){ // bit access
|
|
||||||
int bitmask = 1 << no;
|
|
||||||
if(req){
|
|
||||||
if(newval) bitflags |= bitmask;
|
|
||||||
else bitflags &= ~bitmask;
|
|
||||||
}
|
|
||||||
snprintf(buf, 1023, "flags[%d] = %d\n", no, bitflags & bitmask ? 1 : 0);
|
|
||||||
sl_sock_sendstrmessage(s, buf);
|
|
||||||
}else return RESULT_BADKEY;
|
|
||||||
return RESULT_SILENCE;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static sl_sock_hitem_t handlers[] = {
|
static sl_sock_hitem_t handlers[] = {
|
||||||
#if 0
|
|
||||||
{sl_sock_inthandler, "int", "set/get integer flag", (void*)&iflag},
|
|
||||||
{sl_sock_dblhandler, "dbl", "set/get double flag", (void*)&dflag},
|
|
||||||
{sl_sock_strhandler, "str", "set/get string variable", (void*)&sflag},
|
|
||||||
{keyparhandler, "flags", "set/get bit flags as whole (flags=val) or by bits (flags[bit]=val)", (void*)&kph_number},
|
|
||||||
{show, "show", "show current flags @ server console", NULL},
|
|
||||||
#endif
|
|
||||||
{openh, "open", "open dome", NULL},
|
{openh, "open", "open dome", NULL},
|
||||||
{closeh, "close", "close dome", NULL},
|
{closeh, "close", "close dome", NULL},
|
||||||
{statush, "status", "get dome status", NULL},
|
{statush, "status", "get dome status", NULL},
|
||||||
@@ -251,78 +217,52 @@ static sl_sock_hitem_t handlers[] = {
|
|||||||
|
|
||||||
// dome polling; @return TRUE if all OK
|
// dome polling; @return TRUE if all OK
|
||||||
static int poll_device(){
|
static int poll_device(){
|
||||||
char line[256];
|
char ans[ANSLEN];
|
||||||
if(write_cmd(TXT_GETWARN)){
|
char *data;
|
||||||
DBG("Can't write command warning");
|
if(!(data = term_cmdwans(TXT_GETWARN, TXT_ANS_ERR, ans))) return FALSE;
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
if(write_cmd(TXT_GETSTAT)){
|
|
||||||
DBG("Can't write command getstat");
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
if(write_cmd(TXT_GETWEAT)){
|
|
||||||
DBG("Can't write command getweath");
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
int l = 0;
|
|
||||||
do{
|
|
||||||
l = read_term(line, 256);
|
|
||||||
if(l > 0) sl_RB_write(rb, (uint8_t*) line, l);
|
|
||||||
}while(l > 0);
|
|
||||||
pthread_mutex_lock(&Dome.mutex); // prepare user buffers
|
|
||||||
// read ringbuffer, run parser and change buffers in `Dome`
|
|
||||||
while(sl_RB_readline(rb, line, sizeof(line)) > 0){
|
|
||||||
if(strncmp(line, TXT_ANS_STAT, strlen(TXT_ANS_STAT)) == 0){
|
|
||||||
DBG("Got status ans");
|
|
||||||
int stat;
|
|
||||||
Dome.stattime = sl_dtime();
|
|
||||||
if(sscanf(line + strlen(TXT_ANS_STAT), "%d", &stat) == 1){
|
|
||||||
if(stat == 1111)
|
|
||||||
snprintf(Dome.status, Dome.statlen, "opened");
|
|
||||||
else if(stat == 2222)
|
|
||||||
snprintf(Dome.status, Dome.statlen, "closed");
|
|
||||||
else
|
|
||||||
snprintf(Dome.status, Dome.statlen, "intermediate");
|
|
||||||
}
|
|
||||||
}else if(strncmp(line, TXT_ANS_ERR, strlen(TXT_ANS_ERR)) == 0){
|
|
||||||
DBG("Got status errno");
|
DBG("Got status errno");
|
||||||
int ecode;
|
int I;
|
||||||
if(sscanf(line + strlen(TXT_ANS_ERR), "%d", &ecode) == 1){
|
if(sscanf(data, "%d", &I) == 1){
|
||||||
Dome.errcode = ecode;
|
pthread_mutex_lock(&Dome.mutex);
|
||||||
DBG("errcode: %d", ecode);
|
Dome.errcode = I;
|
||||||
}
|
|
||||||
}else if(strncmp(line, TXT_ANS_WEAT, strlen(TXT_ANS_WEAT)) == 0){
|
|
||||||
DBG("Got weather ans");
|
|
||||||
int weather;
|
|
||||||
Dome.weathtime = sl_dtime();
|
|
||||||
if(sscanf(line + strlen(TXT_ANS_WEAT), "%d", &weather) == 1){
|
|
||||||
if(weather == 0)
|
|
||||||
snprintf(Dome.weather, Dome.weathlen, "good weather");
|
|
||||||
else if (weather == 1)
|
|
||||||
snprintf(Dome.weather, Dome.weathlen, "rain or clouds");
|
|
||||||
else
|
|
||||||
snprintf(Dome.weather, Dome.weathlen, "unknown");
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
DBG("Unknown answer: %s", line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&Dome.mutex);
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
|
DBG("errcode: %d", I);
|
||||||
|
}
|
||||||
|
if(!(data = term_cmdwans(TXT_GETSTAT, TXT_ANS_STAT, ans))) return FALSE;
|
||||||
|
DBG("Got status ans");
|
||||||
|
if(sscanf(data, "%d", &I) == 1){
|
||||||
|
pthread_mutex_lock(&Dome.mutex);
|
||||||
|
if(I == 1111)
|
||||||
|
snprintf(Dome.status, STATBUF_SZ-1, "opened");
|
||||||
|
else if(I == 2222)
|
||||||
|
snprintf(Dome.status, STATBUF_SZ-1, "closed");
|
||||||
|
else
|
||||||
|
snprintf(Dome.status, STATBUF_SZ-1, "intermediate");
|
||||||
|
Dome.stattime = sl_dtime();
|
||||||
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
|
}
|
||||||
|
if(!(data = term_cmdwans(TXT_GETWEAT, TXT_ANS_WEAT, ans))) return FALSE;
|
||||||
|
DBG("Got weather ans");
|
||||||
|
if(sscanf(data, "%d", &I) == 1){
|
||||||
|
pthread_mutex_lock(&Dome.mutex);
|
||||||
|
if(I == 0)
|
||||||
|
snprintf(Dome.weather, STATBUF_SZ-1, "good");
|
||||||
|
else if (I == 1)
|
||||||
|
snprintf(Dome.weather, STATBUF_SZ-1, "rain/clouds");
|
||||||
|
else
|
||||||
|
snprintf(Dome.weather, STATBUF_SZ-1, "unknown");
|
||||||
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
|
}
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void runserver(int isunix, const char *node, int maxclients){
|
void runserver(int isunix, const char *node, int maxclients){
|
||||||
int forbidden = 0;
|
int forbidden = 0;
|
||||||
|
char ans[ANSLEN];
|
||||||
if(locksock) sl_sock_delete(&locksock);
|
if(locksock) sl_sock_delete(&locksock);
|
||||||
if(rb) sl_RB_delete(&rb);
|
if(rb) sl_RB_delete(&rb);
|
||||||
rb = sl_RB_new(BUFSIZ);
|
rb = sl_RB_new(BUFSIZ);
|
||||||
Dome.cmd = CMD_NONE;
|
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);
|
pthread_mutex_init(&Dome.mutex, NULL);
|
||||||
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
|
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
|
||||||
locksock = sl_sock_run_server(type, node, -1, handlers);
|
locksock = sl_sock_run_server(type, node, -1, handlers);
|
||||||
@@ -335,11 +275,14 @@ void runserver(int isunix, const char *node, int maxclients){
|
|||||||
while(locksock && locksock->connected){
|
while(locksock && locksock->connected){
|
||||||
if(ForbidObservations){
|
if(ForbidObservations){
|
||||||
if(!forbidden){
|
if(!forbidden){
|
||||||
if(0 == write_cmd(TXT_CLOSEDOME)) forbidden = 1;
|
if(term_cmdwans(TXT_CLOSEDOME, TXT_ANS_MSGOK, ans)) forbidden = 1;
|
||||||
pthread_mutex_lock(&Dome.mutex);
|
|
||||||
Dome.cmd = CMD_NONE;
|
|
||||||
pthread_mutex_unlock(&Dome.mutex);
|
|
||||||
}
|
}
|
||||||
|
pthread_mutex_lock(&Dome.mutex);
|
||||||
|
if(Dome.cmd != CMD_NONE){
|
||||||
|
Dome.erroredcmd = Dome.cmd;
|
||||||
|
Dome.cmd = CMD_NONE;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
}else forbidden = 0;
|
}else forbidden = 0;
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
if(!locksock->rthread){
|
if(!locksock->rthread){
|
||||||
@@ -347,28 +290,34 @@ void runserver(int isunix, const char *node, int maxclients){
|
|||||||
LOGERR("Server handlers thread is dead");
|
LOGERR("Server handlers thread is dead");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if(sl_dtime() - tgot > T_INTERVAL){
|
double tnow = sl_dtime();
|
||||||
if(poll_device()) tgot = sl_dtime();
|
if(tnow - tgot > T_INTERVAL){
|
||||||
|
if(poll_device()){
|
||||||
|
tgot = tnow;
|
||||||
|
write_header();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if(ForbidObservations) continue;
|
||||||
pthread_mutex_lock(&Dome.mutex);
|
pthread_mutex_lock(&Dome.mutex);
|
||||||
if(Dome.cmd != CMD_NONE){
|
if(Dome.cmd != CMD_NONE){
|
||||||
switch(Dome.cmd){
|
switch(Dome.cmd){
|
||||||
case CMD_OPEN:
|
case CMD_OPEN:
|
||||||
DBG("received command: open");
|
DBG("received command: open");
|
||||||
if(0 == write_cmd(TXT_OPENDOME)) Dome.cmd = CMD_NONE;
|
if(term_cmdwans(TXT_OPENDOME, TXT_ANS_MSGOK, ans)) Dome.cmd = CMD_NONE;
|
||||||
break;
|
break;
|
||||||
case CMD_CLOSE:
|
case CMD_CLOSE:
|
||||||
DBG("received command: close");
|
DBG("received command: close");
|
||||||
if(0 == write_cmd(TXT_CLOSEDOME)) Dome.cmd = CMD_NONE;
|
if(term_cmdwans(TXT_CLOSEDOME, TXT_ANS_MSGOK, ans)) Dome.cmd = CMD_NONE;
|
||||||
break;
|
break;
|
||||||
case CMD_STOP:
|
case CMD_STOP:
|
||||||
DBG("received command: stop");
|
DBG("received command: stop");
|
||||||
if(0 == write_cmd(TXT_STOPDOME)) Dome.cmd = CMD_NONE;
|
if(term_cmdwans(TXT_STOPDOME, TXT_ANS_MSGOK, ans)) Dome.cmd = CMD_NONE;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
DBG("WTF?");
|
DBG("WTF?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Dome.erroredcmd = Dome.cmd; // if command didn't run last time, it will store here
|
||||||
pthread_mutex_unlock(&Dome.mutex);
|
pthread_mutex_unlock(&Dome.mutex);
|
||||||
}
|
}
|
||||||
stopserver();
|
stopserver();
|
||||||
|
|||||||
@@ -23,8 +23,16 @@
|
|||||||
// size of weather/status buffers
|
// size of weather/status buffers
|
||||||
#define STATBUF_SZ 256
|
#define STATBUF_SZ 256
|
||||||
// dome polling interval (clear watchdog & get status)
|
// dome polling interval (clear watchdog & get status)
|
||||||
#define T_INTERVAL (1.0)
|
#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 runserver(int isunix, const char *node, int maxclients);
|
||||||
void stopserver();
|
void stopserver();
|
||||||
void forbid_observations(int forbid);
|
void forbid_observations(int forbid);
|
||||||
|
|||||||
@@ -16,15 +16,17 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "term.h"
|
#include "term.h"
|
||||||
|
|
||||||
static sl_tty_t *dev = NULL; // shoul be global to restore if die
|
static sl_tty_t *dev = NULL; // shoul be global to restore if die
|
||||||
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
// close serial device
|
// close serial device
|
||||||
void close_term(){
|
void term_close(){
|
||||||
if(dev) sl_tty_close(&dev);
|
if(dev) sl_tty_close(&dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,19 +37,19 @@ void close_term(){
|
|||||||
* @param usec - timeout (us), if < 1e-6 - leave default
|
* @param usec - timeout (us), if < 1e-6 - leave default
|
||||||
* @return FALSE if failed
|
* @return FALSE if failed
|
||||||
*/
|
*/
|
||||||
int open_term(char *path, int speed, double usec){
|
int term_open(char *path, int speed, double usec){
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
if(dev) sl_tty_close(&dev);
|
if(dev) sl_tty_close(&dev);
|
||||||
LOGMSG("Try to open serial %s at speed %d", path, speed);
|
LOGMSG("Try to open serial %s at speed %d", path, speed);
|
||||||
DBG("Open serial");
|
DBG("Init serial");
|
||||||
dev = sl_tty_new(path, speed, 4096);
|
dev = sl_tty_new(path, speed, 4096);
|
||||||
|
DBG("Open serial");
|
||||||
if(dev) dev = sl_tty_open(dev, 1);
|
if(dev) dev = sl_tty_open(dev, 1);
|
||||||
if(!dev){
|
if(!dev) goto rtn;
|
||||||
LOGERR("Can't open %s with speed %d. Exit.", path, speed);
|
DBG("Opened");
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
if(usec >= 1e-6){
|
if(usec >= 1e-6){
|
||||||
DBG("set timeout to %gus", usec);
|
DBG("set timeout to %gus", usec);
|
||||||
if(!sl_tty_tmout(usec)){
|
if(sl_tty_tmout(usec) < 0){
|
||||||
LOGWARN("Can't set timeout to %gus", usec);
|
LOGWARN("Can't set timeout to %gus", usec);
|
||||||
WARNX("Can't set timeout to %gus", usec);
|
WARNX("Can't set timeout to %gus", usec);
|
||||||
}
|
}
|
||||||
@@ -56,60 +58,129 @@ int open_term(char *path, int speed, double usec){
|
|||||||
LOGWARN("Can't set exact speed! Opened %s at speed %d", dev->portname, dev->speed);
|
LOGWARN("Can't set exact speed! Opened %s at speed %d", dev->portname, dev->speed);
|
||||||
WARNX("Can't set speed %d (try %d)", speed, dev->speed);
|
WARNX("Can't set speed %d (try %d)", speed, dev->speed);
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
rtn:
|
||||||
if(dev) return TRUE;
|
if(dev) return TRUE;
|
||||||
|
LOGERR("Can't open %s with speed %d. Exit.", path, speed);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief read_term - read data from serial terminal
|
* @brief nonblk_read - internal read
|
||||||
* @param buf - buffer for data
|
* @param ans (o) - buffer for data
|
||||||
* @param length - size of `buf`
|
* @return NULL if failed or `ans`
|
||||||
* @return amount of data read
|
|
||||||
*/
|
*/
|
||||||
int read_term(char *buf, int length){
|
static char *nonblk_read(char ans[ANSLEN]){
|
||||||
static char *bufptr = NULL; // last message, if it was longer than `length`
|
static char *bufptr = NULL; // last message, if it was longer than `length`
|
||||||
static int lastL = 0;
|
static int lastL = 0;
|
||||||
if(!dev || !buf || length < 1) return 0;
|
FNAME();
|
||||||
|
if(!dev) return NULL;
|
||||||
|
int length = ANSLEN - 1;
|
||||||
|
DBG("ok");
|
||||||
|
if(!ans){ // clear
|
||||||
|
DBG("clr");
|
||||||
|
while(sl_tty_read(dev) > 0);
|
||||||
|
bufptr = NULL;
|
||||||
|
lastL = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
if(bufptr && lastL){
|
if(bufptr && lastL){
|
||||||
if(length > lastL) length = lastL;
|
if(length > lastL) length = lastL;
|
||||||
DBG("got %d bytes from old record", length);
|
memcpy(ans, bufptr, length);
|
||||||
memcpy(buf, bufptr, length);
|
|
||||||
if((lastL -= length) < 1){
|
if((lastL -= length) < 1){
|
||||||
lastL = 0; bufptr = NULL;
|
lastL = 0; bufptr = NULL;
|
||||||
}
|
}
|
||||||
return length;
|
ans[length] = 0;
|
||||||
|
DBG("got %d bytes from old record: '%s'", length, ans);
|
||||||
|
return ans;
|
||||||
}
|
}
|
||||||
if(!sl_tty_read(dev)) return 0;
|
int totlen = 0;
|
||||||
DBG("Got from serial %zd bytes", dev->buflen);
|
while(length > 0 && sl_tty_read(dev) > 0){
|
||||||
LOGDBG("Got from serial: %zd bytes", dev->buflen);
|
int lmax = length;
|
||||||
if(length >= (int)dev->buflen){
|
if(lmax >= (int)dev->buflen){
|
||||||
DBG("Full buffer can be copied");
|
DBG("Full buffer can be copied");
|
||||||
length = (int)dev->buflen;
|
lmax = (int)dev->buflen;
|
||||||
bufptr = NULL;
|
|
||||||
lastL = 0;
|
|
||||||
}else{ // store part of data in buffer
|
}else{ // store part of data in buffer
|
||||||
lastL = dev->buflen - length;
|
lastL = dev->buflen - lmax;
|
||||||
DBG("Store %d bytes for next read", lastL);
|
DBG("Store %d bytes for next read", lastL);
|
||||||
bufptr = dev->buf + length;
|
bufptr = dev->buf + lmax;
|
||||||
}
|
}
|
||||||
memcpy(buf, dev->buf, length);
|
memcpy(ans + totlen, dev->buf, lmax);
|
||||||
return length;
|
length -= lmax;
|
||||||
|
totlen += lmax;
|
||||||
|
ans[totlen] = 0;
|
||||||
|
}
|
||||||
|
DBG("totlen: %d", totlen);
|
||||||
|
if(totlen == 0) return NULL;
|
||||||
|
DBG("copied %d: '%s'", totlen, ans);
|
||||||
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief write_term - write data
|
* @brief nonblk_write - internal write
|
||||||
* @param buf - buffer
|
* @param buf (i) - data to write
|
||||||
* @param length - its length
|
* @param length - its length
|
||||||
* @return 0 if OK and 1 if failed
|
* @return 0 if failed or `length`
|
||||||
*/
|
*/
|
||||||
int write_term(const char *buf, int length){
|
static int nonblk_write(const char *buf, int length){
|
||||||
if(!dev || !buf || length < 1) return 0;
|
if(!buf || length < 1) return 0;
|
||||||
return sl_tty_write(dev->comfd, buf, length);
|
if(0 == sl_tty_write(dev->comfd, buf, length)) return length;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write string command
|
/**
|
||||||
int write_cmd(const char *buf){
|
* @brief term_read - read string from terminal
|
||||||
if(!buf || !*buf) return 0;
|
* @param ans (o) - buffer or NULL to clear last data
|
||||||
DBG("Ask to write %s", buf);
|
* @return NULL or pointer to zero-terminated `ans`
|
||||||
return write_term(buf, strlen(buf));
|
*/
|
||||||
|
char *term_read(char ans[ANSLEN]){
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
char *ret = nonblk_read(ans);
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
DBG("read: '%s'", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief term_write - write data and got answer
|
||||||
|
* @param str (i) - zero-terminated string to write
|
||||||
|
* @param ans (o) - NULL to clear incoming data or buffer to read
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
char *term_write(const char *str, char ans[ANSLEN]){
|
||||||
|
if(!str || !*str) return NULL;
|
||||||
|
DBG("Send cmd '%s'", str);
|
||||||
|
char *ret = NULL;
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
if(nonblk_write(str, strlen(str))){
|
||||||
|
usleep(USLEEP_BEFORE_READ);
|
||||||
|
ret = nonblk_read(ans);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
DBG("read: '%s'", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief term_cmdwans - write command and get answer
|
||||||
|
* @param str (i) - command to write
|
||||||
|
* @param prefix (i) - prefix of answer (string should begin with this text)
|
||||||
|
* @param ans (o) - buffer for answer (non-NULL!!!)
|
||||||
|
* @return pointer to data just after `prefix` in `ans` or NULL if no `prefix` found
|
||||||
|
*/
|
||||||
|
char *term_cmdwans(const char *str, const char *prefix, char ans[ANSLEN]){
|
||||||
|
if(!str || !prefix || !ans) return NULL;
|
||||||
|
DBG("Send cmd '%s'", str);
|
||||||
|
char *ret = NULL;
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
if(nonblk_write(str, strlen(str))){
|
||||||
|
usleep(USLEEP_BEFORE_READ);
|
||||||
|
ret = nonblk_read(ans);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
int l = strlen(prefix);
|
||||||
|
DBG("compare %s with %s", ret ? (ret) : "null", prefix);
|
||||||
|
if(!ret || strncmp(ans, prefix, l)) return NULL; // no answer or not found
|
||||||
|
DBG("found");
|
||||||
|
return ans + l;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,19 +18,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// text commands and answers
|
#include <stdint.h>
|
||||||
#define TXT_GETWARN "d#warning\n"
|
|
||||||
#define TXT_OPENDOME "d#opendom\n"
|
|
||||||
#define TXT_CLOSEDOME "d#closdom\n"
|
|
||||||
#define TXT_STOPDOME "d#stopdom\n"
|
|
||||||
#define TXT_GETSTAT "d#get_dom\n"
|
|
||||||
#define TXT_GETWEAT "d#ask_wea\n"
|
|
||||||
#define TXT_ANS_WEAT "d#wea"
|
|
||||||
#define TXT_ANS_STAT "d#pos"
|
|
||||||
#define TXT_ANS_ERR "d#erro"
|
|
||||||
|
|
||||||
int open_term(char *path, int speed, double usec);
|
// pause before reading answer: for stupid baader = 50ms
|
||||||
void close_term();
|
#define USLEEP_BEFORE_READ 50000
|
||||||
int read_term(char *buf, int length);
|
|
||||||
int write_term(const char *buf, int length);
|
// length of answer (including terminating zero)
|
||||||
int write_cmd(const char *buf);
|
#define ANSLEN 128
|
||||||
|
|
||||||
|
int term_open(char *path, int speed, double usec);
|
||||||
|
void term_close();
|
||||||
|
char *term_read(char ans[ANSLEN]);
|
||||||
|
char *term_write(const char *str, char ans[ANSLEN]);
|
||||||
|
char *term_cmdwans(const char *str, const char *prefix, char ans[ANSLEN]);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ LDFLAGS += -lusefull_macros
|
|||||||
SRCS := $(wildcard *.c)
|
SRCS := $(wildcard *.c)
|
||||||
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
|
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
|
||||||
OBJDIR := mk
|
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))
|
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
|
||||||
DEPS := $(OBJS:.o=.d)
|
DEPS := $(OBJS:.o=.d)
|
||||||
TARGFILE := $(OBJDIR)/TARGET
|
TARGFILE := $(OBJDIR)/TARGET
|
||||||
|
|||||||
46
Daemons/teldaemon_astrosib/commands.h
Normal file
46
Daemons/teldaemon_astrosib/commands.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
// text commands and answers
|
||||||
|
#define TXT_FOCUSABS "FOCUSERGO?"
|
||||||
|
#define TXT_FOCUSIN "FOCUSERI?"
|
||||||
|
#define TXT_FOCUSOUT "FOCUSERO?"
|
||||||
|
#define TXT_FOCSTOP "FOCUSERSTOP?\r"
|
||||||
|
#define TXT_FOCGET "FOCUSERGPOS?\r"
|
||||||
|
#define TXT_OPEN "SHUTTEROPEN?1,1,1,1,1\r"
|
||||||
|
#define TXT_CLOSE "SHUTTERCLOSE?1,1,1,1,1\r"
|
||||||
|
#define TXT_STATUS "SHUTTERSTATUS?\r"
|
||||||
|
#define TXT_COOLERON "COOLERON?100\r"
|
||||||
|
#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?"
|
||||||
|
#define TXT_ANS_FOCPOS "OK\rFOCUSERPOS?"
|
||||||
|
|
||||||
|
#define FOC_MINPOS 0
|
||||||
|
#define FOC_MAXPOS 65000
|
||||||
142
Daemons/teldaemon_astrosib/header.c
Normal file
142
Daemons/teldaemon_astrosib/header.c
Normal 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);
|
||||||
|
}
|
||||||
39
Daemons/teldaemon_astrosib/header.h
Normal file
39
Daemons/teldaemon_astrosib/header.h
Normal 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);
|
||||||
@@ -20,81 +20,130 @@
|
|||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <linux/prctl.h>
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
#include "term.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{
|
typedef struct{
|
||||||
int help;
|
int help;
|
||||||
int verbose;
|
int verbose;
|
||||||
int isunix;
|
int isunix;
|
||||||
int maxclients;
|
int maxclients;
|
||||||
int serspeed;
|
int serspeed;
|
||||||
|
int headermask;
|
||||||
double sertmout;
|
double sertmout;
|
||||||
char *logfile;
|
char *logfile;
|
||||||
char *node;
|
char *node;
|
||||||
char *termpath;
|
char *termpath;
|
||||||
char *pidfile;
|
char *pidfile;
|
||||||
|
char *headerfile;
|
||||||
|
char *telescope_name;
|
||||||
} parameters;
|
} parameters;
|
||||||
|
|
||||||
static parameters G = {
|
static parameters G = {
|
||||||
.maxclients = 2,
|
.maxclients = 2,
|
||||||
.serspeed = 9600
|
.serspeed = 9600,
|
||||||
|
.sertmout = 1000.,
|
||||||
|
.pidfile = DEFAULT_PIDFILE,
|
||||||
|
.headerfile = DEFAULT_HEADERFILE,
|
||||||
|
.telescope_name = DEFAULT_TELNAME,
|
||||||
|
.headermask = 0xff,
|
||||||
};
|
};
|
||||||
|
|
||||||
static sl_option_t cmdlnopts[] = {
|
static sl_option_t cmdlnopts[] = {
|
||||||
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
|
{"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)"},
|
{"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)"},
|
{"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"},
|
{"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)"},
|
{"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"},
|
{"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)"},
|
{"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
|
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
|
// SIGUSR1 - FORBID observations
|
||||||
// SIGUSR2 - allow
|
// SIGUSR2 - allow
|
||||||
void signals(int sig){
|
void signals(int sig){
|
||||||
if(sig){
|
if(sig){
|
||||||
|
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){
|
if(sig == SIGUSR1){
|
||||||
forbid_observations(1);
|
forbid_observations(1);
|
||||||
|
LOGMSG("Got signal `observations forbidden`");
|
||||||
|
signal(sig, signals);
|
||||||
return;
|
return;
|
||||||
}else if(sig == SIGUSR2){
|
}else if(sig == SIGUSR2){
|
||||||
forbid_observations(0);
|
forbid_observations(0);
|
||||||
|
LOGMSG("Got signal `observations permitted`");
|
||||||
|
signal(sig, signals);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
signal(sig, SIG_IGN);
|
}
|
||||||
DBG("Get signal %d, quit.\n", sig);
|
LOGDBG("Get signal %d, quit.\n", sig);
|
||||||
LOGERR("Exit with status %d", sig);
|
}
|
||||||
}else LOGERR("Exit");
|
if(childpid == 0){
|
||||||
DBG("Stop server");
|
DBG("Stop server");
|
||||||
|
LOGMSG("Stop server");
|
||||||
stopserver();
|
stopserver();
|
||||||
DBG("Close terminal");
|
DBG("Close terminal");
|
||||||
close_term();
|
LOGMSG("Close terminal");
|
||||||
DBG("Exit");
|
term_close();
|
||||||
|
}else{
|
||||||
|
if(G.pidfile){
|
||||||
|
LOGMSG("Unlink %s", G.pidfile);
|
||||||
|
usleep(10000);
|
||||||
|
unlink(G.pidfile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOGERR("Exit with status %d", sig);
|
||||||
exit(sig);
|
exit(sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv){
|
||||||
sl_init();
|
sl_init();
|
||||||
sl_parseargs(&argc, &argv, cmdlnopts);
|
sl_parseargs(&argc, &argv, cmdlnopts);
|
||||||
if(G.help) sl_showhelp(-1, 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");
|
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;
|
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
|
||||||
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
|
||||||
if(G.logfile) OPENLOG(G.logfile, lvl, 1);
|
if(G.logfile) OPENLOG(G.logfile, lvl, 1);
|
||||||
LOGMSG("Started");
|
LOGMSG("Started");
|
||||||
if(!open_term(G.termpath, G.serspeed, G.sertmout)){
|
if(!term_open(G.termpath, G.serspeed, G.sertmout)){
|
||||||
LOGERR("Can't open %s", G.termpath);
|
LOGERR("Can't open %s", G.termpath);
|
||||||
ERRX("Fatal error");
|
ERRX("Fatal error");
|
||||||
}
|
}
|
||||||
@@ -102,11 +151,26 @@ int main(int argc, char **argv){
|
|||||||
signal(SIGINT, signals);
|
signal(SIGINT, signals);
|
||||||
signal(SIGQUIT, signals);
|
signal(SIGQUIT, signals);
|
||||||
signal(SIGTSTP, SIG_IGN);
|
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(SIGUSR1, signals);
|
||||||
signal(SIGUSR2, signals);
|
signal(SIGUSR2, signals);
|
||||||
runserver(G.isunix, G.node, G.maxclients);
|
runserver(G.isunix, G.node, G.maxclients);
|
||||||
LOGMSG("Ended");
|
LOGERR("Server error -> exit");
|
||||||
DBG("Close");
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "commands.h"
|
||||||
|
#include "header.h"
|
||||||
#include "socket.h"
|
#include "socket.h"
|
||||||
#include "term.h"
|
#include "term.h"
|
||||||
|
|
||||||
@@ -30,21 +32,21 @@ typedef enum{
|
|||||||
CMD_FOCSTOP,
|
CMD_FOCSTOP,
|
||||||
CMD_COOLERON,
|
CMD_COOLERON,
|
||||||
CMD_COOLEROFF,
|
CMD_COOLEROFF,
|
||||||
|
CMD_HEATERON,
|
||||||
|
CMD_HEATEROFF,
|
||||||
CMD_NONE
|
CMD_NONE
|
||||||
} tel_commands_t;
|
} tel_commands_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct{
|
typedef struct{
|
||||||
tel_commands_t cmd; // deferred command
|
|
||||||
int focuserpos; // focuser position
|
int focuserpos; // focuser position
|
||||||
char *status; // device status
|
char status[STATBUF_SZ]; // device status
|
||||||
int statlen; // size of `status` buffer
|
|
||||||
double stattime; // time of last status
|
double stattime; // time of last status
|
||||||
int cooler; // cooler's status
|
int cooler; // cooler's status
|
||||||
|
int heater; // heater's status
|
||||||
double mirrortemp; // T mirror, degC
|
double mirrortemp; // T mirror, degC
|
||||||
double ambienttemp; // T ambient, 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;
|
pthread_mutex_t mutex;
|
||||||
} tel_t;
|
} tel_t;
|
||||||
|
|
||||||
@@ -54,7 +56,6 @@ static tel_t telescope = {0};
|
|||||||
static atomic_bool ForbidObservations = 0; // ==1 if all is forbidden -> close dome and not allow to open
|
static atomic_bool ForbidObservations = 0; // ==1 if all is forbidden -> close dome and not allow to open
|
||||||
|
|
||||||
static sl_sock_t *locksock = NULL; // local server socket
|
static sl_sock_t *locksock = NULL; // local server socket
|
||||||
static sl_ringbuffer_t *rb = NULL; // incoming serial data
|
|
||||||
|
|
||||||
// args of absolute/relative focus move commands
|
// args of absolute/relative focus move commands
|
||||||
static sl_sock_int_t Absmove = {0};
|
static sl_sock_int_t Absmove = {0};
|
||||||
@@ -62,7 +63,19 @@ static sl_sock_int_t Relmove = {0};
|
|||||||
|
|
||||||
void stopserver(){
|
void stopserver(){
|
||||||
if(locksock) sl_sock_delete(&locksock);
|
if(locksock) sl_sock_delete(&locksock);
|
||||||
if(rb) sl_RB_delete(&rb);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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=..."
|
// send "measuret=..."
|
||||||
@@ -106,20 +119,37 @@ static sl_sock_hresult_e fstoph(_U_ sl_sock_t *client, _U_ sl_sock_hitem_t *item
|
|||||||
|
|
||||||
static sl_sock_hresult_e statush(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
static sl_sock_hresult_e statush(sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
|
||||||
char buf[256];
|
char buf[256];
|
||||||
double t = NAN;
|
double t = NAN, mirt, ambt;
|
||||||
|
tel_commands_t lastecmd;
|
||||||
pthread_mutex_lock(&telescope.mutex);
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
if(!*telescope.status || sl_dtime() - telescope.stattime > 3.*T_INTERVAL) snprintf(buf, 255, "%s=unknown\n", item->key);
|
if(!*telescope.status || sl_dtime() - telescope.stattime > 3.*T_INTERVAL) snprintf(buf, 255, "%s=unknown\n", item->key);
|
||||||
else{
|
else{
|
||||||
snprintf(buf, 255, "%s=%s\n", item->key, telescope.status);
|
snprintf(buf, 255, "%s=%s\n", item->key, telescope.status);
|
||||||
t = telescope.stattime;
|
t = telescope.stattime;
|
||||||
}
|
}
|
||||||
|
mirt = telescope.mirrortemp;
|
||||||
|
ambt = telescope.ambienttemp;
|
||||||
|
lastecmd = telescope.errcmd;
|
||||||
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
if(!isnan(t)) sendtmeasured(client, t);
|
if(!isnan(t)) sendtmeasured(client, t);
|
||||||
snprintf(buf, 255, "mirrortemp=%.1f\n", telescope.mirrortemp);
|
snprintf(buf, 255, "mirrortemp=%.1f\n", mirt);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
snprintf(buf, 255, "ambienttemp=%.1f\n", telescope.ambienttemp);
|
snprintf(buf, 255, "ambienttemp=%.1f\n", ambt);
|
||||||
sl_sock_sendstrmessage(client, buf);
|
sl_sock_sendstrmessage(client, buf);
|
||||||
pthread_mutex_unlock(&telescope.mutex);
|
if(lastecmd != CMD_NONE){
|
||||||
|
const char *tcmd;
|
||||||
|
switch(lastecmd){
|
||||||
|
case CMD_OPEN: tcmd = "open"; break;
|
||||||
|
case CMD_CLOSE: tcmd = "close"; break;
|
||||||
|
case CMD_FOCSTOP: tcmd = "focstop"; break;
|
||||||
|
case CMD_COOLERON: case CMD_COOLEROFF: tcmd = "cooler"; break;
|
||||||
|
default: tcmd = "unknown";
|
||||||
|
}
|
||||||
|
snprintf(buf, 255, "errored_command=%s\n", tcmd);
|
||||||
|
sl_sock_sendstrmessage(client, buf);
|
||||||
|
|
||||||
|
}
|
||||||
if(ForbidObservations) sl_sock_sendstrmessage(client, "FORBIDDEN\n");
|
if(ForbidObservations) sl_sock_sendstrmessage(client, "FORBIDDEN\n");
|
||||||
return RESULT_SILENCE;
|
return RESULT_SILENCE;
|
||||||
}
|
}
|
||||||
@@ -143,6 +173,26 @@ static sl_sock_hresult_e coolerh(sl_sock_t *client, sl_sock_hitem_t *item, const
|
|||||||
return RESULT_SILENCE;
|
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
|
// focuser getter
|
||||||
static sl_sock_hresult_e foch(sl_sock_t *client, sl_sock_hitem_t *item, _U_ const char *req){
|
static sl_sock_hresult_e foch(sl_sock_t *client, sl_sock_hitem_t *item, _U_ const char *req){
|
||||||
char buf[256];
|
char buf[256];
|
||||||
@@ -192,13 +242,6 @@ static sl_sock_hresult_e defhandler(struct sl_sock *s, const char *str){
|
|||||||
}
|
}
|
||||||
|
|
||||||
static sl_sock_hitem_t handlers[] = {
|
static sl_sock_hitem_t handlers[] = {
|
||||||
#if 0
|
|
||||||
{sl_sock_inthandler, "int", "set/get integer flag", (void*)&iflag},
|
|
||||||
{sl_sock_dblhandler, "dbl", "set/get double flag", (void*)&dflag},
|
|
||||||
{sl_sock_strhandler, "str", "set/get string variable", (void*)&sflag},
|
|
||||||
{keyparhandler, "flags", "set/get bit flags as whole (flags=val) or by bits (flags[bit]=val)", (void*)&kph_number},
|
|
||||||
{show, "show", "show current flags @ server console", NULL},
|
|
||||||
#endif
|
|
||||||
{sl_sock_inthandler, "focrel", "relative focus move", (void*)&Relmove},
|
{sl_sock_inthandler, "focrel", "relative focus move", (void*)&Relmove},
|
||||||
{sl_sock_inthandler, "focabs", "absolute focus move", (void*)&Absmove},
|
{sl_sock_inthandler, "focabs", "absolute focus move", (void*)&Absmove},
|
||||||
{foch, "focpos", "current focuser position", NULL},
|
{foch, "focpos", "current focuser position", NULL},
|
||||||
@@ -207,81 +250,65 @@ static sl_sock_hitem_t handlers[] = {
|
|||||||
{statush, "status", "get shutters' status and temperatures", NULL},
|
{statush, "status", "get shutters' status and temperatures", NULL},
|
||||||
{fstoph, "focstop", "stop focuser moving", NULL},
|
{fstoph, "focstop", "stop focuser moving", NULL},
|
||||||
{coolerh, "cooler", "get/set cooler status", 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},
|
{dtimeh, "dtime", "get server's UNIX time for all clients connected", NULL},
|
||||||
{NULL, NULL, NULL, NULL}
|
{NULL, NULL, NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
static void serial_parser(){
|
// dome polling; @return TRUE if all OK
|
||||||
char line[256];
|
static int poll_device(){
|
||||||
int l = 0;
|
char ans[ANSLEN];
|
||||||
do{
|
char *data;
|
||||||
l = read_term(line, 256);
|
int I;
|
||||||
if(l > 0){
|
|
||||||
if(l != (int)sl_RB_write(rb, (uint8_t*) line, l)) break;
|
|
||||||
}
|
|
||||||
}while(l > 0);
|
|
||||||
|
|
||||||
pthread_mutex_lock(&telescope.mutex); // prepare user buffers
|
if(!(data = term_cmdwans(TXT_FOCGET, TXT_ANS_FOCPOS, ans))) return FALSE;
|
||||||
// read ringbuffer, run parser and change buffers in `telescope`
|
DBG("\nGot focuser position");
|
||||||
while((l = sl_RB_readto(rb, '\r', (uint8_t*)line, sizeof(line))) > 0){
|
if(sscanf(data, "%d", &I) == 1){
|
||||||
line[l-1] = 0; // substitute '\r' with 0
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
DBG("IN: %s", line);
|
telescope.focuserpos = I;
|
||||||
if(strncmp(line, TXT_ANS_STATUS, strlen(TXT_ANS_STATUS)) == 0){
|
|
||||||
DBG("Got status ans");
|
|
||||||
telescope.stattime = sl_dtime();
|
|
||||||
char *s = line + strlen(TXT_ANS_STATUS);
|
|
||||||
if(strncmp(s, "0,0,0,0,0", 9) == 0) snprintf(telescope.status, telescope.statlen, "closed");
|
|
||||||
else if(strncmp(s, "1,1,1,1,1", 9) == 0) snprintf(telescope.status, telescope.statlen, "opened");
|
|
||||||
else snprintf(telescope.status, telescope.statlen, "intermediate");
|
|
||||||
}else if(strncmp(line, TXT_ANS_COOLERSTAT, strlen(TXT_ANS_COOLERSTAT)) == 0){
|
|
||||||
DBG("Got cooler status");
|
|
||||||
int s;
|
|
||||||
if(sscanf(line + strlen(TXT_ANS_COOLERSTAT), "%d", &s) == 1){
|
|
||||||
telescope.cooler = s;
|
|
||||||
}
|
|
||||||
}else if(strncmp(line, TXT_ANS_COOLERT, strlen(TXT_ANS_COOLERT)) == 0){
|
|
||||||
DBG("Got weather ans");
|
|
||||||
float m, a;
|
|
||||||
if(sscanf(line + strlen(TXT_ANS_COOLERT), "%f,%f", &m, &a) == 2){
|
|
||||||
telescope.ambienttemp = a;
|
|
||||||
telescope.mirrortemp = m;
|
|
||||||
}
|
|
||||||
}else if(strncmp(line, TXT_ANS_FOCPOS, strlen(TXT_ANS_FOCPOS)) == 0){
|
|
||||||
DBG("Got focuser position");
|
|
||||||
int p;
|
|
||||||
if(sscanf(line + strlen(TXT_ANS_FOCPOS), "%d", &p) == 1){
|
|
||||||
telescope.focuserpos = p;
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
DBG("Unknown answer: %s", line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&telescope.mutex);
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// dome polling; @return TRUE if all OK
|
if(!(data = term_cmdwans(TXT_STATUS, TXT_ANS_STATUS, ans))) return FALSE;
|
||||||
static int poll_device(){
|
DBG("\nGot status");
|
||||||
static const char *reqcmds[] = {TXT_FOCGET, TXT_STATUS, TXT_COOLERT, TXT_COOLERSTAT, NULL};
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
for(const char **cmd = reqcmds; *cmd; ++cmd){
|
telescope.stattime = sl_dtime();
|
||||||
if(write_cmd(*cmd)){
|
if(strncmp(data, "0,0,0,0,0", 9) == 0) snprintf(telescope.status, STATBUF_SZ-1, "closed");
|
||||||
LOGWARN("Can't write command %s", *cmd);
|
else if(strncmp(data, "1,1,1,1,1", 9) == 0) snprintf(telescope.status, STATBUF_SZ-1, "opened");
|
||||||
DBG("Can't write command %s", *cmd);
|
else snprintf(telescope.status, STATBUF_SZ-1, "intermediate");
|
||||||
return FALSE;
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
|
|
||||||
|
if(!(data = term_cmdwans(TXT_COOLERT, TXT_ANS_COOLERT, ans))) return FALSE;
|
||||||
|
DBG("\nGot weather ans");
|
||||||
|
float m, a;
|
||||||
|
if(sscanf(data, "%f,%f", &m, &a) == 2){
|
||||||
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
|
telescope.ambienttemp = a;
|
||||||
|
telescope.mirrortemp = m;
|
||||||
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
}
|
}
|
||||||
serial_parser();
|
if(!(data = term_cmdwans(TXT_COOLERSTAT, TXT_ANS_COOLERSTAT, ans))) return FALSE;
|
||||||
|
DBG("\nGot cooler status");
|
||||||
|
if(sscanf(data, "%d", &I) == 1){
|
||||||
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
|
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;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void runserver(int isunix, const char *node, int maxclients){
|
void runserver(int isunix, const char *node, int maxclients){
|
||||||
|
char ans[ANSLEN];
|
||||||
int forbidden = 0;
|
int forbidden = 0;
|
||||||
if(locksock) sl_sock_delete(&locksock);
|
if(locksock) sl_sock_delete(&locksock);
|
||||||
if(rb) sl_RB_delete(&rb);
|
telescope.errcmd = telescope.cmd = CMD_NONE;
|
||||||
rb = sl_RB_new(BUFSIZ);
|
|
||||||
telescope.cmd = CMD_NONE;
|
|
||||||
FREE(telescope.status);
|
|
||||||
telescope.statlen = STATBUF_SZ;
|
|
||||||
telescope.status = MALLOC(char, STATBUF_SZ);
|
|
||||||
pthread_mutex_init(&telescope.mutex, NULL);
|
pthread_mutex_init(&telescope.mutex, NULL);
|
||||||
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
|
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
|
||||||
locksock = sl_sock_run_server(type, node, -1, handlers);
|
locksock = sl_sock_run_server(type, node, -1, handlers);
|
||||||
@@ -294,7 +321,7 @@ void runserver(int isunix, const char *node, int maxclients){
|
|||||||
while(locksock && locksock->connected){
|
while(locksock && locksock->connected){
|
||||||
if(ForbidObservations){
|
if(ForbidObservations){
|
||||||
if(!forbidden){
|
if(!forbidden){
|
||||||
if(0 == write_cmd(TXT_CLOSE)) forbidden = 1;
|
if(term_cmdwans(TXT_CLOSE, TXT_ANS_OK, ans)) forbidden = 1;
|
||||||
pthread_mutex_lock(&telescope.mutex);
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
telescope.cmd = CMD_NONE;
|
telescope.cmd = CMD_NONE;
|
||||||
pthread_mutex_unlock(&telescope.mutex);
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
@@ -308,35 +335,70 @@ void runserver(int isunix, const char *node, int maxclients){
|
|||||||
}
|
}
|
||||||
double tnow = sl_dtime();
|
double tnow = sl_dtime();
|
||||||
if(tnow - tgot > T_INTERVAL){
|
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);
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
if(telescope.cmd != CMD_NONE){
|
tel_commands_t tcmd = telescope.cmd;
|
||||||
switch(telescope.cmd){
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
|
if(tcmd != CMD_NONE){
|
||||||
|
switch(tcmd){
|
||||||
case CMD_OPEN:
|
case CMD_OPEN:
|
||||||
DBG("received command: open");
|
DBG("received command: open");
|
||||||
if(0 == write_cmd(TXT_OPEN)) telescope.cmd = CMD_NONE;
|
if(term_cmdwans(TXT_OPEN, TXT_ANS_OK, ans)){
|
||||||
|
LOGMSG("Open dome");
|
||||||
|
tcmd = CMD_NONE;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_CLOSE:
|
case CMD_CLOSE:
|
||||||
DBG("received command: close");
|
DBG("received command: close");
|
||||||
if(0 == write_cmd(TXT_CLOSE)) telescope.cmd = CMD_NONE;
|
if(term_cmdwans(TXT_CLOSE, TXT_ANS_OK, ans)){
|
||||||
|
LOGMSG("Close dome");
|
||||||
|
tcmd = CMD_NONE;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_FOCSTOP:
|
case CMD_FOCSTOP:
|
||||||
DBG("received command: stop focus");
|
DBG("received command: stop focus");
|
||||||
if(0 == write_cmd(TXT_FOCSTOP)) telescope.cmd = CMD_NONE;
|
LOGMSG("Stop focus");
|
||||||
|
term_write(TXT_FOCSTOP, ans); tcmd = CMD_NONE; // erroreous thing: no answer for this command
|
||||||
break;
|
break;
|
||||||
case CMD_COOLEROFF:
|
case CMD_COOLEROFF:
|
||||||
DBG("received command: cooler off");
|
DBG("received command: cooler off");
|
||||||
if(0 == write_cmd(TXT_COOLEROFF)) telescope.cmd = CMD_NONE;
|
if(term_cmdwans(TXT_COOLEROFF, TXT_ANS_OK, ans)){
|
||||||
|
LOGMSG("Cooler off");
|
||||||
|
tcmd = CMD_NONE;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case CMD_COOLERON:
|
case CMD_COOLERON:
|
||||||
DBG("received command: cooler on");
|
DBG("received command: cooler on");
|
||||||
if(0 == write_cmd(TXT_COOLERON)) telescope.cmd = 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;
|
break;
|
||||||
default:
|
default:
|
||||||
DBG("WTF?");
|
DBG("WTF?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pthread_mutex_lock(&telescope.mutex);
|
||||||
|
telescope.cmd = telescope.errcmd = tcmd;
|
||||||
pthread_mutex_unlock(&telescope.mutex);
|
pthread_mutex_unlock(&telescope.mutex);
|
||||||
// check abs/rel move
|
// check abs/rel move
|
||||||
char buf[256];
|
char buf[256];
|
||||||
@@ -345,7 +407,7 @@ void runserver(int isunix, const char *node, int maxclients){
|
|||||||
if(Absmove.val < FOC_MINPOS || Absmove.val > FOC_MAXPOS) Absmove.timestamp = 0.; // reset wrong data
|
if(Absmove.val < FOC_MINPOS || Absmove.val > FOC_MAXPOS) Absmove.timestamp = 0.; // reset wrong data
|
||||||
else{
|
else{
|
||||||
snprintf(buf, 255, "%s%d\r", TXT_FOCUSABS, (int)Absmove.val);
|
snprintf(buf, 255, "%s%d\r", TXT_FOCUSABS, (int)Absmove.val);
|
||||||
if(0 == write_cmd(buf)){
|
if(term_cmdwans(buf, TXT_ANS_OK, ans)){
|
||||||
DBG("STARTED absmove");
|
DBG("STARTED absmove");
|
||||||
Absmove.timestamp = 0.;
|
Absmove.timestamp = 0.;
|
||||||
}
|
}
|
||||||
@@ -360,13 +422,12 @@ void runserver(int isunix, const char *node, int maxclients){
|
|||||||
DBG("pos=%d", pos);
|
DBG("pos=%d", pos);
|
||||||
if(pos < 0){ cmd = TXT_FOCUSIN; pos = -pos; }
|
if(pos < 0){ cmd = TXT_FOCUSIN; pos = -pos; }
|
||||||
snprintf(buf, 255, "%s%d\r", cmd, pos);
|
snprintf(buf, 255, "%s%d\r", cmd, pos);
|
||||||
if(0 == write_cmd(buf)){
|
if(term_cmdwans(buf, TXT_ANS_OK, ans)){
|
||||||
DBG("STARTED relmove");
|
DBG("STARTED relmove");
|
||||||
Relmove.timestamp = 0.;
|
Relmove.timestamp = 0.;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
serial_parser();
|
|
||||||
}
|
}
|
||||||
stopserver();
|
stopserver();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,19 @@
|
|||||||
// size of weather/status buffers
|
// size of weather/status buffers
|
||||||
#define STATBUF_SZ 256
|
#define STATBUF_SZ 256
|
||||||
// dome polling interval (clear watchdog & get status)
|
// dome polling interval (clear watchdog & get status)
|
||||||
#define T_INTERVAL (1.0)
|
#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 runserver(int isunix, const char *node, int maxclients);
|
||||||
void stopserver();
|
void stopserver();
|
||||||
void forbid_observations(int forbid);
|
void forbid_observations(int forbid);
|
||||||
|
bool get_telescope_data(telstatus_t *t);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
-std=c17
|
-std=c23
|
||||||
@@ -1 +1 @@
|
|||||||
-std=c++17
|
-std=c++23
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
commands.h
|
||||||
|
header.c
|
||||||
|
header.h
|
||||||
main.c
|
main.c
|
||||||
socket.c
|
socket.c
|
||||||
socket.h
|
socket.h
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of the teldaemon project.
|
* This file is part of the baader_dome project.
|
||||||
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@@ -16,15 +16,17 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "term.h"
|
#include "term.h"
|
||||||
|
|
||||||
static sl_tty_t *dev = NULL; // shoul be global to restore if die
|
static sl_tty_t *dev = NULL; // shoul be global to restore if die
|
||||||
|
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
|
||||||
// close serial device
|
// close serial device
|
||||||
void close_term(){
|
void term_close(){
|
||||||
if(dev) sl_tty_close(&dev);
|
if(dev) sl_tty_close(&dev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,19 +37,19 @@ void close_term(){
|
|||||||
* @param usec - timeout (us), if < 1e-6 - leave default
|
* @param usec - timeout (us), if < 1e-6 - leave default
|
||||||
* @return FALSE if failed
|
* @return FALSE if failed
|
||||||
*/
|
*/
|
||||||
int open_term(char *path, int speed, double usec){
|
int term_open(char *path, int speed, double usec){
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
if(dev) sl_tty_close(&dev);
|
if(dev) sl_tty_close(&dev);
|
||||||
LOGMSG("Try to open serial %s at speed %d", path, speed);
|
LOGMSG("Try to open serial %s at speed %d", path, speed);
|
||||||
DBG("Open serial");
|
DBG("Init serial");
|
||||||
dev = sl_tty_new(path, speed, 4096);
|
dev = sl_tty_new(path, speed, 4096);
|
||||||
|
DBG("Open serial");
|
||||||
if(dev) dev = sl_tty_open(dev, 1);
|
if(dev) dev = sl_tty_open(dev, 1);
|
||||||
if(!dev){
|
if(!dev) goto rtn;
|
||||||
LOGERR("Can't open %s with speed %d. Exit.", path, speed);
|
DBG("Opened");
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
if(usec >= 1e-6){
|
if(usec >= 1e-6){
|
||||||
DBG("set timeout to %gus", usec);
|
DBG("set timeout to %gus", usec);
|
||||||
if(!sl_tty_tmout(usec)){
|
if(sl_tty_tmout(usec) < 0){
|
||||||
LOGWARN("Can't set timeout to %gus", usec);
|
LOGWARN("Can't set timeout to %gus", usec);
|
||||||
WARNX("Can't set timeout to %gus", usec);
|
WARNX("Can't set timeout to %gus", usec);
|
||||||
}
|
}
|
||||||
@@ -56,60 +58,129 @@ int open_term(char *path, int speed, double usec){
|
|||||||
LOGWARN("Can't set exact speed! Opened %s at speed %d", dev->portname, dev->speed);
|
LOGWARN("Can't set exact speed! Opened %s at speed %d", dev->portname, dev->speed);
|
||||||
WARNX("Can't set speed %d (try %d)", speed, dev->speed);
|
WARNX("Can't set speed %d (try %d)", speed, dev->speed);
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
rtn:
|
||||||
if(dev) return TRUE;
|
if(dev) return TRUE;
|
||||||
|
LOGERR("Can't open %s with speed %d. Exit.", path, speed);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief read_term - read data from serial terminal
|
* @brief nonblk_read - internal read
|
||||||
* @param buf - buffer for data
|
* @param ans (o) - buffer for data
|
||||||
* @param length - size of `buf`
|
* @return NULL if failed or `ans`
|
||||||
* @return amount of data read
|
|
||||||
*/
|
*/
|
||||||
int read_term(char *buf, int length){
|
static char *nonblk_read(char ans[ANSLEN]){
|
||||||
static char *bufptr = NULL; // last message, if it was longer than `length`
|
static char *bufptr = NULL; // last message, if it was longer than `length`
|
||||||
static int lastL = 0;
|
static int lastL = 0;
|
||||||
if(!dev || !buf || length < 1) return 0;
|
FNAME();
|
||||||
|
if(!dev) return NULL;
|
||||||
|
int length = ANSLEN - 1;
|
||||||
|
DBG("ok");
|
||||||
|
if(!ans){ // clear
|
||||||
|
DBG("clr");
|
||||||
|
while(sl_tty_read(dev) > 0);
|
||||||
|
bufptr = NULL;
|
||||||
|
lastL = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
if(bufptr && lastL){
|
if(bufptr && lastL){
|
||||||
if(length > lastL) length = lastL;
|
if(length > lastL) length = lastL;
|
||||||
DBG("got %d bytes from old record", length);
|
memcpy(ans, bufptr, length);
|
||||||
memcpy(buf, bufptr, length);
|
|
||||||
if((lastL -= length) < 1){
|
if((lastL -= length) < 1){
|
||||||
lastL = 0; bufptr = NULL;
|
lastL = 0; bufptr = NULL;
|
||||||
}
|
}
|
||||||
return length;
|
ans[length] = 0;
|
||||||
|
DBG("got %d bytes from old record: '%s'", length, ans);
|
||||||
|
return ans;
|
||||||
}
|
}
|
||||||
if(!sl_tty_read(dev)) return 0;
|
int totlen = 0;
|
||||||
DBG("Got from serial %zd bytes", dev->buflen);
|
while(length > 0 && sl_tty_read(dev) > 0){
|
||||||
LOGDBG("Got from serial: %zd bytes", dev->buflen);
|
int lmax = length;
|
||||||
if(length >= (int)dev->buflen){
|
if(lmax >= (int)dev->buflen){
|
||||||
DBG("Full buffer can be copied");
|
DBG("Full buffer can be copied");
|
||||||
length = (int)dev->buflen;
|
lmax = (int)dev->buflen;
|
||||||
bufptr = NULL;
|
|
||||||
lastL = 0;
|
|
||||||
}else{ // store part of data in buffer
|
}else{ // store part of data in buffer
|
||||||
lastL = dev->buflen - length;
|
lastL = dev->buflen - lmax;
|
||||||
DBG("Store %d bytes for next read", lastL);
|
DBG("Store %d bytes for next read", lastL);
|
||||||
bufptr = dev->buf + length;
|
bufptr = dev->buf + lmax;
|
||||||
}
|
}
|
||||||
memcpy(buf, dev->buf, length);
|
memcpy(ans + totlen, dev->buf, lmax);
|
||||||
return length;
|
length -= lmax;
|
||||||
|
totlen += lmax;
|
||||||
|
ans[totlen] = 0;
|
||||||
|
}
|
||||||
|
DBG("totlen: %d", totlen);
|
||||||
|
if(totlen == 0) return NULL;
|
||||||
|
DBG("copied %d: '%s'", totlen, ans);
|
||||||
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief write_term - write data
|
* @brief nonblk_write - internal write
|
||||||
* @param buf - buffer
|
* @param buf (i) - data to write
|
||||||
* @param length - its length
|
* @param length - its length
|
||||||
* @return 0 if OK and 1 if failed
|
* @return 0 if failed or `length`
|
||||||
*/
|
*/
|
||||||
int write_term(const char *buf, int length){
|
static int nonblk_write(const char *buf, int length){
|
||||||
if(!dev || !buf || length < 1) return 0;
|
if(!buf || length < 1) return 0;
|
||||||
return sl_tty_write(dev->comfd, buf, length);
|
if(0 == sl_tty_write(dev->comfd, buf, length)) return length;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// write string command
|
/**
|
||||||
int write_cmd(const char *buf){
|
* @brief term_read - read string from terminal
|
||||||
if(!buf || !*buf) return 0;
|
* @param ans (o) - buffer or NULL to clear last data
|
||||||
DBG("Ask to write %s", buf);
|
* @return NULL or pointer to zero-terminated `ans`
|
||||||
return write_term(buf, strlen(buf));
|
*/
|
||||||
|
char *term_read(char ans[ANSLEN]){
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
char *ret = nonblk_read(ans);
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
DBG("read: '%s'", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief term_write - write data and got answer
|
||||||
|
* @param str (i) - zero-terminated string to write
|
||||||
|
* @param ans (o) - NULL to clear incoming data or buffer to read
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
char *term_write(const char *str, char ans[ANSLEN]){
|
||||||
|
if(!str || !*str) return NULL;
|
||||||
|
DBG("Send cmd '%s'", str);
|
||||||
|
char *ret = NULL;
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
if(nonblk_write(str, strlen(str))){
|
||||||
|
usleep(USLEEP_BEFORE_READ);
|
||||||
|
ret = nonblk_read(ans);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
DBG("read: '%s'", ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief term_cmdwans - write command and get answer
|
||||||
|
* @param str (i) - command to write
|
||||||
|
* @param prefix (i) - prefix of answer (string should begin with this text)
|
||||||
|
* @param ans (o) - buffer for answer (non-NULL!!!)
|
||||||
|
* @return pointer to data just after `prefix` in `ans` or NULL if no `prefix` found
|
||||||
|
*/
|
||||||
|
char *term_cmdwans(const char *str, const char *prefix, char ans[ANSLEN]){
|
||||||
|
if(!str || !prefix || !ans) return NULL;
|
||||||
|
DBG("Send cmd '%s'", str);
|
||||||
|
char *ret = NULL;
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
if(nonblk_write(str, strlen(str))){
|
||||||
|
usleep(USLEEP_BEFORE_READ);
|
||||||
|
ret = nonblk_read(ans);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
int l = strlen(prefix);
|
||||||
|
DBG("compare %s with %s", ret ? (ret) : "null", prefix);
|
||||||
|
if(!ret || strncmp(ans, prefix, l)) return NULL; // no answer or not found
|
||||||
|
DBG("found");
|
||||||
|
return ans + l;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* This file is part of the teldaemon project.
|
* This file is part of the baader_dome project.
|
||||||
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
@@ -18,31 +18,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// text commands and answers
|
#include <stdint.h>
|
||||||
#define TXT_FOCUSABS "FOCUSERGO?"
|
|
||||||
#define TXT_FOCUSIN "FOCUSERI?"
|
|
||||||
#define TXT_FOCUSOUT "FOCUSERO?"
|
|
||||||
#define TXT_FOCSTOP "FOCUSERSTOP?\r"
|
|
||||||
#define TXT_FOCGET "FOCUSERGPOS?\r"
|
|
||||||
#define TXT_OPEN "SHUTTEROPEN?1,1,1,1,1\r"
|
|
||||||
#define TXT_CLOSE "SHUTTERCLOSE?1,1,1,1,1\r"
|
|
||||||
#define TXT_STATUS "SHUTTERSTATUS?\r"
|
|
||||||
#define TXT_COOLERON "COOLERON?100\r"
|
|
||||||
#define TXT_COOLEROFF "COOLEROFF?\r"
|
|
||||||
#define TXT_COOLERT "COOLERT?\r"
|
|
||||||
#define TXT_COOLERSTAT "COOLERSTATUS?\r"
|
|
||||||
#define TXT_PING "PING?\r"
|
|
||||||
#define TXT_ANS_OK "OK"
|
|
||||||
#define TXT_ANS_COOLERSTAT "COOLERPWM?"
|
|
||||||
#define TXT_ANS_COOLERT "COOLERT?"
|
|
||||||
#define TXT_ANS_STATUS "SHUTTERSTATUS?"
|
|
||||||
#define TXT_ANS_FOCPOS "FOCUSERPOS?"
|
|
||||||
|
|
||||||
#define FOC_MINPOS 0
|
// pause before reading answer: for stupid baader = 50ms
|
||||||
#define FOC_MAXPOS 65000
|
#define USLEEP_BEFORE_READ 50000
|
||||||
|
|
||||||
int open_term(char *path, int speed, double usec);
|
// length of answer (including terminating zero)
|
||||||
void close_term();
|
#define ANSLEN 128
|
||||||
int read_term(char *buf, int length);
|
|
||||||
int write_term(const char *buf, int length);
|
int term_open(char *path, int speed, double usec);
|
||||||
int write_cmd(const char *buf);
|
void term_close();
|
||||||
|
char *term_read(char ans[ANSLEN]);
|
||||||
|
char *term_write(const char *str, char ans[ANSLEN]);
|
||||||
|
char *term_cmdwans(const char *str, const char *prefix, char ans[ANSLEN]);
|
||||||
|
|||||||
33
Daemons/weather_proxy/Makefile
Normal file
33
Daemons/weather_proxy/Makefile
Normal 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/
|
||||||
73
Daemons/weather_proxy/weather_client.c
Normal file
73
Daemons/weather_proxy/weather_client.c
Normal 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;
|
||||||
|
}
|
||||||
35
Daemons/weather_proxy/weather_clt_example.c
Normal file
35
Daemons/weather_proxy/weather_clt_example.c
Normal 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;
|
||||||
|
}
|
||||||
367
Daemons/weather_proxy/weather_daemon.c
Normal file
367
Daemons/weather_proxy/weather_daemon.c
Normal 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;
|
||||||
|
}
|
||||||
30
Daemons/weather_proxy/weather_data.h
Normal file
30
Daemons/weather_proxy/weather_data.h
Normal 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);
|
||||||
|
|
||||||
1
Daemons/weather_proxy/weather_proxy.cflags
Normal file
1
Daemons/weather_proxy/weather_proxy.cflags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-std=c17
|
||||||
2
Daemons/weather_proxy/weather_proxy.config
Normal file
2
Daemons/weather_proxy/weather_proxy.config
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// Add predefined macros for your project here. For example:
|
||||||
|
// #define THE_ANSWER 42
|
||||||
1
Daemons/weather_proxy/weather_proxy.creator
Normal file
1
Daemons/weather_proxy/weather_proxy.creator
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[General]
|
||||||
1
Daemons/weather_proxy/weather_proxy.cxxflags
Normal file
1
Daemons/weather_proxy/weather_proxy.cxxflags
Normal file
@@ -0,0 +1 @@
|
|||||||
|
-std=c++17
|
||||||
4
Daemons/weather_proxy/weather_proxy.files
Normal file
4
Daemons/weather_proxy/weather_proxy.files
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
weather_client.c
|
||||||
|
weather_clt_example.c
|
||||||
|
weather_daemon.c
|
||||||
|
weather_data.h
|
||||||
0
Daemons/weather_proxy/weather_proxy.includes
Normal file
0
Daemons/weather_proxy/weather_proxy.includes
Normal file
@@ -1,11 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 4.0)
|
cmake_minimum_required(VERSION 4.0)
|
||||||
set(PROJ weatherdaemon)
|
set(PROJ superweatherdaemon)
|
||||||
set(PROJLIB senslib)
|
set(PROJLIB senslib)
|
||||||
set(MAJOR_VERSION "0")
|
set(MAJOR_VERSION "0")
|
||||||
set(MID_VERSION "0")
|
set(MID_VERSION "0")
|
||||||
set(MINOR_VERSION "1")
|
set(MINOR_VERSION "1")
|
||||||
|
|
||||||
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
|
set(SOURCES cmdlnopts.c main.c mainweather.c sensors.c server.c)
|
||||||
|
|
||||||
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
|
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
|
||||||
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
|
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
|
||||||
@@ -15,15 +15,21 @@ message("VER: ${VERSION}")
|
|||||||
option(DEBUG "Compile in debug mode" OFF)
|
option(DEBUG "Compile in debug mode" OFF)
|
||||||
option(DUMMY "Dummy device plugin" ON)
|
option(DUMMY "Dummy device plugin" ON)
|
||||||
option(FDEXAMPLE "Example of file descriptor 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
|
# 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}")
|
message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}")
|
||||||
|
|
||||||
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE
|
||||||
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\"
|
-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\"
|
||||||
-DMAJOR_VERSION=\"${MAJOR_VESION}\")
|
-DMAJOR_VERSION=\"${MAJOR_VERSION}\")
|
||||||
|
|
||||||
set(CMAKE_COLOR_MAKEFILE ON)
|
set(CMAKE_COLOR_MAKEFILE ON)
|
||||||
|
|
||||||
@@ -51,8 +57,8 @@ pkg_check_modules(${PROJ} REQUIRED usefull_macros>=0.3.5)
|
|||||||
#endif()
|
#endif()
|
||||||
|
|
||||||
# static lib for sensors
|
# static lib for sensors
|
||||||
set(LIBSRC "weathlib.c")
|
set(LIBSRC fd.c weathlib.c)
|
||||||
set(LIBHEADER "weathlib.h")
|
set(LIBHEADER weathlib.h)
|
||||||
add_library(${PROJLIB} STATIC ${LIBSRC})
|
add_library(${PROJLIB} STATIC ${LIBSRC})
|
||||||
set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION})
|
set_target_properties(${PROJLIB} PROPERTIES VERSION ${VERSION})
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,324 @@
|
|||||||
Weather daemon for several different weather stations
|
# superweatherdaemon Documentation
|
||||||
=====================================================
|
|
||||||
|
|
||||||
## Usage:
|
## Overview
|
||||||
|
|
||||||
```
|
**superweatherdaemon** is a weather monitoring daemon designed for astronomical observatories. It
|
||||||
Usage: weatherdaemon [args]
|
collects data from multiple heterogeneous weather stations (meteostations) via a plugin
|
||||||
Be careful: command line options have priority over config
|
architecture, computes a unified weather status, and provides control interfaces over TCP and local
|
||||||
Where args are:
|
UNIX sockets. The daemon can issue warnings, change weather level, and even trigger a forced
|
||||||
|
shutdown of instruments (e.g., close dome) when conditions become dangerous.
|
||||||
|
|
||||||
-P, --pidfile=arg pidfile name (default: /tmp/weatherdaemon.pid)
|
The project is written in C, uses CMake as its build system, and relies on the
|
||||||
-c, --conffile=arg configuration file name (consists all or a part of long-named parameters and their values (e.g. plugin=liboldweather.so)
|
[usefull_macros](https://github.com/eddyem/snippets_library) library for utilities and socket
|
||||||
-h, --help show this help
|
management.
|
||||||
-l, --logfile=arg save logs to file (default: none)
|
|
||||||
-p, --plugin=arg add this weather plugin (may be a lot of) (can occur multiple times)
|
## Dependencies
|
||||||
-v, --verb logfile verbocity level (each -v increased)
|
|
||||||
--port=arg network port to connect (default: 12345); hint: use "localhost:port" to make local net socket
|
- **Build tools**: CMake >= 4.0, C compiler with C11 support, `pkg-config`.
|
||||||
--sockpath=arg UNIX socket path (starting from '\0' for anonimous) of command socket
|
- **Library**: `usefull_macros` >= 0.3.5 (`sl_*` functions for logging, sockets, command-line parsing, etc.).
|
||||||
|
- **Optional**: `net-snmp` (for the SNMP UPS plugin).
|
||||||
|
|
||||||
|
On Gentoo/Calculate Linux, install the basic build dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
emerge dev-build/cmake dev-util/pkgconf
|
||||||
|
# usefull_macros must be installed from its own source; follow its documentation.
|
||||||
|
# For SNMP support:
|
||||||
|
emerge net-analyzer/net-snmp
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Building and Installation
|
||||||
|
|
||||||
TODO: brief documentation will be here
|
1. Clone or download the source tree.
|
||||||
|
2. Create a build directory and run CMake:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Customise enabled plugins with `-D` options (all are `ON` by default):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cmake .. -DDUMMY=OFF -DFDEXAMPLE=OFF -DHYDREON=ON -DBTAMETEO=ON ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Available plugin options:
|
||||||
|
- `DUMMY` Dummy weather station for testing.
|
||||||
|
- `FDEXAMPLE` Example file descriptor plugin.
|
||||||
|
- `HYDREON` Hydreon RG-11 rain sensor.
|
||||||
|
- `BTAMETEO` BTA 6-m telescope main meteostation (shared memory).
|
||||||
|
- `REINHARDT` Old Reinhardt meteostation.
|
||||||
|
- `WXA100` Vaisala WXA100 ultrasonic station.
|
||||||
|
- `SNMP` UPS monitoring via SNMP.
|
||||||
|
- `LIGHTNING` AS3935-based lightning sensor.
|
||||||
|
|
||||||
|
4. Building:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
su -c "make install"
|
||||||
|
```
|
||||||
|
|
||||||
|
The main executable `superweatherdaemon` is installed into `bin`; the plugin shared libraries
|
||||||
|
(`lib*.so`) go to the library directory.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The daemon can be configured entirely via command-line options, a configuration file, or both.
|
||||||
|
Command-line options take precedence.
|
||||||
|
|
||||||
|
### Command-line Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `-h`, `--help` | Show help message and exit. |
|
||||||
|
| `-c <file>`, `--conffile <file>` | Use a configuration file. Point non-existant file to get help. |
|
||||||
|
| `-l <path>`, `--logfile=<path>` | Write logs to a file (default: none). |
|
||||||
|
| `-P <path>`, `--pidfile=<path>` | PID file (default `/tmp/superweatherdaemon.pid`). |
|
||||||
|
| `-p <spec>`, `--plugin=<spec>` | Add a weather plugin; can be repeated for different plugins. Format: `path:type:device` (see below). |
|
||||||
|
| `--port=<node>` | Network port for clients (default `12345`). Use `localhost:port` for local access only. |
|
||||||
|
| `--sockpath=<path>` | UNIX socket path (start with `@` for an abstract socket). |
|
||||||
|
| `-T <seconds>`, `--pollt <seconds>` | Max polling interval in seconds (integer). |
|
||||||
|
| `-v`, `--verb` | Increase verbosity level (each `-v` adds 1). |
|
||||||
|
|
||||||
|
### Plugin Specification
|
||||||
|
|
||||||
|
A plugin is a shared library (`.so`) that provides a `sensor_init` function. It is loaded with the
|
||||||
|
`--plugin` option.
|
||||||
|
|
||||||
|
Format:
|
||||||
|
|
||||||
|
```
|
||||||
|
--plugin=library:type:parameter
|
||||||
|
```
|
||||||
|
|
||||||
|
- `library`: path to the shared library, e.g. `libwxa100.so`.
|
||||||
|
- `type`: Connection type `D` for serial device, `U` for UNIX socket, `N` for INET socket.
|
||||||
|
- `parameter`: device path and optional speed (`/dev/ttyS0:9600`), UNIX socket name, or `host:port` for INET.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
--plugin=libreinhardt.so:D:/dev/ttyS0
|
||||||
|
--plugin=libwxa100.so:D:/dev/pl2303_0
|
||||||
|
--plugin=libhydreon.so:D:/dev/ch340_0:1200
|
||||||
|
--plugin=libbtameteo.so (no device, uses shared memory)
|
||||||
|
```
|
||||||
|
|
||||||
|
Multiple plugins are listed in order of **importance** (first ones are considered primary for
|
||||||
|
weather level calculation).
|
||||||
|
|
||||||
|
### Configuration File
|
||||||
|
|
||||||
|
The configuration file uses a simple `key = value` syntax, with `#` for comments. Example:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# network port for clients
|
||||||
|
port = 4444
|
||||||
|
logfile = /var/log/meteo/superweather.log
|
||||||
|
verbose = 2
|
||||||
|
sockpath = "@weather"
|
||||||
|
pollt = 1
|
||||||
|
reinit_delay = 10
|
||||||
|
|
||||||
|
# Weather thresholds
|
||||||
|
ahtung_delay = 1800 # in seconds
|
||||||
|
good_wind = 5.0 # m/s
|
||||||
|
bad_wind = 10.0
|
||||||
|
terrible_wind = 15.0
|
||||||
|
good_humidity = 65.0 # percents
|
||||||
|
bad_humidity = 87.0
|
||||||
|
terrible_humidity = 94.0
|
||||||
|
good_clouds = 2500.0 # for reinhardt sensor in its units
|
||||||
|
bad_clouds = 2000.0
|
||||||
|
terrible_clouds = 500.0
|
||||||
|
clouds_negflag = 1 # 1 means the higher the value the better
|
||||||
|
good_sky = -40.0 # sky minus ambient temperature, degC
|
||||||
|
bad_sky = -10.0
|
||||||
|
terrible_sky = 0.0
|
||||||
|
# plugins - most important first
|
||||||
|
plugin = libwxa100.so:D:/dev/pl2303_0
|
||||||
|
plugin = libhydreon.so:D:/dev/ch340_0:1200
|
||||||
|
plugin = libbtameteo.so
|
||||||
|
plugin = libreinhardt.so:D:/dev/ttyS0
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
superweatherdaemon -c /etc/weather.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
To run on system start you can use OpenRó `rc.local` mechanism.
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
Each weather station is implemented as a shared library exporting a single function:
|
||||||
|
|
||||||
|
```c
|
||||||
|
int sensor_init(sensordata_t *s);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `sensordata_t` structure contains all required callbacks, data pointers, and private fields.
|
||||||
|
See `weathlib.h` for the full definition.
|
||||||
|
|
||||||
|
### Plugin Lifecycle
|
||||||
|
|
||||||
|
1. **Loading**: The main daemon calls `s = sensor_new(...)` to create `sensordata_t` structure,
|
||||||
|
opens given library with `dlopen` and calls `sensor_init` on that new `s`.
|
||||||
|
2. **Initialisation**:
|
||||||
|
- Set `s->name`, `s->Nvalues`, `s->values` array.
|
||||||
|
- Configure the communication channel (file descriptor) using `getFD(s->path)`.
|
||||||
|
- Create a worker thread that periodically reads sensor data and updates `s->values`.
|
||||||
|
Don't forget to lock `s->valmutex` on any operation with `s->values`.
|
||||||
|
3. **Data delivery**: Each time new data is available, call `s->freshdatahandler(s)`
|
||||||
|
(this is set by the daemon to `dumpsensors`). The main daemon then merges the data into the
|
||||||
|
global weather evaluation.
|
||||||
|
4. **Shutdown**: The daemon calls `s->kill(s)`, which must join the thread, close the file
|
||||||
|
descriptor, and free resources. Default `common_kill` handles most of this; plugins can override it.
|
||||||
|
|
||||||
|
If the plugin is disconnected for some reason (for example, the network connection is lost), the
|
||||||
|
daemon will try to reconnect every `reinit_delay` seconds.
|
||||||
|
|
||||||
|
### Available Plugins
|
||||||
|
|
||||||
|
| Library | Sensor | Type |
|
||||||
|
|---------|--------|------|
|
||||||
|
| `libwsdummy.so` | Dummy station outputs random walk data around realistic values. | Test / Development |
|
||||||
|
| `libfdex.so` | Example of a filedescriptor based plugin. Prompts for commaseparated values. | Example |
|
||||||
|
| `libhydreon.so` | Hydreon RG-11 optical rain sensor. | Serial |
|
||||||
|
| `libbtameteo.so` | BTA 6-m telescope main meteostation (shared memory). | Shared Memory |
|
||||||
|
| `libreinhardt.so` | Old Reinhardt meteostation (serial, `?U` command). | Serial |
|
||||||
|
| `libwxa100.so` | Vaisala WXA100 ultrasonic meteostation (serial, `0R0` command). | Serial |
|
||||||
|
| `libsnmp.so` | UPS monitor via SNMP (requires net-snmp). | Network |
|
||||||
|
| `liblightning.so` | AS3935-based lightning sensor (serial). | Serial |
|
||||||
|
|
||||||
|
### Writing a New Plugin
|
||||||
|
|
||||||
|
Use the existing plugins as templates. A minimal plugin must:
|
||||||
|
|
||||||
|
- Include `weathlib.h`.
|
||||||
|
- Define an array of `val_t` describing each measured quantity.
|
||||||
|
- Implement `sensor_init`:
|
||||||
|
- Allocate `s->values`, copy the template array.
|
||||||
|
- Set `s->Nvalues`, `s->name`.
|
||||||
|
- Open the device (use `getFD(s->path)` for serial/sockets) and set `s->fdes`.
|
||||||
|
If your plugin don't need file descriptor, you must set `s->fdes` to any non-negative value.
|
||||||
|
- Create a ring buffer if needed (`sl_RB_new`).
|
||||||
|
- Start the worker thread that reads data, updates values inside `pthread_mutex_lock(&s->valmutex)`,
|
||||||
|
and calls `s->freshdatahandler(s)` (outside mutex locked).
|
||||||
|
- Return `TRUE` on success, `FALSE` on failure (call `s->kill(s)` to clean up).
|
||||||
|
- The `weathlib.h` provides helper functions: `common_onrefresh`, `common_getval`, `common_kill`.
|
||||||
|
|
||||||
|
## Weather Level Calculation
|
||||||
|
|
||||||
|
The daemon combines data from all active plugins and continuously evaluates a global **weather
|
||||||
|
level**:
|
||||||
|
|
||||||
|
- `0` **GOOD**: observations can start safely.
|
||||||
|
- `1` **BAD**: risky to start, but can continue.
|
||||||
|
- `2` **TERRIBLE**: dome must close, instruments park.
|
||||||
|
- `3` **PROHIBITED**: complete shutdown, power off equipment.
|
||||||
|
|
||||||
|
### Criteria
|
||||||
|
|
||||||
|
Each weather parameter (wind speed, humidity, clouds, sky temperature, lightning distance,
|
||||||
|
precipitation, etc.) has configurable thresholds:
|
||||||
|
|
||||||
|
- `good` below/above this (depending on sign) the condition is good.
|
||||||
|
- `bad` above this the condition is bad.
|
||||||
|
- `terrible` above this the condition is terrible.
|
||||||
|
- `prohibited` (if defined) above this the condition goes directly to PROHIBITED.
|
||||||
|
- `negflag` if `1`, a smaller value is worse (e.g., clouds).
|
||||||
|
- `shtdnflag` if `1`, entering the terrible/prohibited range also sets the **FORCE SHUTDOWN** flag.
|
||||||
|
|
||||||
|
### Special Flags
|
||||||
|
|
||||||
|
- **FORCE SHUTDOWN**: Some parameters (e.g., lightning within <= 5km, UPS on battery) carry the
|
||||||
|
`IS_FORCEDSHTDN` meaning and have `shtdnflag = 1`. When their value exceeds the terrible threshold,
|
||||||
|
the forced shutdown flag is raised, which immediately sets the weather level to PROHIBITED and is
|
||||||
|
typically used to cut power.
|
||||||
|
- **Manual FORBID**: An operator can send a signal (`SIGUSR1`) or a socket command to forbid
|
||||||
|
observations, which also forces PROHIBITED. `SIGUSR2` or a socket command clears forbidden flag.
|
||||||
|
|
||||||
|
### Hysteresis
|
||||||
|
|
||||||
|
Once a bad/terrible state is reached, the level is not lowered until `ahtung_delay` seconds have
|
||||||
|
passed since the last serious event. This prevents rapid toggling.
|
||||||
|
|
||||||
|
## Server Commands (Socket API)
|
||||||
|
|
||||||
|
The daemon listens on two interfaces:
|
||||||
|
|
||||||
|
1. **Network socket** (TCP, default port 12345) read-only (in meaning they cannot change any
|
||||||
|
parameters) access for remote clients.
|
||||||
|
2. **Local UNIX socket** (abstract or filesystem, default `@weather`) full control for local applications.
|
||||||
|
|
||||||
|
Commands are sent as plain text strings, terminated by a newline.
|
||||||
|
|
||||||
|
### Common (read-only) Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `get` | Return all collected weather data (all stations). |
|
||||||
|
| `get=<N>` | Return data from plugin `<N>`. |
|
||||||
|
| `list` | List all loaded plugins with their names and value counts. |
|
||||||
|
| `time` | Return server UNIX time (float seconds). |
|
||||||
|
| `chklevel` | Show the `sense` (importance) level of every collected parameter. |
|
||||||
|
| `chklevel=<N>` | Same for a specific plugin. |
|
||||||
|
|
||||||
|
### Local-only (read-write) Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `forbid` | Get current FORBID flag (0/1). |
|
||||||
|
| `forbid=<0/1>` | Set/clear manual FORBID. |
|
||||||
|
| `forceoff` | Get FORCE SHUTDOWN flag. |
|
||||||
|
| `forceoff=<0/1>` | Set/clear it manually. |
|
||||||
|
| `weathlevel` | Get current weather level (0-3 for GOOD-PROHIBITED). |
|
||||||
|
| `weathlevel=<0..3>` | Force weather level (use with caution). |
|
||||||
|
| `setlevel=<plugin>:<param>=<sense>,...` | Change the `sense` field of one or more sensor parameters. Example: `setlevel=1:WIND=3,HUMIDITY=3` disables wind and humidity from station 1. |
|
||||||
|
| `mute=<N>` | Stop refreshing data from plugin `<N>` (mute). |
|
||||||
|
| `unmute=<N>` | Resume refreshing. |
|
||||||
|
| `ismuted=<N>` | Return 1 if muted, 0 otherwise. |
|
||||||
|
|
||||||
|
Reply format: each line is a FITS-like `KEY = value / comment` string; commands that set something
|
||||||
|
usually echo back the variable and its new value. For `get=<N>` each `KEY` have a suffix in square
|
||||||
|
brackets number of plugin, e.g. `WIND[1]= 10.1 / Wind speed, m/s`.
|
||||||
|
|
||||||
|
## Signals
|
||||||
|
|
||||||
|
| Signal | Effect |
|
||||||
|
|--------|--------|
|
||||||
|
| `SIGTERM`, `SIGINT`, `SIGQUIT` | Clean shutdown (removes PID file, kills plugins, destroys sockets). |
|
||||||
|
| `SIGHUP` | Ignored. |
|
||||||
|
| `SIGUSR1` | Set manual FORBID (weather level == PROHIBITED). |
|
||||||
|
| `SIGUSR2` | Clear manual FORBID. |
|
||||||
|
| `SIGPIPE` | Logged, used to detect network plugins disconnections. |
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `CMakeLists.txt` | Top-level build definition. |
|
||||||
|
| `cmdlnopts.c/.h` | Command-line and configuration file parsing. |
|
||||||
|
| `main.c` | Daemon entry point, signal handlers, forking. |
|
||||||
|
| `mainweather.c/.h` | Global weather evaluation, data collection, forced shutdown. |
|
||||||
|
| `sensors.c/.h` | Plugin management (load, unload, getters). |
|
||||||
|
| `server.c/.h` | TCP and UNIX socket servers. |
|
||||||
|
| `weathlib.c/.h` | Common plugin API, value definitions, helper functions. |
|
||||||
|
| `fd.c` | Function `getFD()` to open serial devices or sockets for plugins. |
|
||||||
|
| `example.config` | Sample configuration file. |
|
||||||
|
| `plugins/CMakeLists.txt` | Build file for all plugins. |
|
||||||
|
| `plugins/*.c` | Individual plugin source files. |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The project is released under the **GNU General Public License v3.0** or later. See the headers in
|
||||||
|
the source files for the full legal text.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*For further assistance or to report issues, please contact the maintainer: Edward V. Emelianov
|
||||||
|
<edward.emelianoff@gmail.com>.*
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "cmdlnopts.h"
|
#include "cmdlnopts.h"
|
||||||
|
#include "mainweather.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* here are global parameters initialisation
|
* here are global parameters initialisation
|
||||||
@@ -30,7 +32,7 @@ int help;
|
|||||||
|
|
||||||
// default values for Gdefault & help
|
// default values for Gdefault & help
|
||||||
#define DEFAULT_PORT "12345"
|
#define DEFAULT_PORT "12345"
|
||||||
#define DEFAULT_PID "/tmp/weatherdaemon.pid"
|
#define DEFAULT_PID "/tmp/superweatherdaemon.pid"
|
||||||
|
|
||||||
// DEFAULTS
|
// DEFAULTS
|
||||||
// default global parameters
|
// default global parameters
|
||||||
@@ -38,12 +40,35 @@ static glob_pars defpars = {
|
|||||||
.port = DEFAULT_PORT,
|
.port = DEFAULT_PORT,
|
||||||
.logfile = NULL,
|
.logfile = NULL,
|
||||||
.verb = 0,
|
.verb = 0,
|
||||||
.pidfile = DEFAULT_PID
|
.pidfile = DEFAULT_PID,
|
||||||
};
|
};
|
||||||
// default config: all values should be wrong or empty to understand than user change them
|
// default config: all values should be wrong or empty to understand than user change them
|
||||||
static glob_pars defconf = {
|
static glob_pars defconf = {
|
||||||
.verb = -1,
|
.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;
|
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"}, \
|
{"port", NEED_ARG, NULL, 0, arg_string, APTR(&G.port), "network port to connect (default: " DEFAULT_PORT "); hint: use \"localhost:port\" to make local net socket"}, \
|
||||||
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file (default: none)"}, \
|
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "save logs to file (default: none)"}, \
|
||||||
{"pidfile", NEED_ARG, NULL, 'P', arg_string, APTR(&G.pidfile), "pidfile name (default: " DEFAULT_PID ")"}, \
|
{"pidfile", NEED_ARG, NULL, 'P', arg_string, APTR(&G.pidfile), "pidfile name (default: " DEFAULT_PID ")"}, \
|
||||||
{"sockpath",NEED_ARG, NULL, 0, arg_string, APTR(&G.sockname), "UNIX socket path (starting from '\\0' for anonimous) of command socket"}, \
|
{"sockpath",NEED_ARG, NULL, 0, arg_string, APTR(&G.sockname), "UNIX socket path (starting from '@' for anonimous) of command socket"}, \
|
||||||
{"plugin", MULT_PAR, NULL, 'p', arg_string, APTR(&G.plugins), "add this weather plugin (may be a lot of); FORMAT: \"dlpath:l:dev\", where `dlpath` - path of plugin library; `l` - 'D' for device, 'U' for UNIX-socket or 'N' for INET socket; dev - path to device and speed (like /dev/ttyS0:9600), UNIX socket name or host:port for INET"}, \
|
{"plugin", MULT_PAR, NULL, 'p', arg_string, APTR(&G.plugins), "add this weather plugin (may be a lot of); FORMAT: \"dlpath:l:dev\", where `dlpath` - path of plugin library; `l` - 'D' for device, 'U' for UNIX-socket or 'N' for INET socket; dev - path to device and speed (like /dev/ttyS0:9600), UNIX socket name or host:port for INET"}, \
|
||||||
{"pollt", NEED_ARG, NULL, 'T', arg_int, APTR(&G.pollt), "set maximal polling interval (seconds, integer)"},
|
{"pollt", NEED_ARG, NULL, 'T', arg_int, APTR(&G.pollt), "set maximal polling interval (seconds, integer)"},
|
||||||
|
|
||||||
sl_option_t cmdlnopts[] = {
|
sl_option_t cmdlnopts[] = {
|
||||||
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
|
{"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)"},
|
{"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)"}, \
|
{"verb", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), "logfile verbocity level (each -v increased)"},
|
||||||
COMMON_OPTS
|
COMMON_OPTS
|
||||||
end_option
|
end_option
|
||||||
};
|
};
|
||||||
|
|
||||||
sl_option_t confopts[] = {
|
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
|
COMMON_OPTS
|
||||||
end_option
|
end_option
|
||||||
};
|
};
|
||||||
|
#if 0
|
||||||
static int sortstrings(const void *v1, const void *v2){
|
static int sortstrings(const void *v1, const void *v2){
|
||||||
const char **s1 = (const char **)v1, **s2 = (const char **)v2;
|
const char **s1 = (const char **)v1, **s2 = (const char **)v2;
|
||||||
return strcmp(*s1, *s2);
|
return strcmp(*s1, *s2);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// compare plugins from configuration and command line; add to command line plugins all new
|
// 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){
|
static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
||||||
if(!cmdline) return;
|
if(!cmdline) return;
|
||||||
char **p;
|
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]; }
|
for(int i = 0; i < nconf; ++i){ newarray[i+ncmd] = conf->plugins[i]; }
|
||||||
FREE(conf->plugins);
|
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++);
|
DBG("NOW together:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
||||||
p = newarray;
|
#endif
|
||||||
int nondobuleidx = 0;
|
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;){
|
for(int i = 0; i < newsize;){
|
||||||
int j = i + 1;
|
int j = i + 1;
|
||||||
for(; j < newsize; ++j){
|
for(; j < newsize; ++j){
|
||||||
@@ -118,9 +180,12 @@ static void compplugins(glob_pars *cmdline, glob_pars *conf){
|
|||||||
++nondobuleidx;
|
++nondobuleidx;
|
||||||
i = j;
|
i = j;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef EBUG
|
||||||
DBG("Result:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
DBG("Result:"); p = newarray; while(*p) printf("\t%s\n", *p++);
|
||||||
|
#endif
|
||||||
cmdline->plugins = newarray;
|
cmdline->plugins = newarray;
|
||||||
cmdline->nplugins = 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)
|
if(G.conffile){ // read conffile and fix parameters (cmdline args are in advantage)
|
||||||
glob_pars oldpars = G; // save cmdline opts
|
glob_pars oldpars = G; // save cmdline opts
|
||||||
G = defconf;
|
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));
|
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((0 == strcmp(oldpars.port, DEFAULT_PORT)) && G.port) oldpars.port = G.port;
|
||||||
if(!oldpars.logfile && G.logfile) oldpars.logfile = G.logfile;
|
if(!oldpars.logfile && G.logfile) oldpars.logfile = G.logfile;
|
||||||
if(!oldpars.verb && G.verb > -1) oldpars.verb = G.verb;
|
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((0 == strcmp(oldpars.pidfile, DEFAULT_PID)) && G.pidfile) oldpars.pidfile = G.pidfile;
|
||||||
if(!oldpars.sockname && G.sockname) oldpars.sockname = G.sockname;
|
if(!oldpars.sockname && G.sockname) oldpars.sockname = G.sockname;
|
||||||
// now check plugins
|
// now check plugins
|
||||||
|
|||||||
19
Daemons/weatherdaemon_multimeteo/example.config
Normal file
19
Daemons/weatherdaemon_multimeteo/example.config
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
logfile = /var/log/meteo/superweather.log
|
||||||
|
# node for clients connection, if you point localost:xxxx, then NET socket would have only local access
|
||||||
|
port = 4444
|
||||||
|
# logging level: 0 - ERR, 1 - WARN, 2 - MSG, 3 - DBG, 4 - all shit
|
||||||
|
verbose = 2
|
||||||
|
# path to local command UNIX-socket (first '@' means anonymous)
|
||||||
|
sockpath = "@weather"
|
||||||
|
# sensors polling time - 1s
|
||||||
|
pollt = 1
|
||||||
|
# try to reinit dead sensors each 10s
|
||||||
|
reinit_delay = 10
|
||||||
|
|
||||||
|
# !!! Point plugins in order of meaning: the most important are first !!!
|
||||||
|
# see help for plugins format
|
||||||
|
plugin = libwxa100.so:D:/dev/pl2303_0
|
||||||
|
plugin = libhydreon.so:D:/dev/ch340_0:1200
|
||||||
|
plugin = libbtameteo.so
|
||||||
|
# this should be last as almost a half of its sensors are broken
|
||||||
|
plugin = libreinhardt.so:D:/dev/ttyS0
|
||||||
@@ -29,104 +29,29 @@
|
|||||||
#include <sys/un.h> // unix socket
|
#include <sys/un.h> // unix socket
|
||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "fd.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief openserial - try to open serial device
|
* @brief openserial - try to open serial device
|
||||||
* @param path - path to device and speed, colon-separated (without given speed assume 9600)
|
* @param path - path to device and speed, colon-separated (without given speed assume 9600)
|
||||||
* @return -1 if failed or opened FD
|
* @return -1 if failed or opened FD
|
||||||
* WARNING!!! Memory leakage danger. Don't call this function too much times!
|
* WARNING!!! Memory leakage danger. Don't call this function too much times!
|
||||||
*/
|
*/
|
||||||
static int openserial(char *path){
|
static int openserial(const char *path){
|
||||||
FNAME();
|
FNAME();
|
||||||
int speed = 9600; // default speed
|
int speed = 9600; // default speed
|
||||||
char *colon = strchr(path, ':');
|
char *str = strdup(path);
|
||||||
|
char *colon = strchr(str, ':');
|
||||||
if(colon){
|
if(colon){
|
||||||
*colon++ = 0;
|
*colon++ = 0;
|
||||||
if(!sl_str2i(&speed, colon)){
|
if(!sl_str2i(&speed, colon)){
|
||||||
WARNX("Wrong speed settings: '%s'", colon);
|
WARNX("Wrong speed settings: '%s'", colon);
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sl_tty_t *serial = sl_tty_new(path, 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);
|
|
||||||
FREE(str);
|
FREE(str);
|
||||||
ai.ai_family = AF_UNIX;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case SOCKT_NET:
|
|
||||||
case SOCKT_NETLOCAL:
|
|
||||||
ai.ai_family = AF_INET;
|
|
||||||
char *delim = strchr(path, ':');
|
|
||||||
if(delim){
|
|
||||||
*delim = 0;
|
|
||||||
service = delim+1;
|
|
||||||
if(delim == path) node = NULL; // only port
|
|
||||||
}
|
|
||||||
DBG("node: '%s', service: '%s'", node, service);
|
|
||||||
int e = getaddrinfo(node, service, &ai, &res);
|
|
||||||
if(e){
|
|
||||||
WARNX("getaddrinfo(): %s", gai_strerror(e));
|
|
||||||
return -1;
|
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;
|
int comfd = sl_tty_fdescr(str, "8N1", speed, 1);
|
||||||
default: // never reached
|
DBG("%s @ %d, comfd=%d", str, speed, comfd);
|
||||||
WARNX("Unsupported socket type %d", type);
|
FREE(str);
|
||||||
return -1;
|
return comfd;
|
||||||
}
|
|
||||||
DBG("FD: %d", sock);
|
|
||||||
return sock;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,17 +60,21 @@ static int opensocket(char *path, sl_socktype_e type){
|
|||||||
* WARNING!!! Contents of `path` would be modified in this function!
|
* WARNING!!! Contents of `path` would be modified in this function!
|
||||||
* @return opened file descriptor or -1 in case of error
|
* @return opened file descriptor or -1 in case of error
|
||||||
*/
|
*/
|
||||||
int getFD(char *path){
|
int getFD(const char *path){
|
||||||
if(!path || !*path) return -1;
|
if(!path || !*path || strlen(path) < 2) return -1;
|
||||||
char type = *path;
|
char type = *path;
|
||||||
|
if(path[1] != ':') return -1; // after protocol letter should be delimeter
|
||||||
path += 2;
|
path += 2;
|
||||||
|
if(!*path) return -1; // empty path
|
||||||
switch(type){
|
switch(type){
|
||||||
case 'D': // serial device
|
case 'D': // serial device
|
||||||
return openserial(path);
|
return openserial(path);
|
||||||
case 'N': // INET socket
|
case 'N': // INET socket
|
||||||
return opensocket(path, SOCKT_NET);
|
//return opensocket(path, SOCKT_NET);
|
||||||
|
return sl_sock_open(SOCKT_NET, path, 0, 0);
|
||||||
case 'U': // UNIX socket
|
case 'U': // UNIX socket
|
||||||
return opensocket(path, SOCKT_UNIX);
|
//return opensocket(path, SOCKT_UNIX);
|
||||||
|
return sl_sock_open(SOCKT_UNIX, path, 0, 0);
|
||||||
}
|
}
|
||||||
WARNX("Wrong plugin format: '%c', should be 'D', 'N' or 'U'", type);
|
WARNX("Wrong plugin format: '%c', should be 'D', 'N' or 'U'", type);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -24,42 +24,57 @@
|
|||||||
#include <usefull_macros.h>
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
#include "cmdlnopts.h"
|
#include "cmdlnopts.h"
|
||||||
|
#include "mainweather.h"
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
#include "server.h"
|
#include "server.h"
|
||||||
|
|
||||||
static pid_t childpid = 0;
|
static pid_t childpid = 0;
|
||||||
|
static glob_pars *GP = NULL;
|
||||||
|
|
||||||
|
// SIGUSR1 - FORBID observations
|
||||||
|
// SIGUSR2 - allow
|
||||||
void signals(int signo){
|
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);
|
LOGERR("Killed with status %d", signo);
|
||||||
closeplugins();
|
closeplugins();
|
||||||
kill_servers();
|
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);
|
exit(signo);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void getpipe(int _U_ signo){
|
static void getpipe(int _U_ signo){
|
||||||
WARNX("Get sigpipe!");
|
WARNX("Get sigpipe!");
|
||||||
|
LOGWARN("SIGPIPE: something disconnected?");
|
||||||
// TODO: check all sensors for disconnected one
|
// TODO: check all sensors for disconnected one
|
||||||
signal(SIGPIPE, getpipe);
|
signal(SIGPIPE, getpipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const char *__progname;
|
|
||||||
|
|
||||||
int main(int argc, char **argv){
|
int main(int argc, char **argv){
|
||||||
glob_pars *GP = NULL;
|
|
||||||
sl_init();
|
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);
|
GP = parse_args(argc, argv);
|
||||||
if(!GP) ERRX("Error parsing args");
|
if(!GP) ERRX("Error parsing args");
|
||||||
|
sl_check4running((char*)__progname, GP->pidfile);
|
||||||
if(!GP->sockname) ERRX("Point command socket name");
|
if(!GP->sockname) ERRX("Point command socket name");
|
||||||
if(GP->logfile){
|
if(GP->logfile){
|
||||||
sl_loglevel_e lvl = LOGLEVEL_ERR + GP->verb;
|
sl_loglevel_e lvl = LOGLEVEL_ERR + GP->verb;
|
||||||
@@ -71,6 +86,14 @@ int main(int argc, char **argv){
|
|||||||
if(GP->pollt > 0){
|
if(GP->pollt > 0){
|
||||||
if(!set_pollT((time_t)GP->pollt)) ERRX("Can't set polling time to %d seconds", GP->pollt);
|
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);
|
int nopened = openplugins(GP->plugins, GP->nplugins);
|
||||||
if(nopened < 1){
|
if(nopened < 1){
|
||||||
LOGERR("No plugins found; exit!");
|
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");
|
if(GP->nplugins && GP->nplugins != nopened) LOGWARN("Work without some plugins");
|
||||||
#ifndef EBUG
|
#ifndef EBUG
|
||||||
sl_check4running((char*)__progname, GP->pidfile);
|
sl_daemonize();
|
||||||
while(1){ // guard for dead processes
|
while(1){ // guard for dead processes
|
||||||
childpid = fork();
|
childpid = fork();
|
||||||
if(childpid){
|
if(childpid){
|
||||||
@@ -94,8 +117,11 @@ int main(int argc, char **argv){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#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");
|
if(!start_servers(GP->port, GP->sockname)) ERRX("Can't run server's threads");
|
||||||
while(1);
|
//while(1) pause();
|
||||||
//WARNX("TEST ends");
|
//WARNX("TEST ends");
|
||||||
//signals(0);
|
//signals(0);
|
||||||
return 0; // never reached
|
return 0; // never reached
|
||||||
|
|||||||
586
Daemons/weatherdaemon_multimeteo/mainweather.c
Normal file
586
Daemons/weatherdaemon_multimeteo/mainweather.c
Normal file
@@ -0,0 +1,586 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// collect here weather from all weatherstations sorted by importance
|
||||||
|
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include "mainweather.h"
|
||||||
|
#include "sensors.h"
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
// wind speed history array size (not less than for one hour)
|
||||||
|
#define MAX_HISTORY 3600
|
||||||
|
// throw out data older than 24 hours
|
||||||
|
#define TOO_OLD_DATA 86400
|
||||||
|
// one hour
|
||||||
|
#define T_ONE_HOUR 3600
|
||||||
|
|
||||||
|
static pthread_mutex_t datamutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static int Forbidden = 0;
|
||||||
|
|
||||||
|
// index of meteodata in array
|
||||||
|
enum{
|
||||||
|
NWIND,
|
||||||
|
NWINDMAX,
|
||||||
|
NWINDMAX1,
|
||||||
|
NWINDDIR,
|
||||||
|
NWINDDIR1,
|
||||||
|
NWINDDIR2,
|
||||||
|
NHUMIDITY,
|
||||||
|
NAMB_TEMP,
|
||||||
|
NPRESSURE,
|
||||||
|
NPRECIP,
|
||||||
|
NPRECIP_LEVEL,
|
||||||
|
NMIST,
|
||||||
|
NCLOUDS,
|
||||||
|
NSKYTEMP,
|
||||||
|
NCOMMWEATH,
|
||||||
|
NLASTAHTUNG,
|
||||||
|
NAHTUNGRSN,
|
||||||
|
// NLIGHTDIST,
|
||||||
|
NBADWEATH,
|
||||||
|
NTERRWEATH,
|
||||||
|
NFORCEDSHTDN,
|
||||||
|
NAMOUNT_OF_DATA
|
||||||
|
};
|
||||||
|
|
||||||
|
// starting sense values are VAL_BROKEN except of calculated values
|
||||||
|
// they would be changed later in `fix_new_data` to lowest level
|
||||||
|
static val_t collected_data[NAMOUNT_OF_DATA] = {
|
||||||
|
[NWIND] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||||
|
[NWINDMAX] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX", .comment = "Maximal wind speed for last 24 hours"},
|
||||||
|
[NWINDMAX1] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDMAX1", .comment = "Maximal wind speed for last hour"},
|
||||||
|
[NWINDDIR] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
||||||
|
[NWINDDIR1] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR1", .comment = "Mean wind speed direction for last hour"},
|
||||||
|
[NWINDDIR2] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_OTHER, .name = "WINDDIR2", .comment = "Mean wind speed^2 direction for last hour"},
|
||||||
|
[NHUMIDITY] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||||
|
[NAMB_TEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||||
|
[NPRESSURE] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||||
|
[NPRECIP] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
|
[NPRECIP_LEVEL] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||||
|
[NMIST] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_MIST},
|
||||||
|
[NCLOUDS] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_CLOUDS},
|
||||||
|
[NSKYTEMP] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_SKYTEMP},
|
||||||
|
// [NLIGHTDIST] = {.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
|
||||||
|
// these are calculated values
|
||||||
|
[NCOMMWEATH] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "WEATHER", .comment = "Weather (0..3: good/bad/terrible/prohibited)"},
|
||||||
|
[NLASTAHTUNG] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "EVTTIME", .comment = "UNIX-time of last weather level changing"},
|
||||||
|
[NAHTUNGRSN] = {.sense = VAL_RECOMMENDED, .type = VALT_STRING, .meaning = IS_OTHER, .name = "EVTRSN", .comment = "Last weather level increasing reason"},
|
||||||
|
// virtual values for weather level / flags changing
|
||||||
|
[NBADWEATH] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_BADWEATH, .name = "BADWEATH", .comment = "Flag changing weather level to 'BAD'"},
|
||||||
|
[NTERRWEATH] = {.sense = VAL_BROKEN, .type = VALT_UINT, .meaning = IS_TERRIBLEWEATH, .name = "TERWEATH", .comment = "Flag changing weather level to 'TERRIBLE'"},
|
||||||
|
[NFORCEDSHTDN] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "FORCEOFF", .comment = "All should be powered off NOW"},
|
||||||
|
// {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_OTHER},
|
||||||
|
};
|
||||||
|
|
||||||
|
// additional fields marked as `IS_OTHER` gathered from different stations
|
||||||
|
static int Nadditional = 0;
|
||||||
|
static val_t *additional_data = NULL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief weather_level - set/clear weather level
|
||||||
|
* @param newlvl - -1 for getter or 0..3 for setter
|
||||||
|
* @return current weather level
|
||||||
|
*/
|
||||||
|
int weather_level(int newlvl){
|
||||||
|
if(newlvl > -1 && newlvl <= WEATHER_PROHIBITED){
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
uint32_t curt = time(NULL);
|
||||||
|
int oldlvl = collected_data[NCOMMWEATH].value.u;
|
||||||
|
collected_data[NCOMMWEATH].value.u = newlvl;
|
||||||
|
collected_data[NCOMMWEATH].time = curt;
|
||||||
|
sprintf(collected_data[NAHTUNGRSN].value.str, "MANUAL");
|
||||||
|
collected_data[NAHTUNGRSN].time = curt;
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curt;
|
||||||
|
collected_data[NLASTAHTUNG].time = curt;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
LOGWARN("Manual changing of weather level from %d to %d", oldlvl, newlvl);
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
int curlevel = collected_data[NCOMMWEATH].value.u;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
return curlevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief force_off - set/clear `force off` flag
|
||||||
|
* @param flag - 1 to set, 0 to clear or -1 to get
|
||||||
|
* @return current value
|
||||||
|
*/
|
||||||
|
int force_off(int flag){
|
||||||
|
DBG("Force OFF to %d", flag);
|
||||||
|
if(flag > -1 && flag < 2){
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
uint32_t curt = time(NULL);
|
||||||
|
int oldval = collected_data[NFORCEDSHTDN].value.u;
|
||||||
|
collected_data[NFORCEDSHTDN].value.u = (uint32_t)flag;
|
||||||
|
if(flag) collected_data[NFORCEDSHTDN].time = curt;
|
||||||
|
sprintf(collected_data[NAHTUNGRSN].value.str, "MANUAL");
|
||||||
|
collected_data[NAHTUNGRSN].time = curt;
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curt;
|
||||||
|
collected_data[NLASTAHTUNG].time = curt;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
LOGWARN("Manual changing of FORCED SHUTDOWN from %d to %d", oldval, flag);
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
flag = collected_data[NFORCEDSHTDN].value.u;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
return flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
double array[MAX_HISTORY];
|
||||||
|
double sum;
|
||||||
|
} sumval_t;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
int write_idx;
|
||||||
|
sumval_t C; // (speed * cos(dir))
|
||||||
|
sumval_t C2; // (speed^2 * cos(dir))
|
||||||
|
sumval_t S; // (speed * sin(dir))
|
||||||
|
sumval_t S2; // (speed^2 * sin(dir))
|
||||||
|
} meanwinddir_t;
|
||||||
|
|
||||||
|
static meanwinddir_t winddirs = {0};
|
||||||
|
|
||||||
|
static double update_sum(sumval_t *sv, double newval, int oldidx){
|
||||||
|
double old = sv->array[oldidx];
|
||||||
|
sv->array[oldidx] = newval;
|
||||||
|
return (sv->sum += newval - old);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add current value into floating array and recalculate mean speeds
|
||||||
|
// data mutex should be locked outside this function
|
||||||
|
static void wind_dir_add(double curspeed, double curdir, double *dir, double *dir2){
|
||||||
|
double C, S;
|
||||||
|
sincos(curdir * M_PI / 180., &S, &C);
|
||||||
|
double vS = curspeed * S, vC = curspeed * C, v2S = curspeed * vS, v2C = curspeed * vC;
|
||||||
|
int idx = winddirs.write_idx;
|
||||||
|
winddirs.write_idx = (idx + 1) % MAX_HISTORY;
|
||||||
|
// calculate sums
|
||||||
|
vS = update_sum(&winddirs.S, vS, idx);
|
||||||
|
vC = update_sum(&winddirs.C, vC, idx);
|
||||||
|
v2S = update_sum(&winddirs.S2, v2S, idx);
|
||||||
|
v2C = update_sum(&winddirs.C2, v2C, idx);
|
||||||
|
*dir = atan2(vS, vC) * 180. / M_PI;
|
||||||
|
*dir2 = atan2(v2S, v2C) * 180. / M_PI;
|
||||||
|
if(*dir < 0.) *dir += 360.;
|
||||||
|
if(*dir2 < 0.) *dir2 += 360.;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
double speeds[MAX_HISTORY];
|
||||||
|
time_t timestamps[MAX_HISTORY];
|
||||||
|
int write_idx; // index in `speeds` and `timestamps` for new value
|
||||||
|
|
||||||
|
int deq[MAX_HISTORY]; // array of indexes in queue
|
||||||
|
int deq_head, deq_tail; // queue's head and tail
|
||||||
|
} sliding_max_t;
|
||||||
|
|
||||||
|
static sliding_max_t windspeeds = {0};
|
||||||
|
|
||||||
|
static void add_windspeed(sliding_max_t *sm, double speed, time_t now) {
|
||||||
|
// Write new data portion into queue
|
||||||
|
int idx = sm->write_idx;
|
||||||
|
sm->speeds[idx] = speed;
|
||||||
|
sm->timestamps[idx] = now;
|
||||||
|
sm->write_idx = (idx + 1) % MAX_HISTORY;
|
||||||
|
|
||||||
|
// Remove values older than `TOO_OLD_DATA`
|
||||||
|
time_t cutoff = now - TOO_OLD_DATA;
|
||||||
|
while(sm->deq_head != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < cutoff){
|
||||||
|
sm->deq_head = (sm->deq_head + 1) % MAX_HISTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove small values less than current
|
||||||
|
while(sm->deq_head != sm->deq_tail && sm->speeds[sm->deq[(sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY]] <= speed){
|
||||||
|
sm->deq_tail = (sm->deq_tail - 1 + MAX_HISTORY) % MAX_HISTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new index into queue
|
||||||
|
sm->deq[sm->deq_tail] = idx;
|
||||||
|
sm->deq_tail = (sm->deq_tail + 1) % MAX_HISTORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double get_current_max(sliding_max_t *sm){
|
||||||
|
if(sm->deq_head == sm->deq_tail) return 0.0; // No data
|
||||||
|
return sm->speeds[sm->deq[sm->deq_head]];
|
||||||
|
}
|
||||||
|
|
||||||
|
static double get_max_forT(sliding_max_t *sm, time_t tcutoff){
|
||||||
|
if(sm->deq_head == sm->deq_tail) return 0.0; // No data
|
||||||
|
int idx = sm->deq_head;
|
||||||
|
while(idx != sm->deq_tail && sm->timestamps[sm->deq[sm->deq_head]] < tcutoff){
|
||||||
|
idx = (idx + 1) % MAX_HISTORY;
|
||||||
|
}
|
||||||
|
if(idx == sm->deq_tail) return 0.0; // No fresh data
|
||||||
|
return sm->speeds[sm->deq[idx]];
|
||||||
|
}
|
||||||
|
|
||||||
|
int collected_amount(){
|
||||||
|
return NAMOUNT_OF_DATA + Nadditional;
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_collected(val_t *val, int N){
|
||||||
|
if(!val || N < 0 || N >= NAMOUNT_OF_DATA + Nadditional){
|
||||||
|
DBG("Wrong number (%d) requested or no place for data", N);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
val_t *dptr = (N < NAMOUNT_OF_DATA) ? &collected_data[N] : &additional_data[N-NAMOUNT_OF_DATA];
|
||||||
|
#ifdef EBUG
|
||||||
|
char buf[KEY_LEN+1];
|
||||||
|
get_fieldname(dptr, buf);
|
||||||
|
DBG("Copied data of %d (u=%d, nm=%s, t=%zd)", N, dptr->value.u, buf, dptr->time);
|
||||||
|
#endif
|
||||||
|
*val = *dptr;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief fix_new_data - take only data with `sense value` less than collected have and more recent
|
||||||
|
* @param collected - pointer to collected data
|
||||||
|
* @param fresh - pointer to fresh data
|
||||||
|
* @param force - ==1 to force changing even if this data is older
|
||||||
|
*/
|
||||||
|
static void fix_new_data(val_t *collected, const val_t *fresh, int force){
|
||||||
|
if(!collected || !fresh) return;
|
||||||
|
if(collected->time >= fresh->time){
|
||||||
|
if(!force) return;
|
||||||
|
//DBG("Forced, collected=%g, fresh=%g", val2d(collected), val2d(fresh));
|
||||||
|
if(collected->time - fresh->time > 60) return;
|
||||||
|
//DBG("Not too old");
|
||||||
|
}
|
||||||
|
// lower `collected` level if data is too old
|
||||||
|
if(fresh->time - collected->time > WeatherConf.ahtung_delay) collected->sense = VAL_UNNECESSARY;
|
||||||
|
if(collected->sense < fresh->sense) return;
|
||||||
|
if(collected->sense != fresh->sense) collected->sense = fresh->sense; // take new level
|
||||||
|
//DBG("Refresh collected value");
|
||||||
|
collected->time = fresh->time;
|
||||||
|
if(collected->type == fresh->type){ // good case
|
||||||
|
memcpy(&collected->value, &fresh->value, sizeof(num_t));
|
||||||
|
//DBG("Types are the same");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// bad case: have different types
|
||||||
|
// DON'T convert between string and number types!
|
||||||
|
switch(collected->type){
|
||||||
|
case VALT_UINT:
|
||||||
|
switch(fresh->type){
|
||||||
|
case VALT_INT:
|
||||||
|
collected->value.u = (uint32_t) fresh->value.i;
|
||||||
|
//DBG("i->u");
|
||||||
|
break;
|
||||||
|
case VALT_FLOAT:
|
||||||
|
collected->value.u = (uint32_t) fresh->value.f;
|
||||||
|
//DBG("f->u");
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VALT_INT:
|
||||||
|
switch(fresh->type){
|
||||||
|
case VALT_UINT:
|
||||||
|
collected->value.i = (int32_t) fresh->value.u;
|
||||||
|
//DBG("u->i");
|
||||||
|
break;
|
||||||
|
case VALT_FLOAT:
|
||||||
|
collected->value.i = (int32_t) fresh->value.f;
|
||||||
|
//DBG("f->i");
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VALT_FLOAT:
|
||||||
|
switch(fresh->type){
|
||||||
|
case VALT_UINT:
|
||||||
|
collected->value.f = (float) fresh->value.u;
|
||||||
|
//DBG("u->f");
|
||||||
|
break;
|
||||||
|
case VALT_INT:
|
||||||
|
collected->value.f = (float) fresh->value.i;
|
||||||
|
//DBG("i->f");
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// find value.name in `additional_data`, if need - allocate new memory and update
|
||||||
|
static void update_additional(val_t *value){
|
||||||
|
if(!value) return;
|
||||||
|
int idx = 0;
|
||||||
|
for(; idx < Nadditional; ++idx){
|
||||||
|
if(0 == strcmp(additional_data[idx].name, value->name)) break;
|
||||||
|
}
|
||||||
|
if(idx == Nadditional){ // not found -> allocate
|
||||||
|
additional_data = realloc(additional_data, sizeof(val_t) * (++Nadditional));
|
||||||
|
if(!additional_data){
|
||||||
|
LOGERR("update_additional() can't realloc()");
|
||||||
|
ERR("realloc()");
|
||||||
|
}
|
||||||
|
memcpy(&additional_data[idx], value, sizeof(val_t));
|
||||||
|
DBG("Allocated new field: %s", value->name);
|
||||||
|
}else fix_new_data(&additional_data[idx], value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief chkweatherlevel - increase weather level if need, also check force shutdown flags
|
||||||
|
* @param curlevel - current max weather level
|
||||||
|
* @param curvalue - current value of sensor data
|
||||||
|
* @param curcond - conditions for given value
|
||||||
|
* @return 0 if level wasn't changed, or +1 if was increased
|
||||||
|
*/
|
||||||
|
static int chkweatherlevel(uint32_t *curlevel, double curvalue, weather_cond_t const *curcond){
|
||||||
|
int rtn = 0;
|
||||||
|
double good = curcond->good, bad = curcond->bad, terrible = curcond->terrible, prohibited = curcond->prohibited;
|
||||||
|
int haveproh = (prohibited > terrible) ? 1 : 0; // value have `prohibited` field
|
||||||
|
if(curcond->negflag){ // negate
|
||||||
|
curvalue = -curvalue;
|
||||||
|
good = -good;
|
||||||
|
bad = -bad;
|
||||||
|
terrible = -terrible;
|
||||||
|
prohibited = -prohibited;
|
||||||
|
}
|
||||||
|
int newlevel = -1;
|
||||||
|
if(haveproh && curvalue > prohibited){
|
||||||
|
newlevel = WEATHER_PROHIBITED;
|
||||||
|
DBG("---> new level is PROHIBITED, val=%g", (curcond->negflag) ? -curvalue : curvalue);
|
||||||
|
}else if(curvalue > terrible){
|
||||||
|
newlevel = WEATHER_TERRIBLE;
|
||||||
|
DBG("---> new level is TERRIBLE, val=%g", (curcond->negflag) ? -curvalue : curvalue);
|
||||||
|
}else if(curvalue > bad) newlevel = WEATHER_BAD;
|
||||||
|
else if(curvalue < good) newlevel = WEATHER_GOOD;
|
||||||
|
if(newlevel == -1) return 0;
|
||||||
|
time_t curt = time(NULL);
|
||||||
|
if(curcond->shtdnflag && newlevel >= WEATHER_TERRIBLE){
|
||||||
|
DBG("Forced shutdown flag is set, curvalue: %g", (curcond->negflag) ? -curvalue : curvalue);
|
||||||
|
// set to one collected data flag and its time
|
||||||
|
val_t *f = &collected_data[NFORCEDSHTDN];
|
||||||
|
f->value.u = 1;
|
||||||
|
f->time = (int) curt;
|
||||||
|
DBG("forced = %u", collected_data[NFORCEDSHTDN].value.u);
|
||||||
|
// and set current weather level to prohibited
|
||||||
|
newlevel = WEATHER_PROHIBITED;
|
||||||
|
rtn = 1;
|
||||||
|
}
|
||||||
|
if((uint32_t)newlevel > *curlevel){
|
||||||
|
// TODO: add logging
|
||||||
|
DBG("local level increased to %d", newlevel);
|
||||||
|
*curlevel = (uint32_t)newlevel;
|
||||||
|
rtn = 1;
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// conditions for "bad weather" flag (if it ==1 set BAD WEATH)
|
||||||
|
static weather_cond_t const badweathflag = {.good = 0.1, .bad = 0.5, .terrible = 2.};
|
||||||
|
// conditions for "terrible weather" flag
|
||||||
|
static weather_cond_t const terrweathflag = {.good = 0.1, .bad = 0.5, .terrible = 0.7};
|
||||||
|
// conditions for "prohibited weather" flag
|
||||||
|
static weather_cond_t const prohibweathflag = {.good = 0.1, .bad = 0.5, .terrible = 0.6, .prohibited = 0.7};
|
||||||
|
// conditions for "force shutdown" flag
|
||||||
|
static weather_cond_t const shtdnflag = {.good = 0.1, .bad = 0.5, .terrible = 0.7, .shtdnflag = 1, .prohibited = 0.8};
|
||||||
|
|
||||||
|
void refresh_sensval(sensordata_t *s){
|
||||||
|
//FNAME();
|
||||||
|
//static time_t poll_time = 0;
|
||||||
|
static char reason[KEY_LEN+1] = {0}; // reason of weather level increasing
|
||||||
|
val_t value;
|
||||||
|
if(!s || !s->get_value) return;
|
||||||
|
//if(poll_time == 0) poll_time = get_pollT();
|
||||||
|
static uint32_t curlevel = 0; // this is worse weather leavel, start from best (collect by all sensors through 3*tpoll)
|
||||||
|
static time_t lasttupdate = 0; // last update time of weather level
|
||||||
|
time_t curtime = time(NULL);
|
||||||
|
time_t tpoll = get_pollT(), _3tpoll = 3*tpoll;
|
||||||
|
double dir = -100., dir2 = -100.; // mean wind directions
|
||||||
|
//DBG("%d meteo values", s->Nvalues);
|
||||||
|
for(int i = 0; i < s->Nvalues; ++i){
|
||||||
|
//DBG("\nTry to get %dth value", i);
|
||||||
|
if(!s->get_value(s, &value, i) || value.sense > VAL_RECOMMENDED) continue;
|
||||||
|
//DBG("got value");
|
||||||
|
int idx = -1;
|
||||||
|
double curvalue = val2d(&value);
|
||||||
|
const weather_cond_t *curcond = NULL;
|
||||||
|
switch(value.meaning){
|
||||||
|
case IS_WIND:
|
||||||
|
idx = NWIND;
|
||||||
|
curcond = &WeatherConf.wind;
|
||||||
|
// protect collected wind speeds from destruction in case of simultaneous acces from different plugins
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
add_windspeed(&windspeeds, curvalue, curtime);
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
break;
|
||||||
|
case IS_WINDDIR:
|
||||||
|
idx = NWINDDIR;
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
wind_dir_add(collected_data[NWIND].value.f, value.value.f, &dir, &dir2);
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
break;
|
||||||
|
case IS_HUMIDITY:
|
||||||
|
idx = NHUMIDITY;
|
||||||
|
curcond = &WeatherConf.humidity;
|
||||||
|
break;
|
||||||
|
case IS_AMB_TEMP:
|
||||||
|
idx = NAMB_TEMP;
|
||||||
|
break;
|
||||||
|
case IS_PRESSURE:
|
||||||
|
idx = NPRESSURE;
|
||||||
|
break;
|
||||||
|
case IS_PRECIP:
|
||||||
|
idx = NPRECIP;
|
||||||
|
curcond = &prohibweathflag;
|
||||||
|
if(curvalue > 0.) DBG("IS_PRECIP == 1 !!!");
|
||||||
|
break;
|
||||||
|
case IS_PRECIP_LEVEL:
|
||||||
|
idx = NPRECIP_LEVEL;
|
||||||
|
curcond = &terrweathflag;
|
||||||
|
break;
|
||||||
|
case IS_MIST:
|
||||||
|
idx = NMIST;
|
||||||
|
curcond = &terrweathflag;
|
||||||
|
break;
|
||||||
|
case IS_CLOUDS:
|
||||||
|
idx = NCLOUDS;
|
||||||
|
curcond = &WeatherConf.clouds;
|
||||||
|
break;
|
||||||
|
case IS_SKYTEMP:
|
||||||
|
idx = NSKYTEMP;
|
||||||
|
curcond = &WeatherConf.sky;
|
||||||
|
break;
|
||||||
|
/*case IS_LIGTDIST:
|
||||||
|
idx = NLIGHTDIST;
|
||||||
|
curcond = &WeatherConf.ligtdist;
|
||||||
|
break;*/
|
||||||
|
case IS_BADWEATH:
|
||||||
|
idx = NBADWEATH;
|
||||||
|
curcond = &badweathflag;
|
||||||
|
break;
|
||||||
|
case IS_TERRIBLEWEATH:
|
||||||
|
idx = NTERRWEATH;
|
||||||
|
curcond = &terrweathflag;
|
||||||
|
break;
|
||||||
|
case IS_FORCEDSHTDN:
|
||||||
|
idx = NFORCEDSHTDN;
|
||||||
|
curcond = &shtdnflag;
|
||||||
|
//DBG("%s have shtdn flag", value.name);
|
||||||
|
break;
|
||||||
|
default : break;
|
||||||
|
}
|
||||||
|
if(value.meaning == IS_OTHER){ // check for new or existant field in `additional_data`
|
||||||
|
update_additional(&value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(idx < 0 || idx >= NAMOUNT_OF_DATA) continue;
|
||||||
|
//DBG("IDX=%d", idx);
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
int force = 0;
|
||||||
|
if(curcond){
|
||||||
|
int oldshtdn = collected_data[NFORCEDSHTDN].value.u;
|
||||||
|
if(1 == chkweatherlevel(&curlevel, curvalue, curcond)){
|
||||||
|
get_fieldname(&value, reason); // copy to `reason` reason of last level increasing
|
||||||
|
force = 1;
|
||||||
|
DBG("reason: %s; forceflag=%u, old=%d", reason, collected_data[NFORCEDSHTDN].value.u, oldshtdn);
|
||||||
|
if(collected_data[NFORCEDSHTDN].value.u - oldshtdn == 1){ // got shutdown
|
||||||
|
LOGWARN("Forced shutdown flag is set by '%s' of '%s'", reason, s->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(idx == NFORCEDSHTDN && value.value.u == 0){
|
||||||
|
//DBG("Don't clear forced flag by other station");
|
||||||
|
}else fix_new_data(&collected_data[idx], &value, force);
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
}
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
// refresh max
|
||||||
|
if(dir >= 0.){
|
||||||
|
collected_data[NWINDDIR1].value.f = (float) dir;
|
||||||
|
collected_data[NWINDDIR1].time = curtime;
|
||||||
|
}
|
||||||
|
if(dir2 >= 0.){
|
||||||
|
collected_data[NWINDDIR2].value.f = (float) dir2;
|
||||||
|
collected_data[NWINDDIR2].time = curtime;
|
||||||
|
}
|
||||||
|
if(curtime - collected_data[NWIND].time < tpoll + 1){
|
||||||
|
collected_data[NWINDMAX].value.f = (float) get_current_max(&windspeeds);
|
||||||
|
collected_data[NWINDMAX].time = curtime;
|
||||||
|
collected_data[NWINDMAX1].value.f = (float) get_max_forT(&windspeeds, curtime - T_ONE_HOUR);
|
||||||
|
collected_data[NWINDMAX1].time = curtime;
|
||||||
|
}
|
||||||
|
//DBG("check ahtung");
|
||||||
|
time_t _2update = lasttupdate + _3tpoll;
|
||||||
|
if(Forbidden){
|
||||||
|
collected_data[NCOMMWEATH].value.u = WEATHER_PROHIBITED;
|
||||||
|
collected_data[NCOMMWEATH].time = curtime;
|
||||||
|
}else if(curtime >= _2update){ // need to update weather level
|
||||||
|
if(collected_data[NCOMMWEATH].value.u > curlevel){ // check timeout to make level lower
|
||||||
|
// DBG("curtime: %zd, curahtt: %d, diff: %zd, delay: %d", curtime, collected_data[NLASTAHTUNG].value.u, curtime - collected_data[NLASTAHTUNG].value.u, WeatherConf.ahtung_delay);
|
||||||
|
if(curtime - collected_data[NLASTAHTUNG].value.u > WeatherConf.ahtung_delay){
|
||||||
|
DBG("newlevel: %d, current: %d DECREASED", curlevel, collected_data[NCOMMWEATH].value.u);
|
||||||
|
if(curlevel < WEATHER_TERRIBLE){ // clear forced shutdown flag
|
||||||
|
if(collected_data[NFORCEDSHTDN].value.u){
|
||||||
|
LOGMSG("Clear forced shutdown flag by '%s' and set weather level to %d", s->name, WEATHER_TERRIBLE);
|
||||||
|
DBG("Clear FORCED SHUTDOWN flag");
|
||||||
|
collected_data[NCOMMWEATH].value.u = WEATHER_TERRIBLE;
|
||||||
|
collected_data[NFORCEDSHTDN].value.u = 0;
|
||||||
|
}else collected_data[NCOMMWEATH].value.u = curlevel;
|
||||||
|
}else --collected_data[NCOMMWEATH].value.u;
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curtime;
|
||||||
|
LOGMSG("Station '%s', decrease weather level to %d", s->name, collected_data[NCOMMWEATH].value.u);
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if(collected_data[NCOMMWEATH].value.u < curlevel){ // set to worse
|
||||||
|
DBG("newlevel: %d, current: %d INCREASED", curlevel, collected_data[NCOMMWEATH].value.u);
|
||||||
|
LOGWARN("Station '%s', sensor '%s', increase weather level to %d", s->name, reason, curlevel);
|
||||||
|
collected_data[NCOMMWEATH].value.u = curlevel;
|
||||||
|
if(1 < snprintf(collected_data[NAHTUNGRSN].value.str, STRT_LEN+1, "%s", reason))
|
||||||
|
collected_data[NAHTUNGRSN].time = curtime;
|
||||||
|
}
|
||||||
|
if(curlevel){
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curtime; // refresh last ahtung time only for level > good
|
||||||
|
collected_data[NLASTAHTUNG].time = curtime;
|
||||||
|
collected_data[NAHTUNGRSN].time = curtime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lasttupdate = curtime;
|
||||||
|
curlevel = 0; // wait for next collected max level
|
||||||
|
collected_data[NCOMMWEATH].time = curtime; // refresh `common weather` updating time
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
//DBG("Refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// set/clear `forbid` flag (by signals USR1 and USR2)
|
||||||
|
void forbid_observations(int f){
|
||||||
|
pthread_mutex_lock(&datamutex);
|
||||||
|
if(f) Forbidden = 1;
|
||||||
|
else Forbidden = 0;
|
||||||
|
int curt = (int) time(NULL);
|
||||||
|
// don't use mutexes here as this function called from signal handler
|
||||||
|
collected_data[NLASTAHTUNG].value.u = curt;
|
||||||
|
collected_data[NLASTAHTUNG].time = curt;
|
||||||
|
sprintf(collected_data[NAHTUNGRSN].value.str, "FORBID");
|
||||||
|
collected_data[NAHTUNGRSN].time = curt;
|
||||||
|
pthread_mutex_unlock(&datamutex);
|
||||||
|
DBG("Change FORBID status to %d", f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `forbid` flag getter
|
||||||
|
int is_forbidden(){ return Forbidden; }
|
||||||
69
Daemons/weatherdaemon_multimeteo/mainweather.h
Normal file
69
Daemons/weatherdaemon_multimeteo/mainweather.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
// weather conditions
|
||||||
|
enum{
|
||||||
|
WEATHER_GOOD, // good to start observations
|
||||||
|
WEATHER_BAD, // bad for start, but can run
|
||||||
|
WEATHER_TERRIBLE, // need close the dome
|
||||||
|
WEATHER_PROHIBITED, // need close all, park and power off equipment <-- by SIGUSR1/SIGUSR2 + by FORCEOFF
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
double good; // if value less than this, weather is good
|
||||||
|
double bad; // if value greater than this, weather is bad
|
||||||
|
double terrible; // if value greater than this, weather is terrible
|
||||||
|
double prohibited; // ...
|
||||||
|
int negflag; // reversal flag (good if > val, etc)
|
||||||
|
int shtdnflag; // ==1 to shut down if `terrible`
|
||||||
|
} weather_cond_t;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
int ahtung_delay; // delay to change "bad weather" to good after last "bad event"
|
||||||
|
int reinit_delay; // delay to check all sensors and reinit dead
|
||||||
|
// wind, m/s
|
||||||
|
weather_cond_t wind;
|
||||||
|
// humidity, %%
|
||||||
|
weather_cond_t humidity;
|
||||||
|
// "clouds", > 2500 - OK -> should be negated when check!!!
|
||||||
|
weather_cond_t clouds;
|
||||||
|
// sky temperature minus ambient temperature, degC
|
||||||
|
weather_cond_t sky;
|
||||||
|
// distance to lightning
|
||||||
|
weather_cond_t ligtdist;
|
||||||
|
} weather_conf_t;
|
||||||
|
|
||||||
|
// defined in cmdlnopts.c
|
||||||
|
extern weather_conf_t WeatherConf;
|
||||||
|
|
||||||
|
int collected_amount();
|
||||||
|
int get_collected(val_t *val, int N);
|
||||||
|
|
||||||
|
void forbid_observations(int f);
|
||||||
|
int is_forbidden();
|
||||||
|
|
||||||
|
void refresh_sensval(sensordata_t *s);
|
||||||
|
|
||||||
|
int force_off(int flag);
|
||||||
|
int weather_level(int new);
|
||||||
|
|
||||||
|
//void run_mainweather();
|
||||||
@@ -21,4 +21,46 @@ if(FDEXAMPLE)
|
|||||||
list(APPEND LIBS fdex)
|
list(APPEND LIBS fdex)
|
||||||
endif()
|
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})
|
install(TARGETS ${LIBS} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||||
|
|||||||
149
Daemons/weatherdaemon_multimeteo/plugins/UPS-MIB.csv
Normal file
149
Daemons/weatherdaemon_multimeteo/plugins/UPS-MIB.csv
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
OBJECT_NAME,OBJECT_IDENTIFIER,OBJECT_DATA_TYPE,OBJECT_PREMISSIONS,OBJECT_CLASS,OBJECT_NODE_TYPE,OBJECT_DESCRIPTION
|
||||||
|
upsMIB,1.3.6.1.2.1.33,,,moduleidentity,,"The MIB module to describe Uninterruptible Power Supplies."
|
||||||
|
upsObjects,1.3.6.1.2.1.33.1,,,objectidentity,,""
|
||||||
|
upsIdent,1.3.6.1.2.1.33.1.1,,,objectidentity,,""
|
||||||
|
upsIdentManufacturer,1.3.6.1.2.1.33.1.1.1,displaystring,read-only,objecttype,scalar,"The name of the UPS manufacturer."
|
||||||
|
upsIdentModel,1.3.6.1.2.1.33.1.1.2,displaystring,read-only,objecttype,scalar,"The UPS Model designation."
|
||||||
|
upsIdentUPSSoftwareVersion,1.3.6.1.2.1.33.1.1.3,displaystring,read-only,objecttype,scalar,"The UPS firmware/software version(s). This variable may or may not have the same value as upsIdentAgentSoftwareVersion in some implementations."
|
||||||
|
upsIdentAgentSoftwareVersion,1.3.6.1.2.1.33.1.1.4,displaystring,read-only,objecttype,scalar,"The UPS agent software version. This variable may or may not have the same value as upsIdentUPSSoftwareVersion in some implementations."
|
||||||
|
upsIdentName,1.3.6.1.2.1.33.1.1.5,displaystring,read-write,objecttype,scalar,"A string identifying the UPS. This object should be set by the administrator."
|
||||||
|
upsIdentAttachedDevices,1.3.6.1.2.1.33.1.1.6,displaystring,read-write,objecttype,scalar,"A string identifying the devices attached to the output(s) of the UPS. This object should be set by the administrator."
|
||||||
|
upsBattery,1.3.6.1.2.1.33.1.2,,,objectidentity,,""
|
||||||
|
upsBatteryStatus,1.3.6.1.2.1.33.1.2.1,integer,read-only,objecttype,scalar,"The indication of the capacity remaining in the UPS system's batteries. A value of batteryNormal indicates that the remaining run-time is greater than upsConfigLowBattTime. A value of batteryLow indicates that the remaining battery run-time is less than or equal to upsConfigLowBattTime. A value of batteryDepleted indicates that the UPS will be unable to sustain the present load when and if the utility power is lost (including the possibility that the utility power is currently absent and the UPS is unable to sustain the output). Enumeration: 'unknown': 1, 'batteryNormal': 2, 'batteryDepleted': 4, 'batteryLow': 3."
|
||||||
|
upsSecondsOnBattery,1.3.6.1.2.1.33.1.2.2,nonnegativeinteger,read-only,objecttype,scalar,"If the unit is on battery power, the elapsed time since the UPS last switched to battery power, or the time since the network management subsystem was last restarted, whichever is less. Zero shall be returned if the unit is not on battery power."
|
||||||
|
upsEstimatedMinutesRemaining,1.3.6.1.2.1.33.1.2.3,positiveinteger,read-only,objecttype,scalar,"An estimate of the time to battery charge depletion under the present load conditions if the utility power is off and remains off, or if it were to be lost and remain off."
|
||||||
|
upsEstimatedChargeRemaining,1.3.6.1.2.1.33.1.2.4,integer,read-only,objecttype,scalar,"An estimate of the battery charge remaining expressed as a percent of full charge."
|
||||||
|
upsBatteryVoltage,1.3.6.1.2.1.33.1.2.5,nonnegativeinteger,read-only,objecttype,scalar,"The magnitude of the present battery voltage."
|
||||||
|
upsBatteryCurrent,1.3.6.1.2.1.33.1.2.6,integer32,read-only,objecttype,scalar,"The present battery current."
|
||||||
|
upsBatteryTemperature,1.3.6.1.2.1.33.1.2.7,integer32,read-only,objecttype,scalar,"The ambient temperature at or near the UPS Battery casing."
|
||||||
|
upsInput,1.3.6.1.2.1.33.1.3,,,objectidentity,,""
|
||||||
|
upsInputLineBads,1.3.6.1.2.1.33.1.3.1,counter32,read-only,objecttype,scalar,"A count of the number of times the input entered an out-of-tolerance condition as defined by the manufacturer. This count is incremented by one each time the input transitions from zero out-of-tolerance lines to one or more input lines out-of-tolerance."
|
||||||
|
upsInputNumLines,1.3.6.1.2.1.33.1.3.2,nonnegativeinteger,read-only,objecttype,scalar,"The number of input lines utilized in this device. This variable indicates the number of rows in the input table."
|
||||||
|
upsInputTable,1.3.6.1.2.1.33.1.3.3,,no-access,objecttype,table,"A list of input table entries. The number of entries is given by the value of upsInputNumLines."
|
||||||
|
upsInputEntry,1.3.6.1.2.1.33.1.3.3.1,,no-access,objecttype,row,"An entry containing information applicable to a particular input line."
|
||||||
|
upsInputLineIndex,1.3.6.1.2.1.33.1.3.3.1.1,positiveinteger,no-access,objecttype,column,"The input line identifier."
|
||||||
|
upsInputFrequency,1.3.6.1.2.1.33.1.3.3.1.2,nonnegativeinteger,read-only,objecttype,column,"The present input frequency."
|
||||||
|
upsInputVoltage,1.3.6.1.2.1.33.1.3.3.1.3,nonnegativeinteger,read-only,objecttype,column,"The magnitude of the present input voltage."
|
||||||
|
upsInputCurrent,1.3.6.1.2.1.33.1.3.3.1.4,nonnegativeinteger,read-only,objecttype,column,"The magnitude of the present input current."
|
||||||
|
upsInputTruePower,1.3.6.1.2.1.33.1.3.3.1.5,nonnegativeinteger,read-only,objecttype,column,"The magnitude of the present input true power."
|
||||||
|
upsOutput,1.3.6.1.2.1.33.1.4,,,objectidentity,,""
|
||||||
|
upsOutputSource,1.3.6.1.2.1.33.1.4.1,integer,read-only,objecttype,scalar,"The present source of output power. The enumeration none(2) indicates that there is no source of output power (and therefore no output power), for example, the system has opened the output breaker. Enumeration: 'none': 2, 'normal': 3, 'battery': 5, 'reducer': 7, 'other': 1, 'bypass': 4, 'booster': 6."
|
||||||
|
upsOutputFrequency,1.3.6.1.2.1.33.1.4.2,nonnegativeinteger,read-only,objecttype,scalar,"The present output frequency."
|
||||||
|
upsOutputNumLines,1.3.6.1.2.1.33.1.4.3,nonnegativeinteger,read-only,objecttype,scalar,"The number of output lines utilized in this device. This variable indicates the number of rows in the output table."
|
||||||
|
upsOutputTable,1.3.6.1.2.1.33.1.4.4,,no-access,objecttype,table,"A list of output table entries. The number of entries is given by the value of upsOutputNumLines."
|
||||||
|
upsOutputEntry,1.3.6.1.2.1.33.1.4.4.1,,no-access,objecttype,row,"An entry containing information applicable to a particular output line."
|
||||||
|
upsOutputLineIndex,1.3.6.1.2.1.33.1.4.4.1.1,positiveinteger,no-access,objecttype,column,"The output line identifier."
|
||||||
|
upsOutputVoltage,1.3.6.1.2.1.33.1.4.4.1.2,nonnegativeinteger,read-only,objecttype,column,"The present output voltage."
|
||||||
|
upsOutputCurrent,1.3.6.1.2.1.33.1.4.4.1.3,nonnegativeinteger,read-only,objecttype,column,"The present output current."
|
||||||
|
upsOutputPower,1.3.6.1.2.1.33.1.4.4.1.4,nonnegativeinteger,read-only,objecttype,column,"The present output true power."
|
||||||
|
upsOutputPercentLoad,1.3.6.1.2.1.33.1.4.4.1.5,integer,read-only,objecttype,column,"The percentage of the UPS power capacity presently being used on this output line, i.e., the greater of the percent load of true power capacity and the percent load of VA."
|
||||||
|
upsBypass,1.3.6.1.2.1.33.1.5,,,objectidentity,,""
|
||||||
|
upsBypassFrequency,1.3.6.1.2.1.33.1.5.1,nonnegativeinteger,read-only,objecttype,scalar,"The present bypass frequency."
|
||||||
|
upsBypassNumLines,1.3.6.1.2.1.33.1.5.2,nonnegativeinteger,read-only,objecttype,scalar,"The number of bypass lines utilized in this device. This entry indicates the number of rows in the bypass table."
|
||||||
|
upsBypassTable,1.3.6.1.2.1.33.1.5.3,,no-access,objecttype,table,"A list of bypass table entries. The number of entries is given by the value of upsBypassNumLines."
|
||||||
|
upsBypassEntry,1.3.6.1.2.1.33.1.5.3.1,,no-access,objecttype,row,"An entry containing information applicable to a particular bypass input."
|
||||||
|
upsBypassLineIndex,1.3.6.1.2.1.33.1.5.3.1.1,positiveinteger,no-access,objecttype,column,"The bypass line identifier."
|
||||||
|
upsBypassVoltage,1.3.6.1.2.1.33.1.5.3.1.2,nonnegativeinteger,read-only,objecttype,column,"The present bypass voltage."
|
||||||
|
upsBypassCurrent,1.3.6.1.2.1.33.1.5.3.1.3,nonnegativeinteger,read-only,objecttype,column,"The present bypass current."
|
||||||
|
upsBypassPower,1.3.6.1.2.1.33.1.5.3.1.4,nonnegativeinteger,read-only,objecttype,column,"The present true power conveyed by the bypass."
|
||||||
|
upsAlarm,1.3.6.1.2.1.33.1.6,,,objectidentity,,""
|
||||||
|
upsAlarmsPresent,1.3.6.1.2.1.33.1.6.1,gauge32,read-only,objecttype,scalar,"The present number of active alarm conditions."
|
||||||
|
upsAlarmTable,1.3.6.1.2.1.33.1.6.2,,no-access,objecttype,table,"A list of alarm table entries. The table contains zero, one, or many rows at any moment, depending upon the number of alarm conditions in effect. The table is initially empty at agent startup. The agent creates a row in the table each time a condition is detected and deletes that row when that condition no longer pertains. The agent creates the first row with upsAlarmId equal to 1, and increments the value of upsAlarmId each time a new row is created, wrapping to the first free value greater than or equal to 1 when the maximum value of upsAlarmId would otherwise be exceeded. Consequently, after multiple operations, the table may become sparse, e.g., containing entries for rows 95, 100, 101, and 203 and the entries should not be assumed to be in chronological order because upsAlarmId might have wrapped. Alarms are named by an AutonomousType (OBJECT IDENTIFIER), upsAlarmDescr, to allow a single table to reflect well known alarms plus alarms defined by a particular implementation, i.e., as documented in the private enterprise MIB definition for the device. No two rows will have the same value of upsAlarmDescr, since alarms define conditions. In order to meet this requirement, care should be taken in the definition of alarm conditions to insure that a system cannot enter the same condition multiple times simultaneously. The number of rows in the table at any given time is reflected by the value of upsAlarmsPresent."
|
||||||
|
upsAlarmEntry,1.3.6.1.2.1.33.1.6.2.1,,no-access,objecttype,row,"An entry containing information applicable to a particular alarm."
|
||||||
|
upsAlarmId,1.3.6.1.2.1.33.1.6.2.1.1,positiveinteger,no-access,objecttype,column,"A unique identifier for an alarm condition. This value must remain constant."
|
||||||
|
upsAlarmDescr,1.3.6.1.2.1.33.1.6.2.1.2,autonomoustype,read-only,objecttype,column,"A reference to an alarm description object. The object referenced should not be accessible, but rather be used to provide a unique description of the alarm condition."
|
||||||
|
upsAlarmTime,1.3.6.1.2.1.33.1.6.2.1.3,timestamp,read-only,objecttype,column,"The value of sysUpTime when the alarm condition was detected. If the alarm condition was detected at the time of agent startup and presumably existed before agent startup, the value of upsAlarmTime shall equal 0."
|
||||||
|
upsWellKnownAlarms,1.3.6.1.2.1.33.1.6.3,,,objectidentity,,""
|
||||||
|
upsAlarmBatteryBad,1.3.6.1.2.1.33.1.6.3.1,,,objectidentity,,"One or more batteries have been determined to require replacement."
|
||||||
|
upsAlarmOnBattery,1.3.6.1.2.1.33.1.6.3.2,,,objectidentity,,"The UPS is drawing power from the batteries."
|
||||||
|
upsAlarmLowBattery,1.3.6.1.2.1.33.1.6.3.3,,,objectidentity,,"The remaining battery run-time is less than or equal to upsConfigLowBattTime."
|
||||||
|
upsAlarmDepletedBattery,1.3.6.1.2.1.33.1.6.3.4,,,objectidentity,,"The UPS will be unable to sustain the present load when and if the utility power is lost."
|
||||||
|
upsAlarmTempBad,1.3.6.1.2.1.33.1.6.3.5,,,objectidentity,,"A temperature is out of tolerance."
|
||||||
|
upsAlarmInputBad,1.3.6.1.2.1.33.1.6.3.6,,,objectidentity,,"An input condition is out of tolerance."
|
||||||
|
upsAlarmOutputBad,1.3.6.1.2.1.33.1.6.3.7,,,objectidentity,,"An output condition (other than OutputOverload) is out of tolerance."
|
||||||
|
upsAlarmOutputOverload,1.3.6.1.2.1.33.1.6.3.8,,,objectidentity,,"The output load exceeds the UPS output capacity."
|
||||||
|
upsAlarmOnBypass,1.3.6.1.2.1.33.1.6.3.9,,,objectidentity,,"The Bypass is presently engaged on the UPS."
|
||||||
|
upsAlarmBypassBad,1.3.6.1.2.1.33.1.6.3.10,,,objectidentity,,"The Bypass is out of tolerance."
|
||||||
|
upsAlarmOutputOffAsRequested,1.3.6.1.2.1.33.1.6.3.11,,,objectidentity,,"The UPS has shutdown as requested, i.e., the output is off."
|
||||||
|
upsAlarmUpsOffAsRequested,1.3.6.1.2.1.33.1.6.3.12,,,objectidentity,,"The entire UPS has shutdown as commanded."
|
||||||
|
upsAlarmChargerFailed,1.3.6.1.2.1.33.1.6.3.13,,,objectidentity,,"An uncorrected problem has been detected within the UPS charger subsystem."
|
||||||
|
upsAlarmUpsOutputOff,1.3.6.1.2.1.33.1.6.3.14,,,objectidentity,,"The output of the UPS is in the off state."
|
||||||
|
upsAlarmUpsSystemOff,1.3.6.1.2.1.33.1.6.3.15,,,objectidentity,,"The UPS system is in the off state."
|
||||||
|
upsAlarmFanFailure,1.3.6.1.2.1.33.1.6.3.16,,,objectidentity,,"The failure of one or more fans in the UPS has been detected."
|
||||||
|
upsAlarmFuseFailure,1.3.6.1.2.1.33.1.6.3.17,,,objectidentity,,"The failure of one or more fuses has been detected."
|
||||||
|
upsAlarmGeneralFault,1.3.6.1.2.1.33.1.6.3.18,,,objectidentity,,"A general fault in the UPS has been detected."
|
||||||
|
upsAlarmDiagnosticTestFailed,1.3.6.1.2.1.33.1.6.3.19,,,objectidentity,,"The result of the last diagnostic test indicates a failure."
|
||||||
|
upsAlarmCommunicationsLost,1.3.6.1.2.1.33.1.6.3.20,,,objectidentity,,"A problem has been encountered in the communications between the agent and the UPS."
|
||||||
|
upsAlarmAwaitingPower,1.3.6.1.2.1.33.1.6.3.21,,,objectidentity,,"The UPS output is off and the UPS is awaiting the return of input power."
|
||||||
|
upsAlarmShutdownPending,1.3.6.1.2.1.33.1.6.3.22,,,objectidentity,,"A upsShutdownAfterDelay countdown is underway."
|
||||||
|
upsAlarmShutdownImminent,1.3.6.1.2.1.33.1.6.3.23,,,objectidentity,,"The UPS will turn off power to the load in less than 5 seconds; this may be either a timed shutdown or a low battery shutdown."
|
||||||
|
upsAlarmTestInProgress,1.3.6.1.2.1.33.1.6.3.24,,,objectidentity,,"A test is in progress, as initiated and indicated by the Test Group. Tests initiated via other implementation-specific mechanisms can indicate the presence of the testing in the alarm table, if desired, via a OBJECT-IDENTITY macro in the MIB document specific to that implementation and are outside the scope of this OBJECT-IDENTITY."
|
||||||
|
upsTest,1.3.6.1.2.1.33.1.7,,,objectidentity,,""
|
||||||
|
upsTestId,1.3.6.1.2.1.33.1.7.1,object identifier,read-write,objecttype,scalar,"The test is named by an OBJECT IDENTIFIER which allows a standard mechanism for the initiation of tests, including the well known tests identified in this document as well as those introduced by a particular implementation, i.e., as documented in the private enterprise MIB definition for the device. Setting this variable initiates the named test. Sets to this variable require the presence of upsTestSpinLock in the same SNMP message. The set request will be rejected with an appropriate error message if the requested test cannot be performed, including attempts to start a test when another test is already in progress. The status of the current or last test is maintained in upsTestResultsSummary. Tests in progress may be aborted by setting the upsTestId variable to upsTestAbortTestInProgress. Read operations return the value of the name of the test in progress if a test is in progress or the name of the last test performed if no test is in progress, unless no test has been run, in which case the well known value upsTestNoTestsInitiated is returned."
|
||||||
|
upsTestSpinLock,1.3.6.1.2.1.33.1.7.2,testandincr,read-write,objecttype,scalar,"A spin lock on the test subsystem. The spinlock is used as follows. Before starting a test, a manager-station should make sure that a test is not in progress as follows: try_again: get (upsTestSpinLock) while (upsTestResultsSummary == inProgress) { /* loop while a test is running for another manager */ short delay get (upsTestSpinLock) } lock_value = upsTestSpinLock /* no test in progress, start the test */ set (upsTestSpinLock = lock_value, upsTestId = requested_test) if (error_index == 1) { /* (upsTestSpinLock failed) */ /* if problem is not access control, then some other manager slipped in ahead of us */ goto try_again } if (error_index == 2) { /* (upsTestId) */ /* cannot perform the test */ give up } /* test started ok */ /* wait for test completion by polling upsTestResultsSummary */ get (upsTestSpinLock, upsTestResultsSummary, upsTestResultsDetail) while (upsTestResultsSummary == inProgress) { short delay get (upsTestSpinLock, upsTestResultsSummary, upsTestResultsDetail) } /* when test completes, retrieve any additional test results */ /* if upsTestSpinLock == lock_value + 1, then these are our test */ /* results (as opposed to another manager's */ The initial value of upsTestSpinLock at agent initialization shall be 1."
|
||||||
|
upsTestResultsSummary,1.3.6.1.2.1.33.1.7.3,integer,read-only,objecttype,scalar,"The results of the current or last UPS diagnostics test performed. The values for donePass(1), doneWarning(2), and doneError(3) indicate that the test completed either successfully, with a warning, or with an error, respectively. The value aborted(4) is returned for tests which are aborted by setting the value of upsTestId to upsTestAbortTestInProgress. Tests which have not yet concluded are indicated by inProgress(5). The value noTestsInitiated(6) indicates that no previous test results are available, such as is the case when no tests have been run since the last reinitialization of the network management subsystem and the system has no provision for non- volatile storage of test results. Enumeration: 'doneError': 3, 'noTestsInitiated': 6, 'donePass': 1, 'doneWarning': 2, 'aborted': 4, 'inProgress': 5."
|
||||||
|
upsTestResultsDetail,1.3.6.1.2.1.33.1.7.4,displaystring,read-only,objecttype,scalar,"Additional information about upsTestResultsSummary. If no additional information available, a zero length string is returned."
|
||||||
|
upsTestStartTime,1.3.6.1.2.1.33.1.7.5,timestamp,read-only,objecttype,scalar,"The value of sysUpTime at the time the test in progress was initiated, or, if no test is in progress, the time the previous test was initiated. If the value of upsTestResultsSummary is noTestsInitiated(6), upsTestStartTime has the value 0."
|
||||||
|
upsTestElapsedTime,1.3.6.1.2.1.33.1.7.6,timeinterval,read-only,objecttype,scalar,"The amount of time, in TimeTicks, since the test in progress was initiated, or, if no test is in progress, the previous test took to complete. If the value of upsTestResultsSummary is noTestsInitiated(6), upsTestElapsedTime has the value 0."
|
||||||
|
upsWellKnownTests,1.3.6.1.2.1.33.1.7.7,,,objectidentity,,""
|
||||||
|
upsTestNoTestsInitiated,1.3.6.1.2.1.33.1.7.7.1,,,objectidentity,,"No tests have been initiated and no test is in progress."
|
||||||
|
upsTestAbortTestInProgress,1.3.6.1.2.1.33.1.7.7.2,,,objectidentity,,"The test in progress is to be aborted / the test in progress was aborted."
|
||||||
|
upsTestGeneralSystemsTest,1.3.6.1.2.1.33.1.7.7.3,,,objectidentity,,"The manufacturer's standard test of UPS device systems."
|
||||||
|
upsTestQuickBatteryTest,1.3.6.1.2.1.33.1.7.7.4,,,objectidentity,,"A test that is sufficient to determine if the battery needs replacement."
|
||||||
|
upsTestDeepBatteryCalibration,1.3.6.1.2.1.33.1.7.7.5,,,objectidentity,,"The system is placed on battery to a discharge level, set by the manufacturer, sufficient to determine battery replacement and battery run-time with a high degree of confidence. WARNING: this test will leave the battery in a low charge state and will require time for recharging to a level sufficient to provide normal battery duration for the protected load."
|
||||||
|
upsControl,1.3.6.1.2.1.33.1.8,,,objectidentity,,""
|
||||||
|
upsShutdownType,1.3.6.1.2.1.33.1.8.1,integer,read-write,objecttype,scalar,"This object determines the nature of the action to be taken at the time when the countdown of the upsShutdownAfterDelay and upsRebootWithDuration objects reaches zero. Setting this object to output(1) indicates that shutdown requests should cause only the output of the UPS to turn off. Setting this object to system(2) indicates that shutdown requests will cause the entire UPS system to turn off. Enumeration: 'output': 1, 'system': 2."
|
||||||
|
upsShutdownAfterDelay,1.3.6.1.2.1.33.1.8.2,integer,read-write,objecttype,scalar,"Setting this object will shutdown (i.e., turn off) either the UPS output or the UPS system (as determined by the value of upsShutdownType at the time of shutdown) after the indicated number of seconds, or less if the UPS batteries become depleted. Setting this object to 0 will cause the shutdown to occur immediately. Setting this object to -1 will abort the countdown. If the system is already in the desired state at the time the countdown reaches 0, then nothing will happen. That is, there is no additional action at that time if upsShutdownType = system and the system is already off. Similarly, there is no additional action at that time if upsShutdownType = output and the output is already off. When read, upsShutdownAfterDelay will return the number of seconds remaining until shutdown, or -1 if no shutdown countdown is in effect. On some systems, if the agent is restarted while a shutdown countdown is in effect, the countdown may be aborted. Sets to this object override any upsShutdownAfterDelay already in effect."
|
||||||
|
upsStartupAfterDelay,1.3.6.1.2.1.33.1.8.3,integer,read-write,objecttype,scalar,"Setting this object will start the output after the indicated number of seconds, including starting the UPS, if necessary. Setting this object to 0 will cause the startup to occur immediately. Setting this object to -1 will abort the countdown. If the output is already on at the time the countdown reaches 0, then nothing will happen. Sets to this object override the effect of any upsStartupAfterDelay countdown or upsRebootWithDuration countdown in progress. When read, upsStartupAfterDelay will return the number of seconds until startup, or -1 if no startup countdown is in effect. If the countdown expires during a utility failure, the startup shall not occur until the utility power is restored. On some systems, if the agent is restarted while a startup countdown is in effect, the countdown is aborted."
|
||||||
|
upsRebootWithDuration,1.3.6.1.2.1.33.1.8.4,integer,read-write,objecttype,scalar,"Setting this object will immediately shutdown (i.e., turn off) either the UPS output or the UPS system (as determined by the value of upsShutdownType at the time of shutdown) for a period equal to the indicated number of seconds, after which time the output will be started, including starting the UPS, if necessary. If the number of seconds required to perform the request is greater than the requested duration, then the requested shutdown and startup cycle shall be performed in the minimum time possible, but in no case shall this require more than the requested duration plus 60 seconds. When read, upsRebootWithDuration shall return the number of seconds remaining in the countdown, or -1 if no countdown is in progress. If the startup should occur during a utility failure, the startup shall not occur until the utility power is restored."
|
||||||
|
upsAutoRestart,1.3.6.1.2.1.33.1.8.5,integer,read-write,objecttype,scalar,"Setting this object to 'on' will cause the UPS system to restart after a shutdown if the shutdown occurred during a power loss as a result of either a upsShutdownAfterDelay or an internal battery depleted condition. Setting this object to 'off' will prevent the UPS system from restarting after a shutdown until an operator manually or remotely explicitly restarts it. If the UPS is in a startup or reboot countdown, then the UPS will not restart until that delay has been satisfied. Enumeration: 'on': 1, 'off': 2."
|
||||||
|
upsConfig,1.3.6.1.2.1.33.1.9,,,objectidentity,,""
|
||||||
|
upsConfigInputVoltage,1.3.6.1.2.1.33.1.9.1,nonnegativeinteger,read-write,objecttype,scalar,"The magnitude of the nominal input voltage. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigInputFreq,1.3.6.1.2.1.33.1.9.2,nonnegativeinteger,read-write,objecttype,scalar,"The nominal input frequency. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigOutputVoltage,1.3.6.1.2.1.33.1.9.3,nonnegativeinteger,read-write,objecttype,scalar,"The magnitude of the nominal output voltage. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigOutputFreq,1.3.6.1.2.1.33.1.9.4,nonnegativeinteger,read-write,objecttype,scalar,"The nominal output frequency. On those systems which support read-write access to this object, if there is an attempt to set this variable to a value that is not supported, the request must be rejected and the agent shall respond with an appropriate error message, i.e., badValue for SNMPv1, or inconsistentValue for SNMPv2."
|
||||||
|
upsConfigOutputVA,1.3.6.1.2.1.33.1.9.5,nonnegativeinteger,read-only,objecttype,scalar,"The magnitude of the nominal Volt-Amp rating."
|
||||||
|
upsConfigOutputPower,1.3.6.1.2.1.33.1.9.6,nonnegativeinteger,read-only,objecttype,scalar,"The magnitude of the nominal true power rating."
|
||||||
|
upsConfigLowBattTime,1.3.6.1.2.1.33.1.9.7,nonnegativeinteger,read-write,objecttype,scalar,"The value of upsEstimatedMinutesRemaining at which a lowBattery condition is declared. For agents which support only discrete (discontinuous) values, then the agent shall round up to the next supported value. If the requested value is larger than the largest supported value, then the largest supported value shall be selected."
|
||||||
|
upsConfigAudibleStatus,1.3.6.1.2.1.33.1.9.8,integer,read-write,objecttype,scalar,"The requested state of the audible alarm. When in the disabled state, the audible alarm should never sound. The enabled state is self-describing. Setting this object to muted(3) when the audible alarm is sounding shall temporarily silence the alarm. It will remain muted until it would normally stop sounding and the value returned for read operations during this period shall equal muted(3). At the end of this period, the value shall revert to enabled(2). Writes of the value muted(3) when the audible alarm is not sounding shall be accepted but otherwise shall have no effect. Enumeration: 'disabled': 1, 'muted': 3, 'enabled': 2."
|
||||||
|
upsConfigLowVoltageTransferPoint,1.3.6.1.2.1.33.1.9.9,nonnegativeinteger,read-write,objecttype,scalar,"The minimum input line voltage allowed before the UPS system transfers to battery backup."
|
||||||
|
upsConfigHighVoltageTransferPoint,1.3.6.1.2.1.33.1.9.10,nonnegativeinteger,read-write,objecttype,scalar,"The maximum line voltage allowed before the UPS system transfers to battery backup."
|
||||||
|
upsTraps,1.3.6.1.2.1.33.2,,,objectidentity,,""
|
||||||
|
upsTrapOnBattery,1.3.6.1.2.1.33.2.1,,,notificationtype,,"The UPS is operating on battery power. This trap is persistent and is resent at one minute intervals until the UPS either turns off or is no longer running on battery."
|
||||||
|
upsTrapTestCompleted,1.3.6.1.2.1.33.2.2,,,notificationtype,,"This trap is sent upon completion of a UPS diagnostic test."
|
||||||
|
upsTrapAlarmEntryAdded,1.3.6.1.2.1.33.2.3,,,notificationtype,,"This trap is sent each time an alarm is inserted into to the alarm table. It is sent on the insertion of all alarms except for upsAlarmOnBattery and upsAlarmTestInProgress."
|
||||||
|
upsTrapAlarmEntryRemoved,1.3.6.1.2.1.33.2.4,,,notificationtype,,"This trap is sent each time an alarm is removed from the alarm table. It is sent on the removal of all alarms except for upsAlarmTestInProgress."
|
||||||
|
upsConformance,1.3.6.1.2.1.33.3,,,objectidentity,,""
|
||||||
|
upsCompliances,1.3.6.1.2.1.33.3.1,,,objectidentity,,""
|
||||||
|
upsSubsetCompliance,1.3.6.1.2.1.33.3.1.1,,,modulecompliance,,"The compliance statement for UPSs that only support the two-contact communication protocol."
|
||||||
|
upsBasicCompliance,1.3.6.1.2.1.33.3.1.2,,,modulecompliance,,"The compliance statement for UPSs that support full-featured functions, such as control."
|
||||||
|
upsFullCompliance,1.3.6.1.2.1.33.3.1.3,,,modulecompliance,,"The compliance statement for UPSs that support advanced full-featured functions."
|
||||||
|
upsGroups,1.3.6.1.2.1.33.3.2,,,objectidentity,,""
|
||||||
|
upsSubsetGroups,1.3.6.1.2.1.33.3.2.1,,,objectidentity,,""
|
||||||
|
upsSubsetIdentGroup,1.3.6.1.2.1.33.3.2.1.1,,,objectgroup,,"The upsSubsetIdentGroup defines objects which are common across all UPSs which meet subset compliance. Most devices which conform to the upsSubsetIdentGroup will provide access to these objects via a proxy agent. If the proxy agent is compatible with multiple UPS types, configuration of the proxy agent will require specifying some of these values, either individually, or as a group (perhaps through a table lookup mechanism based on the UPS model number)."
|
||||||
|
upsSubsetBatteryGroup,1.3.6.1.2.1.33.3.2.1.2,,,objectgroup,,"The upsSubsetBatteryGroup defines the objects that are common to battery groups of two-contact UPSs."
|
||||||
|
upsSubsetInputGroup,1.3.6.1.2.1.33.3.2.1.3,,,objectgroup,,"The upsSubsetInputGroup defines the objects that are common to the Input groups of two-contact UPSs."
|
||||||
|
upsSubsetOutputGroup,1.3.6.1.2.1.33.3.2.1.4,,,objectgroup,,"The upsSubsetOutputGroup defines the objects that are common to the Output groups of two-contact UPSs."
|
||||||
|
upsSubsetAlarmGroup,1.3.6.1.2.1.33.3.2.1.6,,,objectgroup,,"The upsSubsetAlarmGroup defines the objects that are common to the Alarm groups of two-contact UPSs."
|
||||||
|
upsSubsetControlGroup,1.3.6.1.2.1.33.3.2.1.8,,,objectgroup,,"The upsSubsetControlGroup defines the objects that are common to the Control groups of two-contact UPSs."
|
||||||
|
upsSubsetConfigGroup,1.3.6.1.2.1.33.3.2.1.9,,,objectgroup,,"The upsSubsetConfigGroup defines the objects that are common to the Config groups of two-contact UPSs."
|
||||||
|
upsBasicGroups,1.3.6.1.2.1.33.3.2.2,,,objectidentity,,""
|
||||||
|
upsBasicIdentGroup,1.3.6.1.2.1.33.3.2.2.1,,,objectgroup,,"The upsBasicIdentGroup defines objects which are common to the Ident group of compliant UPSs which support basic functions."
|
||||||
|
upsBasicBatteryGroup,1.3.6.1.2.1.33.3.2.2.2,,,objectgroup,,"The upsBasicBatteryGroup defines the objects that are common to the battery groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicInputGroup,1.3.6.1.2.1.33.3.2.2.3,,,objectgroup,,"The upsBasicInputGroup defines the objects that are common to the Input groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicOutputGroup,1.3.6.1.2.1.33.3.2.2.4,,,objectgroup,,"The upsBasicOutputGroup defines the objects that are common to the Output groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicBypassGroup,1.3.6.1.2.1.33.3.2.2.5,,,objectgroup,,"The upsBasicBypassGroup defines the objects that are common to the Bypass groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicAlarmGroup,1.3.6.1.2.1.33.3.2.2.6,,,objectgroup,,"The upsBasicAlarmGroup defines the objects that are common to the Alarm groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicTestGroup,1.3.6.1.2.1.33.3.2.2.7,,,objectgroup,,"The upsBasicTestGroup defines the objects that are common to the Test groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicControlGroup,1.3.6.1.2.1.33.3.2.2.8,,,objectgroup,,"The upsBasicControlGroup defines the objects that are common to the Control groups of compliant UPSs which support basic functions."
|
||||||
|
upsBasicConfigGroup,1.3.6.1.2.1.33.3.2.2.9,,,objectgroup,,"The upsBasicConfigGroup defines the objects that are common to the Config groups of UPSs which support basic functions."
|
||||||
|
upsFullGroups,1.3.6.1.2.1.33.3.2.3,,,objectidentity,,""
|
||||||
|
upsFullIdentGroup,1.3.6.1.2.1.33.3.2.3.1,,,objectgroup,,"The upsFullIdentGroup defines objects which are common to the Ident group of fully compliant UPSs."
|
||||||
|
upsFullBatteryGroup,1.3.6.1.2.1.33.3.2.3.2,,,objectgroup,,"The upsFullBatteryGroup defines the objects that are common to the battery groups of fully compliant UPSs."
|
||||||
|
upsFullInputGroup,1.3.6.1.2.1.33.3.2.3.3,,,objectgroup,,"The upsFullInputGroup defines the objects that are common to the Input groups of fully compliant UPSs."
|
||||||
|
upsFullOutputGroup,1.3.6.1.2.1.33.3.2.3.4,,,objectgroup,,"The upsFullOutputGroup defines the objects that are common to the Output groups of fully compliant UPSs."
|
||||||
|
upsFullBypassGroup,1.3.6.1.2.1.33.3.2.3.5,,,objectgroup,,"The upsFullBypassGroup defines the objects that are common to the Bypass groups of fully compliant UPSs."
|
||||||
|
upsFullAlarmGroup,1.3.6.1.2.1.33.3.2.3.6,,,objectgroup,,"The upsFullAlarmGroup defines the objects that are common to the Alarm groups of fully compliant UPSs."
|
||||||
|
upsFullTestGroup,1.3.6.1.2.1.33.3.2.3.7,,,objectgroup,,"The upsFullTestGroup defines the objects that are common to the Test groups of fully compliant UPSs."
|
||||||
|
upsFullControlGroup,1.3.6.1.2.1.33.3.2.3.8,,,objectgroup,,"The upsFullControlGroup defines the objects that are common to the Control groups of fully compliant UPSs."
|
||||||
|
upsFullConfigGroup,1.3.6.1.2.1.33.3.2.3.9,,,objectgroup,,"The upsFullConfigGroup defines the objects that are common to the Config groups of fully compliant UPSs."
|
||||||
|
351
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.c
Normal file
351
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.c
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
// Copyright: V.S. Shergin, vsher@sao.ru
|
||||||
|
// fixed for x86_64 E.V. Emelianov, edward.emelianoff@gmail.com
|
||||||
|
#include "bta_shdata.h"
|
||||||
|
#include <usefull_macros.h>
|
||||||
|
|
||||||
|
#include <crypt.h>
|
||||||
|
|
||||||
|
#pragma pack(push, 4)
|
||||||
|
// Main command channel (level 5)
|
||||||
|
struct CMD_Queue mcmd = {{"Mcmd"}, 0200,0,-1,0};
|
||||||
|
// Operator command channel (level 4)
|
||||||
|
struct CMD_Queue ocmd = {{"Ocmd"}, 0200,0,-1,0};
|
||||||
|
// User command channel (level 2/3)
|
||||||
|
struct CMD_Queue ucmd = {{"Ucmd"}, 0200,0,-1,0};
|
||||||
|
|
||||||
|
#define MSGLEN (80)
|
||||||
|
static char msg[MSGLEN];
|
||||||
|
#define PERR(...) do{snprintf(msg, MSGLEN, __VA_ARGS__); perror(msg);} while(0)
|
||||||
|
|
||||||
|
#ifndef BTA_MODULE
|
||||||
|
volatile struct BTA_Data *sdt = NULL;
|
||||||
|
volatile struct BTA_Local *sdtl = NULL;
|
||||||
|
|
||||||
|
volatile struct SHM_Block sdat = {
|
||||||
|
{"Sdat"},
|
||||||
|
sizeof(struct BTA_Data),
|
||||||
|
2048,0444,
|
||||||
|
SHM_RDONLY,
|
||||||
|
bta_data_init,
|
||||||
|
bta_data_check,
|
||||||
|
bta_data_close,
|
||||||
|
ClientSide,-1,NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
int snd_id = -1; // client sender ID
|
||||||
|
int cmd_src_pid = 0; // next command source PID
|
||||||
|
uint32_t cmd_src_ip = 0;// next command source IP
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init data
|
||||||
|
*/
|
||||||
|
void bta_data_init() {
|
||||||
|
sdt = (struct BTA_Data *)sdat.addr;
|
||||||
|
sdtl = (struct BTA_Local *)(sdat.addr+sizeof(struct BTA_Data));
|
||||||
|
if(sdat.side == ClientSide) {
|
||||||
|
if(sdt->magic != sdat.key.code) {
|
||||||
|
WARN("Wrong shared data (maybe server turned off)");
|
||||||
|
}
|
||||||
|
if(sdt->version == 0) {
|
||||||
|
WARN("Null shared data version (maybe server turned off)");
|
||||||
|
}
|
||||||
|
else if(sdt->version != BTA_Data_Ver) {
|
||||||
|
WARN("Wrong shared data version: I'am - %d, but server - %d ...",
|
||||||
|
BTA_Data_Ver, sdt->version );
|
||||||
|
}
|
||||||
|
if(sdt->size != sdat.size) {
|
||||||
|
if(sdt->size > sdat.size) {
|
||||||
|
WARN("Wrong shared area size: I needs - %d, but server - %d ...",
|
||||||
|
sdat.size, sdt->size );
|
||||||
|
} else {
|
||||||
|
WARN("Attention! Too little shared data structure!");
|
||||||
|
WARN("I needs - %d, but server gives only %d ...",
|
||||||
|
sdat.size, sdt->size );
|
||||||
|
WARN("May be server's version too old!?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* ServerSide */
|
||||||
|
if(sdt->magic == sdat.key.code &&
|
||||||
|
sdt->version == BTA_Data_Ver &&
|
||||||
|
sdt->size == sdat.size)
|
||||||
|
return;
|
||||||
|
memset(sdat.addr, 0, sdat.maxsize);
|
||||||
|
sdt->magic = sdat.key.code;
|
||||||
|
sdt->version = BTA_Data_Ver;
|
||||||
|
sdt->size = sdat.size;
|
||||||
|
Tel_Hardware = Hard_On;
|
||||||
|
Pos_Corr = PC_On;
|
||||||
|
TrkOk_Mode = UseDiffVel | UseDiffAZ ;
|
||||||
|
inp_B = 591.;
|
||||||
|
Pressure = 595.;
|
||||||
|
PEP_code_A = 0x002aaa;
|
||||||
|
PEP_code_Z = 0x002aaa;
|
||||||
|
PEP_code_P = 0x002aaa;
|
||||||
|
PEP_code_F = 0x002aaa;
|
||||||
|
PEP_code_D = 0x002aaa;
|
||||||
|
DomeSEW_N = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bta_data_check() {
|
||||||
|
if(!sdt) return 0;
|
||||||
|
return( (sdt->magic == sdat.key.code) && (sdt->version == BTA_Data_Ver) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void bta_data_close() {
|
||||||
|
if(!sdt) return;
|
||||||
|
if(sdat.side == ServerSide) {
|
||||||
|
sdt->magic = 0;
|
||||||
|
sdt->version = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate shared memory segment
|
||||||
|
*/
|
||||||
|
int get_shm_block(volatile struct SHM_Block *sb, int server) {
|
||||||
|
int getsize = (server)? sb->maxsize : sb->size;
|
||||||
|
// first try to find existing one
|
||||||
|
sb->id = shmget(sb->key.code, getsize, sb->mode);
|
||||||
|
if(sb->id < 0 && errno == ENOENT && server){
|
||||||
|
// if no - try to create a new one
|
||||||
|
int cresize = sb->maxsize;
|
||||||
|
if(sb->size > cresize){
|
||||||
|
WARN("Wrong shm maxsize(%d) < realsize(%d)",sb->maxsize,sb->size);
|
||||||
|
cresize = sb->size;
|
||||||
|
}
|
||||||
|
sb->id = shmget(sb->key.code, cresize, IPC_CREAT|IPC_EXCL|sb->mode);
|
||||||
|
}
|
||||||
|
if(sb->id < 0){
|
||||||
|
if(server)
|
||||||
|
PERR("Can't create shared memory segment '%s'",sb->key.name);
|
||||||
|
else
|
||||||
|
PERR("Can't find shared segment '%s' (maybe no server process) ",sb->key.name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// attach it to our memory space
|
||||||
|
sb->addr = (unsigned char *) shmat(sb->id, NULL, sb->atflag);
|
||||||
|
if((long)sb->addr == -1){
|
||||||
|
PERR("Can't attach shared memory segment '%s'",sb->key.name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(server && (shmctl(sb->id, SHM_LOCK, NULL) < 0)){
|
||||||
|
PERR("Can't prevents swapping of shared memory segment '%s'",sb->key.name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
DBG("Create & attach shared memory segment '%s' %dbytes", sb->key.name, sb->size);
|
||||||
|
sb->side = server;
|
||||||
|
if(sb->init != NULL)
|
||||||
|
sb->init();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int close_shm_block(volatile struct SHM_Block *sb){
|
||||||
|
int ret;
|
||||||
|
if(sb->close != NULL)
|
||||||
|
sb->close();
|
||||||
|
if(sb->side == ServerSide) {
|
||||||
|
// ret = shmctl(sb->id, SHM_UNLOCK, NULL);
|
||||||
|
ret = shmctl(sb->id, IPC_RMID, NULL);
|
||||||
|
}
|
||||||
|
ret = shmdt (sb->addr);
|
||||||
|
return(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create|Find command queue
|
||||||
|
*/
|
||||||
|
void get_cmd_queue(struct CMD_Queue *cq, int server){
|
||||||
|
if (!server && cq->id >= 0) { //if already in use set current
|
||||||
|
snd_id = cq->id;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// first try to find existing one
|
||||||
|
cq->id = msgget(cq->key.code, cq->mode);
|
||||||
|
// if no - try to create a new one
|
||||||
|
if(cq->id<0 && errno == ENOENT && server)
|
||||||
|
cq->id = msgget(cq->key.code, IPC_CREAT|IPC_EXCL|cq->mode);
|
||||||
|
if(cq->id<0){
|
||||||
|
if(server)
|
||||||
|
PERR("Can't create comand queue '%s'",cq->key.name);
|
||||||
|
else
|
||||||
|
PERR("Can't find comand queue '%s' (maybe no server process) ",cq->key.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cq->side = server;
|
||||||
|
if(server){
|
||||||
|
char buf[120]; /* выбросить все команды из очереди */
|
||||||
|
while(msgrcv(cq->id, (struct msgbuf *)buf, 112, 0, IPC_NOWAIT) > 0);
|
||||||
|
}else
|
||||||
|
snd_id = cq->id;
|
||||||
|
cq->acckey = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // BTA_MODULE
|
||||||
|
|
||||||
|
|
||||||
|
int check_shm_block(volatile struct SHM_Block *sb){
|
||||||
|
if(sb->check){
|
||||||
|
return(sb->check());
|
||||||
|
}
|
||||||
|
else return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set access key in current channel
|
||||||
|
*/
|
||||||
|
void set_acckey(uint32_t newkey){
|
||||||
|
if(snd_id < 0) return;
|
||||||
|
if(ucmd.id == snd_id) ucmd.acckey = newkey;
|
||||||
|
else if(ocmd.id == snd_id) ocmd.acckey = newkey;
|
||||||
|
else if(mcmd.id == snd_id) mcmd.acckey = newkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup source data for one following command if default values
|
||||||
|
* (IP == 0 - local, PID = current) not suits
|
||||||
|
*/
|
||||||
|
void set_cmd_src(uint32_t ip, int pid) {
|
||||||
|
cmd_src_pid = pid;
|
||||||
|
cmd_src_ip = ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma pack(push, 4)
|
||||||
|
/**
|
||||||
|
* Send client commands to server
|
||||||
|
*/
|
||||||
|
void send_cmd(int cmd_code, char *buf, int size) {
|
||||||
|
struct my_msgbuf mbuf;
|
||||||
|
if(snd_id < 0) return;
|
||||||
|
if(size > 100) size = 100;
|
||||||
|
if(cmd_code > 0)
|
||||||
|
mbuf.mtype = cmd_code;
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
if(ucmd.id == snd_id) mbuf.acckey = ucmd.acckey;
|
||||||
|
else if(ocmd.id == snd_id) mbuf.acckey = ocmd.acckey;
|
||||||
|
else if(mcmd.id == snd_id) mbuf.acckey = mcmd.acckey;
|
||||||
|
|
||||||
|
mbuf.src_pid = cmd_src_pid ? cmd_src_pid : getpid();
|
||||||
|
mbuf.src_ip = cmd_src_ip;
|
||||||
|
cmd_src_pid = cmd_src_ip = 0;
|
||||||
|
|
||||||
|
if(size > 0)
|
||||||
|
memcpy(mbuf.mtext, buf, size);
|
||||||
|
else {
|
||||||
|
mbuf.mtext[0] = 0;
|
||||||
|
size = 1;
|
||||||
|
}
|
||||||
|
msgsnd(snd_id, (struct msgbuf *)&mbuf, size+12, IPC_NOWAIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_cmd_noarg(int cmd_code) {
|
||||||
|
send_cmd(cmd_code, NULL, 0);
|
||||||
|
}
|
||||||
|
void send_cmd_str(int cmd_code, char *arg) {
|
||||||
|
send_cmd(cmd_code, arg, strlen(arg)+1);
|
||||||
|
}
|
||||||
|
void send_cmd_i1(int cmd_code, int32_t arg1) {
|
||||||
|
send_cmd(cmd_code, (char *)&arg1, sizeof(int32_t));
|
||||||
|
}
|
||||||
|
void send_cmd_i2(int cmd_code, int32_t arg1, int32_t arg2) {
|
||||||
|
int32_t ibuf[2];
|
||||||
|
ibuf[0] = arg1;
|
||||||
|
ibuf[1] = arg2;
|
||||||
|
send_cmd(cmd_code, (char *)ibuf, 2*sizeof(int32_t));
|
||||||
|
}
|
||||||
|
void send_cmd_i3(int cmd_code, int32_t arg1, int32_t arg2, int32_t arg3) {
|
||||||
|
int32_t ibuf[3];
|
||||||
|
ibuf[0] = arg1;
|
||||||
|
ibuf[1] = arg2;
|
||||||
|
ibuf[2] = arg3;
|
||||||
|
send_cmd(cmd_code, (char *)ibuf, 3*sizeof(int32_t));
|
||||||
|
}
|
||||||
|
void send_cmd_i4(int cmd_code, int32_t arg1, int32_t arg2, int32_t arg3, int32_t arg4) {
|
||||||
|
int32_t ibuf[4];
|
||||||
|
ibuf[0] = arg1;
|
||||||
|
ibuf[1] = arg2;
|
||||||
|
ibuf[2] = arg3;
|
||||||
|
ibuf[3] = arg4;
|
||||||
|
send_cmd(cmd_code, (char *)ibuf, 4*sizeof(int32_t));
|
||||||
|
}
|
||||||
|
void send_cmd_d1(int32_t cmd_code, double arg1) {
|
||||||
|
send_cmd(cmd_code, (char *)&arg1, sizeof(double));
|
||||||
|
}
|
||||||
|
void send_cmd_d2(int cmd_code, double arg1, double arg2) {
|
||||||
|
double dbuf[2];
|
||||||
|
dbuf[0] = arg1;
|
||||||
|
dbuf[1] = arg2;
|
||||||
|
send_cmd(cmd_code, (char *)dbuf, 2*sizeof(double));
|
||||||
|
}
|
||||||
|
void send_cmd_i1d1(int cmd_code, int32_t arg1, double arg2) {
|
||||||
|
struct {
|
||||||
|
int32_t ival;
|
||||||
|
double dval;
|
||||||
|
} buf;
|
||||||
|
buf.ival = arg1;
|
||||||
|
buf.dval = arg2;
|
||||||
|
send_cmd(cmd_code, (char *)&buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
void send_cmd_i2d1(int cmd_code, int32_t arg1, int32_t arg2, double arg3) {
|
||||||
|
struct {
|
||||||
|
int32_t ival[2];
|
||||||
|
double dval;
|
||||||
|
} buf;
|
||||||
|
buf.ival[0] = arg1;
|
||||||
|
buf.ival[1] = arg2;
|
||||||
|
buf.dval = arg3;
|
||||||
|
send_cmd(cmd_code, (char *)&buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
void send_cmd_i3d1(int cmd_code, int32_t arg1, int32_t arg2, int32_t arg3, double arg4) {
|
||||||
|
struct {
|
||||||
|
int32_t ival[3];
|
||||||
|
double dval;
|
||||||
|
} buf;
|
||||||
|
buf.ival[0] = arg1;
|
||||||
|
buf.ival[1] = arg2;
|
||||||
|
buf.ival[2] = arg3;
|
||||||
|
buf.dval = arg4;
|
||||||
|
send_cmd(cmd_code, (char *)&buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
void encode_lev_passwd(char *passwd, int nlev, uint32_t *keylev, uint32_t *codlev){
|
||||||
|
char salt[4];
|
||||||
|
char *encr;
|
||||||
|
union {
|
||||||
|
uint32_t ui;
|
||||||
|
char c[4];
|
||||||
|
} key, cod;
|
||||||
|
sprintf(salt,"L%1d",nlev);
|
||||||
|
encr = (char *)crypt(passwd, salt);
|
||||||
|
cod.c[0] = encr[2];
|
||||||
|
key.c[0] = encr[3];
|
||||||
|
cod.c[1] = encr[4];
|
||||||
|
key.c[1] = encr[5];
|
||||||
|
cod.c[2] = encr[6];
|
||||||
|
key.c[2] = encr[7];
|
||||||
|
cod.c[3] = encr[8];
|
||||||
|
key.c[3] = encr[9];
|
||||||
|
*keylev = key.ui;
|
||||||
|
*codlev = cod.ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
int find_lev_passwd(char *passwd, uint32_t *keylev, uint32_t *codlev){
|
||||||
|
int nlev;
|
||||||
|
for(nlev = 5; nlev > 0; --nlev){
|
||||||
|
encode_lev_passwd(passwd, nlev, keylev, codlev);
|
||||||
|
if(*codlev == code_Lev(nlev)) break;
|
||||||
|
}
|
||||||
|
return(nlev);
|
||||||
|
}
|
||||||
|
|
||||||
|
int check_lev_passwd(char *passwd){
|
||||||
|
uint32_t keylev,codlev;
|
||||||
|
int nlev;
|
||||||
|
nlev = find_lev_passwd(passwd, &keylev, &codlev);
|
||||||
|
if(nlev > 0) set_acckey(keylev);
|
||||||
|
return(nlev);
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
850
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.h
Normal file
850
Daemons/weatherdaemon_multimeteo/plugins/bta_shdata.h
Normal file
@@ -0,0 +1,850 @@
|
|||||||
|
// Copyright: V.S. Shergin, vsher@sao.ru
|
||||||
|
// fixed for x86_64 E.V. Emelianov, edward.emelianoff@gmail.com
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/ipc.h>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
#include <sys/msg.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#pragma pack(push, 4)
|
||||||
|
/*
|
||||||
|
* Shared memory block
|
||||||
|
*/
|
||||||
|
struct SHM_Block {
|
||||||
|
union {
|
||||||
|
char name[5]; // memory segment identificator
|
||||||
|
key_t code;
|
||||||
|
} key;
|
||||||
|
int32_t size; // size of memory used
|
||||||
|
int32_t maxsize; // size when created
|
||||||
|
int32_t mode; // access mode (rwxrwxrwx)
|
||||||
|
int32_t atflag; // connection mode (SHM_RDONLY or 0)
|
||||||
|
void (*init)(); // init function
|
||||||
|
int32_t (*check)(); // test function
|
||||||
|
void (*close)(); // deinit function
|
||||||
|
int32_t side; // connection type: client/server
|
||||||
|
int32_t id; // connection identificator
|
||||||
|
uint8_t *addr; // connection address
|
||||||
|
};
|
||||||
|
|
||||||
|
extern volatile struct SHM_Block sdat;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Command queue descriptor
|
||||||
|
*/
|
||||||
|
struct CMD_Queue {
|
||||||
|
union {
|
||||||
|
char name[5]; // queue key
|
||||||
|
key_t code;
|
||||||
|
} key;
|
||||||
|
int32_t mode; // access mode (rwxrwxrwx)
|
||||||
|
int32_t side; // connection type (Sender/Receiver - server/client)
|
||||||
|
int32_t id; // connection identificator
|
||||||
|
uint32_t acckey; // access key (for transmission from client to server)
|
||||||
|
};
|
||||||
|
|
||||||
|
extern struct CMD_Queue mcmd;
|
||||||
|
extern struct CMD_Queue ocmd;
|
||||||
|
extern struct CMD_Queue ucmd;
|
||||||
|
|
||||||
|
void send_cmd_noarg(int);
|
||||||
|
void send_cmd_str(int, char *);
|
||||||
|
void send_cmd_i1(int, int32_t);
|
||||||
|
void send_cmd_i2(int, int32_t, int32_t);
|
||||||
|
void send_cmd_i3(int, int32_t, int32_t, int32_t);
|
||||||
|
void send_cmd_i4(int, int32_t, int32_t, int32_t, int32_t);
|
||||||
|
void send_cmd_d1(int, double);
|
||||||
|
void send_cmd_d2(int, double, double);
|
||||||
|
void send_cmd_i1d1(int, int32_t, double);
|
||||||
|
void send_cmd_i2d1(int, int32_t, int32_t, double);
|
||||||
|
void send_cmd_i3d1(int, int32_t, int32_t, int32_t, double);
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Command list *
|
||||||
|
*******************************************************************************/
|
||||||
|
/* name code args type */
|
||||||
|
// Stop telescope
|
||||||
|
#define StopTel 1
|
||||||
|
#define StopTeleskope() send_cmd_noarg( 1 )
|
||||||
|
// High/low speed
|
||||||
|
#define StartHS 2
|
||||||
|
#define StartHighSpeed() send_cmd_noarg( 2 )
|
||||||
|
#define StartLS 3
|
||||||
|
#define StartLowSpeed() send_cmd_noarg( 3 )
|
||||||
|
// Timer setup (Ch7_15 or SysTimer)
|
||||||
|
#define SetTmr 4
|
||||||
|
#define SetTimerMode(T) send_cmd_i1 ( 4, (int)(T))
|
||||||
|
// Simulation (modeling) mode
|
||||||
|
#define SetModMod 5
|
||||||
|
#define SetModelMode(M) send_cmd_i1 ( 5, (int)(M))
|
||||||
|
// Azimuth speed code
|
||||||
|
#define SetCodA 6
|
||||||
|
#define SetPKN_A(iA,sA) send_cmd_i2 ( 6, (int)(iA),(int)(sA))
|
||||||
|
// Zenith speed code
|
||||||
|
#define SetCodZ 7
|
||||||
|
#define SetPKN_Z(iZ) send_cmd_i1 ( 7, (int)(iZ))
|
||||||
|
// Parangle speed code
|
||||||
|
#define SetCodP 8
|
||||||
|
#define SetPKN_P(iP) send_cmd_i1 ( 8, (int)(iP))
|
||||||
|
// Set Az velocity
|
||||||
|
#define SetVA 9
|
||||||
|
#define SetSpeedA(vA) send_cmd_d1 ( 9, (double)(vA))
|
||||||
|
// Set Z velocity
|
||||||
|
#define SetVZ 10
|
||||||
|
#define SetSpeedZ(vZ) send_cmd_d1 (10, (double)(vZ))
|
||||||
|
// Set P velocity
|
||||||
|
#define SetVP 11
|
||||||
|
#define SetSpeedP(vP) send_cmd_d1 (11, (double)(vP))
|
||||||
|
// Set new polar coordinates
|
||||||
|
#define SetAD 12
|
||||||
|
#define SetRADec(Alp,Del) send_cmd_d2 (12, (double)(Alp),(double)(Del))
|
||||||
|
// Set new azimutal coordinates
|
||||||
|
#define SetAZ 13
|
||||||
|
#define SetAzimZ(A,Z) send_cmd_d2 (13, (double)(A),(double)(Z))
|
||||||
|
// Goto new object by polar coords
|
||||||
|
#define GoToAD 14
|
||||||
|
#define GoToObject() send_cmd_noarg(14 )
|
||||||
|
// Start steering to object by polar coords
|
||||||
|
#define MoveToAD 15
|
||||||
|
#define MoveToObject() send_cmd_noarg(15 )
|
||||||
|
// Go to object by azimutal coords
|
||||||
|
#define GoToAZ 16
|
||||||
|
#define GoToAzimZ() send_cmd_noarg(16 )
|
||||||
|
// Set A&Z for simulation
|
||||||
|
#define WriteAZ 17
|
||||||
|
#define WriteModelAZ() send_cmd_noarg(17 )
|
||||||
|
// Set P2 mode
|
||||||
|
#define SetModP 18
|
||||||
|
#define SetPMode(pmod) send_cmd_i1 (18, (int)(pmod))
|
||||||
|
// Move(+-1)/Stop(0) P2
|
||||||
|
#define P2Move 19
|
||||||
|
#define MoveP2(dir) send_cmd_i1 (19, (int)(dir))
|
||||||
|
// Move(+-2,+-1)/Stop(0) focus
|
||||||
|
#define FocMove 20
|
||||||
|
#define MoveFocus(speed,time) send_cmd_i1d1(20,(int)(speed),(double)(time))
|
||||||
|
// Use/don't use pointing correction system
|
||||||
|
#define UsePCorr 21
|
||||||
|
#define SwitchPosCorr(pc_flag) send_cmd_i1 (21, (int)(pc_flag))
|
||||||
|
// Tracking flags
|
||||||
|
#define SetTrkFlags 22
|
||||||
|
#define SetTrkOkMode(trk_flags) send_cmd_i1 (22, (int)(trk_flags))
|
||||||
|
// Set focus (0 - primary, 1 - N1, 2 - N2)
|
||||||
|
#define SetTFoc 23
|
||||||
|
#define SetTelFocus(N) send_cmd_i1 ( 23, (int)(N))
|
||||||
|
// Set intrinsic move parameters by RA/Decl
|
||||||
|
#define SetVAD 24
|
||||||
|
#define SetVelAD(VAlp,VDel) send_cmd_d2 (24, (double)(VAlp),(double)(VDel))
|
||||||
|
// Reverse Azimuth direction when pointing
|
||||||
|
#define SetRevA 25
|
||||||
|
#define SetAzRevers(amod) send_cmd_i1 (25, (int)(amod))
|
||||||
|
// Set P2 velocity
|
||||||
|
#define SetVP2 26
|
||||||
|
#define SetVelP2(vP2) send_cmd_d1 (26, (double)(vP2))
|
||||||
|
// Set pointing target
|
||||||
|
#define SetTarg 27
|
||||||
|
#define SetSysTarg(Targ) send_cmd_i1 (27, (int)(Targ))
|
||||||
|
// Send message to all clients (+write into protocol)
|
||||||
|
#define SendMsg 28
|
||||||
|
#define SendMessage(Mesg) send_cmd_str (28, (char *)(Mesg))
|
||||||
|
// RA/Decl user correction
|
||||||
|
#define CorrAD 29
|
||||||
|
#define DoADcorr(dAlp,dDel) send_cmd_d2 (29, (double)(dAlp),(double)(dDel))
|
||||||
|
// A/Z user correction
|
||||||
|
#define CorrAZ 30
|
||||||
|
#define DoAZcorr(dA,dZ) send_cmd_d2 (30, (double)(dA),(double)(dZ))
|
||||||
|
// sec A/Z user correction speed
|
||||||
|
#define SetVCAZ 31
|
||||||
|
#define SetVCorr(vA,vZ) send_cmd_d2 (31, (double)(vA),(double)(vZ))
|
||||||
|
// move P2 with given velocity for a given time
|
||||||
|
#define P2MoveTo 32
|
||||||
|
#define MoveP2To(vP2,time) send_cmd_d2 (32, (double)(vP2),(double)(time))
|
||||||
|
// Go to t/Decl position
|
||||||
|
#define GoToTD 33
|
||||||
|
#define GoToSat() send_cmd_noarg (33 )
|
||||||
|
// Move to t/Decl
|
||||||
|
#define MoveToTD 34
|
||||||
|
#define MoveToSat() send_cmd_noarg (34 )
|
||||||
|
// Empty command for synchronisation
|
||||||
|
#define NullCom 35
|
||||||
|
#define SyncCom() send_cmd_noarg (35 )
|
||||||
|
// Button "Start"
|
||||||
|
#define StartTel 36
|
||||||
|
#define StartTeleskope() send_cmd_noarg(36 )
|
||||||
|
// Set telescope mode
|
||||||
|
#define SetTMod 37
|
||||||
|
#define SetTelMode(M) send_cmd_i1 ( 37, (int)(M))
|
||||||
|
// Turn telescope on (oil etc)
|
||||||
|
#define TelOn 38
|
||||||
|
#define TeleskopeOn() send_cmd_noarg(38 )
|
||||||
|
// Dome mode
|
||||||
|
#define SetModD 39
|
||||||
|
#define SetDomeMode(dmod) send_cmd_i1 (39, (int)(dmod))
|
||||||
|
// Move(+-3,+-2,+-1)/Stop(0) dome
|
||||||
|
#define DomeMove 40
|
||||||
|
#define MoveDome(speed,time) send_cmd_i1d1(40,(int)(speed),(double)(time))
|
||||||
|
// Set account password
|
||||||
|
#define SetPass 41
|
||||||
|
#define SetPasswd(LPass) send_cmd_str (41, (char *)(LPass))
|
||||||
|
// Set code of access level
|
||||||
|
#define SetLevC 42
|
||||||
|
#define SetLevCode(Nlev,Cod) send_cmd_i2(42, (int)(Nlev),(int)(Cod))
|
||||||
|
// Set key for access level
|
||||||
|
#define SetLevK 43
|
||||||
|
#define SetLevKey(Nlev,Key) send_cmd_i2(43, (int)(Nlev),(int)(Key))
|
||||||
|
// Setup network
|
||||||
|
#define SetNet 44
|
||||||
|
#define SetNetAcc(Mask,Addr) send_cmd_i2(44, (int)(Mask),(int)(Addr))
|
||||||
|
// Input meteo data
|
||||||
|
#define SetMet 45
|
||||||
|
#define SetMeteo(m_id,m_val) send_cmd_i1d1(45,(int)(m_id),(double)(m_val))
|
||||||
|
// Cancel meteo data
|
||||||
|
#define TurnMetOff 46
|
||||||
|
#define TurnMeteoOff(m_id) send_cmd_i1 (46, (int)(m_id))
|
||||||
|
// Set time correction (IERS DUT1=UT1-UTC)
|
||||||
|
#define SetDUT1 47
|
||||||
|
#define SetDtime(dT) send_cmd_d1 (47, (double)(dT))
|
||||||
|
// Set polar motion (IERS polar motion)
|
||||||
|
#define SetPM 48
|
||||||
|
#define SetPolMot(Xp,Yp) send_cmd_d2 (48, (double)(Xp),(double)(Yp))
|
||||||
|
// Get SEW parameter
|
||||||
|
#define GetSEW 49
|
||||||
|
#define GetSEWparam(Ndrv,Indx,Cnt) send_cmd_i3(49,(int)(Ndrv),(int)(Indx),(int)(Cnt))
|
||||||
|
// Set SEW parameter
|
||||||
|
#define PutSEW 50
|
||||||
|
#define PutSEWparam(Ndrv,Indx,Key,Val) send_cmd_i4(50,(int)(Ndrv),(int)(Indx),(int)(Key),(int)(Val))
|
||||||
|
// Set lock flags
|
||||||
|
#define SetLocks 51
|
||||||
|
#define SetLockFlags(f) send_cmd_i1 (SetLocks, (int)(f))
|
||||||
|
// Clear lock flags
|
||||||
|
#define ClearLocks 52
|
||||||
|
#define ClearLockFlags(f) send_cmd_i1 (ClearLocks, (int)(f))
|
||||||
|
// Set PEP-RK bits
|
||||||
|
#define SetRKbits 53
|
||||||
|
#define AddRKbits(f) send_cmd_i1 (SetRKbits, (int)(f))
|
||||||
|
// Clear PEP-RK bits
|
||||||
|
#define ClrRKbits 54
|
||||||
|
#define ClearRKbits(f) send_cmd_i1 (ClrRKbits, (int)(f))
|
||||||
|
// Set SEW dome motor number (for indication)
|
||||||
|
#define SetSEWnd 55
|
||||||
|
#define SetDomeDrive(ND) send_cmd_i1 (SetSEWnd, (int)(ND))
|
||||||
|
// Turn SEW controllers of dome on/off
|
||||||
|
#define SEWsDome 56
|
||||||
|
#define DomeSEW(OnOff) send_cmd_i1 (SEWsDome, (int)(OnOff))
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* BTA data structure definitions *
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#define ServPID (sdt->pid) // PID of main program
|
||||||
|
// model
|
||||||
|
#define UseModel (sdt->model) // model variants
|
||||||
|
enum{
|
||||||
|
NoModel = 0 // OFF
|
||||||
|
,CheckModel // control motors by model
|
||||||
|
,DriveModel // "blind" management without real sensors
|
||||||
|
,FullModel // full model without telescope
|
||||||
|
};
|
||||||
|
// timer
|
||||||
|
#define ClockType (sdt->timer) // which timer to use
|
||||||
|
enum{
|
||||||
|
Ch7_15 = 0 // Inner timer with synchronisation by CH7_15
|
||||||
|
,SysTimer // System timer (synchronisation unknown)
|
||||||
|
,ExtSynchro // External synchronisation (bta_time or xntpd)
|
||||||
|
};
|
||||||
|
// system
|
||||||
|
#define Sys_Mode (sdt->system) // main system mode
|
||||||
|
enum{
|
||||||
|
SysStop = 0 // Stop
|
||||||
|
,SysWait // Wait for start (pointing)
|
||||||
|
,SysPointAZ // Pointing by A/Z
|
||||||
|
,SysPointAD // Pointing by RA/Decl
|
||||||
|
,SysTrkStop // Tracking stop
|
||||||
|
,SysTrkStart // Start tracking (acceleration to nominal velocity)
|
||||||
|
,SysTrkMove // Tracking move to object
|
||||||
|
,SysTrkSeek // Tracking in seeking mode
|
||||||
|
,SysTrkOk // Tracking OK
|
||||||
|
,SysTrkCorr // Correction of tracking position
|
||||||
|
,SysTest // Test
|
||||||
|
};
|
||||||
|
// sys_target
|
||||||
|
#define Sys_Target (sdt->sys_target) // system pointing target
|
||||||
|
enum{
|
||||||
|
TagPosition = 0 // point by A/Z
|
||||||
|
,TagObject // point by RA/Decl
|
||||||
|
,TagNest // point to "nest"
|
||||||
|
,TagZenith // point to zenith
|
||||||
|
,TagHorizon // point to horizon
|
||||||
|
,TagStatObj // point to statinary object (t/Decl)
|
||||||
|
};
|
||||||
|
// tel_focus
|
||||||
|
#define Tel_Focus (sdt->tel_focus) // telescope focus type
|
||||||
|
enum{
|
||||||
|
Prime = 0
|
||||||
|
,Nasmyth1
|
||||||
|
,Nasmyth2
|
||||||
|
};
|
||||||
|
// PCS
|
||||||
|
#define PosCor_Coeff (sdt->pc_coeff) // pointing correction system coefficients
|
||||||
|
// tel_state
|
||||||
|
#define Tel_State (sdt->tel_state) // telescope state
|
||||||
|
#define Req_State (sdt->req_state) // required state
|
||||||
|
enum{
|
||||||
|
Stopping = 0
|
||||||
|
,Pointing
|
||||||
|
,Tracking
|
||||||
|
};
|
||||||
|
// tel_hard_state
|
||||||
|
#define Tel_Hardware (sdt->tel_hard_state) // Power state
|
||||||
|
enum{
|
||||||
|
Hard_Off = 0
|
||||||
|
,Hard_On
|
||||||
|
};
|
||||||
|
// tel_mode
|
||||||
|
#define Tel_Mode (sdt->tel_mode) // telescope mode
|
||||||
|
enum{
|
||||||
|
Automatic = 0 // Automatic (normal) mode
|
||||||
|
,Manual = 1 // manual mode
|
||||||
|
,ZenHor = 2 // work when Z<5 || Z>80
|
||||||
|
,A_Move = 4 // hand move by A
|
||||||
|
,Z_Move = 8 // hand move by Z
|
||||||
|
,Balance =0x10// balancing
|
||||||
|
};
|
||||||
|
// az_mode
|
||||||
|
#define Az_Mode (sdt->az_mode) // azimuth reverce
|
||||||
|
enum{
|
||||||
|
Rev_Off = 0 // move by nearest way
|
||||||
|
,Rev_On // move by longest way
|
||||||
|
};
|
||||||
|
// p2_state
|
||||||
|
#define P2_State (sdt->p2_state) // P2 motor state
|
||||||
|
#define P2_Mode (sdt->p2_req_mode)
|
||||||
|
enum{
|
||||||
|
P2_Off = 0 // Stop
|
||||||
|
,P2_On // Guiding
|
||||||
|
,P2_Plus // Move to +
|
||||||
|
,P2_Minus = -2 // Move to -
|
||||||
|
};
|
||||||
|
// focus_state
|
||||||
|
#define Foc_State (sdt->focus_state) // focus motor state
|
||||||
|
enum{
|
||||||
|
Foc_Hminus = -2// fast "-" move
|
||||||
|
,Foc_Lminus // slow "-" move
|
||||||
|
,Foc_Off // Off
|
||||||
|
,Foc_Lplus // slow "+" move
|
||||||
|
,Foc_Hplus // fast "+" move
|
||||||
|
};
|
||||||
|
// dome_state
|
||||||
|
#define Dome_State (sdt->dome_state) // dome motors state
|
||||||
|
enum{
|
||||||
|
D_Hminus = -3 // speeds: low, medium, high
|
||||||
|
,D_Mminus
|
||||||
|
,D_Lminus
|
||||||
|
,D_Off // off
|
||||||
|
,D_Lplus
|
||||||
|
,D_Mplus
|
||||||
|
,D_Hplus
|
||||||
|
,D_On = 7 // auto
|
||||||
|
};
|
||||||
|
// pcor_mode
|
||||||
|
#define Pos_Corr (sdt->pcor_mode) // pointing correction mode
|
||||||
|
enum{
|
||||||
|
PC_Off = 0
|
||||||
|
,PC_On
|
||||||
|
};
|
||||||
|
// trkok_mode
|
||||||
|
#define TrkOk_Mode (sdt->trkok_mode) // tracking mode
|
||||||
|
enum{
|
||||||
|
UseDiffVel = 1 // Isodrome (correction by real motors speed)
|
||||||
|
,UseDiffAZ = 2 // Tracking by coordinate difference
|
||||||
|
,UseDFlt = 4 // Turn on digital filter
|
||||||
|
};
|
||||||
|
// input RA/Decl values
|
||||||
|
#define InpAlpha (sdt->i_alpha)
|
||||||
|
#define InpDelta (sdt->i_delta)
|
||||||
|
// current source RA/Decl values
|
||||||
|
#define SrcAlpha (sdt->s_alpha)
|
||||||
|
#define SrcDelta (sdt->s_delta)
|
||||||
|
// intrinsic object velocity
|
||||||
|
#define VelAlpha (sdt->v_alpha)
|
||||||
|
#define VelDelta (sdt->v_delta)
|
||||||
|
// input A/Z values
|
||||||
|
#define InpAzim (sdt->i_azim)
|
||||||
|
#define InpZdist (sdt->i_zdist)
|
||||||
|
// calculated values
|
||||||
|
#define CurAlpha (sdt->c_alpha)
|
||||||
|
#define CurDelta (sdt->c_delta)
|
||||||
|
// current values (from sensors)
|
||||||
|
#define tag_A (sdt->tag_a)
|
||||||
|
#define tag_Z (sdt->tag_z)
|
||||||
|
#define tag_P (sdt->tag_p)
|
||||||
|
// calculated corrections
|
||||||
|
#define pos_cor_A (sdt->pcor_a)
|
||||||
|
#define pos_cor_Z (sdt->pcor_z)
|
||||||
|
#define refract_Z (sdt->refr_z)
|
||||||
|
// reverse calculation corr.
|
||||||
|
#define tel_cor_A (sdt->tcor_a)
|
||||||
|
#define tel_cor_Z (sdt->tcor_z)
|
||||||
|
#define tel_ref_Z (sdt->tref_z)
|
||||||
|
// coords difference
|
||||||
|
#define Diff_A (sdt->diff_a)
|
||||||
|
#define Diff_Z (sdt->diff_z)
|
||||||
|
#define Diff_P (sdt->diff_p)
|
||||||
|
// base object velocity
|
||||||
|
#define vel_objA (sdt->vbasea)
|
||||||
|
#define vel_objZ (sdt->vbasez)
|
||||||
|
#define vel_objP (sdt->vbasep)
|
||||||
|
// correction by real speed
|
||||||
|
#define diff_vA (sdt->diffva)
|
||||||
|
#define diff_vZ (sdt->diffvz)
|
||||||
|
#define diff_vP (sdt->diffvp)
|
||||||
|
// motor speed
|
||||||
|
#define speedA (sdt->speeda)
|
||||||
|
#define speedZ (sdt->speedz)
|
||||||
|
#define speedP (sdt->speedp)
|
||||||
|
// last precipitation time
|
||||||
|
#define Precip_time (sdt->m_time_precip)
|
||||||
|
// reserved
|
||||||
|
#define Reserve (sdt->reserve)
|
||||||
|
// real motor speed (''/sec)
|
||||||
|
#define req_speedA (sdt->rspeeda)
|
||||||
|
#define req_speedZ (sdt->rspeedz)
|
||||||
|
#define req_speedP (sdt->rspeedp)
|
||||||
|
// model speed
|
||||||
|
#define mod_vel_A (sdt->simvela)
|
||||||
|
#define mod_vel_Z (sdt->simvelz)
|
||||||
|
#define mod_vel_P (sdt->simvelp)
|
||||||
|
#define mod_vel_F (sdt->simvelf)
|
||||||
|
#define mod_vel_D (sdt->simvelf)
|
||||||
|
// telescope & hand correction state
|
||||||
|
/*
|
||||||
|
* 0x8000 - ÁÚÉÍÕÔ ÐÏÌÏÖÉÔÅÌØÎÙÊ
|
||||||
|
* 0x4000 - ÏÔÒÁÂÏÔËÁ ×ËÌ.
|
||||||
|
* 0x2000 - ÒÅÖÉÍ ×ÅÄÅÎÉÑ
|
||||||
|
* 0x1000 - ÏÔÒÁÂÏÔËÁ P2 ×ËÌ.
|
||||||
|
* 0x01F0 - ÓË.ËÏÒÒ. 0.2 0.4 1.0 2.0 5.0("/ÓÅË)
|
||||||
|
* 0x000F - ÎÁÐÒ.ËÏÒÒ. +Z -Z +A -A
|
||||||
|
*/
|
||||||
|
#define code_KOST (sdt->kost)
|
||||||
|
// different time (UTC, stellar, local)
|
||||||
|
#define M_time (sdt->m_time)
|
||||||
|
#define S_time (sdt->s_time)
|
||||||
|
#define L_time (sdt->l_time)
|
||||||
|
// PPNDD sensor (rough) code
|
||||||
|
#define ppndd_A (sdt->ppndd_a)
|
||||||
|
#define ppndd_Z (sdt->ppndd_z)
|
||||||
|
#define ppndd_P (sdt->ppndd_p)
|
||||||
|
#define ppndd_B (sdt->ppndd_b) // atm. pressure
|
||||||
|
// DUP sensor (precise) code (Gray code)
|
||||||
|
#define dup_A (sdt->dup_a)
|
||||||
|
#define dup_Z (sdt->dup_z)
|
||||||
|
#define dup_P (sdt->dup_p)
|
||||||
|
#define dup_F (sdt->dup_f)
|
||||||
|
#define dup_D (sdt->dup_d)
|
||||||
|
// binary 14-digit precise code
|
||||||
|
#define low_A (sdt->low_a)
|
||||||
|
#define low_Z (sdt->low_z)
|
||||||
|
#define low_P (sdt->low_p)
|
||||||
|
#define low_F (sdt->low_f)
|
||||||
|
#define low_D (sdt->low_d)
|
||||||
|
// binary 23-digit rough code
|
||||||
|
#define code_A (sdt->code_a)
|
||||||
|
#define code_Z (sdt->code_z)
|
||||||
|
#define code_P (sdt->code_p)
|
||||||
|
#define code_B (sdt->code_b)
|
||||||
|
#define code_F (sdt->code_f)
|
||||||
|
#define code_D (sdt->code_d)
|
||||||
|
// ADC PCL818 (8-channel) codes
|
||||||
|
#define ADC(N) (sdt->adc[(N)])
|
||||||
|
#define code_T1 ADC(0) // External temperature code
|
||||||
|
#define code_T2 ADC(1) // In-dome temperature code
|
||||||
|
#define code_T3 ADC(2) // Mirror temperature code
|
||||||
|
#define code_Wnd ADC(3) // Wind speed code
|
||||||
|
// calculated values
|
||||||
|
#define val_A (sdt->val_a) // A, ''
|
||||||
|
#define val_Z (sdt->val_z) // Z, ''
|
||||||
|
#define val_P (sdt->val_p) // P, ''
|
||||||
|
#define val_B (sdt->val_b) // atm. pressure, mm.hg.
|
||||||
|
#define val_F (sdt->val_f) // focus, mm
|
||||||
|
#define val_D (sdt->val_d) // Dome Az, ''
|
||||||
|
#define val_T1 (sdt->val_t1) // ext. T, degrC
|
||||||
|
#define val_T2 (sdt->val_t2) // in-dome T, degrC
|
||||||
|
#define val_T3 (sdt->val_t3) // mirror T, degrC
|
||||||
|
#define val_Wnd (sdt->val_wnd) // wind speed, m/s
|
||||||
|
// RA/Decl calculated by A/Z
|
||||||
|
#define val_Alp (sdt->val_alp)
|
||||||
|
#define val_Del (sdt->val_del)
|
||||||
|
// measured speed
|
||||||
|
#define vel_A (sdt->vel_a)
|
||||||
|
#define vel_Z (sdt->vel_z)
|
||||||
|
#define vel_P (sdt->vel_p)
|
||||||
|
#define vel_F (sdt->vel_f)
|
||||||
|
#define vel_D (sdt->vel_d)
|
||||||
|
// system messages queue
|
||||||
|
#define MesgNum 3
|
||||||
|
#define MesgLen 39
|
||||||
|
// message type
|
||||||
|
enum{
|
||||||
|
MesgEmpty = 0
|
||||||
|
,MesgInfor
|
||||||
|
,MesgWarn
|
||||||
|
,MesgFault
|
||||||
|
,MesgLog
|
||||||
|
};
|
||||||
|
#define Sys_Mesg(N) (sdt->sys_msg_buf[N])
|
||||||
|
// access levels
|
||||||
|
#define code_Lev1 (sdt->code_lev[0]) // remote observer - only information
|
||||||
|
#define code_Lev2 (sdt->code_lev[1]) // local observer - input coordinates
|
||||||
|
#define code_Lev3 (sdt->code_lev[2]) // main observer - correction by A/Z, P2/F management
|
||||||
|
#define code_Lev4 (sdt->code_lev[3]) // operator - start/stop telescope, testing
|
||||||
|
#define code_Lev5 (sdt->code_lev[4]) // main operator - full access
|
||||||
|
#define code_Lev(x) (sdt->code_lev[(x-1)])
|
||||||
|
// network settings
|
||||||
|
#define NetMask (sdt->netmask) // subnet mask (usually 255.255.255.0)
|
||||||
|
#define NetWork (sdt->netaddr) // subnet address (for ex.: 192.168.3.0)
|
||||||
|
#define ACSMask (sdt->acsmask) // ACS network mask (for ex.: 255.255.255.0)
|
||||||
|
#define ACSNet (sdt->acsaddr) // ACS subnet address (for ex.: 192.168.13.0)
|
||||||
|
// meteo data
|
||||||
|
#define MeteoMode (sdt->meteo_stat)
|
||||||
|
enum{
|
||||||
|
INPUT_B = 1 // pressure
|
||||||
|
,INPUT_T1 = 2 // external T
|
||||||
|
,INPUT_T2 = 4 // in-dome T
|
||||||
|
,INPUT_T3 = 8 // mirror T
|
||||||
|
,INPUT_WND = 0x10 // wind speed
|
||||||
|
,INPUT_HMD = 0x20 // humidity
|
||||||
|
};
|
||||||
|
#define SENSOR_B (INPUT_B <<8) // external data flags
|
||||||
|
#define SENSOR_T1 (INPUT_T1 <<8)
|
||||||
|
#define SENSOR_T2 (INPUT_T2 <<8)
|
||||||
|
#define SENSOR_T3 (INPUT_T3 <<8)
|
||||||
|
#define SENSOR_WND (INPUT_WND<<8)
|
||||||
|
#define SENSOR_HMD (INPUT_HMD<<8)
|
||||||
|
#define ADC_B (INPUT_B <<16) // reading from ADC flags
|
||||||
|
#define ADC_T1 (INPUT_T1 <<16)
|
||||||
|
#define ADC_T2 (INPUT_T2 <<16)
|
||||||
|
#define ADC_T3 (INPUT_T3 <<16)
|
||||||
|
#define ADC_WND (INPUT_WND<<16)
|
||||||
|
#define ADC_HMD (INPUT_HMD<<16)
|
||||||
|
#define NET_B (INPUT_B <<24) // got by network flags
|
||||||
|
#define NET_T1 (INPUT_T1 <<24)
|
||||||
|
#define NET_T3 (INPUT_T3 <<24)
|
||||||
|
#define NET_WND (INPUT_WND<<24)
|
||||||
|
#define NET_HMD (INPUT_HMD<<24)
|
||||||
|
// input meteo values
|
||||||
|
#define inp_B (sdt->inp_b) // atm.pressure (mm.hg)
|
||||||
|
#define inp_T1 (sdt->inp_t1) // ext T
|
||||||
|
#define inp_T2 (sdt->inp_t2) // in-dome T
|
||||||
|
#define inp_T3 (sdt->inp_t3) // mirror T
|
||||||
|
#define inp_Wnd (sdt->inp_wnd) // wind
|
||||||
|
// values used for refraction calculation
|
||||||
|
#define Temper (sdt->temper)
|
||||||
|
#define Pressure (sdt->press)
|
||||||
|
// last wind gust time
|
||||||
|
#define Wnd10_time (sdt->m_time10)
|
||||||
|
#define Wnd15_time (sdt->m_time15)
|
||||||
|
// IERS DUT1
|
||||||
|
#define DUT1 (sdt->dut1)
|
||||||
|
// sensors reading time
|
||||||
|
#define A_time (sdt->a_time)
|
||||||
|
#define Z_time (sdt->z_time)
|
||||||
|
#define P_time (sdt->p_time)
|
||||||
|
// input speeds
|
||||||
|
#define speedAin (sdt->speedain)
|
||||||
|
#define speedZin (sdt->speedzin)
|
||||||
|
#define speedPin (sdt->speedpin)
|
||||||
|
// acceleration (''/sec^2)
|
||||||
|
#define acc_A (sdt->acc_a)
|
||||||
|
#define acc_Z (sdt->acc_z)
|
||||||
|
#define acc_P (sdt->acc_p)
|
||||||
|
#define acc_F (sdt->acc_f)
|
||||||
|
#define acc_D (sdt->acc_d)
|
||||||
|
// SEW code
|
||||||
|
#define code_SEW (sdt->code_sew)
|
||||||
|
// sew data
|
||||||
|
#define statusSEW(Drv) (sdt->sewdrv[(Drv)-1].status)
|
||||||
|
#define statusSEW1 (sdt->sewdrv[0].status)
|
||||||
|
#define statusSEW2 (sdt->sewdrv[1].status)
|
||||||
|
#define statusSEW3 (sdt->sewdrv[2].status)
|
||||||
|
#define speedSEW(Drv) (sdt->sewdrv[(Drv)-1].set_speed)
|
||||||
|
#define speedSEW1 (sdt->sewdrv[0].set_speed)
|
||||||
|
#define speedSEW2 (sdt->sewdrv[1].set_speed)
|
||||||
|
#define speedSEW3 (sdt->sewdrv[2].set_speed)
|
||||||
|
#define vel_SEW(Drv) (sdt->sewdrv[(Drv)-1].mes_speed)
|
||||||
|
#define vel_SEW1 (sdt->sewdrv[0].mes_speed)
|
||||||
|
#define vel_SEW2 (sdt->sewdrv[1].mes_speed)
|
||||||
|
#define vel_SEW3 (sdt->sewdrv[2].mes_speed)
|
||||||
|
#define currentSEW(Drv) (sdt->sewdrv[(Drv)-1].current)
|
||||||
|
#define currentSEW1 (sdt->sewdrv[0].current)
|
||||||
|
#define currentSEW2 (sdt->sewdrv[1].current)
|
||||||
|
#define currentSEW3 (sdt->sewdrv[2].current)
|
||||||
|
#define indexSEW(Drv) (sdt->sewdrv[(Drv)-1].index)
|
||||||
|
#define indexSEW1 (sdt->sewdrv[0].index)
|
||||||
|
#define indexSEW2 (sdt->sewdrv[1].index)
|
||||||
|
#define indexSEW3 (sdt->sewdrv[2].index)
|
||||||
|
#define valueSEW(Drv) (sdt->sewdrv[(Drv)-1].value.l)
|
||||||
|
#define valueSEW1 (sdt->sewdrv[0].value.l)
|
||||||
|
#define valueSEW2 (sdt->sewdrv[1].value.l)
|
||||||
|
#define valueSEW3 (sdt->sewdrv[2].value.l)
|
||||||
|
#define bvalSEW(Drv,Nb) (sdt->sewdrv[(Drv)-1].value.b[Nb])
|
||||||
|
// 23-digit PEP-controllers code
|
||||||
|
#define PEP_code_A (sdt->pep_code_a)
|
||||||
|
#define PEP_code_Z (sdt->pep_code_z)
|
||||||
|
#define PEP_code_P (sdt->pep_code_p)
|
||||||
|
// PEP end-switches code
|
||||||
|
#define switch_A (sdt->pep_sw_a)
|
||||||
|
enum{
|
||||||
|
Sw_minus_A = 1 // negative A value
|
||||||
|
,Sw_plus240_A = 2 // end switch +240degr
|
||||||
|
,Sw_minus240_A = 4 // end switch -240degr
|
||||||
|
,Sw_minus45_A = 8 // "horizon" end switch
|
||||||
|
};
|
||||||
|
#define switch_Z (sdt->pep_sw_z)
|
||||||
|
enum{
|
||||||
|
Sw_0_Z = 1
|
||||||
|
,Sw_5_Z = 2
|
||||||
|
,Sw_20_Z = 4
|
||||||
|
,Sw_60_Z = 8
|
||||||
|
,Sw_80_Z = 0x10
|
||||||
|
,Sw_90_Z = 0x20
|
||||||
|
};
|
||||||
|
#define switch_P (sdt->pep_sw_p)
|
||||||
|
enum{
|
||||||
|
Sw_No_P = 0 // no switches
|
||||||
|
,Sw_22_P = 1 // 22degr
|
||||||
|
,Sw_89_P = 2 // 89degr
|
||||||
|
,Sw_Sm_P = 0x80 // Primary focus smoke sensor
|
||||||
|
};
|
||||||
|
// PEP codes
|
||||||
|
#define PEP_code_F (sdt->pep_code_f)
|
||||||
|
#define PEP_code_D (sdt->pep_code_d)
|
||||||
|
#define PEP_code_Rin (sdt->pep_code_ri)
|
||||||
|
#define PEP_code_Rout (sdt->pep_code_ro)
|
||||||
|
// PEP flags
|
||||||
|
#define PEP_A_On (sdt->pep_on[0])
|
||||||
|
#define PEP_A_Off (PEP_A_On==0)
|
||||||
|
#define PEP_Z_On (sdt->pep_on[1])
|
||||||
|
#define PEP_Z_Off (PEP_Z_On==0)
|
||||||
|
#define PEP_P_On (sdt->pep_on[2])
|
||||||
|
#define PEP_P_Off (PEP_P_On==0)
|
||||||
|
#define PEP_F_On (sdt->pep_on[3])
|
||||||
|
#define PEP_F_Off (PEP_F_On==0)
|
||||||
|
#define PEP_D_On (sdt->pep_on[4])
|
||||||
|
#define PEP_D_Off (PEP_D_On==0)
|
||||||
|
#define PEP_R_On (sdt->pep_on[5])
|
||||||
|
#define PEP_R_Off ((PEP_R_On&1)==0)
|
||||||
|
#define PEP_R_Inp ((PEP_R_On&2)!=0)
|
||||||
|
#define PEP_K_On (sdt->pep_on[6])
|
||||||
|
#define PEP_K_Off ((PEP_K_On&1)==0)
|
||||||
|
#define PEP_K_Inp ((PEP_K_On&2)!=0)
|
||||||
|
// IERS polar motion
|
||||||
|
#define polarX (sdt->xpol)
|
||||||
|
#define polarY (sdt->ypol)
|
||||||
|
// current Julian date, sidereal time correction by "Equation of the Equinoxes"
|
||||||
|
#define JDate (sdt->jdate)
|
||||||
|
#define EE_time (sdt->eetime)
|
||||||
|
// humidity value (%%) & hand input
|
||||||
|
#define val_Hmd (sdt->val_hmd)
|
||||||
|
#define inp_Hmd (sdt->val_hmd)
|
||||||
|
// worm position, mkm
|
||||||
|
#define worm_A (sdt->worm_a)
|
||||||
|
#define worm_Z (sdt->worm_z)
|
||||||
|
// locking flags
|
||||||
|
#define LockFlags (sdt->lock_flags)
|
||||||
|
enum{
|
||||||
|
Lock_A = 1
|
||||||
|
,Lock_Z = 2
|
||||||
|
,Lock_P = 4
|
||||||
|
,Lock_F = 8
|
||||||
|
,Lock_D = 0x10
|
||||||
|
};
|
||||||
|
#define A_Locked (LockFlags&Lock_A)
|
||||||
|
#define Z_Locked (LockFlags&Lock_Z)
|
||||||
|
#define P_Locked (LockFlags&Lock_P)
|
||||||
|
#define F_Locked (LockFlags&Lock_F)
|
||||||
|
#define D_Locked (LockFlags&Lock_D)
|
||||||
|
// SEW dome divers speed
|
||||||
|
#define Dome_Speed (sdt->sew_dome_speed)
|
||||||
|
// SEW dome drive number (for indication)
|
||||||
|
#define DomeSEW_N (sdt->sew_dome_num)
|
||||||
|
// SEW dome driver parameters
|
||||||
|
#define statusSEWD (sdt->sewdomedrv.status) // controller status
|
||||||
|
#define speedSEWD (sdt->sewdomedrv.set_speed) // speed, rpm
|
||||||
|
#define vel_SEWD (sdt->sewdomedrv.mes_speed) /*ÉÚÍÅÒÅÎÎÁÑ ÓËÏÒÏÓÔØ ÏÂ/ÍÉÎ (rpm)*/
|
||||||
|
#define currentSEWD (sdt->sewdomedrv.current) // current, A
|
||||||
|
#define indexSEWD (sdt->sewdomedrv.index) // parameter index
|
||||||
|
#define valueSEWD (sdt->sewdomedrv.value.l) // parameter value
|
||||||
|
// dome PEP codes
|
||||||
|
#define PEP_code_Din (sdt->pep_code_di) // data in
|
||||||
|
#define PEP_Dome_SEW_Ok 0x200
|
||||||
|
#define PEP_Dome_Cable_Ok 0x100
|
||||||
|
#define PEP_code_Dout (sdt->pep_code_do) // data out
|
||||||
|
#define PEP_Dome_SEW_On 0x10
|
||||||
|
#define PEP_Dome_SEW_Off 0x20
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* BTA data structure *
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#define BTA_Data_Ver 2
|
||||||
|
struct BTA_Data {
|
||||||
|
int32_t magic; // magic value
|
||||||
|
int32_t version; // BTA_Data_Ver
|
||||||
|
int32_t size; // sizeof(struct BTA_Data)
|
||||||
|
int32_t pid; // main process PID
|
||||||
|
int32_t model; // model modes
|
||||||
|
int32_t timer; // timer selected
|
||||||
|
int32_t system; // main system mode
|
||||||
|
int32_t sys_target; // system pointing target
|
||||||
|
int32_t tel_focus; // telescope focus type
|
||||||
|
double pc_coeff[8]; // pointing correction system coefficients
|
||||||
|
int32_t tel_state; // telescope state
|
||||||
|
int32_t req_state; // new (required) state
|
||||||
|
int32_t tel_hard_state; // Power state
|
||||||
|
int32_t tel_mode; // telescope mode
|
||||||
|
int32_t az_mode; // azimuth reverce
|
||||||
|
int32_t p2_state; // P2 motor state
|
||||||
|
int32_t p2_req_mode; // P2 required state
|
||||||
|
int32_t focus_state; // focus motor state
|
||||||
|
int32_t dome_state; // dome motors state
|
||||||
|
int32_t pcor_mode; // pointing correction mode
|
||||||
|
int32_t trkok_mode; // tracking mode
|
||||||
|
double i_alpha, i_delta; // input values
|
||||||
|
double s_alpha, s_delta; // source
|
||||||
|
double v_alpha, v_delta; // intrinsic vel.
|
||||||
|
double i_azim, i_zdist; // input A/Z
|
||||||
|
double c_alpha, c_delta; // calculated values
|
||||||
|
double tag_a, tag_z, tag_p; // current values (from sensors)
|
||||||
|
double pcor_a, pcor_z, refr_z; // calculated corrections
|
||||||
|
double tcor_a, tcor_z, tref_z; // reverse calculation corr.
|
||||||
|
double diff_a, diff_z, diff_p; // coords difference
|
||||||
|
double vbasea,vbasez,vbasep; // base object velocity
|
||||||
|
double diffva,diffvz,diffvp; // correction by real speed
|
||||||
|
double speeda,speedz,speedp; // motor speed
|
||||||
|
double m_time_precip; // last precipitation time
|
||||||
|
uint8_t reserve[16]; // reserved
|
||||||
|
double rspeeda, rspeedz, rspeedp; // real motor speed (''/sec)
|
||||||
|
double simvela, simvelz, simvelp, simvelf, simveld; // model speed
|
||||||
|
uint32_t kost; // telescope & hand correction state
|
||||||
|
double m_time, s_time, l_time; // different time (UTC, stellar, local)
|
||||||
|
uint32_t ppndd_a, ppndd_z, ppndd_p, ppndd_b; // PPNDD sensor (rough) code
|
||||||
|
uint32_t dup_a, dup_z, dup_p, dup_f, dup_d; // DUP sensor (precise) code (Gray code)
|
||||||
|
uint32_t low_a, low_z, low_p, low_f, low_d; // binary 14-digit precise code
|
||||||
|
uint32_t code_a, code_z, code_p, code_b, code_f, code_d; // binary 23-digit rough code
|
||||||
|
uint32_t adc[8]; // ADC PCL818 (8-channel) codes
|
||||||
|
double val_a, val_z, val_p, val_b, val_f, val_d;
|
||||||
|
double val_t1, val_t2, val_t3, val_wnd; // calculated values
|
||||||
|
double val_alp, val_del; // RA/Decl calculated by A/Z
|
||||||
|
double vel_a, vel_z, vel_p, vel_f, vel_d; // measured speed
|
||||||
|
// system messages queue
|
||||||
|
struct SysMesg {
|
||||||
|
int32_t seq_num;
|
||||||
|
char type; // message type
|
||||||
|
char text[MesgLen]; // message itself
|
||||||
|
} sys_msg_buf[MesgNum];
|
||||||
|
// access levels
|
||||||
|
uint32_t code_lev[5];
|
||||||
|
// network settings
|
||||||
|
uint32_t netmask, netaddr, acsmask, acsaddr;
|
||||||
|
int32_t meteo_stat; // meteo data
|
||||||
|
double inp_b, inp_t1, inp_t2, inp_t3, inp_wnd; // input meteo values
|
||||||
|
double temper, press; // values used for refraction calculation
|
||||||
|
double m_time10, m_time15; // last wind gust time
|
||||||
|
double dut1; // IERS DUT1 (src: ftp://maia.usno.navy.mil/ser7/ser7.dat), DUT1 = UT1-UTC
|
||||||
|
double a_time, z_time, p_time; // sensors reading time
|
||||||
|
double speedain, speedzin, speedpin; // input speeds
|
||||||
|
double acc_a, acc_z, acc_p, acc_f, acc_d; // acceleration (''/sec^2)
|
||||||
|
uint32_t code_sew; // SEW code
|
||||||
|
struct SEWdata { // sew data
|
||||||
|
int32_t status;
|
||||||
|
double set_speed; // target speed, rpm
|
||||||
|
double mes_speed; // measured speed, rpm
|
||||||
|
double current; // measured current, A
|
||||||
|
int32_t index; // parameter number
|
||||||
|
union{ // parameter code
|
||||||
|
uint8_t b[4];
|
||||||
|
uint32_t l;
|
||||||
|
} value;
|
||||||
|
} sewdrv[3];
|
||||||
|
uint32_t pep_code_a, pep_code_z, pep_code_p; // 23-digit PEP-controllers code
|
||||||
|
uint32_t pep_sw_a, pep_sw_z, pep_sw_p; // PEP end-switches code
|
||||||
|
uint32_t pep_code_f, pep_code_d, pep_code_ri, pep_code_ro; // PEP codes
|
||||||
|
uint8_t pep_on[10]; // PEP flags
|
||||||
|
double xpol, ypol; // IERS polar motion (src: ftp://maia.usno.navy.mil/ser7/ser7.dat)
|
||||||
|
double jdate, eetime; // current Julian date, sidereal time correction by "Equation of the Equinoxes"
|
||||||
|
double val_hmd, inp_hmd; // humidity value (%%) & hand input
|
||||||
|
double worm_a, worm_z; // worm position, mkm
|
||||||
|
/* ÆÌÁÇÉ ÂÌÏËÉÒÏ×ËÉ ÕÐÒÁ×ÌÅÎÉÑ ÕÚÌÁÍÉ */
|
||||||
|
uint32_t lock_flags; // locking flags
|
||||||
|
int32_t sew_dome_speed; // SEW dome divers speed: D_Lplus, D_Hminus etc
|
||||||
|
int32_t sew_dome_num; // SEW dome drive number (for indication)
|
||||||
|
struct SEWdata sewdomedrv; // SEW dome driver parameters
|
||||||
|
uint32_t pep_code_di, pep_code_do; // dome PEP codes
|
||||||
|
};
|
||||||
|
|
||||||
|
extern volatile struct BTA_Data *sdt;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Local data structure *
|
||||||
|
*******************************************************************************/
|
||||||
|
// Oil pressure, MPa
|
||||||
|
#define PressOilA (sdtl->pr_oil_a)
|
||||||
|
#define PressOilZ (sdtl->pr_oil_z)
|
||||||
|
#define PressOilTank (sdtl->pr_oil_t)
|
||||||
|
// Oil themperature, degrC
|
||||||
|
#define OilTemper1 (sdtl->t_oil_1) // oil
|
||||||
|
#define OilTemper2 (sdtl->t_oil_2) // water
|
||||||
|
|
||||||
|
// Local data structure
|
||||||
|
struct BTA_Local {
|
||||||
|
uint8_t reserve[120]; // reserved data
|
||||||
|
double pr_oil_a,pr_oil_z,pr_oil_t; // Oil pressure
|
||||||
|
double t_oil_1,t_oil_2; // Oil themperature
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message buffer structure
|
||||||
|
*/
|
||||||
|
struct my_msgbuf {
|
||||||
|
int32_t mtype; // message type
|
||||||
|
uint32_t acckey; // client access key
|
||||||
|
uint32_t src_pid; // source PID
|
||||||
|
uint32_t src_ip; // IP of command source or 0 for local
|
||||||
|
char mtext[100]; // message itself
|
||||||
|
};
|
||||||
|
|
||||||
|
extern volatile struct BTA_Local *sdtl;
|
||||||
|
extern int snd_id;
|
||||||
|
extern int cmd_src_pid;
|
||||||
|
extern uint32_t cmd_src_ip;
|
||||||
|
|
||||||
|
#define ClientSide 0
|
||||||
|
#define ServerSide 1
|
||||||
|
|
||||||
|
#ifndef BTA_MODULE
|
||||||
|
void bta_data_init();
|
||||||
|
int bta_data_check();
|
||||||
|
void bta_data_close();
|
||||||
|
int get_shm_block(volatile struct SHM_Block *sb, int server);
|
||||||
|
int close_shm_block(volatile struct SHM_Block *sb);
|
||||||
|
void get_cmd_queue(struct CMD_Queue *cq, int server);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int check_shm_block(volatile struct SHM_Block *sb);
|
||||||
|
|
||||||
|
void encode_lev_passwd(char *passwd, int nlev, uint32_t *keylev, uint32_t *codlev);
|
||||||
|
int find_lev_passwd(char *passwd, uint32_t *keylev, uint32_t *codlev);
|
||||||
|
int check_lev_passwd(char *passwd);
|
||||||
|
void set_acckey(uint32_t newkey);
|
||||||
|
|
||||||
|
// restore packing
|
||||||
|
#pragma pack(pop)
|
||||||
|
//#pragma GCC diagnostic pop
|
||||||
|
|
||||||
87
Daemons/weatherdaemon_multimeteo/plugins/btameteo.c
Normal file
87
Daemons/weatherdaemon_multimeteo/plugins/btameteo.c
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bta_shdata.h"
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
#define SENSOR_NAME "BTA 6-m telescope main meteostation"
|
||||||
|
|
||||||
|
enum{
|
||||||
|
NWIND,
|
||||||
|
NHUMIDITY,
|
||||||
|
NAMB_TEMP,
|
||||||
|
NPRESSURE,
|
||||||
|
NPRECIP,
|
||||||
|
NAMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const val_t values[NAMOUNT] = {
|
||||||
|
[NWIND] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||||
|
[NHUMIDITY] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||||
|
[NAMB_TEMP] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||||
|
[NPRESSURE] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||||
|
[NPRECIP] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
|
};
|
||||||
|
|
||||||
|
static void *mainthread(void *s){
|
||||||
|
FNAME();
|
||||||
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
|
if(check_shm_block(&sdat)){
|
||||||
|
//DBG("Got next");
|
||||||
|
time_t tnow = time(NULL);
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i)
|
||||||
|
sensor->values[i].time = tnow;
|
||||||
|
sensor->values[NWIND].value.f = val_Wnd;
|
||||||
|
sensor->values[NPRESSURE].value.f = val_B;
|
||||||
|
sensor->values[NAMB_TEMP].value.f = val_T1;
|
||||||
|
sensor->values[NHUMIDITY].value.f = val_Hmd;
|
||||||
|
//DBG("Tprecip=%.1f, tnow=%.1f", Precip_time, sl_dtime());
|
||||||
|
sensor->values[NPRECIP].value.u = (tnow - (time_t)Precip_time < 60) ? 1 : 0;
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
|
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||||
|
}else break; // no connection?
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
DBG("Lost connection -> suicide");
|
||||||
|
sensor->kill(sensor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sensor_init(sensordata_t *s){
|
||||||
|
FNAME();
|
||||||
|
if(!s) return FALSE;
|
||||||
|
if(!get_shm_block(&sdat, ClientSide)){
|
||||||
|
WARNX("Can't get BTA shared memory block or create main thread");
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
|
s->Nvalues = NAMOUNT;
|
||||||
|
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
||||||
|
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
WARN("Can't create main thread");
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
s->fdes = 0;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
|
|
||||||
#include "weathlib.h"
|
#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`
|
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_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_PRESSURE},
|
||||||
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
{.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||||
{.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
{.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
|
//{.sense = VAL_FORCEDSHTDN, .type = VALT_FLOAT, .meaning = IS_LIGTDIST},
|
||||||
};
|
};
|
||||||
|
|
||||||
static void *mainthread(void _U_ *U){
|
static void *mainthread(void *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
double t0 = sl_dtime();
|
double t0 = sl_dtime();
|
||||||
while(1){
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
float f = sensor.values[0].value.f + (drand48() - 0.5) / 2.;
|
while(sensor->fdes > -1){
|
||||||
if(f >= 0.) sensor.values[0].value.f = f;
|
//DBG("locked");
|
||||||
f = sensor.values[1].value.f + (drand48() - 0.5) * 4.;
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
if(f > 160. && f < 200.) sensor.values[1].value.f = f;
|
float f = sensor->values[0].value.f + (drand48() - 0.5) / 2.;
|
||||||
f = sensor.values[2].value.f + (drand48() - 0.5) / 20.;
|
if(f >= 0.) sensor->values[0].value.f = f;
|
||||||
if(f > 13. && f < 21.) sensor.values[2].value.f = f;
|
f = sensor->values[1].value.f + (drand48() - 0.5) * 4.;
|
||||||
f = sensor.values[3].value.f + (drand48() - 0.5) / 100.;
|
if(f > 160. && f < 200.) sensor->values[1].value.f = f;
|
||||||
if(f > 585. && f < 615.) sensor.values[3].value.f = f;
|
f = sensor->values[2].value.f + (drand48() - 0.5) / 2.;
|
||||||
f = sensor.values[4].value.f + (drand48() - 0.5) / 10.;
|
if(f > 13. && f < 21.) sensor->values[2].value.f = f;
|
||||||
if(f > 60. && f <= 100.) sensor.values[4].value.f = f;
|
f = sensor->values[3].value.f + (drand48() - 0.5) / 100.;
|
||||||
sensor.values[5].value.u = (f > 98.) ? 1 : 0;
|
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);
|
time_t cur = time(NULL);
|
||||||
for(int i = 0; i < NS; ++i) sensor.values[i].time = cur;
|
for(int i = 0; i < NS-1; ++i) sensor->values[i].time = cur;
|
||||||
if(sensor.freshdatahandler) sensor.freshdatahandler(&sensor);
|
/*f = sensor->values[6].value.f - (drand48() - 0.52);
|
||||||
while(sl_dtime() - t0 < sensor.tpoll) usleep(500);
|
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();
|
t0 = sl_dtime();
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int init(struct sensordata_t* s, int N, time_t pollt, int _U_ fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(pthread_create(&s->thread, NULL, mainthread, NULL)) return 0;
|
if(!s) return FALSE;
|
||||||
if(pollt) s->tpoll = pollt;
|
s->Nvalues = NS;
|
||||||
|
strncpy(s->name, SENSOR_NAME, NAME_LEN);
|
||||||
s->values = MALLOC(val_t, NS);
|
s->values = MALLOC(val_t, NS);
|
||||||
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
||||||
sensor.values[0].value.f = 1.;
|
s->values[0].value.f = 1.;
|
||||||
sensor.values[1].value.f = 180.;
|
s->values[1].value.f = 180.;
|
||||||
sensor.values[2].value.f = 17.;
|
s->values[2].value.f = 17.;
|
||||||
sensor.values[3].value.f = 600.;
|
s->values[3].value.f = 600.;
|
||||||
sensor.values[4].value.f = 80.;
|
s->values[4].value.f = 89.;
|
||||||
sensor.values[5].value.u = 0;
|
s->values[5].value.u = 0;
|
||||||
sensor.PluginNo = N;
|
//s->values[6].value.f = 4.5;
|
||||||
return NS;
|
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
s->fdes = 0;
|
||||||
/**
|
|
||||||
* @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];
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t sensor = {
|
|
||||||
.name = "Dummy weatherstation",
|
|
||||||
.Nvalues = NS,
|
|
||||||
.init = init,
|
|
||||||
.onrefresh = common_onrefresh,
|
|
||||||
.get_value = getval,
|
|
||||||
.kill = common_kill,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -23,10 +23,9 @@
|
|||||||
|
|
||||||
// dummy example of file descriptors usage
|
// dummy example of file descriptors usage
|
||||||
|
|
||||||
|
#define SENSOR_NAME "Dummy socket or serial device weatherstation"
|
||||||
#define NS (4)
|
#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`
|
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_WIND},
|
||||||
{.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
{.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},
|
{.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;
|
int gotvals = 0;
|
||||||
char *token = strtok(buf, ",");
|
char *token = strtok(buf, ",");
|
||||||
time_t tnow = time(NULL);
|
time_t tnow = time(NULL);
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
while(token && gotvals < NS){
|
while(token && gotvals < NS){
|
||||||
double v;
|
double v;
|
||||||
DBG("TOKEN: %s", token);
|
DBG("TOKEN: %s", token);
|
||||||
if(sl_str2d(&v, token)){
|
if(sl_str2d(&v, token)){
|
||||||
DBG("next value: %g", v);
|
DBG("next value: %g", v);
|
||||||
sensor.values[gotvals].value.f = (float) v;
|
sensor->values[gotvals].value.f = (float) v;
|
||||||
sensor.values[gotvals].time = tnow;
|
sensor->values[gotvals].time = tnow;
|
||||||
++gotvals;
|
++gotvals;
|
||||||
}
|
}
|
||||||
token = strtok(NULL, ",");
|
token = strtok(NULL, ",");
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
DBG("GOT: %d", gotvals);
|
DBG("GOT: %d", gotvals);
|
||||||
return gotvals;
|
return gotvals;
|
||||||
}
|
}
|
||||||
@@ -77,75 +78,66 @@ static ssize_t writedata(int fd, const char *str, size_t size){
|
|||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *mainthread(void _U_ *U){
|
static void *mainthread(void *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
time_t task = 0;
|
time_t task = 0;
|
||||||
const char begging[] = "Enter comma-separated data: wind, exttemp, pressure, humidity\n";
|
const char begging[] = "Enter comma-separated data: wind, exttemp, pressure, humidity\n";
|
||||||
char buf[128];
|
char buf[128];
|
||||||
while(sensor.fdes > -1){
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
time_t tnow = time(NULL);
|
time_t tnow = time(NULL);
|
||||||
int canread = sl_canread(sensor.fdes);
|
int canread = sl_canread(sensor->fdes);
|
||||||
if(canread < 0){
|
if(canread < 0){
|
||||||
WARNX("Disconnected fd %d", sensor.fdes);
|
WARNX("Disconnected fd %d", sensor->fdes);
|
||||||
break;
|
break;
|
||||||
}else if(canread == 1){
|
}else if(canread == 1){
|
||||||
ssize_t got = read(sensor.fdes, buf, 128);
|
ssize_t got = read(sensor->fdes, buf, 128);
|
||||||
if(got > 0){
|
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){
|
}else if(got < 0){
|
||||||
DBG("Disconnected?");
|
DBG("Disconnected?");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(sl_RB_readline(sensor.ringbuffer, buf, 127) > 0){
|
if(sl_RB_readline(sensor->ringbuffer, buf, 127) > 0){
|
||||||
if(NS == format_values(buf) && sensor.freshdatahandler)
|
if(NS == format_values(sensor, buf) && sensor->freshdatahandler)
|
||||||
sensor.freshdatahandler(&sensor);
|
sensor->freshdatahandler(sensor);
|
||||||
}
|
}
|
||||||
if(sensor.tpoll){
|
if(sensor->tpoll){
|
||||||
if(tnow >= task){
|
if(tnow >= task){
|
||||||
DBG("write %s", begging);
|
DBG("write %s", begging);
|
||||||
ssize_t got = writedata(sensor.fdes, begging, sizeof(begging)-1);
|
ssize_t got = writedata(sensor->fdes, begging, sizeof(begging)-1);
|
||||||
if(got > 0) task = tnow + sensor.tpoll;
|
if(got > 0) task = tnow + sensor->tpoll;
|
||||||
else if(got < 0){
|
else if(got < 0){
|
||||||
close(sensor.fdes);
|
close(sensor->fdes);
|
||||||
sensor.fdes = -1;
|
sensor->fdes = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DBG("OOOOps!");
|
DBG("OOOOps!");
|
||||||
|
sensor->kill(sensor);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int init(struct sensordata_t *s, int N, time_t pollt, int fd){
|
int sensor_init(sensordata_t *s){
|
||||||
FNAME();
|
FNAME();
|
||||||
if(!s) return -1;
|
if(!s) return FALSE;
|
||||||
|
int fd = getFD(s->path);
|
||||||
|
if(fd < 0) return FALSE;
|
||||||
s->fdes = fd;
|
s->fdes = fd;
|
||||||
if(s->fdes < 0) return -1;
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
sensor.PluginNo = N;
|
|
||||||
if(pollt) s->tpoll = pollt;
|
|
||||||
if(pthread_create(&s->thread, NULL, mainthread, NULL)) return -1;
|
|
||||||
s->values = MALLOC(val_t, NS);
|
s->values = MALLOC(val_t, NS);
|
||||||
// don't use memcpy, as `values` could be aligned
|
// don't use memcpy, as `values` could be aligned
|
||||||
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
for(int i = 0; i < NS; ++i) s->values[i] = values[i];
|
||||||
if(!(s->ringbuffer = sl_RB_new(BUFSIZ))){
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ))){
|
||||||
WARNX("Can't init ringbuffer!");
|
WARNX("Can't init ringbuffer!");
|
||||||
return -1;
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
return NS;
|
if(pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
sensordata_t sensor = {
|
|
||||||
.name = "Dummy socket or serial device weatherstation",
|
|
||||||
.Nvalues = NS,
|
|
||||||
.init = init,
|
|
||||||
.onrefresh = common_onrefresh,
|
|
||||||
.get_value = getval,
|
|
||||||
.kill = common_kill,
|
|
||||||
};
|
|
||||||
|
|||||||
214
Daemons/weatherdaemon_multimeteo/plugins/hydreon.c
Normal file
214
Daemons/weatherdaemon_multimeteo/plugins/hydreon.c
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
// HYDREON rain sensor
|
||||||
|
|
||||||
|
#define SENSOR_NAME "Hydreon RG-11 rain sensor"
|
||||||
|
|
||||||
|
// amount of datafields
|
||||||
|
#define RREGNUM 6
|
||||||
|
#define RGBITNUM 8
|
||||||
|
#define SREGNUM 16
|
||||||
|
|
||||||
|
// RGBits values:
|
||||||
|
// PeakRS overflow (>255)
|
||||||
|
#define PkOverThr (1<<0)
|
||||||
|
// is raining (after several PKOverThr by fixed time)
|
||||||
|
#define Raining (1<<1)
|
||||||
|
// outern relay is on (after bucket overflows from 18 to 0)
|
||||||
|
#define Out1On (1<<2)
|
||||||
|
// heater is on
|
||||||
|
#define HtrOn (1<<3)
|
||||||
|
// ambient light @0 (murky, twilight)
|
||||||
|
#define IsDark (1<<4)
|
||||||
|
// ???
|
||||||
|
#define Cndnstn (1<<5)
|
||||||
|
// ???
|
||||||
|
#define Freeze (1<<6)
|
||||||
|
// ???
|
||||||
|
#define Storm (1<<7)
|
||||||
|
|
||||||
|
// minimal packet length (without slow registers)
|
||||||
|
#define REGMINLEN (14)
|
||||||
|
// standard packet length
|
||||||
|
#define REGLEN (18)
|
||||||
|
#define BUFLEN (32)
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
uint8_t PeakRS; // water intensity (255 - continuous)
|
||||||
|
uint8_t SPeakRS; // most time == PeakRS
|
||||||
|
uint8_t RainAD8; // (???)
|
||||||
|
uint8_t LRA; // average rain activity (~envelope of PeakRS)
|
||||||
|
uint8_t TransRat; // amount of measurements per second (???)
|
||||||
|
uint8_t AmbLNoise; // ambient noise RMS (???)
|
||||||
|
uint8_t RGBits; // flags
|
||||||
|
uint8_t SlowRegIngex; // slow register index
|
||||||
|
uint8_t SlowRegValue; // slow register value
|
||||||
|
} rg11;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
uint8_t RevLevel; // (??? == 14)
|
||||||
|
uint8_t EmLevel; // (???) seems correlated with RainAD8
|
||||||
|
uint8_t RecEmStr; // (???) seems correlated with RainAD8
|
||||||
|
uint8_t ABLevel; // (??? == 7..12)
|
||||||
|
uint8_t TmprtrF; // (inner T)
|
||||||
|
uint8_t PUGain; // (??? == 37)
|
||||||
|
uint8_t ClearTR; // (??? almost constant == 121..149)
|
||||||
|
uint8_t AmbLight; // ambient light
|
||||||
|
uint8_t Bucket; // Intergal PeakRS. When no rain, decreased near 4 hours per 1 unit
|
||||||
|
uint8_t Barrel; // Integral Bucket (increases when Bucket goes through 12->14 after last overflow). Decreased near 2 hours per 1 unit
|
||||||
|
uint8_t RGConfig; // (??? == 0)
|
||||||
|
uint8_t DwellT; // 100 - no rain, 50 - low, 5 - max rain (like exponental function)
|
||||||
|
uint8_t SinceRn; // (0..20) increases every minute after rain is over
|
||||||
|
uint8_t MonoStb; // when Raining==1, MonoStb=15, then decrements when no rain (1 unit per ~1minute)
|
||||||
|
uint8_t LightAD; // (???) seems correlated with RainAD8
|
||||||
|
uint8_t RainThr; // (??? == 12)
|
||||||
|
} slowregs;
|
||||||
|
|
||||||
|
enum{
|
||||||
|
NPRECIP = 0,
|
||||||
|
NPRECIP_LEVEL,
|
||||||
|
NSINCERN,
|
||||||
|
NPOW,
|
||||||
|
NAVG,
|
||||||
|
NAMBL,
|
||||||
|
NFREEZ,
|
||||||
|
NAMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const val_t values[NAMOUNT] = { // fields `name` and `comment` have no sense until value meaning is `IS_OTHER`
|
||||||
|
[NPRECIP] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
|
[NPRECIP_LEVEL] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||||
|
[NSINCERN] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "TSINCERN", .comment = "Minutes since rain (20 means a lot of)"},
|
||||||
|
[NPOW] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RAINPOW", .comment = "Rain strength, 0..255"},
|
||||||
|
[NAVG] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RAINAVG", .comment = "Average rain strength, 0..255"},
|
||||||
|
[NAMBL] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RSAMBL", .comment = "Ambient light by rain sensor, 0..255"},
|
||||||
|
[NFREEZ] = {.sense = VAL_UNNECESSARY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "RSFREEZ", .comment = "Rain sensor is freezed"},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int getv(char s, uint8_t *v){
|
||||||
|
if(s >= '0' && s <= '9'){
|
||||||
|
*v = s - '0';
|
||||||
|
return 1;
|
||||||
|
}else if(s >= 'a' && s <= 'f'){
|
||||||
|
*v = 10 + s - 'a';
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
DBG("'%c' not a HEX", s);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int encodepacket(const char *buf, int len, rg11 *Rregs, slowregs *Sregs){
|
||||||
|
DBG("got buffer: %s[%d]", buf, len);
|
||||||
|
uint8_t databuf[REGLEN/2] = {0};
|
||||||
|
static slowregs slow = {0};
|
||||||
|
if(len != REGMINLEN && len != REGLEN){
|
||||||
|
DBG("Wrong buffer len!");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
for(int i = 0; i < len; ++i){
|
||||||
|
int l = i&1; // low part
|
||||||
|
int idx = i/2; // data index
|
||||||
|
uint8_t v;
|
||||||
|
if(!getv(buf[i], &v)) return FALSE;
|
||||||
|
if(l) databuf[idx] |= v;
|
||||||
|
else databuf[idx] |= v << 4;
|
||||||
|
}
|
||||||
|
if(Rregs) memcpy(Rregs, databuf, sizeof(rg11));
|
||||||
|
rg11 r = *((rg11*)databuf);
|
||||||
|
uint8_t *s = (uint8_t*) &slow;
|
||||||
|
if(len == REGLEN){
|
||||||
|
if(r.SlowRegIngex < 16){
|
||||||
|
s[r.SlowRegIngex] = r.SlowRegValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(Sregs) memcpy(Sregs, &slow, sizeof(slowregs));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *mainthread(void *s){
|
||||||
|
FNAME();
|
||||||
|
char buf[128];
|
||||||
|
rg11 Rregs;
|
||||||
|
slowregs Sregs;
|
||||||
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
|
time_t tnow = time(NULL);
|
||||||
|
int canread = sl_canread(sensor->fdes);
|
||||||
|
if(canread < 0){
|
||||||
|
WARNX("Disconnected fd %d", sensor->fdes);
|
||||||
|
break;
|
||||||
|
}else if(canread == 1){
|
||||||
|
ssize_t got = read(sensor->fdes, buf, 128);
|
||||||
|
if(got > 0){
|
||||||
|
//DBG("write into buffer: %s[%zd]", buf, got);
|
||||||
|
sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got);
|
||||||
|
}else if(got < 0){
|
||||||
|
DBG("Disconnected?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int got = sl_RB_readto(sensor->ringbuffer, 's', (uint8_t*)buf, 127);
|
||||||
|
if(got > 0){
|
||||||
|
buf[--got] = 0;
|
||||||
|
if(encodepacket(buf, got, &Rregs, &Sregs)){
|
||||||
|
DBG("refresh...");
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i)
|
||||||
|
sensor->values[i].time = tnow;
|
||||||
|
sensor->values[NPRECIP].value.u = (Rregs.RGBits & (Raining | Storm)) ? 1 : 0;
|
||||||
|
float f = Sregs.Barrel * 256.f + Sregs.Bucket - 14.f;
|
||||||
|
sensor->values[NPRECIP_LEVEL].value.f = (f > 0.f) ? f : 0.f;
|
||||||
|
sensor->values[NSINCERN].value.u = Sregs.SinceRn;
|
||||||
|
sensor->values[NPOW].value.u = Rregs.PeakRS;
|
||||||
|
sensor->values[NAVG].value.u = Rregs.LRA;
|
||||||
|
sensor->values[NAMBL].value.u = Sregs.AmbLight;
|
||||||
|
sensor->values[NFREEZ].value.u = (Rregs.RGBits & Freeze) ? 1 : 0;
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
|
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBG("OOOOps!");
|
||||||
|
sensor->kill(sensor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sensor_init(sensordata_t *s){
|
||||||
|
FNAME();
|
||||||
|
if(!s) return FALSE;
|
||||||
|
int fd = getFD(s->path);
|
||||||
|
if(fd < 0) return FALSE;
|
||||||
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
|
s->fdes = fd;
|
||||||
|
s->Nvalues = NAMOUNT;
|
||||||
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
|
// don't use memcpy, as `values` could be aligned
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
201
Daemons/weatherdaemon_multimeteo/plugins/lightning.c
Normal file
201
Daemons/weatherdaemon_multimeteo/plugins/lightning.c
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin for AS3935-based lightning sensor
|
||||||
|
* https://github.com/eddyem/stm32samples/tree/master/F1:F103/AS3935-lightning
|
||||||
|
**/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
#define SENSOR_NAME "AS3935 lightning sensor"
|
||||||
|
// minimal distance for forced shutdown
|
||||||
|
#define MINDIST (5.0)
|
||||||
|
// time to check wether sensor is alive, seconds
|
||||||
|
#define TCHECK (30)
|
||||||
|
|
||||||
|
// indexes for text commands and answers
|
||||||
|
enum{
|
||||||
|
CMD_INTERRUPT,
|
||||||
|
CMD_ENERGY,
|
||||||
|
CMD_DISTANCE,
|
||||||
|
ANS_LIGHTNING,
|
||||||
|
ANS_NOICE,
|
||||||
|
ANS_DISTURBER,
|
||||||
|
CMD_AMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char * commands[CMD_AMOUNT] = {
|
||||||
|
[CMD_INTERRUPT] = "INTERRUPT",
|
||||||
|
[CMD_ENERGY] = "energy",
|
||||||
|
[CMD_DISTANCE] = "distance",
|
||||||
|
[ANS_LIGHTNING] = "LIGHTNING",
|
||||||
|
[ANS_NOICE] = "NOICE",
|
||||||
|
[ANS_DISTURBER] = "DISTURBER",
|
||||||
|
};
|
||||||
|
|
||||||
|
// indexes of weather values
|
||||||
|
enum{
|
||||||
|
NINTERRUPT,
|
||||||
|
NENERGY,
|
||||||
|
NDISTANCE,
|
||||||
|
NLIGHTNING,
|
||||||
|
NSENSNO,
|
||||||
|
NAMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const val_t values[NAMOUNT] = {
|
||||||
|
[NINTERRUPT] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LINTR", .comment = "Lightning int.: 3 - lightning, 4 - noice, 5 - disturber"},
|
||||||
|
[NENERGY] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LENERGY", .comment = "Last lightning energy"},
|
||||||
|
[NDISTANCE] = {.sense = VAL_OBLIGATORY, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LIGTDIST", .comment = "Distance to last lightning, km"},
|
||||||
|
[NLIGHTNING] = {.sense = VAL_FORCEDSHTDN, .type = VALT_UINT, .meaning = IS_FORCEDSHTDN, .name = "LIGHTNIN", .comment = "Lightning event occured < 5km"},
|
||||||
|
[NSENSNO] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_OTHER, .name = "LSENSNO", .comment = "Last lightning event sensor number"},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief parse_string - parsing of sensor's answer
|
||||||
|
* @param str - text string with data
|
||||||
|
* @param val - value of key
|
||||||
|
* @return index of key in `values` or -1 if not found
|
||||||
|
*/
|
||||||
|
int parse_string(const char *str, uint32_t *val, uint32_t *nsens){
|
||||||
|
if(!str) return -1;
|
||||||
|
char key[SL_KEY_LEN], value[SL_VAL_LEN];
|
||||||
|
DBG("String: %s", str);
|
||||||
|
if(2 != sl_get_keyval(str, key, value)) return -1;
|
||||||
|
DBG("key=%s, val=%s", key, value);
|
||||||
|
int l = strlen(key);
|
||||||
|
int SensNo = key[--l] - '0';
|
||||||
|
DBG("SensNo=%d", SensNo);
|
||||||
|
if(SensNo < 0 || SensNo > 1) return -1;
|
||||||
|
key[l] = 0;
|
||||||
|
int idx = 0;
|
||||||
|
for(; idx < NLIGHTNING; ++idx){
|
||||||
|
if(0 == strcmp(key, commands[idx])) break;
|
||||||
|
}
|
||||||
|
if(idx == NLIGHTNING) return -1;
|
||||||
|
uint32_t u32;
|
||||||
|
if(idx == 0){ // check interrupt source
|
||||||
|
if(strstr(value, commands[ANS_LIGHTNING])) u32 = ANS_LIGHTNING;
|
||||||
|
else if(strstr(value, commands[ANS_DISTURBER])) u32 = ANS_DISTURBER;
|
||||||
|
else u32 = ANS_NOICE;
|
||||||
|
}else u32 = (uint32_t)atoi(value);
|
||||||
|
DBG("idx = %u, val=%u", idx, u32);
|
||||||
|
if(val) *val = u32;
|
||||||
|
if(nsens) *nsens = (uint32_t)SensNo;
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *mainthread(void *s){
|
||||||
|
FNAME();
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
time_t tpoll = 0;
|
||||||
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
|
time_t tnow = time(NULL);
|
||||||
|
if(tnow - tpoll > sensor->tpoll){
|
||||||
|
int dlen = sprintf(buf, "%s0\n%s1\n", commands[CMD_DISTANCE], commands[CMD_DISTANCE]);
|
||||||
|
if(dlen != write(sensor->fdes, buf, dlen)){
|
||||||
|
WARN("Can't ask new data from lightning monitor");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DBG("poll @%zd, pollt=%zd", tnow, sensor->tpoll);
|
||||||
|
tpoll = tnow;
|
||||||
|
}
|
||||||
|
int canread = sl_canread(sensor->fdes);
|
||||||
|
if(canread < 0){
|
||||||
|
WARNX("Disconnected fd %d", sensor->fdes);
|
||||||
|
break;
|
||||||
|
}else if(canread == 1){
|
||||||
|
ssize_t got = read(sensor->fdes, buf, BUFSIZ);
|
||||||
|
if(got > 0){
|
||||||
|
sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got);
|
||||||
|
}else if(got < 0){
|
||||||
|
WARNX("Disconnected?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(sl_RB_datalen(sensor->ringbuffer) > BUFSIZ-1){
|
||||||
|
WARNX("Overfull? Clear data from ring buffer");
|
||||||
|
sl_RB_clearbuf(sensor->ringbuffer);
|
||||||
|
}
|
||||||
|
int gotfresh = FALSE;
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
|
while(1){
|
||||||
|
if(sl_RB_readline(sensor->ringbuffer, buf, BUFSIZ-1) > 0){
|
||||||
|
tpoll = tnow;
|
||||||
|
uint32_t val, nsens;
|
||||||
|
int idx = parse_string(buf, &val, &nsens);
|
||||||
|
if(idx > -1){
|
||||||
|
DBG("Got index=%d", idx);
|
||||||
|
gotfresh = TRUE;
|
||||||
|
if(idx == NINTERRUPT && val == ANS_LIGHTNING){
|
||||||
|
DBG("Interrupt: lightning");
|
||||||
|
sensor->values[NSENSNO].value.u = nsens;
|
||||||
|
sensor->values[NSENSNO].time = tnow;
|
||||||
|
|
||||||
|
}
|
||||||
|
sensor->values[idx].value.u = val;
|
||||||
|
sensor->values[idx].time = tnow;
|
||||||
|
}
|
||||||
|
}else break;
|
||||||
|
}
|
||||||
|
// now check values
|
||||||
|
if(sensor->values[NINTERRUPT].time == tnow && sensor->values[NINTERRUPT].value.u == ANS_LIGHTNING){ // fresh strike
|
||||||
|
if(tnow - sensor->values[NDISTANCE].time < 3 && sensor->values[NDISTANCE].value.u <= MINDIST){ // ahtung!
|
||||||
|
if(sensor->values[NLIGHTNING].value.u == 0) DBG("Ahtung!");
|
||||||
|
sensor->values[NLIGHTNING].time = tnow;
|
||||||
|
sensor->values[NLIGHTNING].value.u = 1;
|
||||||
|
}
|
||||||
|
}else if(tnow - sensor->values[NINTERRUPT].time > TCHECK && sensor->values[NLIGHTNING].value.u){ // remove old lightning flag
|
||||||
|
DBG("Clear ahtung");
|
||||||
|
sensor->values[NLIGHTNING].value.u = 0;
|
||||||
|
sensor->values[NLIGHTNING].time = tnow;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
|
if(gotfresh) DBG("got fresh data");
|
||||||
|
if(gotfresh && sensor->freshdatahandler){
|
||||||
|
DBG("Run fresh data handler");
|
||||||
|
sensor->freshdatahandler(sensor);
|
||||||
|
}
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
DBG("suicide");
|
||||||
|
sensor->kill(sensor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sensor_init(sensordata_t *s){
|
||||||
|
FNAME();
|
||||||
|
if(!s) return FALSE;
|
||||||
|
int fd = getFD(s->path);
|
||||||
|
if(fd < 0) return FALSE;
|
||||||
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
|
s->fdes = fd;
|
||||||
|
s->Nvalues = NAMOUNT;
|
||||||
|
s->tpoll = TCHECK;
|
||||||
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
185
Daemons/weatherdaemon_multimeteo/plugins/reinhardt.c
Normal file
185
Daemons/weatherdaemon_multimeteo/plugins/reinhardt.c
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of the weatherdaemon project.
|
||||||
|
* Copyright 2026 Edward V. Emelianov <edward.emelianoff@gmail.com>.
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "weathlib.h"
|
||||||
|
|
||||||
|
#define SENSOR_NAME "Old Reinhard meteostation"
|
||||||
|
|
||||||
|
//static const char *emultemplate = "<?U> 06:50:36, 20.01.00, TE-2.20, DR1405.50, WU2057.68, RT0.00, WK1.00, WR177.80, WT-2.20, FE0.69, RE0.00, WG7.36, WV260.03, TI0.00, FI0.00,";
|
||||||
|
|
||||||
|
enum{
|
||||||
|
NWIND,
|
||||||
|
NWINDDIR,
|
||||||
|
NHUMIDITY,
|
||||||
|
NAMB_TEMP,
|
||||||
|
NPRESSURE,
|
||||||
|
NCLOUDS,
|
||||||
|
NPRECIP,
|
||||||
|
NPRECIPLVL,
|
||||||
|
NAMOUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const val_t values[NAMOUNT] = {
|
||||||
|
[NWIND] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_WIND},
|
||||||
|
[NWINDDIR] = {.sense = VAL_RECOMMENDED,.type = VALT_FLOAT, .meaning = IS_WINDDIR},
|
||||||
|
[NHUMIDITY] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_HUMIDITY},
|
||||||
|
[NAMB_TEMP] = {.sense = VAL_RECOMMENDED, .type = VALT_FLOAT, .meaning = IS_AMB_TEMP},
|
||||||
|
[NPRESSURE] = {.sense = VAL_BROKEN, .type = VALT_FLOAT, .meaning = IS_PRESSURE},
|
||||||
|
[NCLOUDS] = {.sense = VAL_OBLIGATORY, .type = VALT_FLOAT, .meaning = IS_CLOUDS},
|
||||||
|
[NPRECIP] = {.sense = VAL_RECOMMENDED, .type = VALT_UINT, .meaning = IS_PRECIP},
|
||||||
|
[NPRECIPLVL]= {.sense = VAL_UNNECESSARY,.type = VALT_FLOAT, .meaning = IS_PRECIP_LEVEL},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief getpar - get parameter value
|
||||||
|
* @param string (i) - string where to search
|
||||||
|
* @param Val (o) - value found
|
||||||
|
* @param Name - parameter name
|
||||||
|
* @return TRUE if found
|
||||||
|
*/
|
||||||
|
static int getpar(char *string, double *Val, char *Name){
|
||||||
|
if(!string || !Val || !Name) return FALSE;
|
||||||
|
char *p = strstr(string, Name);
|
||||||
|
if(!p) return FALSE;
|
||||||
|
p += strlen(Name);
|
||||||
|
//DBG("search %s", Name);
|
||||||
|
char *endptr;
|
||||||
|
*Val = strtod(p, &endptr);
|
||||||
|
//DBG("eptr=%s, val=%g", endptr, *Val);
|
||||||
|
if(endptr == string){
|
||||||
|
WARNX("Double value not found");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *mainthread(void *s){
|
||||||
|
FNAME();
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
time_t tpoll = 0;
|
||||||
|
sensordata_t *sensor = (sensordata_t *)s;
|
||||||
|
while(sensor->fdes > -1){
|
||||||
|
time_t tnow = time(NULL);
|
||||||
|
if(tnow - tpoll > sensor->tpoll){
|
||||||
|
if(4 != write(sensor->fdes, "?U\r\n", 4)){
|
||||||
|
WARN("Can't ask new data");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
DBG("poll @%zd, pollt=%zd", tnow, sensor->tpoll);
|
||||||
|
tpoll = tnow;
|
||||||
|
}
|
||||||
|
int canread = sl_canread(sensor->fdes);
|
||||||
|
if(canread < 0){
|
||||||
|
WARNX("Disconnected fd %d", sensor->fdes);
|
||||||
|
break;
|
||||||
|
}else if(canread == 1){
|
||||||
|
ssize_t got = read(sensor->fdes, buf, BUFSIZ);
|
||||||
|
if(got > 0){
|
||||||
|
sl_RB_write(sensor->ringbuffer, (uint8_t*)buf, got);
|
||||||
|
}else if(got < 0){
|
||||||
|
DBG("Disconnected?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(sl_RB_datalen(sensor->ringbuffer) > BUFSIZ-1){
|
||||||
|
WARNX("Overfull? Clear data from ring buffer");
|
||||||
|
sl_RB_clearbuf(sensor->ringbuffer);
|
||||||
|
}
|
||||||
|
if(sl_RB_readto(sensor->ringbuffer, '\n', (uint8_t*)buf, BUFSIZ-1) > 0){
|
||||||
|
tnow = time(NULL);
|
||||||
|
DBG("Got next: %s", buf);
|
||||||
|
pthread_mutex_lock(&sensor->valmutex);
|
||||||
|
double d;
|
||||||
|
//int Ngot = 0;
|
||||||
|
if(getpar(buf, &d, "RE")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NPRECIPLVL].value.f = (float) d;
|
||||||
|
sensor->values[NPRECIPLVL].time = tnow;
|
||||||
|
DBG("Got precip. lvl: %g", d);
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "RT")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NPRECIP].value.u = (d > 0.) ? 1 : 0;
|
||||||
|
sensor->values[NPRECIP].time = tnow;
|
||||||
|
DBG("Got precip.: %g", d);
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "WU")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NCLOUDS].value.f = (float) d;
|
||||||
|
sensor->values[NCLOUDS].time = tnow;
|
||||||
|
DBG("Got clouds.: %g", d);
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "TE")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NAMB_TEMP].value.f = (float) d;
|
||||||
|
sensor->values[NAMB_TEMP].time = tnow;
|
||||||
|
DBG("Got ext. T: %g", d);
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "WG")){
|
||||||
|
//++Ngot;
|
||||||
|
d /= 3.6;
|
||||||
|
DBG("Wind: %g", d);
|
||||||
|
sensor->values[NWIND].value.f = (float) d;
|
||||||
|
sensor->values[NWIND].time = tnow;
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "WR")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NWINDDIR].value.f = (float) d;
|
||||||
|
sensor->values[NWINDDIR].time = tnow;
|
||||||
|
DBG("Winddir: %g", d);
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "DR")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NPRESSURE].value.f = (float) (d * 0.7500616);
|
||||||
|
sensor->values[NPRESSURE].time = tnow;
|
||||||
|
DBG("Pressure: %g", d);
|
||||||
|
}
|
||||||
|
if(getpar(buf, &d, "FE")){
|
||||||
|
//++Ngot;
|
||||||
|
sensor->values[NHUMIDITY].value.f = (float) d;
|
||||||
|
sensor->values[NHUMIDITY].time = tnow;
|
||||||
|
DBG("Humidity: %g", d);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&sensor->valmutex);
|
||||||
|
if(sensor->freshdatahandler) sensor->freshdatahandler(sensor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sensor->kill(sensor);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int sensor_init(sensordata_t *s){
|
||||||
|
FNAME();
|
||||||
|
if(!s) return FALSE;
|
||||||
|
int fd = getFD(s->path);
|
||||||
|
if(fd < 0) return FALSE;
|
||||||
|
s->Nvalues = NAMOUNT;
|
||||||
|
s->fdes = fd;
|
||||||
|
snprintf(s->name, NAME_LEN, "%s", SENSOR_NAME);
|
||||||
|
s->values = MALLOC(val_t, NAMOUNT);
|
||||||
|
for(int i = 0; i < NAMOUNT; ++i) s->values[i] = values[i];
|
||||||
|
if(!(s->ringbuffer = sl_RB_new(BUFSIZ)) ||
|
||||||
|
pthread_create(&s->thread, NULL, mainthread, (void*)s)){
|
||||||
|
s->kill(s);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user