Compare commits

...

29 Commits

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

25
.gitignore vendored
View File

@@ -1,3 +1,17 @@
# executables
*
!*.*
!*/
!Makefile
![Rr][Ee][Aa][Dd][Mm][Ee]
# build dirs
mk/
build/
.qtc_clangd
.qtcreator
# Prerequisites
*.d
@@ -23,6 +37,11 @@
*.so
*.so.*
# build dirs
mk/
build/
# diff
.qtcreator/
*.log
*.tgz
*.tar.gz
*.7z
*.zip

View File

@@ -15,6 +15,8 @@ set(CMAKE_COLOR_MAKEFILE ON)
option(DEBUG "Compile in debug mode" OFF)
option(EXAMPLES "Compile also some examples" ON)
option(BUILD_SHARED "Build shared libarary" OFF)
# cmake -DDEBUG=on -> debugging
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")
# library
add_library(${PROJ} SHARED ${SOURCES})
if(BUILD_SHARED)
add_library(${PROJ} SHARED ${SOURCES})
else()
add_library(${PROJ} STATIC ${SOURCES})
endif()
# library header files
set(LIBHEADER "sidservo.h")
# -I

View File

@@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@@ -25,34 +26,70 @@
#include "PID.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;
PIDController_t *pid = (PIDController_t*)calloc(1, sizeof(PIDController_t));
pid->gain = *gain;
DBG("Created PID with P=%g, I=%g, D=%g\n", gain->P, gain->I, gain->D);
pid->pidIarrSize = Iarrsz;
pid->pidIarray = (double*)calloc(Iarrsz, sizeof(double));
curtime(&pid->prevT);
return pid;
}
// don't clear lastT!
void pid_clear(PIDController_t *pid){
static void pid_clear(PIDController_t *pid){
if(!pid) return;
DBG("CLEAR PID PARAMETERS");
bzero(pid->pidIarray, sizeof(double) * pid->pidIarrSize);
pid->integral = 0.;
pid->prev_error = 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)->pidIarray) free((*pid)->pidIarray);
free(*pid);
*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
double oldi = pid->pidIarray[pid->curIidx], newi = error * dt;
//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;
double derivative = (error - pid->prev_error) / dt;
pid->prev_error = error;
double sum = pid->gain.P * error + pid->gain.I * pid->integral + pid->gain.D * derivative;
DBG("P=%g, I=%g, D=%g; sum=%g", pid->gain.P * error, pid->gain.I * pid->integral, pid->gain.D * derivative, sum);
DBG("pid pars: P=%g, I=%g, D=%f", pid->gain.P, pid->gain.I, pid->gain.D);
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;
}
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
* @param tagpos - given coordinate of target position
* @param endpoint - endpoint for this coordinate
* @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);
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);
return axis->speed.val; // data is too old or wrong
}
double error = tagpos->val - axis->position.val, fe = fabs(error);
DBG("error: %g", error);
PIDController_t *pid = NULL;
DBG("error: %g'', cur speed: %g (deg/s)", error * 180. * 3600. / M_PI, axis->speed.val*180./M_PI);
switch(axis->state){
case AXIS_SLEWING:
if(fe < Conf.MaxPointingErr){
if(fe < Conf.MaxFinePointingErr){
axis->state = AXIS_POINTING;
DBG("--> Pointing");
pid = pidpair->PIDC;
}else{
DBG("Slewing...");
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){
axis->state = AXIS_GUIDING;
DBG("--> Guiding");
pid = pidpair->PIDV;
}else if(fe > Conf.MaxPointingErr){
DBG("--> Slewing");
axis->state = AXIS_SLEWING;
return NAN;
} else pid = pidpair->PIDC;
}
break;
case AXIS_GUIDING:
pid = pidpair->PIDV;
if(fe > Conf.MaxFinePointingErr){
DBG("--> Pointing");
axis->state = AXIS_POINTING;
pid = pidpair->PIDC;
}else if(fe < Conf.MaxGuidingErr){
DBG("At target");
// 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;
case AXIS_GONNASTOP:
case AXIS_STOPPED: // start pointing to target; will change speed next time
DBG("AXIS STOPPED!!!! --> Slewing");
axis->state = AXIS_SLEWING;
return getspeed(tagpos, pidpair, axis);
return getspeed(tagpos, pid, axis);
case AXIS_ERROR:
DBG("Can't move from erroneous state");
return 0.;
@@ -137,17 +162,7 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
DBG("WTF? Where is a PID?");
return axis->speed.val;
}
double dtpid = timediff(&tagpos->t, &pid->prevT);
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
return pid_calculate(pid, axis->position.val, tagpos);
}
/**
@@ -157,18 +172,14 @@ static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, axisdata_t
* @return error code
*/
mcc_errcodes_t correct2(const coordval_pair_t *target){
static PIDpair_t pidX = {0}, pidY = {0};
if(!pidX.PIDC){
pidX.PIDC = pid_create(&Conf.XPIDC, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidX.PIDC) return MCC_E_FATAL;
pidX.PIDV = pid_create(&Conf.XPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidX.PIDV) return MCC_E_FATAL;
static PIDController_t *pidX = NULL, *pidY = NULL;
if(!pidX){
pidX = pid_create(&Conf.XPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidX) return MCC_E_FATAL;
}
if(!pidY.PIDC){
pidY.PIDC = pid_create(&Conf.YPIDC, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidY.PIDC) return MCC_E_FATAL;
pidY.PIDV = pid_create(&Conf.YPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidY.PIDV) return MCC_E_FATAL;
if(!pidY){
pidY = pid_create(&Conf.YPIDV, Conf.PIDCycleDt / Conf.PIDRefreshDt);
if(!pidY) return MCC_E_FATAL;
}
mountdata_t m;
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.position = m.encXposition;
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(target->X.val < axis.position.val) Xsign = -1.;
tagspeed.X = Xlimits.max.speed;
@@ -191,7 +202,7 @@ mcc_errcodes_t correct2(const coordval_pair_t *target){
axis.state = m.Ystate;
axis.position = m.encYposition;
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(target->Y.val < axis.position.val) Ysign = -1.;
tagspeed.Y = Ylimits.max.speed;
@@ -205,6 +216,7 @@ mcc_errcodes_t correct2(const coordval_pair_t *target){
setStat(xstate, ystate);
}
coordpair_t endpoint;
#if 0
// allow at least PIDMaxDt moving with target speed
double dv = fabs(tagspeed.X - m.encXspeed.val);
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
+ tagspeed.Y * tagspeed.Y / Ylimits.max.accel / 2.;
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.);
return Mount.moveWspeed(&endpoint, &tagspeed);
}

View File

@@ -22,19 +22,10 @@
#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);
void pid_clear(PIDController_t *pid);
void pid_delete(PIDController_t **pid);
double pid_calculate(PIDController_t *pid, double error, double dt);
*/
mcc_errcodes_t correct2(const coordval_pair_t *target);

View File

@@ -65,8 +65,12 @@ void signals(int sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
}
DBG("Stop!");
Mount.stop();
usleep(10000);
DBG("Quit");
Mount.quit();
usleep(10000);
DBG("close");
if(fcoords) fclose(fcoords);
exit(sig);

View File

@@ -109,17 +109,27 @@ static void runtraectory(traectory_fn tfn){
}
if(!Mount.currentT(&tcur)) continue;
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;
double t = Mount.timeFromStart();
if(fabs(telXY.X.val) > G.Xmax || fabs(telXY.Y.val) > G.Ymax || t - tstart > G.tmax) break;
if(!traectory_point(&traectXY, t)) break;
if(fabs(telXY.X.val) > G.Xmax || fabs(telXY.Y.val) > G.Ymax || t - tstart > G.tmax){
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.t = target.Y.t = tcur;
if(t0.tv_nsec == 0 && t0.tv_sec == 0) dumpt0(&t0);
else{
//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));
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));
@@ -146,10 +156,12 @@ int main(int argc, char **argv){
ERRX("Can't open error log %s", G.errlog);
else
fprintf(errlog, "# time Xerr'' Yerr'' // target - real\n");
setbuf(errlog, NULL);
}
if(G.coordsoutput){
if(!(fcoords = fopen(G.coordsoutput, "w")))
ERRX("Can't open %s", G.coordsoutput);
setbuf(fcoords, NULL);
}else fcoords = stdout;
Config = readServoConf(G.conffile);
if(!Config || G.dumpconf){
@@ -164,21 +176,22 @@ int main(int argc, char **argv){
return 1;
}
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);
if(e != MCC_E_OK){
WARNX("Can't init devices");
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(SIGHUP, SIG_IGN); // hup - ignore
signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
chk0(G.Ncycles);
// chk0(G.Ncycles);
logmnt(fcoords, NULL);
if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread");
;

View File

@@ -42,6 +42,7 @@ int init_traectory(traectory_fn f, coordpair_t *XY0){
cur_traectory = f;
XYstart = *XY0;
tstart = Mount.timeFromStart();
DBG("inited @ %gs from start", tstart);
mountdata_t mdata;
int ntries = 0;
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)
*/
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;
if(!cur_traectory(&pt, t)) return FALSE;
if(!cur_traectory(&pt, t)){
DBG("error in cur_traectory");
return FALSE;
}
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;
}
@@ -88,6 +99,7 @@ int telpos(coordval_pair_t *curpos){
// X=X0+1'/s, Y=Y0+15''/s
int Linear(coordpair_t *nextpt, double t){
coordpair_t pt;
DBG("t=%g, tfromstart=%g, dt=%g", t, tstart, t-tstart);
pt.X = XYstart.X + ASEC2RAD(0.1) * (t - tstart);
pt.Y = XYstart.Y + ASEC2RAD(15.)* (t - tstart);
if(nextpt) *nextpt = pt;
@@ -98,7 +110,7 @@ int Linear(coordpair_t *nextpt, double t){
int SinCos(coordpair_t *nextpt, double t){
coordpair_t pt;
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;
return TRUE;
}
@@ -111,7 +123,7 @@ typedef struct{
static tr_names names[] = {
{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}
};

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

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

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!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>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
<value type="QByteArray">{cf63021e-ef53-49b0-b03b-2f2570cdf3b6}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
@@ -40,9 +40,9 @@
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">false</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">1</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
@@ -51,10 +51,10 @@
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">true</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
@@ -79,7 +79,7 @@
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">8</value>
<value type="int" key="ClangTools.ParallelJobs">4</value>
<value type="bool" key="ClangTools.PreferConfigFile">true</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
@@ -96,12 +96,12 @@
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{91347f2c-5221-46a7-80b1-0a054ca02f79}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/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.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
@@ -133,7 +133,7 @@
<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.DisplayName">По умолчанию</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>
@@ -168,7 +168,6 @@
<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>
@@ -204,7 +203,6 @@
<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>

View File

@@ -13,12 +13,14 @@ examples/dumpswing.c
examples/goto.c
examples/scmd_traectory.c
examples/simpleconv.h
kalman.c
main.c
sidservo.h
serial.c
examples/CMakeLists.txt
examples/traectories.c
examples/traectories.h
kalman.h
main.h
movingmodel.c
movingmodel.h

View File

@@ -161,6 +161,8 @@ double LS_calc_slope(less_square_t *l, double x, double t){
if(!l) return 0.;
size_t idx = l->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;
l->x[idx] = x; l->t2[idx] = t2;
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;
double n = (double)l->arraysz;
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.;
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;
return (numerator / denominator);
}
@@ -200,6 +202,7 @@ static mcc_errcodes_t init(conf_t *c){
if(!Xmodel || !Ymodel || !openMount()) return MCC_E_FAILED;
return MCC_E_OK;
}
DBG("Try to open mount device");
if(!Conf.MountDevPath || Conf.MountDevSpeed < MOUNT_BAUDRATE_MIN){
DBG("Define mount device path and speed");
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);
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
if(Conf.EncoderSpeedInterval < Conf.EncoderReqInterval * MCC_CONF_MIN_SPEEDC || Conf.EncoderSpeedInterval > MCC_CONF_MAX_SPEEDINT){
DBG("Wrong speed interval");
ret = MCC_E_BADFORMAT;
}
if(!SSrawcmd(CMD_EXITACM, NULL)) ret = MCC_E_FAILED;
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
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
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();
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();
// and refresh data after updating
DBG("Wait for next mount reading");
t0 = timefromstart();
while(timefromstart() - t0 < Conf.MountReqInterval * 3.) usleep(1000);
while(timefromstart() - t0 < Conf.MountReqInterval * 5.) usleep(1000);
DBG("ALL READY!");
return e;
}
@@ -288,10 +311,6 @@ static mcc_errcodes_t move2(const coordpair_t *target){
cmd.Ymot = target->Y;
cmd.Xspeed = Xlimits.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);
}
@@ -328,10 +347,7 @@ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed
cmd.Ymot = target->Y;
cmd.Xspeed = speed->X;
cmd.Yspeed = speed->Y;
mcc_errcodes_t r = shortcmd(&cmd);
if(r != MCC_E_OK) return r;
setslewingstate();
return MCC_E_OK;
return shortcmd(&cmd);
}
/**

View File

@@ -1,46 +0,0 @@
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#define NFILT (5)
static double filterK[NFILT];
static void buildFilter(){
filterK[NFILT-1] = 1.;
double sum = 1.;
for(int i = NFILT-2; i > -1; --i){
filterK[i] = (filterK[i+1] + 1.) * 1.1;
sum += filterK[i];
}
for(int i = 0; i < NFILT; ++i){
filterK[i] /= sum;
fprintf(stderr, "%d: %g\n", i, filterK[i]);
}
}
static double filter(double val){
static int ctr = 0;
static double lastvals[NFILT] = {0.};
for(int i = NFILT-1; i > 0; --i) lastvals[i] = lastvals[i-1];
lastvals[0] = val;
double r = 0.;
if(ctr < NFILT){
++ctr;
return val;
}
for(int i = 0; i < NFILT; ++i) r += filterK[i] * lastvals[i];
return r;
}
int main(int argc, char **argv){
buildFilter();
printf("Signal\tNoiced\tFiltered\n");
for(int i = 0; i < 100; ++i){
double di = (double)i;
double sig = di * di / 1e5 + sin(i * M_PI / 1500.);
double noiced = sig + 0.1 * (drand48() - 0.5);
printf("%.3f\t%.3f\t%.3f\n", sig, noiced, filter(noiced));
}
return 0;
}

View File

@@ -31,6 +31,7 @@
#include <sys/stat.h>
#include <unistd.h>
#include "kalman.h"
#include "main.h"
#include "movingmodel.h"
#include "serial.h"
@@ -49,7 +50,13 @@ static pthread_mutex_t mntmutex = PTHREAD_MUTEX_INITIALIZER,
// encoders thread and mount thread
static pthread_t encthread, mntthread;
// 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
typedef struct __attribute__((packed)){
uint8_t magick;
@@ -71,7 +78,6 @@ void getXspeed(){
mountdata.encXspeed.val = speed;
mountdata.encXspeed.t = mountdata.encXposition.t;
}
//DBG("Xspeed=%g", mountdata.encXspeed.val);
}
void getYspeed(){
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);
}
#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
static int getencbyte(){
if(encfd[0] < 0) return -1;
@@ -232,43 +167,60 @@ static int getencbyte(){
}while(1);
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;
uint8_t byte;
/**
* @brief readmntdata - read data
* @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;
/* ssize_t l = read(mntfd, &byte, 1);
//DBG("MNT read=%zd byte=0x%X", l, byte);
if(l == 0) return -1;
if(l != 1) return -2; // disconnected ??
return (int) byte;*/
struct timeval tv = mnt1Rtmout;
do{
FD_ZERO(&rfds);
FD_SET(mntfd, &rfds);
struct timeval tv = mntRtmout;
//DBG("select");
int retval = select(mntfd + 1, &rfds, NULL, NULL, &tv);
//DBG("returned %d", retval);
if(retval < 0){
if(errno == EINTR) continue;
DBG("Error in select()");
return -1;
}
//DBG("FD_ISSET = %d", FD_ISSET(mntfd, &rfds));
if(FD_ISSET(mntfd, &rfds)){
ssize_t l = read(mntfd, &byte, 1);
//DBG("MNT read=%zd byte=0x%X", l, byte);
if(l != 1){
ssize_t l = read(mntfd, buffer, maxlen);
if(l == 0){
DBG("read ZERO");
continue;
}
if(l < 0){
DBG("Mount disconnected?");
return -2; // disconnected ??
}
buffer += l;
maxlen -= l;
got += l;
}else{
DBG("no new data after %d bytes (%s)", got, buffer - got);
break;
} else return -1;
}while(1);
return (int)byte;
}
tv = mntRtmout;
}while(maxlen);
return got;
}
// clear data from input buffer
static void clrmntbuf(){
if(mntfd < 0) return;
uint8_t byte;
uint8_t bytes[256];
fd_set rfds;
do{
FD_ZERO(&rfds);
@@ -281,9 +233,10 @@ static void clrmntbuf(){
break;
}
if(FD_ISSET(mntfd, &rfds)){
ssize_t l = read(mntfd, &byte, 1);
if(l != 1) break;
} else break;
ssize_t l = read(mntfd, &bytes, 256);
if(l < 1) break;
DBG("clr got %zd bytes: %s", l, bytes);
}else break;
}while(1);
}
@@ -293,7 +246,7 @@ static void *encoderthread1(void _U_ *u){
uint8_t databuf[ENC_DATALEN];
int wridx = 0, errctr = 0;
struct timespec tcur;
while(encfd[0] > -1 && errctr < MAX_ERR_CTR){
while(encfd[0] > -1 && errctr < MAX_ERR_CTR && !GlobExit){
int b = getencbyte();
if(b == -2) ++errctr;
if(b < 0) continue;
@@ -328,9 +281,12 @@ typedef struct{
// write to buffer next data portion; return FALSE in case of error
static int readstrings(buf_t *buf, int fd){
FNAME();
if(!buf){DBG("Empty buffer"); return FALSE;}
int L = XYBUFSZ - buf->len;
if(L < 0){
DBG("buf not initialized!");
buf->len = 0;
}
if(L == 0){
DBG("buffer overfull: %d!", buf->len);
char *lastn = strrchr(buf->buf, '\n');
@@ -344,6 +300,7 @@ static int readstrings(buf_t *buf, int fd){
}else buf->len = 0;
L = XYBUFSZ - buf->len;
}
//DBG("read %d bytes from %d", L, fd);
int got = read(fd, &buf->buf[buf->len], L);
if(got < 0){
DBG("read()");
@@ -351,13 +308,16 @@ static int readstrings(buf_t *buf, int fd){
}else if(got == 0){ DBG("NO data"); return TRUE; }
buf->len += got;
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 if got, FALSE if no data found
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)
char *last = &buf->buf[buf->len - 1];
//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
static int asknext(int fd){
FNAME();
//FNAME();
if(fd < 0) return FALSE;
int i = 0;
for(; i < 5; ++i){
@@ -399,12 +359,24 @@ static void *encoderthread2(void _U_ *u){
pfds[0].fd = encfd[0]; pfds[0].events = POLLIN;
pfds[1].fd = encfd[1]; pfds[1].events = POLLIN;
double t0[2], tstart;
buf_t strbuf[2];
buf_t strbuf[2] = {0};
long msrlast[2]; // last encoder data
double mtlast[2]; // last measurement time
asknext(encfd[0]); asknext(encfd[1]);
t0[0] = t0[1] = tstart = timefromstart();
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
if(poll(pfds, 2, 0) < 0){
DBG("poll()");
@@ -414,6 +386,7 @@ static void *encoderthread2(void _U_ *u){
for(int i = 0; i < 2; ++i){
if(pfds[i].revents && POLLIN){
if(!readstrings(&strbuf[i], encfd[i])){
DBG("ERR");
++errctr;
break;
}
@@ -421,16 +394,37 @@ static void *encoderthread2(void _U_ *u){
double curt = timefromstart();
if(getdata(&strbuf[i], &msrlast[i])) mtlast[i] = curt;
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){
//DBG("time OK");
pthread_mutex_lock(&datamutex);
double pos = (double)msrlast[i];
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);
/*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();
//mountdata.encXspeed.val = kf[i].x[1];
//mountdata.encXspeed.t = mountdata.encXposition.t;
}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);
getYspeed();
//mountdata.encYspeed.val = kf[i].x[1];
//mountdata.encYspeed.t = mountdata.encYposition.t;
}
pthread_mutex_unlock(&datamutex);
}
@@ -443,7 +437,7 @@ static void *encoderthread2(void _U_ *u){
}
}
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);
for(int i = 0; i < 2; ++i){
if(encfd[i] > -1){
@@ -487,10 +481,45 @@ static void chkModStopped(double *prev, double cur, int *nstopped, axis_status_t
*prev = cur;
}
// Next two functions runs under locked mountdata_t mutex and shouldn't lock it again!!
static axis_status_t chkstopstat(int32_t *prev, int32_t cur, int32_t tag, int *nstopped, axis_status_t stat){
if(*prev == INT32_MAX){
stat = AXIS_STOPPED;
DBG("START");
}else if(stat == AXIS_GONNASTOP || (stat != AXIS_STOPPED && cur == tag)){ // got command "stop" or motor is on target
if(*prev == cur){
DBG("Test for stop, nstopped=%d", *nstopped);
if(++(*nstopped) > MOTOR_STOPPED_CNT){
stat = AXIS_STOPPED;
DBG("AXIS stopped");
}
}else *nstopped = 0;
}else if(*prev != cur){
DBG("AXIS moving");
*nstopped = 0;
}
*prev = cur;
return stat;
}
// check for stopped/pointing states
static void ChkStopped(const SSstat *s, mountdata_t *m){
static int32_t Xmot_prev = INT32_MAX, Ymot_prev = INT32_MAX; // previous coordinates
static int Xnstopped = 0, Ynstopped = 0; // counters to get STOPPED state
axis_status_t Xstat, Ystat;
Xstat = chkstopstat(&Xmot_prev, s->Xmot, m->Xtarget, &Xnstopped, m->Xstate);
Ystat = chkstopstat(&Ymot_prev, s->Ymot, m->Ytarget, &Ynstopped, m->Ystate);
if(Xstat != m->Xstate || Ystat != m->Ystate){
DBG("Status changed");
mountdata.Xstate = Xstat;
mountdata.Ystate = Ystat;
}
}
// main mount thread
static void *mountthread(void _U_ *u){
int errctr = 0;
uint8_t buf[2*sizeof(SSstat)];
uint8_t buf[sizeof(SSstat)];
SSstat *status = (SSstat*) buf;
bzero(&mountdata, sizeof(mountdata));
double t0 = timefromstart(), tstart = t0, tcur = t0;
@@ -499,7 +528,7 @@ static void *mountthread(void _U_ *u){
if(Conf.RunModel){
double Xprev = NAN, Yprev = NAN; // previous coordinates
int xcnt = 0, ycnt = 0;
while(1){
while(!GlobExit){
coordpair_t c;
movestate_t xst, yst;
// now change data
@@ -508,20 +537,20 @@ static void *mountthread(void _U_ *u){
if(!curtime(&tnow) || (tcur = timefromstart()) < 0.) continue;
pthread_mutex_lock(&datamutex);
mountdata.encXposition.t = mountdata.encYposition.t = tnow;
mountdata.encXposition.val = c.X;
mountdata.encYposition.val = c.Y;
mountdata.encXposition.val = c.X + (drand48() - 0.5)*1e-6; // .2arcsec error
mountdata.encYposition.val = c.Y + (drand48() - 0.5)*1e-6;
//DBG("t=%g, X=%g, Y=%g", tnow, c.X.val, c.Y.val);
if(tcur - oldmt > Conf.MountReqInterval){
oldmillis = mountdata.millis = (uint32_t)((tcur - tstart) * 1e3);
mountdata.motYposition.t = mountdata.motXposition.t = tnow;
if(xst == ST_MOVE)
mountdata.motXposition.val = c.X + (c.X - mountdata.motXposition.val)*(drand48() - 0.5)/100.;
else
mountdata.motXposition.val = c.X;
//else
// mountdata.motXposition.val = c.X;
if(yst == ST_MOVE)
mountdata.motYposition.val = c.Y + (c.Y - mountdata.motYposition.val)*(drand48() - 0.5)/100.;
else
mountdata.motYposition.val = c.Y;
//else
// mountdata.motYposition.val = c.Y;
oldmt = tcur;
}else mountdata.millis = oldmillis;
chkModStopped(&Xprev, c.X, &xcnt, &mountdata.Xstate);
@@ -537,7 +566,7 @@ static void *mountthread(void _U_ *u){
// cmd to send
data_t *cmd_getstat = cmd2dat(CMD_GETSTAT);
if(!cmd_getstat) goto failed;
while(mntfd > -1 && errctr < MAX_ERR_CTR){
while(mntfd > -1 && errctr < MAX_ERR_CTR && !GlobExit){
// read data to status
struct timespec tcur;
if(!curtime(&tcur)) continue;
@@ -558,6 +587,7 @@ static void *mountthread(void _U_ *u){
pthread_mutex_lock(&datamutex);
// now change data
SSconvstat(status, &mountdata, &tcur);
ChkStopped(status, &mountdata);
pthread_mutex_unlock(&datamutex);
// allow writing & getters
do{
@@ -591,11 +621,12 @@ static int ttyopen(const char *path, speed_t speed){
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
tty.c_iflag = 0; // don't do any changes in input stream
tty.c_oflag = 0; // don't do any changes in output stream
tty.c_cflag = BOTHER | CS8 | CREAD | CLOCAL; // other speed, 8bit, RW, ignore line ctrl
// wihthout "HUPCL" it doesn't disconnects
tty.c_cflag = HUPCL | BOTHER | CS8 | CREAD | CLOCAL; // other speed, 8bit, RW, ignore line ctrl
tty.c_ispeed = speed;
tty.c_ospeed = speed;
//tty.c_cc[VMIN] = 0; // non-canonical mode
//tty.c_cc[VTIME] = 5;
tty.c_cc[VMIN] = 0; // non-canonical mode
tty.c_cc[VTIME] = 0;
if(ioctl(fd, TCSETS2, &tty)){
DBG("Can't set TTY settings");
close(fd);
@@ -610,15 +641,18 @@ static int ttyopen(const char *path, speed_t speed){
// return FALSE if failed
int openEncoder(){
// TODO: open real devices in "model" mode too!
if(Conf.RunModel) return TRUE;
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
DBG("One device");
if(encfd[0] > -1) close(encfd[0]);
encfd[0] = ttyopen(Conf.EncoderDevPath, (speed_t) Conf.EncoderDevSpeed);
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)){
close(encfd[0]);
encfd[0] = -1;
@@ -632,8 +666,6 @@ int openEncoder(){
encfd[i] = ttyopen(paths[i], (speed_t) Conf.EncoderDevSpeed);
if(encfd[i] < 0) return FALSE;
}
encRtmout.tv_sec = 0;
encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed;
if(pthread_create(&encthread, NULL, encoderthread2, NULL)){
for(int i = 0; i < 2; ++i){
close(encfd[i]);
@@ -648,6 +680,7 @@ int openEncoder(){
// return FALSE if failed
int openMount(){
// TODO: open real devices in "model" mode too!
if(Conf.RunModel) goto create_thread;
if(mntfd > -1) close(mntfd);
DBG("Open mount %s @ %d", Conf.MountDevPath, Conf.MountDevSpeed);
@@ -655,16 +688,13 @@ int openMount(){
if(mntfd < 0) return FALSE;
DBG("mntfd=%d", mntfd);
// clear buffer
while(getmntbyte() > -1);
/*int g = write(mntfd, "XXS\r", 4);
DBG("Written %d", g);
uint8_t buf[100];
do{
ssize_t l = read(mntfd, buf, 100);
DBG("got %zd", l);
}while(1);*/
clrmntbuf();
/*
mnt1Rtmout.tv_sec = 0;
mnt1Rtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes * 10bits / speed
mntRtmout.tv_sec = 0;
mntRtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes * 10bits / speed
mntRtmout.tv_usec = mnt1Rtmout.tv_usec / 50;
*/
create_thread:
if(pthread_create(&mntthread, NULL, mountthread, NULL)){
DBG("Can't create mount thread");
@@ -680,15 +710,18 @@ create_thread:
// close all opened serial devices and quit threads
void closeSerial(){
// TODO: close devices in "model" mode too!
if(Conf.RunModel) return;
GlobExit = 1;
pthread_mutex_unlock(&datamutex);
DBG("Give 100ms to proper close");
usleep(100000);
DBG("Force closed all devices");
if(mntfd > -1){
DBG("Cancel mount thread");
pthread_cancel(mntthread);
DBG("join mount thread");
pthread_join(mntthread, NULL);
DBG("close mount fd");
close(mntfd);
if(mntfd > -1) close(mntfd);
mntfd = -1;
}
if(encfd[0] > -1){
@@ -697,13 +730,14 @@ void closeSerial(){
DBG("join encoder thread");
pthread_join(encthread, NULL);
DBG("close encoder's fd");
close(encfd[0]);
if(encfd[0] > -1) close(encfd[0]);
encfd[0] = -1;
if(Conf.SepEncoder == 2 && encfd[1] > -1){
close(encfd[1]);
encfd[1] = -1;
}
}
GlobExit = 0;
}
// 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");
return FALSE;
}
//DBG("clrbuf");
clrmntbuf();
if(out){
//DBG("write %zd bytes (%s)", out->len, out->buf);
if(out->len != (size_t)write(mntfd, out->buf, out->len)){
DBG("written bytes not equal to need");
return FALSE;
}
//DBG("eol, mntfd=%d", mntfd);
if(needeol){
int g = write(mntfd, "\r", 1); // add EOL
(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;
in->len = 0;
for(size_t i = 0; i < in->maxlen; ++i){
int b = getmntbyte();
if(b < 0) break; // nothing to read -> go out
in->buf[in->len++] = (uint8_t) b;
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;
return FALSE;
}
while(getmntbyte() > -1);
in->len = got;
return TRUE;
}
@@ -761,22 +798,24 @@ static int wr(const data_t *out, data_t *in, int needeol){
* @return FALSE if failed
*/
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);
int ret = wr(out, in, 1);
pthread_mutex_unlock(&mntmutex);
//DBG("Got %gus", (timefromstart()-t0)*1e6);
return ret;
}
// send binary data - without EOL
int MountWriteReadRaw(const data_t *out, data_t *in){
if(Conf.RunModel) return -1;
if(Conf.RunModel) return FALSE;
pthread_mutex_lock(&mntmutex);
int ret = wr(out, in, 0);
pthread_mutex_unlock(&mntmutex);
return ret;
}
#ifdef EBUG
#if 0
static void logscmd(SSscmd *c){
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);
@@ -799,31 +838,37 @@ static int bincmd(uint8_t *cmd, int len){
if(!dlcmd) dlcmd = cmd2dat(CMD_LONGCMD);
int ret = FALSE;
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)){
((SSscmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
DBG("Short command");
#ifdef EBUG
//DBG("Short command");
#if 0
logscmd((SSscmd*)cmd);
#endif
if(!wr(dscmd, NULL, 1)) goto rtn;
}else if(len == sizeof(SSlcmd)){
((SSlcmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
DBG("Long command");
#ifdef EBUG
// DBG("Long command");
#if 0
loglcmd((SSlcmd*)cmd);
#endif
if(!wr(dlcmd, NULL, 1)) goto rtn;
}else{
goto rtn;
}
data_t d;
SSstat ans;
data_t d, in;
d.buf = cmd;
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");
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:
pthread_mutex_unlock(&mntmutex);
return ret;

View 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

View File

@@ -136,10 +136,11 @@ typedef struct{
} extradata_t;
typedef enum{
AXIS_STOPPED,
AXIS_SLEWING,
AXIS_POINTING,
AXIS_GUIDING,
AXIS_STOPPED, // stop
AXIS_GONNASTOP, // stop command run
AXIS_SLEWING, // go to target with maximal speed
AXIS_POINTING, // axis is in pointing zone, use PID
AXIS_GUIDING, // near target
AXIS_ERROR,
} axis_status_t;
@@ -157,6 +158,9 @@ typedef struct{
uint32_t millis;
double temperature;
double voltage;
// target X/Y position by last `short` or `long` command
int32_t Xtarget; // in SidServo's counts
int32_t Ytarget; // -//-
} mountdata_t;
typedef struct{

View File

@@ -26,8 +26,12 @@
#include "serial.h"
#include "ssii.h"
int X_ENC_ZERO, Y_ENC_ZERO;
double X_MOT_STEPSPERREV = 1., Y_MOT_STEPSPERREV = 1., X_ENC_STEPSPERREV = 1., Y_ENC_STEPSPERREV = 1.;
int X_ENC_ZERO = 0, Y_ENC_ZERO = 0; // will be filled later from config
// 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 checksum = 0;
@@ -40,30 +44,6 @@ uint16_t SScalcChecksum(uint8_t *buf, int len){
return checksum;
}
// Next three functions runs under locked mountdata_t mutex and shouldn't call locked it again!!
static 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
* @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;
m->motXposition.val = X_MOT2RAD(s->Xmot);
m->motYposition.val = Y_MOT2RAD(s->Ymot);
ChkStopped(s, m);
m->motXposition.t = m->motYposition.t = *t;
// fill encoder data from here, as there's no separate enc thread
if(!Conf.SepEncoder){
DBG("ENCODER from SSII");
m->encXposition.val = Xenc2rad(s->Xenc);
DBG("encx: %g", m->encXposition.val);
m->encYposition.val = Yenc2rad(s->Yenc);
@@ -165,14 +145,17 @@ int SSsetterI(const char *cmd, int32_t ival){
}
int SSstop(int emerg){
FNAME();
int i = 0;
const char *cmdx = (emerg) ? CMD_EMSTOPX : CMD_STOPX;
const char *cmdy = (emerg) ? CMD_EMSTOPY : CMD_STOPY;
setStat(AXIS_GONNASTOP, AXIS_GONNASTOP);
for(; i < 10; ++i){
if(!SStextcmd(cmdx, NULL)) continue;
if(SStextcmd(cmdy, NULL)) break;
}
if(i == 10) return FALSE;
DBG("Stopped");
return TRUE;
}

View File

@@ -209,12 +209,14 @@ extern double X_MOT_STEPSPERREV, Y_MOT_STEPSPERREV, X_ENC_STEPSPERREV, Y_ENC_STE
// convert angle in radians to +-pi
static inline __attribute__((always_inline)) double ang2half(double ang){
ang = fmod(ang, 2.*M_PI);
if(ang < -M_PI) ang += 2.*M_PI;
else if(ang > M_PI) ang -= 2.*M_PI;
return ang;
}
// convert to only positive: 0..2pi
static inline __attribute__((always_inline)) double ang2full(double ang){
ang = fmod(ang, 2.*M_PI);
if(ang < 0.) ang += 2.*M_PI;
else if(ang > 2.*M_PI) ang -= 2.*M_PI;
return ang;

View File

@@ -29,7 +29,7 @@
// make datetime/pressure/temperature corrections each CORRECTIONS_TIMEDIFF seconds
#define CORRECTIONS_TIMEDIFF (3600)
#define TELESCOPE_NAME "'Astrosib-500 (1)'"
#define TELESCOPE_NAME "'Astrosib-500 (2)'"
// telescope statuses
typedef enum{

View File

@@ -274,7 +274,7 @@ int mygetchar(){ // getchar() without need of pressing ENTER
/******************************************************************************\
* TTY with select()
\******************************************************************************/
static struct termio oldtty, tty; // TTY flags
static struct termios oldtty, tty; // TTY flags
static int comfd = -1; // TTY fd
// run on exit:

View File

@@ -33,6 +33,8 @@
#include <errno.h>
#include <err.h>
#include <locale.h>
#include <sys/ioctl.h>
#if defined GETTEXT_PACKAGE && defined LOCALEDIR
/*
* GETTEXT
@@ -47,7 +49,6 @@
#endif
#include <stdlib.h>
#include <termios.h>
#include <termio.h>
#include <sys/time.h>
#include <time.h>
#include <sys/types.h>

117
Daemons/2DO.txt Normal file
View File

@@ -0,0 +1,117 @@
(хорошо бы нигде не ориентироваться на DNS, везде писать IP)
### superweatherdaemon ###
Все "станции" (* - готово):
- старая метео *
- новая метео *
- датчик дождя *
- датчик грозы (молния <=5км -> FORCEOFF=1; молния >=10км - FORCEOFF=0)
поля: LIGTPWR, LIGTDIST, LIGTTIME
- ИБП в стойке (через минуту после выключения света FORCEOFF=1; через пять минут после включения - FORCEOFF=0)
поля: POWERED (0 - внешнее питание комплекса отключено, 1 - включено)
- ИК-allsky (как будет готов - получение "процента облачности" и "температуры неба")
поля: CLOUDS, SKYTEMP
В выдаваемую клиентам информацию добавить "стандартное" поле FORCEOFF (если ==1 - парковаться, закрываться и выключаться,
в т.ч. компьютер).
Только ИБП и датчик грозы влияют на поле FORCEOFF. FORCEOFF автоматом ставит WEATHER=3.
Флаг FORCEOFF - исключительная прерогатива "супердемона", как и поле WEATHER. Значение флага снимается лишь по устареванию,
как для прочих полей с проверкой на слишком старые данные. Лишь если снят FORCEOFF, поле WEATHER может начать понижать
уровень. Сразу со снятием флага FORCEOFF, снижаем уровень WEATHER до 2. Следовательно, запускать наблюдения можно будет
лишь через 2N секунд (кстати, добавить в конфиг время ожидания понижения уровня погоды) после подачи питания.
??? Добавить "стандартные" поля: LIGTPWR, LIGTDIST, LIGTTIME (например, если гроза еще дальше 5км, писать эти данные
в FITS-шапку).
Обязательно логгировать источники повышения и понижения уровня погоды, а также источник установки флага FORCEOFF.
В управляющий UNIX-сокет добавить комады:
- forceoff - чтобы можно было вручную ставить/снимать флаг
- weath - вручную сменять уровень погоды
- mute/unmute плагинов (по номеру из list); если плагин mute, то вся информация от него игнорируется
- ?
### Демон общего питания (robometeo) ###
Мониторит FORCEOFF. При ==1 выжидает OFF_T_WAIT секунд, затем начинает проверять все 5 "роботелов": если купол закрыт и не пингуется,
отключаем питание оборудования и питание купола (кроме первого). Через OFF_T_FORCE секунд после сигнала даже если комп пингуется,
но купол закрыт, питание отключаем. Интервалы времени - в конфигурационный файл.
Если все отключено, ждет, пока не наступит 0. Потом выжидает еще ON_WAIT секунд, после чего последовательно подает питание
на все купола, выжидает ON_FORCE секунд и поочередно включает нагрузку всех ИБП куполов. Проверяет пингуемость компов и
пишет в лог, во сколько какой запинговался.
Аналогично с приборами в стойке: после OFF_R_WAIT секунд пинговать roboserv, robonas и robostorage; как не пингуются все -
выключать питание стойки. Либо же принудительно отключать после OFF_R_FORCE секунд. При снятии флага выжидаем ON_WAIT секунд,
затем включаем напряжение стойки и пингуем ее компы, логгируем.
В конфиг-файл: IP-адреса и номера релюшек оборудования телескопов и стойки, например,
telescopes=Astro-M1, Astro-M2...
Astro-M1=192.168.70.33:1:-1 # Название из telescopes = IP:номер реле оборудования:номер реле питания или -1
Astro-M2=192.168.70.35:2:22
...
equipment=roboserv,robonas,robostorage
roboserv=192.168.70.6:11:-1 # аналогично должен вести себя 192.168.70.6:-1:11
...
(при разборе конфига группировать по номеру реле оборудования и номеру реле питания; не отключать реле питания, пока
не отключены все компы, кроме выхода таймаута)
### weather proxy ###
Добавить флаг forceoff.
### Clients ###
Все клиентские демоны используют weather_proxy и его библиотеку, чтобы получать из SHM погодные данные.
Демоны купола, телескопа и монтировки обязаны создавать в /tmp файлы с FITS-шапками (по ним "демон питания" узнает, что
можно выключать компьютер).
Клиенты реагируют на WEATHER:
0 - можно работать
1 - нельзя открываться, но можно продолжать работать
2 - форсированное закрывание купола, останов телескопа, остальное без изменений
3 - закрывание всего, парковка телескопа
## Демон купола ##
Форсированное закрывание при WEATHER>1 или FORCEOFF=1.
## Демон телескопа ##
Форсированное закрывание при WEATHER>2 или FORCEOFF=1.
## Демон монтировки ##
Останов при WEATHER=2 или FORCEOFF=1, парковка при WEATHER=3 (в случае FORCEOFF=1 ни в коем случае не парковать).
ДОБАВИТЬ ключ HDRTIME // UNIX-time of last activity -> в этом ключе содержится sl_dtime() обновления шапки (нужно для
демона питания, чтобы контролировать, когда отмерла монтировка).
## Демон питания оборудования и компьютера ##
Этот демон запускается из-под рута. Принимает от пользователя команды вклчения-выключения оборудования (сюда же можно
воткнуть управление плоским полем, пинание монтировки на включение, управление светом).
При получении FORCEOFF=1, сначала щелкается кнопка отключения монтировки. Далее
проверяются DOMFITSHDR (DOMESTAT= closed), TELFITSHDR (TELSTAT = closed) и MOUNTFITSHDR (HDRTIME должен быть минимум
на 30 секунд старше текущего времени).
Если все ОК, то выключается навесное оборудование и монтировка, а следом - и сам компьютер получает сигнал poweroff.
sync(); // Flush disk buffer to prevent data loss
reboot(RB_POWER_OFF);
или (правильней?): system("shutdown -P now")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More