Compare commits

...

23 Commits

Author SHA1 Message Date
fa305a7cc0 fix encoders 2026-03-11 10:05:02 +03:00
205a190820 some fixes, sill have troubles with dummy socket/fd 2026-03-06 12:20:10 +03:00
7c2aaf1cb0 add serial/socket plugin example 2026-02-27 18:31:22 +03:00
54778dcf9a add pre-pre-alpha 2026-02-27 12:05:40 +03:00
Edward Emelianov
31752b58cc polltest 2026-01-26 22:27:51 +03:00
09642743a6 add some bash scripts 2026-01-26 17:16:49 +03:00
a80347643f fixed for new vesrion of usefull_macros 2026-01-26 16:54:50 +03:00
Edward Emelianov
50cbaea550 some work done 2026-01-24 00:57:11 +03:00
c8449c916a fixed for no-connect error 2026-01-20 09:27:06 +03:00
74d9ebb75f .. 2025-12-08 21:04:06 +03:00
Edward Emelianov
2e9241f079 time 2025-11-01 19:54:18 +03:00
2aa8502796 . 2025-11-01 15:39:55 +03:00
f6edc12b01 fixed time 2025-11-01 15:02:11 +03:00
27a7388164 OK 2025-09-26 14:57:24 +03:00
730629e933 found another manual 2025-09-25 17:44:45 +03:00
b77cb2277b starting of 'SILA' tool 2025-09-25 17:27:48 +03:00
581cdc60f9 is moving model really works? 2025-08-06 15:09:07 +03:00
Edward Emelianov
46ff11df58 next 2025-07-29 22:14:29 +03:00
Edward Emelianov
e1f0a0804f SSII - is a really piece of shit! 2025-07-23 22:55:23 +03:00
Edward Emelianov
7634856967 refactoring 2025-07-23 00:01:33 +03:00
89815e8981 force to new usefull_macros lib 2025-06-23 16:35:29 +03:00
db2c0e2d9c add wind direction 2025-06-23 11:33:58 +03:00
453a56429d fix weatherdaemon for new usefull_macros 2025-06-23 11:08:39 +03:00
191 changed files with 11749 additions and 1054 deletions

3
.gitignore vendored
View File

@@ -23,3 +23,6 @@
*.so *.so
*.so.* *.so.*
# build dirs
mk/
build/

View File

@@ -49,7 +49,7 @@ static glob_pars const Gdefault = {
* Define command line options by filling structure: * Define command line options by filling structure:
* name has_arg flag val type argptr help * name has_arg flag val type argptr help
*/ */
static myoption cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
// common options // common options
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"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_DEV ")")}, {"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), _("serial device name (default: )" DEFAULT_DEV ")")},
@@ -75,10 +75,10 @@ glob_pars *parse_args(int argc, char **argv){
char helpstring[1024], *hptr = helpstring; char helpstring[1024], *hptr = helpstring;
snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n");
// format of help: "Usage: progname [args]\n" // format of help: "Usage: progname [args]\n"
change_helpstring(helpstring); sl_helpstring(helpstring);
// parse arguments // parse arguments
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts); if(help) sl_showhelp(-1, cmdlnopts);
if(argc > 0){ if(argc > 0){
WARNX("Ignore %d unknown parameters: "); WARNX("Ignore %d unknown parameters: ");
for (i = 0; i < argc; i++) for (i = 0; i < argc; i++)

View File

@@ -57,7 +57,7 @@ static void dumpRchanges(rg11 *new, rg11 *old){
int start = 1; int start = 1;
for(int i = 0; i < RREGNUM; ++i){ for(int i = 0; i < RREGNUM; ++i){
if(o[i] != n[i]){ if(o[i] != n[i]){
sl_putlogt(start, globlog, LOGLEVEL_MSG, "%s=%d", regname(i), n[i]); sl_putlogt(start, sl_globlog, LOGLEVEL_MSG, "%s=%d", regname(i), n[i]);
DBG("%s=%d", regname(i), n[i]); DBG("%s=%d", regname(i), n[i]);
if(start) start = 0; if(start) start = 0;
} }
@@ -68,7 +68,7 @@ static void dumpRchanges(rg11 *new, rg11 *old){
uint8_t f = 1; uint8_t f = 1;
for(int i = 0; i < RGBITNUM; ++i, f <<= 1){ for(int i = 0; i < RGBITNUM; ++i, f <<= 1){
if(xOr & f){ if(xOr & f){
sl_putlogt(start, globlog, LOGLEVEL_MSG, "%s=%d", rgbitname(i), (new->RGBits & f) ? 1 : 0); sl_putlogt(start, sl_globlog, LOGLEVEL_MSG, "%s=%d", rgbitname(i), (new->RGBits & f) ? 1 : 0);
DBG("%s=%d", rgbitname(i), (new->RGBits & f) ? 1 : 0); DBG("%s=%d", rgbitname(i), (new->RGBits & f) ? 1 : 0);
if(start) start = 0; if(start) start = 0;
} }
@@ -82,7 +82,7 @@ static void dumpSchanges(slowregs *new, slowregs *old){
int start = 1; int start = 1;
for(int i = 0; i < SREGNUM; ++i){ for(int i = 0; i < SREGNUM; ++i){
if(o[i] != n[i]){ if(o[i] != n[i]){
sl_putlogt(start, globlog, LOGLEVEL_MSG, "%s=%d", slowname(i), n[i]); sl_putlogt(start, sl_globlog, LOGLEVEL_MSG, "%s=%d", slowname(i), n[i]);
DBG("%s=%d", slowname(i), n[i]); DBG("%s=%d", slowname(i), n[i]);
if(start) start = 0; if(start) start = 0;
} }
@@ -147,12 +147,12 @@ static void puttotable(rg11 *R, slowregs *S){
} }
int main(int argc, char **argv){ int main(int argc, char **argv){
initial_setup(); sl_init();
char *self = strdup(argv[0]); char *self = strdup(argv[0]);
G = parse_args(argc, argv); G = parse_args(argc, argv);
if(G->timeout < 5) ERRX("Timeout should be not less than 5 seconds"); if(G->timeout < 5) ERRX("Timeout should be not less than 5 seconds");
if(!G->logfile && !G->outfile) ERRX("Point at least log or output file name"); if(!G->logfile && !G->outfile) ERRX("Point at least log or output file name");
check4running(self, G->pidfile); sl_check4running(self, G->pidfile);
if(!hydreon_open(G->device)) return 1; if(!hydreon_open(G->device)) return 1;
if(G->logfile) OPENLOG(G->logfile, LOGLEVEL_ANY, 0); if(G->logfile) OPENLOG(G->logfile, LOGLEVEL_ANY, 0);
if(G->outfile){ if(G->outfile){
@@ -166,9 +166,9 @@ int main(int argc, char **argv){
signal(SIGINT, signals); // ctrl+C - quit signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
double t0 = dtime(); double t0 = sl_dtime();
puttotable(NULL, NULL); puttotable(NULL, NULL);
while(dtime() - t0 < (double)G->timeout){ // dump only changes while(sl_dtime() - t0 < (double)G->timeout){ // dump only changes
if(!hydreon_getpacket(&Rregs, &Sregs)) continue; if(!hydreon_getpacket(&Rregs, &Sregs)) continue;
int changes = FALSE; int changes = FALSE;
if(memcmp(&Rregs, &oRregs, RREGNUM + 1)){ // Rregs changed -> log changes if(memcmp(&Rregs, &oRregs, RREGNUM + 1)){ // Rregs changed -> log changes
@@ -182,7 +182,7 @@ int main(int argc, char **argv){
changes = TRUE; changes = TRUE;
} }
if(changes) puttotable(&Rregs, &Sregs); if(changes) puttotable(&Rregs, &Sregs);
t0 = dtime(); t0 = sl_dtime();
} }
signals(-1); // never reached signals(-1); // never reached
return 0; return 0;

View File

@@ -20,7 +20,7 @@
#include "hydreon.h" #include "hydreon.h"
static TTY_descr *dev = NULL; static sl_tty_t *dev = NULL;
// regular registers names // regular registers names
static const char* rregnames[RREGNUM] = { static const char* rregnames[RREGNUM] = {
@@ -121,7 +121,7 @@ int hydreon_getpacket(rg11 *Rregs, slowregs *Sregs){
if(!dev) return 0; if(!dev) return 0;
static int buflen = 0; static int buflen = 0;
static char strbuf[BUFLEN]; static char strbuf[BUFLEN];
int l = read_tty(dev); int l = sl_tty_read(dev);
if(l < 1) return FALSE; if(l < 1) return FALSE;
char s = dev->buf[0]; char s = dev->buf[0];
if(s == 's'){ // start of new packet -> encode old if(s == 's'){ // start of new packet -> encode old
@@ -146,13 +146,13 @@ int hydreon_getpacket(rg11 *Rregs, slowregs *Sregs){
* @return TRUE or FALSE if failed * @return TRUE or FALSE if failed
*/ */
int hydreon_open(const char *devname){ int hydreon_open(const char *devname){
dev = new_tty((char*)devname, 1200, 1); dev = sl_tty_new((char*)devname, 1200, 1);
if(!dev) return FALSE; if(!dev) return FALSE;
dev = tty_open(dev, 1); dev = sl_tty_open(dev, 1);
if(!dev) return FALSE; if(!dev) return FALSE;
return TRUE; return TRUE;
} }
void hydreon_close(){ void hydreon_close(){
if(dev) close_tty(&dev); if(dev) sl_tty_close(&dev);
} }

View File

@@ -0,0 +1,221 @@
/*
* This file is part of the libsidservo project.
* Copyright 2025 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 <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include "main.h"
#include "PID.h"
#include "serial.h"
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));
return pid;
}
// don't clear lastT!
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;
}
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 flowing integral
double oldi = pid->pidIarray[pid->curIidx], newi = error * dt;
//DBG("oldi/new: %g, %g", oldi, newi);
pid->pidIarray[pid->curIidx++] = newi;
if(pid->curIidx >= pid->pidIarrSize) pid->curIidx = 0;
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);
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
*/
static double getspeed(const coordval_t *tagpos, PIDpair_t *pidpair, 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;
switch(axis->state){
case AXIS_SLEWING:
if(fe < Conf.MaxPointingErr){
axis->state = AXIS_POINTING;
DBG("--> Pointing");
pid = pidpair->PIDC;
}else{
DBG("Slewing...");
return NAN; // max speed for given axis
}
break;
case AXIS_POINTING:
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);
break;
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);
case AXIS_ERROR:
DBG("Can't move from erroneous state");
return 0.;
}
if(!pid){
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
}
/**
* @brief correct2 - recalculate PID and move telescope to new point with new speed
* @param target - target position (for error calculations)
* @param endpoint - stop point (some far enough point to stop in case of hang)
* @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;
}
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;
}
mountdata_t m;
coordpair_t tagspeed; // absolute value of speed
double Xsign = 1., Ysign = 1.; // signs of speed (for target calculation)
if(MCC_E_OK != Mount.getMountData(&m)) return MCC_E_FAILED;
axisdata_t axis;
DBG("state: %d/%d", m.Xstate, m.Ystate);
axis.state = m.Xstate;
axis.position = m.encXposition;
axis.speed = m.encXspeed;
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;
}else{
if(tagspeed.X < 0.){ tagspeed.X = -tagspeed.X; Xsign = -1.; }
if(tagspeed.X > Xlimits.max.speed) tagspeed.X = Xlimits.max.speed;
}
axis_status_t xstate = axis.state;
axis.state = m.Ystate;
axis.position = m.encYposition;
axis.speed = m.encYspeed;
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;
}else{
if(tagspeed.Y < 0.){ tagspeed.Y = -tagspeed.Y; Ysign = -1.; }
if(tagspeed.Y > Ylimits.max.speed) tagspeed.Y = Ylimits.max.speed;
}
axis_status_t ystate = axis.state;
if(m.Xstate != xstate || m.Ystate != ystate){
DBG("State changed");
setStat(xstate, ystate);
}
coordpair_t endpoint;
// 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
+ Conf.PIDMaxDt * tagspeed.X // PIDMaxDt const speed moving
+ tagspeed.X * tagspeed.X / Xlimits.max.accel / 2.; // stopping
endpoint.X = m.encXposition.val + Xsign * adder;
dv = fabs(tagspeed.Y - m.encYspeed.val);
adder = dv/Ylimits.max.accel * (m.encYspeed.val + dv / 2.)
+ Conf.PIDMaxDt * tagspeed.Y
+ tagspeed.Y * tagspeed.Y / Ylimits.max.accel / 2.;
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

@@ -0,0 +1,40 @@
/*
* This file is part of the libsidservo project.
* Copyright 2025 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 <stddef.h>
#include "sidservo.h"
typedef struct {
PIDpar_t gain; // PID gains
double prev_error; // Previous error
double integral; // Integral term
double *pidIarray; // array for Integral
struct timespec prevT; // time of previous correction
size_t pidIarrSize; // it's size
size_t curIidx; // and index of current element
} PIDController_t;
PIDController_t *pid_create(const PIDpar_t *gain, size_t Iarrsz);
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

@@ -0,0 +1,141 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <stdio.h>
#include <usefull_macros.h>
#include "Dramp.h"
static movestate_t state = ST_STOP;
static moveparam_t target, Min, Max;
static double T0 = -1., Xlast0; // time when move starts, last stage starting coordinate
static moveparam_t curparams = {0}; // current coordinate/speed/acceleration
static double T1 = -1.; // time to switch into minimal speed for best coord tolerance
typedef enum{
STAGE_NORMALSPEED,
STAGE_MINSPEED,
STAGE_STOPPED
} movingsage_t;
static movingsage_t movingstage = STAGE_STOPPED;
static int initlims(limits_t *lim){
if(!lim) return FALSE;
Min = lim->min;
Max = lim->max;
return TRUE;
}
static int calc(moveparam_t *x, double t){
DBG("target: %g, tagspeed: %g (maxspeed: %g, minspeed: %g)", x->coord, x->speed, Max.speed, Min.speed);
if(!x || t < 0.) return FALSE;
if(x->speed > Max.speed || x->speed < Min.speed || x->coord < Min.coord || x->coord > Max.coord) return FALSE;
double adist = fabs(x->coord - curparams.coord);
DBG("want dist: %g", adist);
if(adist < coord_tolerance) return TRUE; // we are at place
if(adist < time_tick * Min.speed) return FALSE; // cannot reach with current parameters
target = *x;
if(x->speed * time_tick > adist) target.speed = adist / (10. * time_tick); // take at least 10 ticks to reach position
if(target.speed < Min.speed) target.speed = Min.speed;
DBG("Approximate tag speed: %g", target.speed);
T0 = t;
// calculate time to switch into minimal speed
T1 = -1.; // no min speed phase
if(target.speed > Min.speed){
double dxpertick = target.speed * time_tick;
DBG("dX per one tick: %g", dxpertick);
double ticks_need = floor(adist / dxpertick);
DBG("ticks need: %g", ticks_need);
if(ticks_need < 1.) return FALSE; // cannot reach
if(fabs(ticks_need * dxpertick - adist) > coord_tolerance){
DBG("Need to calculate slow phase; can't reach for %g ticks at current speed", ticks_need);
double dxpersmtick = Min.speed * time_tick;
DBG("dX per smallest tick: %g", dxpersmtick);
while(--ticks_need > 1.){
double part = adist - ticks_need * dxpertick;
double smticks = floor(part / dxpersmtick);
double least = part - smticks * dxpersmtick;
if(least < coord_tolerance) break;
}
DBG("now BIG ticks: %g, T1=T0+%g", ticks_need, ticks_need*time_tick);
T1 = t + ticks_need * time_tick;
}
}
state = ST_MOVE;
Xlast0 = curparams.coord;
if(target.speed > Min.speed) movingstage = STAGE_NORMALSPEED;
else movingstage = STAGE_MINSPEED;
if(x->coord < curparams.coord) target.speed *= -1.; // real speed
curparams.speed = target.speed;
return TRUE;
}
static void stop(double _U_ t){
T0 = -1.;
curparams.accel = 0.;
curparams.speed = 0.;
state = ST_STOP;
movingstage = STAGE_STOPPED;
}
static movestate_t proc(moveparam_t *next, double t){
if(T0 < 0.) return ST_STOP;
curparams.coord = Xlast0 + (t - T0) * curparams.speed;
//DBG("coord: %g (dTmon: %g, speed: %g)", curparams.coord, t-T0, curparams.speed);
int ooops = FALSE; // oops - we are over target!
if(curparams.speed < 0.){ if(curparams.coord < target.coord) ooops = TRUE;}
else{ if(curparams.coord > target.coord) ooops = TRUE; }
if(ooops){
DBG("OOOps! We are (%g) over target (%g) -> stop", curparams.coord, target.coord);
stop(t);
if(next) *next = curparams;
return state;
}
if(movingstage == STAGE_NORMALSPEED && T1 > 0.){ // check need of T1
if(t >= T1){
DBG("T1=%g, t=%g -->", T1, t);
curparams.speed = (curparams.speed > 0.) ? Min.speed : -Min.speed;
movingstage = STAGE_MINSPEED;
Xlast0 = curparams.coord;
T0 = T1;
DBG("Go further with minimal speed");
}
}
if(fabs(curparams.coord - target.coord) < coord_tolerance){ // we are at place
DBG("OK, we are in place");
stop(t);
}
if(next) *next = curparams;
return state;
}
static movestate_t getst(moveparam_t *cur){
if(cur) *cur = curparams;
return state;
}
movemodel_t dumb = {
.init_limits = initlims,
.calculate = calc,
.proc_move = proc,
.stop = stop,
.emergency_stop = stop,
.get_state = getst,
};

View File

@@ -0,0 +1,23 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 "moving_private.h"
extern movemodel_t dumb;

View File

@@ -0,0 +1,57 @@
# run `make DEF=...` to add extra defines
PROGRAM := moving
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros -lm
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines -std=gnu99
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
DEPS := $(OBJS:.o=.d)
TARGFILE := $(OBJDIR)/TARGET
CC = gcc
#TARGET := RELEASE
ifeq ($(shell test -e $(TARGFILE) && echo -n yes),yes)
TARGET := $(file < $(TARGFILE))
else
TARGET := RELEASE
endif
ifeq ($(TARGET), DEBUG)
.DEFAULT_GOAL := debug
endif
release: $(PROGRAM)
debug: CFLAGS += -DEBUG -Werror
debug: TARGET := DEBUG
debug: $(PROGRAM)
$(TARGFILE): $(OBJDIR)
@echo -e "\t\tTARGET: $(TARGET)"
@echo "$(TARGET)" > $(TARGFILE)
$(PROGRAM) : $(TARGFILE) $(OBJS)
@echo -e "\t\tLD $(PROGRAM)"
$(CC) $(OBJS) $(LDFLAGS) -o $(PROGRAM)
$(OBJDIR):
@mkdir $(OBJDIR)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR)/%.o: %.c
@echo -e "\t\tCC $<"
$(CC) $< -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@
clean:
@echo -e "\t\tCLEAN"
@rm -rf $(OBJDIR) 2>/dev/null || true
xclean: clean
@rm -f $(PROGRAM)
.PHONY: clean xclean

View File

@@ -0,0 +1,64 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <usefull_macros.h>
#include "PID.h"
PIDController_t *pid_create(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;
pid->pidIarrSize = Iarrsz;
pid->pidIarray = (double*)calloc(Iarrsz, sizeof(double));
return pid;
}
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;
}
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 flowing integral
double oldi = pid->pidIarray[pid->curIidx], newi = error * dt;
DBG("oldi/new: %g, %g", oldi, newi);
pid->pidIarray[pid->curIidx++] = newi;
if(pid->curIidx >= pid->pidIarrSize) pid->curIidx = 0;
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);
return sum;
}

View File

@@ -0,0 +1,39 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <stddef.h>
typedef struct{
double P, I, D;
} PIDpar_t;
typedef struct {
PIDpar_t gain; // PID gains
double prev_error; // Previous error
double integral; // Integral term
double *pidIarray; // array for Integral
size_t pidIarrSize; // it's size
size_t curIidx; // and index of current element
} PIDController_t;
PIDController_t *pid_create(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);

View File

@@ -0,0 +1,27 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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/>.
*/
// quasi non-jerk s-ramp
#include <stdio.h>
#include <usefull_macros.h>
#include "Sramp.h"
movemodel_t s_shaped = { 0 };

View File

@@ -0,0 +1,23 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 "moving_private.h"
extern movemodel_t s_shaped;

View File

@@ -0,0 +1,223 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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/>.
*/
// simplest trapezioidal ramp
#include <math.h>
#include <stdio.h>
#include <strings.h>
#include <usefull_macros.h>
#include "Tramp.h"
#undef DBG
#define DBG(...)
static movestate_t state = ST_STOP;
static moveparam_t Min, Max; // `Min` acceleration not used!
typedef enum{
STAGE_ACCEL, // start from zero speed and accelerate to Max speed
STAGE_MAXSPEED, // go with target speed
STAGE_DECEL, // go from target speed to zero
STAGE_STOPPED, // stop
STAGE_AMOUNT
} movingstage_t;
static movingstage_t movingstage = STAGE_STOPPED;
static double Times[STAGE_AMOUNT] = {0}; // time when each stage starts
static moveparam_t Params[STAGE_AMOUNT] = {0}; // starting parameters for each stage
static moveparam_t curparams = {0}; // current coordinate/speed/acceleration
static int initlims(limits_t *lim){
if(!lim) return FALSE;
Min = lim->min;
Max = lim->max;
return TRUE;
}
static void emstop(double _U_ t){
curparams.accel = 0.;
curparams.speed = 0.;
bzero(Times, sizeof(Times));
bzero(Params, sizeof(Params));
state = ST_STOP;
movingstage = STAGE_STOPPED;
}
static void stop(double t){
if(state == ST_STOP || movingstage == STAGE_STOPPED) return;
movingstage = STAGE_DECEL;
state = ST_MOVE;
Times[STAGE_DECEL] = t;
Params[STAGE_DECEL].speed = curparams.speed;
if(curparams.speed > 0.) Params[STAGE_DECEL].accel = -Max.accel;
else Params[STAGE_DECEL].accel = Max.accel;
Params[STAGE_DECEL].coord = curparams.coord;
// speed: v=v2+a2(t-t2), v2 and a2 have different signs; t3: v3=0 -> t3=t2-v2/a2
Times[STAGE_STOPPED] = t - curparams.speed / Params[STAGE_DECEL].accel;
// coordinate: x=x2+v2(t-t2)+a2(t-t2)^2/2 -> x3=x2+v2(t3-t2)+a2(t3-t2)^2/2
double dt = Times[STAGE_STOPPED] - t;
Params[STAGE_STOPPED].coord = curparams.coord + curparams.speed * dt +
Params[STAGE_DECEL].accel * dt * dt / 2.;
}
/**
* @brief calc - moving calculation
* @param x - using max speed (>0!!!) and coordinate
* @param t - current time value
* @return FALSE if can't move with given parameters
*/
static int calc(moveparam_t *x, double t){
if(!x) return FALSE;
if(x->coord < Min.coord || x->coord > Max.coord) return FALSE;
if(x->speed < Min.speed || x->speed > Max.speed) return FALSE;
double Dx = fabs(x->coord - curparams.coord); // full distance
double sign = (x->coord > curparams.coord) ? 1. : -1.; // sign of target accelerations and speeds
// we have two variants: with or without stage with constant speed
double dt23 = x->speed / Max.accel; // time of deceleration stage for given speed
double dx23 = x->speed * dt23 / 2.; // distance on dec stage (abs)
DBG("Dx=%g, sign=%g, dt23=%g, dx23=%g", Dx, sign, dt23, dx23);
double setspeed = x->speed; // new max speed (we can change it if need)
double dt01, dx01; // we'll fill them depending on starting conditions
Times[0] = t;
Params[0].speed = curparams.speed;
Params[0].coord = curparams.coord;
double curspeed = fabs(curparams.speed);
double dt0s = curspeed / Max.accel; // time of stopping phase
double dx0s = curspeed * dt0s / 2.; // distance
DBG("dt0s=%g, dx0s=%g", dt0s, dx0s);
if(dx0s > Dx){
WARNX("distance too short");
return FALSE;
}
if(fabs(Dx - dx0s) < coord_tolerance){ // just stop and we'll be on target
DBG("Distance good to just stop");
stop(t);
return TRUE;
}
if(curparams.speed * sign < 0. || state == ST_STOP){ // we should change speed sign
// after stop we will have full profile
double dxs3 = Dx - dx0s;
double newspeed = sqrt(Max.accel * dxs3);
if(newspeed < setspeed) setspeed = newspeed; // we can't reach user speed
DBG("dxs3=%g, setspeed=%g", dxs3, setspeed);
dt01 = fabs(sign*setspeed - curparams.speed) / Max.accel;
Params[0].accel = sign * Max.accel;
if(state == ST_STOP) dx01 = setspeed * dt01 / 2.;
else dx01 = dt01 * (dt01 / 2. * Max.accel - curspeed);
DBG("dx01=%g, dt01=%g", dx01, dt01);
}else{ // increase or decrease speed without stopping phase
dt01 = fabs(sign*setspeed - curparams.speed) / Max.accel;
double a = sign * Max.accel;
if(sign * curparams.speed < 0.){DBG("change direction"); a = -a;}
else if(curspeed > setspeed){ DBG("lower speed @ this direction"); a = -a;}
//double a = (curspeed > setspeed) ? -Max.accel : Max.accel;
dx01 = curspeed * dt01 + a * dt01 * dt01 / 2.;
DBG("dt01=%g, a=%g, dx01=%g", dt01, a, dx01);
if(dx01 + dx23 > Dx){ // calculate max speed
setspeed = sqrt(Max.accel * Dx - curspeed * curspeed / 2.);
if(setspeed < curspeed){
setspeed = curparams.speed;
dt01 = 0.; dx01 = 0.;
Params[0].accel = 0.;
}else{
Params[0].accel = a;
dt01 = fabs(setspeed - curspeed) / Max.accel;
dx01 = curspeed * dt01 + Max.accel * dt01 * dt01 / 2.;
}
}else Params[0].accel = a;
}
if(setspeed < Min.speed){
WARNX("New speed should be too small");
return FALSE;
}
moveparam_t *p = &Params[STAGE_MAXSPEED];
p->accel = 0.; p->speed = sign * setspeed;
p->coord = curparams.coord + dx01 * sign;
Times[STAGE_MAXSPEED] = Times[0] + dt01;
dt23 = setspeed / Max.accel;
dx23 = setspeed * dt23 / 2.;
// calculate dx12 and dt12
double dx12 = Dx - dx01 - dx23;
if(dx12 < -coord_tolerance){
WARNX("Oops, WTF dx12=%g?", dx12);
return FALSE;
}
double dt12 = dx12 / setspeed;
p = &Params[STAGE_DECEL];
p->accel = -sign * Max.accel;
p->speed = sign * setspeed;
p->coord = Params[STAGE_MAXSPEED].coord + sign * dx12;
Times[STAGE_DECEL] = Times[STAGE_MAXSPEED] + dt12;
p = &Params[STAGE_STOPPED];
p->accel = 0.; p->speed = 0.; p->coord = x->coord;
Times[STAGE_STOPPED] = Times[STAGE_DECEL] + dt23;
for(int i = 0; i < 4; ++i)
DBG("%d: t=%g, coord=%g, speed=%g, accel=%g", i,
Times[i], Params[i].coord, Params[i].speed, Params[i].accel);
state = ST_MOVE;
movingstage = STAGE_ACCEL;
return TRUE;
}
static movestate_t proc(moveparam_t *next, double t){
if(state == ST_STOP) goto ret;
for(movingstage_t s = STAGE_STOPPED; s >= 0; --s){
if(Times[s] <= t){ // check time for current stage
movingstage = s;
break;
}
}
if(movingstage == STAGE_STOPPED){
curparams.coord = Params[STAGE_STOPPED].coord;
emstop(t);
goto ret;
}
// calculate current parameters
double dt = t - Times[movingstage];
double a = Params[movingstage].accel;
double v0 = Params[movingstage].speed;
double x0 = Params[movingstage].coord;
curparams.accel = a;
curparams.speed = v0 + a * dt;
curparams.coord = x0 + v0 * dt + a * dt * dt / 2.;
ret:
if(next) *next = curparams;
return state;
}
static movestate_t getst(moveparam_t *cur){
if(cur) *cur = curparams;
return state;
}
static double gettstop(){
return Times[STAGE_STOPPED];
}
movemodel_t trapez = {
.init_limits = initlims,
.stop = stop,
.emergency_stop = emstop,
.get_state = getst,
.calculate = calc,
.proc_move = proc,
.stoppedtime = gettstop,
};

View File

@@ -0,0 +1,23 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 "moving_private.h"
extern movemodel_t trapez;

View File

@@ -0,0 +1,222 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <stdio.h>
#include <strings.h>
#include <usefull_macros.h>
#include "moving.h"
#include "PID.h"
// errors for states: slewing/pointing/guiding
// 10-degrees zone - Coordinate-driven PID
#define MAX_POINTING_ERR (36000.)
// 1-arcminute zone - Velocity-dtiven PID
#define MAX_GUIDING_ERR (60.)
// timeout to "forget" old data from I sum array; seconds
#define PID_I_PERIOD (3.)
// PID for coordinate-driven and velocity-driven parts
static PIDController_t *pidC = NULL, *pidV = NULL;
static movemodel_t *model = NULL;
static FILE *coordslog = NULL;
typedef enum{
Slewing,
Pointing,
Guiding
} state_t;
static state_t state = Slewing;
typedef struct{
int help;
char *ramptype;
char *xlog;
double dTmon;
double dTcorr;
double Tend;
double minerr;
double startcoord;
double error;
PIDpar_t gainC, gainV;
} pars;
static pars G = {
.dTmon = 0.01,
.dTcorr = 0.05,
.Tend = 100.,
.minerr = 0.1,
.gainC.P = 0.1,
.gainV.P = 0.1,
.startcoord = 100.,
};
static limits_t limits = {
.min = {.coord = -1e6, .speed = 0.01, .accel = 0.1},
.max = {.coord = 6648000, .speed = 36000., .accel = 36000.}
};
static sl_option_t opts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"tmon", NEED_ARG, NULL, 'T', arg_double, APTR(&G.dTmon), "time interval for monitoring (seconds, default: 0.001)"},
{"tcor", NEED_ARG, NULL, 't', arg_double, APTR(&G.dTcorr), "time interval for corrections (seconds, default: 0.05)"},
{"xlog", NEED_ARG, NULL, 'l', arg_string, APTR(&G.xlog), "log file name for coordinates logging"},
{"tend", NEED_ARG, NULL, 'e', arg_double, APTR(&G.Tend), "end time of monitoring (seconds, default: 100)"},
{"minerr", NEED_ARG, NULL, 'm', arg_double, APTR(&G.minerr), "minimal error for corrections (units, default: 0.1)"},
{"propC", NEED_ARG, NULL, 'P', arg_double, APTR(&G.gainC.P), "P-coefficient of coordinate-driven PID"},
{"integC", NEED_ARG, NULL, 'I', arg_double, APTR(&G.gainC.I), "I-coefficient of coordinate-driven PID"},
{"diffC", NEED_ARG, NULL, 'D', arg_double, APTR(&G.gainC.D), "D-coefficient of coordinate-driven PID"},
{"propV", NEED_ARG, NULL, 'p', arg_double, APTR(&G.gainV.P), "P-coefficient of velocity-driven PID"},
{"integV", NEED_ARG, NULL, 'i', arg_double, APTR(&G.gainV.I), "I-coefficient of velocity-driven PID"},
{"diffV", NEED_ARG, NULL, 'd', arg_double, APTR(&G.gainV.D), "D-coefficient of velocity-driven PID"},
{"xstart", NEED_ARG, NULL, '0', arg_double, APTR(&G.startcoord), "starting coordinate of target"},
{"error", NEED_ARG, NULL, 'E', arg_double, APTR(&G.error), "error range"},
// TODO: add parameters for limits setting
end_option
};
// calculate coordinate target for given time (starting from zero)
static double target_coord(double t){
if(t > 20. && t < 30.) return 0.;
//double pos = G.startcoord + 15. * t + G.error * (drand48() - 0.5);
double pos = G.startcoord + 15. * sin(2*M_PI * t / 10.) + G.error * (drand48() - 0.5);
return pos;
}
static double getNewSpeed(const moveparam_t *p, double targcoord, double dt){
double error = targcoord - p->coord, fe = fabs(error);
PIDController_t *pid = NULL;
switch(state){
case Slewing:
if(fe < MAX_POINTING_ERR){
pid_clear(pidC);
state = Pointing;
green("--> Pointing\n");
pid = pidC;
}else{
red("Slewing...\n");
return (error > 0.) ? limits.max.speed : -limits.max.speed;
}
break;
case Pointing:
if(fe < MAX_GUIDING_ERR){
pid_clear(pidV);
state = Guiding;
green("--> Guiding\n");
pid = pidV;
}else if(fe > MAX_POINTING_ERR){
red("--> Slewing\n");
state = Slewing;
return (error > 0.) ? limits.max.speed : -limits.max.speed;
} else pid = pidC;
break;
case Guiding:
pid= pidV;
if(fe > MAX_GUIDING_ERR){
red("--> Pointing\n");
state = Pointing;
pid_clear(pidC);
pid = pidC;
}else if(fe < G.minerr){
green("At target\n");
}else printf("Current error: %g\n", fe);
break;
}
if(!pid){
WARNX("where is PID?"); return p->speed;
}
double tagspeed = pid_calculate(pid, error, dt);
if(state == Guiding) return p->speed + tagspeed;
return tagspeed;
}
// -P0.8 -D0.1 -I0.02 -p20 -d.5 -i.02
// another: P0.8 -D0.1 -I0.02 -p5 -d0.9 -i0.1
static void start_model(double Tend){
double T = 0., Tcorr = 0.;
moveparam_t target;
uint64_t N = 0;
double errmax = 0., errsum = 0., errsum2 = 0.;
while(T <= Tend){
moveparam_t p;
movestate_t st = model->get_state(&p);
if(st == ST_MOVE) st = model->proc_move(&p, T);
double nextcoord = target_coord(T);
double error = nextcoord - p.coord;
if(state == Guiding){
double ae = fabs(error);
if(ae > errmax) errmax = ae;
errsum += error; errsum2 += error * error;
++N;
}
if(T - Tcorr >= G.dTcorr){ // check correction
double speed = getNewSpeed(&p, nextcoord, T - Tcorr);
target.coord = (speed > 0) ? p.coord + 5e5 : p.coord - 5e5;
target.speed = fabs(speed);
double res_speed = limits.max.speed / 2.;
if(target.speed > limits.max.speed){
target.speed = limits.max.speed;
res_speed = limits.max.speed / 4.;
}else if(target.speed < limits.min.speed){
target.speed = limits.min.speed;
res_speed = limits.min.speed * 4.;
}
if(!move_to(&target, T)){
target.speed = res_speed;
if(!move_to(&target, T))
WARNX("move(): can't move to %g with max speed %g", target.coord, target.speed);
}
DBG("%g: tag/cur speed= %g / %g; tag/cur pos = %g / %g; err = %g", T, target.speed, p.speed, target.coord, p.coord, error);
Tcorr = T;
}
// make log
fprintf(coordslog, "%-9.4f\t%-10.4f\t%-10.4f\t%-10.4f\t%-10.4f\t%-10.4f\n",
T, nextcoord, p.coord, p.speed, p.accel, error);
T += G.dTmon;
}
printf("\n\n\n"); red("Calculated errors in `guiding` mode:\n");
double mean = errsum / (double)N;
printf("max error: %g, mean error: %g, std: %g\n\n", errmax, mean, sqrt(errsum2/(double)N - mean*mean));
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, opts);
if(G.help) sl_showhelp(-1, opts);
if(G.xlog){
coordslog = fopen(G.xlog, "w");
if(!coordslog) ERR("Can't open %s", G.xlog);
} else coordslog = stdout;
if(G.dTmon <= 0.) ERRX("tmon should be > 0.");
if(G.dTcorr <= 0. || G.dTcorr > 1.) ERRX("tcor should be > 0. and < 1.");
if(G.Tend <= 0.) ERRX("tend should be > 0.");
pidC = pid_create(&G.gainC, PID_I_PERIOD / G.dTcorr);
pidV = pid_create(&G.gainV, PID_I_PERIOD / G.dTcorr);
if(!pidC || !pidV) ERRX("Can't init PID regulators");
model = init_moving(&limits);
if(!model) ERRX("Can't init moving model: check parameters");
fprintf(coordslog, "%-9s\t%-10s\t%-10s\t%-10s\t%-10s\t%-10s\n", "time", "target", "curpos", "speed", "accel", "error");
start_model(G.Tend);
pid_delete(&pidC);
pid_delete(&pidV);
fclose(coordslog);
return 0;
}

View File

@@ -0,0 +1,89 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <stdio.h>
#include <usefull_macros.h>
#include <pthread.h>
#include <time.h>
#include "moving.h"
#include "moving_private.h"
#include "Tramp.h"
static movemodel_t *model = &trapez;
double coord_tolerance = COORD_TOLERANCE_DEFAULT;
double time_tick = TIME_TICK_DEFAULT;
// difference of time from first call, using nanoseconds
double nanot(){
static struct timespec *start = NULL;
struct timespec now;
if(!start){
start = MALLOC(struct timespec, 1);
if(!start) return -1.;
if(clock_gettime(CLOCK_REALTIME, start)) return -1.;
}
if(clock_gettime(CLOCK_REALTIME, &now)) return -1.;
//DBG("was: %ld, now: %ld", start->tv_nsec, now.tv_nsec);
double nd = ((double)now.tv_nsec - (double)start->tv_nsec) * 1e-9;
double sd = (double)now.tv_sec - (double)start->tv_sec;
return sd + nd;
}
static void chkminmax(double *min, double *max){
if(*min <= *max) return;
double t = *min;
*min = *max;
*max = t;
}
movemodel_t *init_moving(limits_t *l){
if(!l) return FALSE;
if(!model->init_limits) return NULL;
moveparam_t *max = &l->max, *min = &l->min;
if(min->speed < 0.) min->speed = -min->speed;
if(max->speed < 0.) max->speed = -max->speed;
if(min->accel < 0.) min->accel = -min->accel;
if(max->accel < 0.) max->accel = -max->accel;
chkminmax(&min->coord, &max->coord);
chkminmax(&min->speed, &max->speed);
chkminmax(&min->accel, &max->accel);
if(!model->init_limits(l)) return NULL;
return model;
}
int move_to(moveparam_t *target, double t){
if(!target || !model) return FALSE;
DBG("MOVE to %g at speed %g", target->coord, target->speed);
// only positive velocity
if(target->speed < 0.) target->speed = -target->speed;
// don't mind about acceleration - user cannot set it now
return model->calculate(target, t);
}
int init_coordtol(double tolerance){
if(tolerance < COORD_TOLERANCE_MIN || tolerance > COORD_TOLERANCE_MAX) return FALSE;
coord_tolerance = tolerance;
return TRUE;
}
int init_timetick(double tick){
if(tick < TIME_TICK_MIN || tick > TIME_TICK_MAX) return FALSE;
time_tick = tick;
return TRUE;
}

View File

@@ -0,0 +1,62 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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
// tolerance, time ticks
#define COORD_TOLERANCE_DEFAULT (0.01)
#define COORD_TOLERANCE_MIN (0.0001)
#define COORD_TOLERANCE_MAX (10.)
#define TIME_TICK_DEFAULT (0.0001)
#define TIME_TICK_MIN (1e-9)
#define TIME_TICK_MAX (10.)
typedef enum{
ST_STOP, // stopped
ST_MOVE, // moving
ST_AMOUNT
} movestate_t;
typedef struct{ // all values could be both as positive and negative
double coord;
double speed;
double accel;
} moveparam_t;
typedef struct{
moveparam_t min;
moveparam_t max;
} limits_t;
typedef struct{
int (*init_limits)(limits_t *lim); // init values of limits, jerk
int (*calculate)(moveparam_t *target, double t); // calculate stages of traectory beginning from t
movestate_t (*proc_move)(moveparam_t *next, double t); // calculate next model point for time t
movestate_t (*get_state)(moveparam_t *cur); // get current moving state
void (*stop)(double t); // stop by ramp
void (*emergency_stop)(double t); // stop with highest acceleration
double (*stoppedtime)(); // time when moving will ends
} movemodel_t;
extern double coord_tolerance;
double nanot();
movemodel_t *init_moving(limits_t *l);
int init_coordtol(double tolerance);
int init_timetick(double tick);
int move_to(moveparam_t *target, double t);

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.0, 2025-07-29T13:32:31. -->
<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">false</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">true</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">false</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.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">1</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>
</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/C-files/mountcontrol.git/moving_model</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">Сборка</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="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">Очистка</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.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<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>
<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">Развёртывание</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>
<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="bool" key="Analyzer.Valgrind.ShowReachable">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="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</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">Развёртывание</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>
<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="bool" key="Analyzer.Valgrind.ShowReachable">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="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</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>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,184 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 16.0.0, 2025-03-18T22:08:04. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</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">false</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">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="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.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.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">8</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>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Big/Data/00__Small_tel/moving_model</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>
</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="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</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>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 @@
-std=c++17

View File

@@ -0,0 +1,12 @@
Dramp.c
Dramp.h
PID.c
PID.h
Sramp.c
Sramp.h
Tramp.c
Tramp.h
main.c
moving.c
moving.h
moving_private.h

View File

@@ -0,0 +1,26 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 "moving.h"
extern double coord_tolerance;
extern double time_tick;

View File

@@ -0,0 +1,4 @@
#!/usr/bin/gnuplot
plot for [col in "target curpos speed error"] 'coords' using 1:col with lines title columnheader
pause mouse

View File

@@ -0,0 +1,8 @@
#!/usr/bin/gnuplot
#set term pdf
#set output "output.pdf"
while(1){
plot for [col in "target curpos speed error"] 'coords' using 1:col with lines title columnheader
pause 1
}

View File

@@ -0,0 +1,6 @@
#!/usr/bin/gnuplot
set terminal jpeg size 1000,500
set output "all.jpg"
plot for [col in "target curpos speed error"] 'coords' using 1:col with lines title columnheader

View File

@@ -0,0 +1,5 @@
#!/usr/bin/gnuplot
set term pdf
set output "output.pdf"
plot for [col=2:4] 'coordlog' using 1:col with lines title columnheader

View File

@@ -0,0 +1,4 @@
#!/usr/bin/gnuplot
plot 'coords' using 1:5 with lines title columnheader
pause mouse

View File

@@ -0,0 +1,4 @@
#!/usr/bin/gnuplot
plot 'coords' using 1:6 with lines title columnheader
pause mouse

View File

@@ -0,0 +1,6 @@
#!/usr/bin/gnuplot
while(1){
plot 'coords' using 1:6 with lines title columnheader
pause 1
}

View File

@@ -0,0 +1,5 @@
#!/usr/bin/gnuplot
set term jpeg size 1000,500
set output "error.jpg"
plot 'coords' using 1:6 with lines title columnheader

View File

@@ -0,0 +1,3 @@
fix encoders opening for several tries
encoderthread2() - change main cycle (remove pause, read data independently, ask for new only after timeout after last request)
Read HW config even in model mode

View File

@@ -59,6 +59,6 @@ extern conf_t Conf;
fprintf(stderr, "\n");} while(0) fprintf(stderr, "\n");} while(0)
#else // EBUG #else // EBUG
#define FNAME() do{}while(0) #define FNAME()
#define DBG(...) do{}while(0) #define DBG(...)
#endif // EBUG #endif // EBUG

View File

@@ -11,3 +11,4 @@ add_executable(dump_s dumpmoving_scmd.c dump.c conf.c)
add_executable(dumpswing dumpswing.c dump.c conf.c) add_executable(dumpswing dumpswing.c dump.c conf.c)
add_executable(traectory_s scmd_traectory.c dump.c traectories.c conf.c) add_executable(traectory_s scmd_traectory.c dump.c traectories.c conf.c)
add_executable(SSIIconf SSIIconf.c conf.c) add_executable(SSIIconf SSIIconf.c conf.c)
add_executable(slewNtrack dumpmoving_dragNtrack.c dump.c conf.c)

View File

@@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <usefull_macros.h> #include <usefull_macros.h>
@@ -33,7 +34,9 @@ typedef struct{
static hardware_configuration_t HW = {0}; static hardware_configuration_t HW = {0};
static parameters G = {0}; static parameters G = {
.conffile = "servo.conf",
};
static sl_option_t cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
@@ -50,6 +53,86 @@ static sl_option_t confopts[] = {
end_option end_option
}; };
static void dumpaxis(char axis, axis_config_t *c){
#define STRUCTPAR(p) (c)->p
#define DUMP(par) do{printf("%c%s=%.10g\n", axis, #par, STRUCTPAR(par));}while(0)
#define DUMPD(par) do{printf("%c%s=%g\n", axis, #par, RAD2DEG(STRUCTPAR(par)));}while(0)
DUMPD(accel);
DUMPD(backlash);
DUMPD(errlimit);
DUMP(propgain);
DUMP(intgain);
DUMP(derivgain);
DUMP(outplimit);
DUMP(currlimit);
DUMP(intlimit);
DUMP(motor_stepsperrev);
DUMP(axis_stepsperrev);
#undef DUMP
#undef DUMPD
}
static void dumpxbits(xbits_t *c){
#define DUMPBIT(f) do{printf("X%s=%d\n", #f, STRUCTPAR(f));}while(0)
DUMPBIT(motrev);
DUMPBIT(motpolarity);
DUMPBIT(encrev);
DUMPBIT(dragtrack);
DUMPBIT(trackplat);
DUMPBIT(handpaden);
DUMPBIT(newpad);
DUMPBIT(guidemode);
#undef DUMPBIT
}
static void dumpybits(ybits_t *c){
#define DUMPBIT(f) do{printf("Y%s=%d\n", #f, STRUCTPAR(f));}while(0)
DUMPBIT(motrev);
DUMPBIT(motpolarity);
DUMPBIT(encrev);
DUMPBIT(slewtrack);
DUMPBIT(digin_sens);
printf("Ydigin=%d\n", c->digin);
#undef DUMPBIT
}
static void dumpHWconf(){
#undef STRUCTPAR
#define STRUCTPAR(p) (HW).p
#define DUMP(par) do{printf("%s=%g\n", #par, STRUCTPAR(par));}while(0)
#define DUMPD(par) do{printf("%s=%g\n", #par, RAD2DEG(STRUCTPAR(par)));}while(0)
#define DUMPU8(par) do{printf("%s=%u\n", #par, (uint8_t)STRUCTPAR(par));}while(0)
#define DUMPU32(par) do{printf("%s=%u\n", #par, (uint32_t)STRUCTPAR(par));}while(0)
green("X axis configuration:\n");
dumpaxis('X', &HW.Xconf);
green("X bits:\n");
dumpxbits(&HW.xbits);
green("Y axis configuration:\n");
dumpaxis('Y', &HW.Yconf);
green("Y bits:\n");
dumpybits(&HW.ybits);
green("Other:\n");
printf("address=%d\n", HW.address);
DUMP(eqrate);
DUMP(eqadj);
DUMP(trackgoal);
DUMPD(latitude);
DUMPU32(Xsetpr);
DUMPU32(Ysetpr);
DUMPU32(Xmetpr);
DUMPU32(Ymetpr);
DUMPD(Xslewrate);
DUMPD(Yslewrate);
DUMPD(Xpanrate);
DUMPD(Ypanrate);
DUMPD(Xguiderate);
DUMPD(Yguiderate);
DUMPU32(baudrate);
DUMPD(locsdeg);
DUMPD(locsspeed);
DUMPD(backlspd);
}
int main(int argc, char** argv){ int main(int argc, char** argv){
sl_init(); sl_init();
sl_parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
@@ -64,13 +147,17 @@ int main(int argc, char** argv){
} }
if(MCC_E_OK != Mount.init(sconf)) ERRX("Can't init mount"); if(MCC_E_OK != Mount.init(sconf)) ERRX("Can't init mount");
if(MCC_E_OK != Mount.getHWconfig(&HW)) ERRX("Can't read configuration"); if(MCC_E_OK != Mount.getHWconfig(&HW)) ERRX("Can't read configuration");
/*
char *c = sl_print_opts(confopts, TRUE); char *c = sl_print_opts(confopts, TRUE);
green("Got configuration:\n"); green("Got configuration:\n");
printf("%s\n", c); printf("%s\n", c);
FREE(c); FREE(c);
*/
dumpHWconf();
/*
if(G.hwconffile && G.writeconf){ if(G.hwconffile && G.writeconf){
; ;
} }*/
Mount.quit(); Mount.quit();
return 0; return 0;
} }

View File

@@ -24,10 +24,32 @@
static conf_t Config = { static conf_t Config = {
.MountDevPath = "/dev/ttyUSB0", .MountDevPath = "/dev/ttyUSB0",
.MountDevSpeed = 19200, .MountDevSpeed = 19200,
.EncoderDevPath = "/dev/ttyUSB1", .EncoderXDevPath = "/dev/encoder_X0",
.EncoderYDevPath = "/dev/encoder_Y0",
.EncoderDevSpeed = 153000, .EncoderDevSpeed = 153000,
.MountReqInterval = 0.1, .MountReqInterval = 0.1,
.SepEncoder = 1 .EncoderReqInterval = 0.001,
.SepEncoder = 2,
.EncoderSpeedInterval = 0.05,
.EncodersDisagreement = 1e-5, // 2''
.PIDMaxDt = 1.,
.PIDRefreshDt = 0.1,
.PIDCycleDt = 5.,
.XPIDC.P = 0.5,
.XPIDC.I = 0.1,
.XPIDC.D = 0.2,
.XPIDV.P = 0.09,
.XPIDV.I = 0.0,
.XPIDV.D = 0.05,
.YPIDC.P = 0.5,
.YPIDC.I = 0.1,
.YPIDC.D = 0.2,
.YPIDV.P = 0.09,
.YPIDV.I = 0.0,
.YPIDV.D = 0.05,
.MaxPointingErr = 0.13962634,
.MaxFinePointingErr = 0.026179939,
.MaxGuidingErr = 4.8481368e-7,
}; };
static sl_option_t opts[] = { static sl_option_t opts[] = {
@@ -35,8 +57,35 @@ static sl_option_t opts[] = {
{"MountDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.MountDevSpeed), "serial speed of mount device"}, {"MountDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.MountDevSpeed), "serial speed of mount device"},
{"EncoderDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderDevPath), "path to encoder device"}, {"EncoderDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderDevPath), "path to encoder device"},
{"EncoderDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.EncoderDevSpeed), "serial speed of encoder device"}, {"EncoderDevSpeed", NEED_ARG, NULL, 0, arg_int, APTR(&Config.EncoderDevSpeed), "serial speed of encoder device"},
{"SepEncoder", NEED_ARG, NULL, 0, arg_int, APTR(&Config.SepEncoder), "encoder is separate device (1 - one device, 2 - two devices)"},
{"EncoderXDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderXDevPath), "path to X encoder (/dev/encoderX0)"},
{"EncoderYDevPath", NEED_ARG, NULL, 0, arg_string, APTR(&Config.EncoderYDevPath), "path to Y encoder (/dev/encoderY0)"},
{"EncodersDisagreement", NEED_ARG,NULL, 0, arg_double, APTR(&Config.EncodersDisagreement),"acceptable disagreement between motor and axis encoders"},
{"MountReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.MountReqInterval), "interval of mount requests (not less than 0.05s)"}, {"MountReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.MountReqInterval), "interval of mount requests (not less than 0.05s)"},
{"SepEncoder", NO_ARGS, NULL, 0, arg_int, APTR(&Config.SepEncoder), "encoder is separate device"}, {"EncoderReqInterval",NEED_ARG, NULL, 0, arg_double, APTR(&Config.EncoderReqInterval),"interval of encoder requests (in case of sep=2)"},
{"EncoderSpeedInterval", NEED_ARG,NULL, 0, arg_double, APTR(&Config.EncoderSpeedInterval),"interval of speed calculations, s"},
{"RunModel", NEED_ARG, NULL, 0, arg_int, APTR(&Config.RunModel), "instead of real hardware run emulation"},
{"PIDMaxDt", NEED_ARG, NULL, 0, arg_double, APTR(&Config.PIDMaxDt), "maximal PID refresh time interval (if larger all old data will be cleared)"},
{"PIDRefreshDt", NEED_ARG, NULL, 0, arg_double, APTR(&Config.PIDRefreshDt), "normal PID refresh interval by master process"},
{"PIDCycleDt", NEED_ARG, NULL, 0, arg_double, APTR(&Config.PIDCycleDt), "PID I cycle time (analog of \"RC\" for PID on opamps)"},
{"XPIDCP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.P), "P of X PID (coordinate driven)"},
{"XPIDCI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.I), "I of X PID (coordinate driven)"},
{"XPIDCD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDC.D), "D of X PID (coordinate driven)"},
{"YPIDCP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDC.P), "P of Y PID (coordinate driven)"},
{"YPIDCI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDC.I), "I of Y PID (coordinate driven)"},
{"YPIDCD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDC.D), "D of Y PID (coordinate driven)"},
{"XPIDVP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDV.P), "P of X PID (velocity driven)"},
{"XPIDVI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDV.I), "I of X PID (velocity driven)"},
{"XPIDVD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.XPIDV.D), "D of X PID (velocity driven)"},
{"YPIDVP", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.P), "P of Y PID (velocity driven)"},
{"YPIDVI", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.I), "I of Y PID (velocity driven)"},
{"YPIDVD", NEED_ARG, NULL, 0, arg_double, APTR(&Config.YPIDV.D), "D of Y PID (velocity driven)"},
{"MaxPointingErr", NEED_ARG, NULL, 0, arg_double, APTR(&Config.MaxPointingErr), "if angle < this, change state from \"slewing\" to \"pointing\" (coarse pointing): 8 degrees"},
{"MaxFinePointingErr",NEED_ARG, NULL, 0, arg_double, APTR(&Config.MaxFinePointingErr), "if angle < this, chane state from \"pointing\" to \"guiding\" (fine poinging): 1.5 deg"},
{"MaxGuidingErr", NEED_ARG, NULL, 0, arg_double, APTR(&Config.MaxGuidingErr), "if error less than this value we suppose that target is captured and guiding is good (true guiding): 0.1''"},
{"XEncZero", NEED_ARG, NULL, 0, arg_int, APTR(&Config.XEncZero), "X axis encoder approximate zero position"},
{"YEncZero", NEED_ARG, NULL, 0, arg_int, APTR(&Config.YEncZero), "Y axis encoder approximate zero position"},
// {"",NEED_ARG, NULL, 0, arg_double, APTR(&Config.), ""},
end_option end_option
}; };
@@ -61,5 +110,19 @@ void dumpConf(){
} }
void confHelp(){ void confHelp(){
sl_showhelp(-1, opts); sl_conf_showhelp(-1, opts);
}
const char* errcodes[MCC_E_AMOUNT] = {
[MCC_E_OK] = "OK",
[MCC_E_FATAL] = "Fatal error",
[MCC_E_BADFORMAT] = "Wrong data format",
[MCC_E_ENCODERDEV] = "Encoder error",
[MCC_E_MOUNTDEV] = "Mount error",
[MCC_E_FAILED] = "Failed to run"
};
// return string with error code
const char *EcodeStr(mcc_errcodes_t e){
if(e >= MCC_E_AMOUNT) return "Wrong error code";
return errcodes[e];
} }

View File

@@ -25,3 +25,4 @@
void confHelp(); void confHelp();
conf_t *readServoConf(const char *filename); conf_t *readServoConf(const char *filename);
void dumpConf(); void dumpConf();
const char *EcodeStr(mcc_errcodes_t e);

View File

@@ -23,6 +23,51 @@
#include "dump.h" #include "dump.h"
#include "simpleconv.h" #include "simpleconv.h"
// starting dump time (to conform different logs)
static struct timespec dumpT0 = {0};
#if 0
// amount of elements used for encoders' data filtering
#define NFILT (10)
static double filterK[NFILT];
static double lastvals[2][NFILT] = {0};
static int need2buildFilter = 1;
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;
}
static double filter(double val, int idx){
if(need2buildFilter){
buildFilter();
need2buildFilter = 0;
}
static int ctr[2] = {0};
for(int i = NFILT-1; i > 0; --i) lastvals[idx][i] = lastvals[idx][i-1];
lastvals[idx][0] = val;
double r = 0.;
if(ctr[idx] < NFILT){
++ctr[idx];
return val;
}
for(int i = 0; i < NFILT; ++i) r += filterK[i] * lastvals[idx][i];
return r;
}
#endif
// return starting time of dump
void dumpt0(struct timespec *t){
if(t) *t = dumpT0;
}
/** /**
* @brief logmnt - log mount data into file * @brief logmnt - log mount data into file
* @param fcoords - file to dump * @param fcoords - file to dump
@@ -31,18 +76,16 @@
void logmnt(FILE *fcoords, mountdata_t *m){ void logmnt(FILE *fcoords, mountdata_t *m){
if(!fcoords) return; if(!fcoords) return;
//DBG("LOG %s", m ? "data" : "header"); //DBG("LOG %s", m ? "data" : "header");
static double t0 = -1.;
if(!m){ // write header if(!m){ // write header
fprintf(fcoords, "# time Xmot(deg) Ymot(deg) Xenc(deg) Yenc(deg) millis T V\n"); fprintf(fcoords, " time Xmot(deg) Ymot(deg) Xenc(deg) Yenc(deg) VX(d/s) VY(d/s) millis\n");
return; return;
} }else if(dumpT0.tv_sec == 0) dumpT0 = m->encXposition.t;
if(t0 < 0.) t0 = m->encposition.msrtime.tv_sec + (double)(m->encposition.msrtime.tv_usec) / 1e6;
double t = m->encposition.msrtime.tv_sec + (double)(m->encposition.msrtime.tv_usec) / 1e6 - t0;
// write data // write data
fprintf(fcoords, "%12.6f %10.6f %10.6f %10.6f %10.6f %10u %6.1f %4.1f\n", fprintf(fcoords, "%12.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10u\n",
t, RAD2DEG(m->motposition.X), RAD2DEG(m->motposition.Y), Mount.timeDiff(&m->encXposition.t, &dumpT0), RAD2DEG(m->motXposition.val), RAD2DEG(m->motYposition.val),
RAD2DEG(m->encposition.X), RAD2DEG(m->encposition.Y), RAD2DEG(m->encXposition.val), RAD2DEG(m->encYposition.val),
m->millis, m->temperature, m->voltage); RAD2DEG(m->encXspeed.val), RAD2DEG(m->encYspeed.val),
m->millis);
fflush(fcoords); fflush(fcoords);
} }
@@ -64,45 +107,48 @@ void dumpmoving(FILE *fcoords, double t, int N){
WARNX("Can't get mount data"); WARNX("Can't get mount data");
LOGWARN("Can't get mount data"); LOGWARN("Can't get mount data");
} }
uint32_t millis = mdata.encposition.msrtime.tv_usec; uint32_t mdmillis = mdata.millis;
struct timespec encXt = mdata.encXposition.t;
int ctr = -1; int ctr = -1;
double xlast = mdata.motposition.X, ylast = mdata.motposition.Y; double xlast = mdata.motXposition.val, ylast = mdata.motYposition.val;
double t0 = sl_dtime(); double t0 = Mount.timeFromStart();
//DBG("millis = %u", millis); while(Mount.timeFromStart() - t0 < t && ctr < N){
while(sl_dtime() - t0 < t && ctr < N){
usleep(1000); usleep(1000);
if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;} if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;}
if(mdata.encposition.msrtime.tv_usec == millis) continue; //double tmsr = (mdata.encXposition.t + mdata.encYposition.t) / 2.;
//DBG("Got new data, posX=%g, posY=%g", mdata.motposition.X, mdata.motposition.Y); struct timespec msrt = mdata.encXposition.t;
millis = mdata.encposition.msrtime.tv_usec; if(msrt.tv_nsec == encXt.tv_nsec) continue;
encXt = msrt;
if(fcoords) logmnt(fcoords, &mdata); if(fcoords) logmnt(fcoords, &mdata);
if(mdata.motposition.X != xlast || mdata.motposition.Y != ylast){ if(mdata.millis == mdmillis) continue;
xlast = mdata.motposition.X; //DBG("ctr=%d, motpos=%g/%g", ctr, mdata.motXposition.val, mdata.motYposition.val);
ylast = mdata.motposition.Y; mdmillis = mdata.millis;
if(mdata.motXposition.val != xlast || mdata.motYposition.val != ylast){
xlast = mdata.motXposition.val;
ylast = mdata.motYposition.val;
ctr = 0; ctr = 0;
}else ++ctr; }else ++ctr;
} }
DBG("Exit dumping; tend=%g, tmon=%g", t, Mount.timeFromStart() - t0);
} }
/** /**
* @brief waitmoving - wait until moving by both axes stops at least for N cycles * @brief waitmoving - wait until moving by both axiss stops at least for N cycles
* @param N - amount of stopped cycles * @param N - amount of stopped cycles
*/ */
void waitmoving(int N){ void waitmoving(int N){
mountdata_t mdata; mountdata_t mdata;
int ctr = -1; int ctr = -1;
uint32_t millis = 0; uint32_t millis = 0;
double xlast = 0., ylast = 0.; //double xlast = 0., ylast = 0.;
DBG("Wait moving for %d stopped times", N);
while(ctr < N){ while(ctr < N){
usleep(10000); usleep(10000);
if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;} if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;}
if(mdata.millis == millis) continue; if(mdata.millis == millis) continue;
millis = mdata.millis; millis = mdata.millis;
if(mdata.motposition.X != xlast || mdata.motposition.Y != ylast){ if(mdata.Xstate != AXIS_STOPPED || mdata.Ystate != AXIS_STOPPED) ctr = 0;
xlast = mdata.motposition.X; else ++ctr;
ylast = mdata.motposition.Y;
ctr = 0;
}else ++ctr;
} }
} }
@@ -112,7 +158,7 @@ void waitmoving(int N){
* @param Y (o) - encoder position (or NULL) * @param Y (o) - encoder position (or NULL)
* @return FALSE if failed * @return FALSE if failed
*/ */
int getPos(coords_t *mot, coords_t *enc){ int getPos(coordval_pair_t *mot, coordval_pair_t *enc){
mountdata_t mdata = {0}; mountdata_t mdata = {0};
int errcnt = 0; int errcnt = 0;
do{ do{
@@ -126,19 +172,25 @@ int getPos(coords_t *mot, coords_t *enc){
WARNX("Can't read mount status"); WARNX("Can't read mount status");
return FALSE; return FALSE;
} }
if(mot) *mot = mdata.motposition; if(mot){
if(enc) *enc = mdata.encposition; mot->X = mdata.motXposition;
mot->Y = mdata.motYposition;
}
if(enc){
enc->X = mdata.encXposition;
enc->Y = mdata.encYposition;
}
return TRUE; return TRUE;
} }
// check current position and go to 0 if non-zero // check current position and go to 0 if non-zero
void chk0(int ncycles){ void chk0(int ncycles){
coords_t M; coordval_pair_t M;
if(!getPos(&M, NULL)) signals(2); if(!getPos(&M, NULL)) signals(2);
if(M.X || M.Y){ if(M.X.val || M.Y.val){
WARNX("Mount position isn't @ zero; moving"); WARNX("Mount position isn't @ zero; moving");
double zero = 0.; coordpair_t zero = {0., 0.};
Mount.moveTo(&zero, &zero); Mount.moveTo(&zero);
waitmoving(ncycles); waitmoving(ncycles);
green("Now mount @ zero\n"); green("Now mount @ zero\n");
} }

View File

@@ -25,5 +25,6 @@
void logmnt(FILE *fcoords, mountdata_t *m); void logmnt(FILE *fcoords, mountdata_t *m);
void dumpmoving(FILE *fcoords, double t, int N); void dumpmoving(FILE *fcoords, double t, int N);
void waitmoving(int N); void waitmoving(int N);
int getPos(coords_t *mot, coords_t *enc); int getPos(coordval_pair_t *mot, coordval_pair_t *enc);
void chk0(int ncycles); void chk0(int ncycles);
void dumpt0(struct timespec *t);

View File

@@ -73,6 +73,7 @@ int main(int argc, char **argv){
conf_t *Config = readServoConf(G.conffile); conf_t *Config = readServoConf(G.conffile);
if(!Config){ if(!Config){
dumpConf(); dumpConf();
confHelp();
return 1; return 1;
} }
if(G.coordsoutput){ if(G.coordsoutput){
@@ -85,18 +86,19 @@ int main(int argc, char **argv){
LOGMSG("Mount device %s @ %d", Config->MountDevPath, Config->MountDevSpeed); LOGMSG("Mount device %s @ %d", Config->MountDevPath, Config->MountDevSpeed);
LOGMSG("Encoder device %s @ %d", Config->EncoderDevPath, Config->EncoderDevSpeed); LOGMSG("Encoder device %s @ %d", Config->EncoderDevPath, Config->EncoderDevSpeed);
if(MCC_E_OK != Mount.init(Config)) ERRX("Can't init devices"); if(MCC_E_OK != Mount.init(Config)) ERRX("Can't init devices");
coords_t M; coordval_pair_t M;
if(!getPos(&M, NULL)) ERRX("Can't get current position"); if(!getPos(&M, NULL)) ERRX("Can't get current position");
signal(SIGTERM, signals); // kill (-15) - quit signal(SIGTERM, signals); // kill (-15) - quit
signal(SIGHUP, SIG_IGN); // hup - ignore signal(SIGHUP, SIG_IGN); // hup - ignore
signal(SIGINT, signals); // ctrl+C - quit signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
double tagx = DEG2RAD(45.) + M.X, tagy = DEG2RAD(45.) + M.Y; coordpair_t tag = {.X = DEG2RAD(45.) + M.X.val, .Y = DEG2RAD(45.) + M.Y.val};
if(MCC_E_OK != Mount.moveTo(&tagx, &tagy)) if(MCC_E_OK != Mount.moveTo(&tag))
ERRX("Can't move to 45, 45"); ERRX("Can't move to 45, 45");
dumpmoving(fcoords, 30., G.Ncycles); dumpmoving(fcoords, 30., G.Ncycles);
Mount.moveTo(&M.X, &M.Y); tag.X = M.X.val; tag.Y = M.Y.val;
Mount.moveTo(&tag);
dumpmoving(fcoords, 30., G.Ncycles); dumpmoving(fcoords, 30., G.Ncycles);
signals(0); signals(0);
return 0; return 0;

View File

@@ -0,0 +1,228 @@
/*
* This file is part of the libsidservo project.
* Copyright 2025 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/>.
*/
// move telescope to target using short command and force it to track mode
// also do some corrections while moving
#include <math.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <usefull_macros.h>
#include "conf.h"
#include "dump.h"
#include "sidservo.h"
#include "simpleconv.h"
// Original XXI=6827
// XXD=136546
// XXB=4915666
typedef struct{
int help;
int Ncycles;
double reqint;
char *coordsoutput;
char *conffile;
} parameters;
static parameters G = {
.Ncycles = 40,
.reqint = -1.,
};
static FILE *fcoords = NULL;
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"ncycles", NEED_ARG, NULL, 'n', arg_int, APTR(&G.Ncycles), "N cycles in stopped state (default: 40)"},
{"coordsfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.coordsoutput),"output file with coordinates log"},
{"reqinterval", NEED_ARG, NULL, 'i', arg_double, APTR(&G.reqint), "mount requests interval (default: 0.1)"},
{"conffile", NEED_ARG, NULL, 'C', arg_string, APTR(&G.conffile), "configuration file name"},
end_option
};
static mcc_errcodes_t return2zero();
void signals(int sig){
if(sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
}
return2zero();
sleep(5);
Mount.quit();
exit(sig);
}
// dump thread
static void *dumping(void _U_ *u){
dumpmoving(fcoords, 3600., G.Ncycles);
return NULL;
}
// return TRUE if motor position is reached +- 0.01 degrees
#define XYcount (DEG2RAD(0.3))
// tag in degrees!
static int Wait(double tag, int isX){
mountdata_t mdata;
red("Wait for %g degrees\n", tag);
tag = DEG2RAD(tag);
int errcnt = 0;
uint32_t millis = 0;
double curpos = 0.;
double t0 = sl_dtime();
do{
if(MCC_E_OK != Mount.getMountData(&mdata)) ++errcnt;
else{
errcnt = 0;
if(mdata.millis == millis) continue;
millis = mdata.millis;
if(isX) curpos = mdata.motXposition.val;
else curpos = mdata.motYposition.val;
}
double t = sl_dtime();
if(t - t0 > 1.){
t0 = t;
printf("\t\tCurrent MOT X/Y: %g / %g deg\n", RAD2DEG(mdata.motXposition.val),
RAD2DEG(mdata.motYposition.val));
}
}while(fabs(curpos - tag) > XYcount && errcnt < 10);
if(errcnt >= 10){
WARNX("Too much errors");
return FALSE;
}
green("%s reached position %g degrees\n", (isX) ? "X" : "Y", RAD2DEG(tag));
fflush(stdout);
return TRUE;
}
// previous GOTO coords/speeds for `mkcorr`
static coordpair_t lastTag = {0}, lastSpeed = {0};
// slew to given position and start tracking
// pos/speed in deg and deg/s
static mcc_errcodes_t gotos(const coordpair_t *target, const coordpair_t *speed){
short_command_t cmd = {0};
DBG("Try to move to (%g, %g) with speed (%g, %g)",
target->X, target->Y, speed->X, speed->Y);
cmd.Xmot = DEG2RAD(target->X); cmd.Ymot = DEG2RAD(target->Y);
cmd.Xspeed = DEG2RAD(speed->X);
cmd.Yspeed = DEG2RAD(speed->Y);
lastTag = *target;
lastSpeed = *speed;
/*cmd.xychange = 1;
cmd.XBits = 108;
cmd.YBits = 28;*/
return Mount.shortCmd(&cmd);
}
static mcc_errcodes_t return2zero(){
short_command_t cmd = {0};
DBG("Try to move to zero");
cmd.Xmot = 0.; cmd.Ymot = 0.;
coordpair_t maxspd;
if(MCC_E_OK != Mount.getMaxSpeed(&maxspd)) return MCC_E_FAILED;
cmd.Xspeed = maxspd.X;
cmd.Yspeed = maxspd.Y;
/*cmd.xychange = 1;
cmd.XBits = 100;
cmd.YBits = 20;*/
return Mount.shortCmd(&cmd);
}
static mcc_errcodes_t mkcorr(coordpair_t *adder, coordpair_t *time){
long_command_t cmd = {0};
cmd.Xspeed = DEG2RAD(lastSpeed.X);
cmd.Yspeed = DEG2RAD(lastSpeed.Y);
cmd.Xmot = DEG2RAD(lastTag.X);
cmd.Ymot = DEG2RAD(lastTag.Y);
cmd.Xadder = DEG2RAD(adder->X); cmd.Yadder = DEG2RAD(adder->Y);
cmd.Xatime = time->X; cmd.Yatime = time->Y;
return Mount.longCmd(&cmd);
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) sl_showhelp(-1, cmdlnopts);
if(G.coordsoutput){
if(!(fcoords = fopen(G.coordsoutput, "w")))
ERRX("Can't open %s", G.coordsoutput);
}else fcoords = stdout;
conf_t *Config = readServoConf(G.conffile);
if(!Config){
dumpConf();
return 1;
}
if(G.reqint > 0.) Config->MountReqInterval = G.reqint;
if(MCC_E_OK != Mount.init(Config)){
WARNX("Can't init devices");
return 1;
}
//if(!getPos(&M, NULL)) ERRX("Can't get current position");
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
// move to X=40 degr with different speeds
pthread_t dthr;
logmnt(fcoords, NULL);
if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread");
// move to 10/10
coordpair_t coords, speeds, adders, tadd;
coords = (coordpair_t){.X = 10., .Y = 20.};
speeds = (coordpair_t){.X = 1., .Y = 2.};
adders = (coordpair_t){.X = 0.01, .Y = 0.01};
tadd = (coordpair_t){.X = 1., .Y = 2.};
green("Goto\n");
if(MCC_E_OK != gotos(&coords, &speeds)) ERRX("Can't go");
DBG("c/s: %g %g %g %g", coords.X, coords.Y, speeds.X, speeds.Y);
green("Waiting X==4\n");
Wait(4., 1);
// now we are at point by Y but still moving by X; make small correction by X/Y into '+'
green("Mkcorr 1\n");
if(MCC_E_OK != mkcorr(&adders, &tadd)) ERRX("Can't make corr");
green("Waiting X==6\n");
Wait(6., 1);
green("Goto more\n");
coords = (coordpair_t){.X = 20., .Y = 30.};
if(MCC_E_OK != gotos(&coords, &speeds)) ERRX("Can't go");
DBG("c/s: %g %g %g %g", coords.X, coords.Y, speeds.X, speeds.Y);
green("Waiting Y==14\n");
Wait(14., 0);
// now we are @ point, make the same small correction again
green("Mkcorr 2\n");
if(MCC_E_OK != mkcorr(&coords, &speeds)) ERRX("Can't make corr");
// wait for 5 seconds
green("Wait for 5 seconds\n");
sleep(5);
// return to zero and wait
green("Return 2 zero and wait\n");
if(MCC_E_OK != return2zero()) ERRX("Can't return");
Wait(0., 0);
Wait(0., 1);
// wait moving ends
pthread_join(dthr, NULL);
signals(0);
return 0;
}

View File

@@ -49,7 +49,7 @@ static parameters G = {
static FILE *fcoords = NULL; static FILE *fcoords = NULL;
static coords_t M; static coordval_pair_t M;
static sl_option_t cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
@@ -77,8 +77,8 @@ static void *dumping(void _U_ *u){
return NULL; return NULL;
} }
// return TRUE if motor position is reached +- 0.1 degrees // return TRUE if motor position is reached +- 0.01 degrees
#define XYcount (DEG2RAD(0.1)) #define XYcount (DEG2RAD(0.01))
static int Wait(double tag){ static int Wait(double tag){
mountdata_t mdata; mountdata_t mdata;
red("Wait for %g degrees\n", RAD2DEG(tag)); red("Wait for %g degrees\n", RAD2DEG(tag));
@@ -92,8 +92,8 @@ static int Wait(double tag){
errcnt = 0; errcnt = 0;
if(mdata.millis == millis) continue; if(mdata.millis == millis) continue;
millis = mdata.millis; millis = mdata.millis;
if(*G.axis == 'X') curpos = mdata.motposition.X; if(*G.axis == 'X') curpos = mdata.motXposition.val;
else curpos = mdata.motposition.Y; else curpos = mdata.motYposition.val;
if(sign == 0.) sign = (curpos > tag) ? 1. : -1.; if(sign == 0.) sign = (curpos > tag) ? 1. : -1.;
//printf("%s=%g deg, need %g deg; delta=%g arcmin\n", G.axis, RAD2DEG(curpos), //printf("%s=%g deg, need %g deg; delta=%g arcmin\n", G.axis, RAD2DEG(curpos),
// RAD2DEG(tag), RAD2DEG(sign*(curpos - tag))*60.); // RAD2DEG(tag), RAD2DEG(sign*(curpos - tag))*60.);
@@ -104,27 +104,26 @@ static int Wait(double tag){
return FALSE; return FALSE;
} }
green("%s reached position %g degrees\n", G.axis, RAD2DEG(tag)); green("%s reached position %g degrees\n", G.axis, RAD2DEG(tag));
fflush(stdout);
return TRUE; return TRUE;
} }
// move X/Y to 40 degr with given speed until given coord // move X/Y to 40 degr with given speed until given coord
static void move(double target, double limit, double speed){ static void move(double target, double limit, double speed){
#define SCMD() do{if(MCC_E_OK != Mount.shortCmd(&cmd)) ERRX("Can't run command"); }while(0)
green("Move %s to %g until %g with %gdeg/s\n", G.axis, target, limit, speed); green("Move %s to %g until %g with %gdeg/s\n", G.axis, target, limit, speed);
short_command_t cmd = {0}; short_command_t cmd = {0};
if(*G.axis == 'X' || *G.axis == 'B'){ if(*G.axis == 'X' || *G.axis == 'B'){
cmd.Xmot = DEG2RAD(target) + M.X; cmd.Xmot = DEG2RAD(target) + M.X.val;
cmd.Xspeed = DEG2RAD(speed); cmd.Xspeed = DEG2RAD(speed);
limit = DEG2RAD(limit) + M.X; limit = DEG2RAD(limit) + M.X.val;
} }
if(*G.axis == 'Y' || *G.axis == 'B'){ if(*G.axis == 'Y' || *G.axis == 'B'){
cmd.Ymot = DEG2RAD(target) + M.Y; cmd.Ymot = DEG2RAD(target) + M.Y.val;
cmd.Yspeed = DEG2RAD(speed); cmd.Yspeed = DEG2RAD(speed);
limit = DEG2RAD(limit) + M.Y; if(*G.axis != 'B') limit = DEG2RAD(limit) + M.Y.val;
} }
SCMD(); if(MCC_E_OK != Mount.shortCmd(&cmd)) ERRX("Can't run command");
if(!Wait(limit)) signals(9); if(!Wait(limit)) signals(9);
#undef SCMD
} }
@@ -160,21 +159,19 @@ int main(int argc, char **argv){
pthread_t dthr; pthread_t dthr;
logmnt(fcoords, NULL); logmnt(fcoords, NULL);
if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread"); if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread");
// goto 1 degr with 1'/s // goto 30' with 5'/s
move(10., 1., 1./60.); move(10., 30./60., 5./60.);
// goto 2 degr with 2'/s // goto 1' with 10'/s
move(10., 2., 2./60.); move(10., 1., 10./60.);
// goto 3 degr with 5'/s // goto 3degr with 15'/s
move(10., 3., 5./60.); move(10., 3., 15./60.);
// goto 4 degr with 10'/s // and go back with 7deg/s
move(10., 4., 10./60.); move(0., 0., 7.);
// and go back with 5deg/s // be sure to move @ starting position
move(0., 0., 5.); coordpair_t tag = {.X = M.X.val, .Y = M.Y.val};
// be sure to move @ 0,0 Mount.moveTo(&tag);
Mount.moveTo(&M.X, &M.Y);
// wait moving ends // wait moving ends
pthread_join(dthr, NULL); pthread_join(dthr, NULL);
#undef SCMD
signals(0); signals(0);
return 0; return 0;
} }

View File

@@ -55,7 +55,7 @@ static sl_option_t cmdlnopts[] = {
{"coordsfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.coordsoutput),"output file with coordinates log"}, {"coordsfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.coordsoutput),"output file with coordinates log"},
{"axis", NEED_ARG, NULL, 'a', arg_string, APTR(&G.axis), "axis to move (X or Y)"}, {"axis", NEED_ARG, NULL, 'a', arg_string, APTR(&G.axis), "axis to move (X or Y)"},
{"period", NEED_ARG, NULL, 'p', arg_double, APTR(&G.period), "swinging period (could be not reached if amplitude is too small) - not more than 900s (default: 1)"}, {"period", NEED_ARG, NULL, 'p', arg_double, APTR(&G.period), "swinging period (could be not reached if amplitude is too small) - not more than 900s (default: 1)"},
{"amplitude", NEED_ARG, NULL, 'A', arg_double, APTR(&G.amplitude), "max amplitude (could be not reaced if period is too small) - not more than 45deg (default: 5)"}, {"amplitude", NEED_ARG, NULL, 'A', arg_double, APTR(&G.amplitude), "max amplitude (could be not reached if period is too small): [-45:45]deg (default: 5)"},
{"nswings", NEED_ARG, NULL, 'N', arg_int, APTR(&G.Nswings), "amount of swing periods (default: 10)"}, {"nswings", NEED_ARG, NULL, 'N', arg_int, APTR(&G.Nswings), "amount of swing periods (default: 10)"},
{"conffile", NEED_ARG, NULL, 'C', arg_int, APTR(&G.conffile), "configuration file name"}, {"conffile", NEED_ARG, NULL, 'C', arg_int, APTR(&G.conffile), "configuration file name"},
end_option end_option
@@ -83,18 +83,18 @@ void waithalf(double t){
uint32_t millis = 0; uint32_t millis = 0;
double xlast = 0., ylast = 0.; double xlast = 0., ylast = 0.;
while(ctr < 5){ while(ctr < 5){
if(sl_dtime() >= t) return; if(Mount.timeFromStart() >= t) return;
usleep(1000); usleep(1000);
if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;} if(MCC_E_OK != Mount.getMountData(&mdata)){ WARNX("Can't get data"); continue;}
if(mdata.millis == millis) continue; if(mdata.millis == millis) continue;
millis = mdata.millis; millis = mdata.millis;
if(mdata.motposition.X != xlast || mdata.motposition.Y != ylast){ if(mdata.motXposition.val != xlast || mdata.motYposition.val != ylast){
DBG("NEQ: old=%g, now=%g", RAD2DEG(ylast), RAD2DEG(mdata.motposition.Y)); //DBG("NEQ: old=%g, now=%g", RAD2DEG(ylast), RAD2DEG(mdata.motYposition.val));
xlast = mdata.motposition.X; xlast = mdata.motXposition.val;
ylast = mdata.motposition.Y; ylast = mdata.motYposition.val;
ctr = 0; ctr = 0;
}else{ }else{
DBG("EQ: old=%g, now=%g", RAD2DEG(ylast), RAD2DEG(mdata.motposition.Y)); //DBG("EQ: old=%g, now=%g", RAD2DEG(ylast), RAD2DEG(mdata.motYposition.val));
++ctr; ++ctr;
} }
} }
@@ -110,15 +110,28 @@ int main(int argc, char **argv){
return 1; return 1;
} }
if(G.coordsoutput){ if(G.coordsoutput){
if(!(fcoords = fopen(G.coordsoutput, "w"))) if(!(fcoords = fopen(G.coordsoutput, "w"))){
ERRX("Can't open %s", G.coordsoutput); WARNX("Can't open %s", G.coordsoutput);
return 1;
}
}else fcoords = stdout; }else fcoords = stdout;
if(G.Ncycles < 7) ERRX("Ncycles should be >7"); if(G.Ncycles < 2){
if(G.amplitude < 0.01 || G.amplitude > 45.) WARNX("Ncycles should be >2");
ERRX("Amplitude should be from 0.01 to 45 degrees"); return 1;
if(G.period < 0.1 || G.period > 900.) }
ERRX("Period should be from 0.1 to 900s"); double absamp = fabs(G.amplitude);
if(G.Nswings < 1) ERRX("Nswings should be more than 0"); if(absamp < 0.01 || absamp > 45.){
WARNX("Amplitude should be from 0.01 to 45 degrees");
return 1;
}
if(G.period < 0.1 || G.period > 900.){
WARNX("Period should be from 0.1 to 900s");
return 1;
}
if(G.Nswings < 1){
WARNX("Nswings should be more than 0");
return 1;
}
conf_t *Config = readServoConf(G.conffile); conf_t *Config = readServoConf(G.conffile);
if(!Config){ if(!Config){
dumpConf(); dumpConf();
@@ -145,25 +158,30 @@ int main(int argc, char **argv){
}else{ }else{
tagX = 0.; tagY = DEG2RAD(G.amplitude); tagX = 0.; tagY = DEG2RAD(G.amplitude);
} }
double t = sl_dtime(), t0 = t; double t = Mount.timeFromStart(), t0 = t;
double divide = 2., rtagX = -tagX, rtagY = -tagY; coordpair_t tag = {.X = tagX, .Y = tagY}, rtag = {.X = -tagX, .Y = -tagY};
double divide = 2.;
for(int i = 0; i < G.Nswings; ++i){ for(int i = 0; i < G.Nswings; ++i){
Mount.moveTo(&tagX, &tagY); Mount.moveTo(&tag);
DBG("CMD: %g", sl_dtime()-t0); DBG("CMD: %g", Mount.timeFromStart()-t0);
t += G.period / divide; t += G.period / divide;
divide = 1.; divide = 1.;
waithalf(t); waithalf(t);
DBG("Moved to +, t=%g", t-t0); DBG("Moved to +, t=%g", t-t0);
DBG("CMD: %g", sl_dtime()-t0); DBG("CMD: %g", Mount.timeFromStart()-t0);
Mount.moveTo(&rtagX, &rtagY); Mount.moveTo(&rtag);
t += G.period; t += G.period;
waithalf(t); waithalf(t);
DBG("Moved to -, t=%g", t-t0); DBG("Moved to -, t=%g", t-t0);
DBG("CMD: %g", sl_dtime()-t0); DBG("CMD: %g", Mount.timeFromStart()-t0);
} }
double zero = 0.; green("Move to zero @ %g\n", Mount.timeFromStart());
tag = (coordpair_t){0};
// be sure to move @ 0,0 // be sure to move @ 0,0
Mount.moveTo(&zero, &zero); if(MCC_E_OK != Mount.moveTo(&tag)){
Mount.emergStop();
Mount.moveTo(&tag);
}
// wait moving ends // wait moving ends
pthread_join(dthr, NULL); pthread_join(dthr, NULL);
#undef SCMD #undef SCMD

View File

@@ -40,7 +40,7 @@ typedef struct{
} parameters; } parameters;
static parameters G = { static parameters G = {
.Ncycles = 40, .Ncycles = 10,
.X = NAN, .X = NAN,
.Y = NAN, .Y = NAN,
}; };
@@ -61,12 +61,13 @@ static FILE* fcoords = NULL;
static pthread_t dthr; static pthread_t dthr;
void signals(int sig){ void signals(int sig){
pthread_cancel(dthr);
if(sig){ if(sig){
signal(sig, SIG_IGN); signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig); DBG("Get signal %d, quit.\n", sig);
} }
DBG("Quit");
Mount.quit(); Mount.quit();
DBG("close");
if(fcoords) fclose(fcoords); if(fcoords) fclose(fcoords);
exit(sig); exit(sig);
} }
@@ -88,46 +89,56 @@ int main(int _U_ argc, char _U_ **argv){
return 1; return 1;
} }
if(MCC_E_OK != Mount.init(Config)) ERRX("Can't init mount"); if(MCC_E_OK != Mount.init(Config)) ERRX("Can't init mount");
coords_t M; coordval_pair_t M, E;
if(!getPos(&M, NULL)) ERRX("Can't get current position"); if(!getPos(&M, &E)) ERRX("Can't get current position");
printf("Current time: %.10f\n", Mount.timeFromStart());
if(G.coordsoutput){ if(G.coordsoutput){
if(!G.wait) green("When logging I should wait until moving ends; added '-w'"); if(!G.wait) green("When logging I should wait until moving ends; added '-w'\n");
G.wait = 1; G.wait = 1;
}
if(G.coordsoutput){
if(!(fcoords = fopen(G.coordsoutput, "w"))) if(!(fcoords = fopen(G.coordsoutput, "w")))
ERRX("Can't open %s", G.coordsoutput); ERRX("Can't open %s", G.coordsoutput);
logmnt(fcoords, NULL); logmnt(fcoords, NULL);
if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread"); if(pthread_create(&dthr, NULL, dumping, NULL)) ERRX("Can't run dump thread");
} }
printf("Mount position: X=%g, Y=%g\n", RAD2DEG(M.X), RAD2DEG(M.Y)); M.X.val = RAD2DEG(M.X.val);
M.Y.val = RAD2DEG(M.Y.val);
printf("Mount position: X=%g, Y=%g; encoders: X=%g, Y=%g\n", M.X.val, M.Y.val,
RAD2DEG(E.X.val), RAD2DEG(E.Y.val));
if(isnan(G.X) && isnan(G.Y)) goto out; if(isnan(G.X) && isnan(G.Y)) goto out;
double *xtag = NULL, *ytag = NULL, xr, yr; coordpair_t tag;
if(!isnan(G.X)){ if(isnan(G.X)){
xr = DEG2RAD(G.X); if(G.relative) G.X = 0.;
if(G.relative) xr += M.X; else G.X = M.X.val;
xtag = &xr;
} }
if(!isnan(G.Y)){ if(isnan(G.Y)){
yr = DEG2RAD(G.Y); if(G.relative) G.Y = 0.;
if(G.relative) yr += M.Y; else G.Y = M.Y.val;
ytag = &yr; }
if(G.relative){
G.X += M.X.val;
G.Y += M.Y.val;
}
printf("Moving to X=%gdeg, Y=%gdeg\n", G.X, G.Y);
tag.X = DEG2RAD(G.X); tag.Y = DEG2RAD(G.Y);
mcc_errcodes_t e = Mount.moveTo(&tag);
if(MCC_E_OK != e){
WARNX("Cant go to given coordinates: %s\n", EcodeStr(e));
goto out;
} }
printf("Moving to ");
if(xtag) printf("X=%gdeg ", G.X);
if(ytag) printf("Y=%gdeg", G.Y);
printf("\n");
Mount.moveTo(xtag, ytag);
if(G.wait){ if(G.wait){
sleep(1); sleep(1);
waitmoving(G.Ncycles); waitmoving(G.Ncycles);
if(!getPos(&M, NULL)) WARNX("Can't get current position"); if(!getPos(&M, NULL)) WARNX("Can't get current position");
else printf("New mount position: X=%g, Y=%g\n", RAD2DEG(M.X), RAD2DEG(M.Y)); else printf("New mount position: X=%g, Y=%g\n", RAD2DEG(M.X.val), RAD2DEG(M.Y.val));
} }
out: out:
DBG("JOIN");
if(G.coordsoutput) pthread_join(dthr, NULL); if(G.coordsoutput) pthread_join(dthr, NULL);
DBG("QUIT");
if(G.wait){ if(G.wait){
if(getPos(&M, NULL)) printf("Mount position: X=%g, Y=%g\n", RAD2DEG(M.X), RAD2DEG(M.Y)); usleep(250000); // pause to refresh coordinates
if(getPos(&M, &E)) printf("Mount position: X=%g, Y=%g; encoders: X=%g, Y=%g\n", RAD2DEG(M.X.val), RAD2DEG(M.Y.val),
RAD2DEG(E.X.val), RAD2DEG(E.Y.val));
Mount.quit(); Mount.quit();
} }
return 0; return 0;

View File

@@ -30,6 +30,7 @@
typedef struct{ typedef struct{
int help; int help;
int dumpconf;
int Ncycles; // n cycles to wait stop int Ncycles; // n cycles to wait stop
double reqint; // requests interval (seconds) double reqint; // requests interval (seconds)
double Xmax; // maximal X to stop double Xmax; // maximal X to stop
@@ -38,11 +39,13 @@ typedef struct{
double X0; // starting point of traectory (-30..30 degr) double X0; // starting point of traectory (-30..30 degr)
double Y0; // -//- double Y0; // -//-
char *coordsoutput; // dump file char *coordsoutput; // dump file
char *errlog; // log with position errors
char *tfn; // traectory function name char *tfn; // traectory function name
char *conffile; char *conffile;
} parameters; } parameters;
static FILE *fcoords = NULL; static conf_t *Config = NULL;
static FILE *fcoords = NULL, *errlog = NULL;
static pthread_t dthr; static pthread_t dthr;
static parameters G = { static parameters G = {
.Ncycles = 40, .Ncycles = 40,
@@ -61,21 +64,24 @@ static sl_option_t cmdlnopts[] = {
{"coordsfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.coordsoutput),"output file with coordinates log"}, {"coordsfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.coordsoutput),"output file with coordinates log"},
{"reqinterval", NEED_ARG, NULL, 'i', arg_double, APTR(&G.reqint), "mount requests interval (default: 0.1 second)"}, {"reqinterval", NEED_ARG, NULL, 'i', arg_double, APTR(&G.reqint), "mount requests interval (default: 0.1 second)"},
{"traectory", NEED_ARG, NULL, 't', arg_string, APTR(&G.tfn), "used traectory function (default: sincos)"}, {"traectory", NEED_ARG, NULL, 't', arg_string, APTR(&G.tfn), "used traectory function (default: sincos)"},
{"xmax", NEED_ARG, NULL, 'X', arg_double, APTR(&G.Xmax), "maximal X coordinate for traectory (default: 45 degrees)"}, {"xmax", NEED_ARG, NULL, 'X', arg_double, APTR(&G.Xmax), "maximal abs X coordinate for traectory (default: 45 degrees)"},
{"ymax", NEED_ARG, NULL, 'Y', arg_double, APTR(&G.Ymax), "maximal X coordinate for traectory (default: 45 degrees)"}, {"ymax", NEED_ARG, NULL, 'Y', arg_double, APTR(&G.Ymax), "maximal abs Y coordinate for traectory (default: 45 degrees)"},
{"tmax", NEED_ARG, NULL, 'T', arg_double, APTR(&G.tmax), "maximal duration time of emulation (default: 300 seconds)"}, {"tmax", NEED_ARG, NULL, 'T', arg_double, APTR(&G.tmax), "maximal duration time of emulation (default: 300 seconds)"},
{"x0", NEED_ARG, NULL, '0', arg_double, APTR(&G.X0), "starting X-coordinate of traectory (default: 10 degrees)"}, {"x0", NEED_ARG, NULL, '0', arg_double, APTR(&G.X0), "starting X-coordinate of traectory (default: 10 degrees)"},
{"y0", NEED_ARG, NULL, '1', arg_double, APTR(&G.Y0), "starting Y-coordinate of traectory (default: 10 degrees)"}, {"y0", NEED_ARG, NULL, '1', arg_double, APTR(&G.Y0), "starting Y-coordinate of traectory (default: 10 degrees)"},
{"conffile", NEED_ARG, NULL, 'C', arg_string, APTR(&G.conffile), "configuration file name"}, {"conffile", NEED_ARG, NULL, 'C', arg_string, APTR(&G.conffile), "configuration file name"},
{"errlog", NEED_ARG, NULL, 'e', arg_string, APTR(&G.errlog), "file with errors log"},
{"dumpconf", NO_ARGS, NULL, 'D', arg_int, APTR(&G.dumpconf), "dump current configuration"},
end_option end_option
}; };
void signals(int sig){ void signals(int sig){
pthread_cancel(dthr);
if(sig){ if(sig){
signal(sig, SIG_IGN); signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig); DBG("Get signal %d, quit.\n", sig);
} }
Mount.stop();
sleep(1);
Mount.quit(); Mount.quit();
if(fcoords) fclose(fcoords); if(fcoords) fclose(fcoords);
exit(sig); exit(sig);
@@ -89,22 +95,40 @@ static void *dumping(void _U_ *u){
// calculate // calculate
static void runtraectory(traectory_fn tfn){ static void runtraectory(traectory_fn tfn){
if(!tfn) return; if(!tfn) return;
coords_t telXY, traectXY; coordval_pair_t telXY;
double t0 = sl_dtime(); coordval_pair_t target;
uint32_t susec_last = 0; coordpair_t traectXY;
double tlast = 0., tstart = Mount.timeFromStart();
long tlastXnsec = 0, tlastYnsec = 0;
struct timespec tcur, t0 = {0};
dumpt0(&t0);
while(1){ while(1){
if(!telpos(&telXY)){ if(!telpos(&telXY)){
WARNX("No next telescope position"); WARNX("No next telescope position");
return; return;
} }
if(telXY.msrtime.tv_usec == susec_last) continue; // last measure - don't mind if(!Mount.currentT(&tcur)) continue;
susec_last = telXY.msrtime.tv_usec; if(telXY.X.t.tv_nsec == tlastXnsec && telXY.Y.t.tv_nsec == tlastYnsec) continue; // last measure - don't mind
double t = sl_dtime(); DBG("\n\nTELPOS: %g'/%g' (%.6f/%.6f)", RAD2AMIN(telXY.X.val), RAD2AMIN(telXY.Y.val), RAD2DEG(telXY.X.val), RAD2DEG(telXY.Y.val));
if(telXY.X > G.Xmax || telXY.Y > G.Ymax || t - t0 > G.tmax) break; 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(!traectory_point(&traectXY, t)) break;
DBG("%g: dX=%.1f'', dY=%.1f''", t-t0, RAD2ASEC(traectXY.X-telXY.X), RAD2ASEC(traectXY.Y-telXY.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("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));
} }
WARNX("No next traectory point"); if(MCC_E_OK != Mount.correctTo(&target)) WARNX("Error of correction!");
while((t = Mount.timeFromStart()) - tlast < Config->PIDRefreshDt) usleep(500);
tlast = t;
}
WARNX("No next traectory point or emulation ends");
} }
int main(int argc, char **argv){ int main(int argc, char **argv){
@@ -117,12 +141,18 @@ int main(int argc, char **argv){
G.Xmax = DEG2RAD(G.Xmax); G.Ymax = DEG2RAD(G.Ymax); G.Xmax = DEG2RAD(G.Xmax); G.Ymax = DEG2RAD(G.Ymax);
if(G.X0 < -30. || G.X0 > 30. || G.Y0 < -30. || G.Y0 > 30.) if(G.X0 < -30. || G.X0 > 30. || G.Y0 < -30. || G.Y0 > 30.)
ERRX("X0 and Y0 should be -30..30 degrees"); ERRX("X0 and Y0 should be -30..30 degrees");
if(G.errlog){
if(!(errlog = fopen(G.errlog, "w")))
ERRX("Can't open error log %s", G.errlog);
else
fprintf(errlog, "# time Xerr'' Yerr'' // target - real\n");
}
if(G.coordsoutput){ if(G.coordsoutput){
if(!(fcoords = fopen(G.coordsoutput, "w"))) if(!(fcoords = fopen(G.coordsoutput, "w")))
ERRX("Can't open %s", G.coordsoutput); ERRX("Can't open %s", G.coordsoutput);
}else fcoords = stdout; }else fcoords = stdout;
conf_t *Config = readServoConf(G.conffile); Config = readServoConf(G.conffile);
if(!Config){ if(!Config || G.dumpconf){
dumpConf(); dumpConf();
return 1; return 1;
} }
@@ -133,7 +163,7 @@ int main(int argc, char **argv){
print_tr_names(); print_tr_names();
return 1; return 1;
} }
coords_t c = {.X = DEG2RAD(G.X0), .Y = DEG2RAD(G.Y0)}; coordpair_t c = {.X = DEG2RAD(G.X0), .Y = DEG2RAD(G.Y0)};
if(!init_traectory(tfn, &c)){ if(!init_traectory(tfn, &c)){
ERRX("Can't init traectory"); ERRX("Can't init traectory");
return 1; return 1;

View File

@@ -1,8 +1,25 @@
# Current configuration
MountDevPath=/dev/ttyUSB0 MountDevPath=/dev/ttyUSB0
MountDevSpeed=19200 MountDevSpeed=19200
EncoderDevPath=(null)
EncoderDevSpeed=1000000
MountReqInterval=0.1
EncoderReqInterval=0.001
SepEncoder=2
EncoderXDevPath=/dev/encoder_X0 EncoderXDevPath=/dev/encoder_X0
EncoderYDevPath=/dev/encoder_Y0 EncoderYDevPath=/dev/encoder_Y0
MountReqInterval=0.05 EncoderSpeedInterval=0.05
SepEncoder=2 RunModel=1
EncoderReqInterval=0.01 XPIDCP=0.8
EncoderDevSpeed=1000000 XPIDCI=0.0
XPIDCD=0.0
YPIDCP=0.5
YPIDCI=0.0
YPIDCD=0.0
XPIDVP=0.2
XPIDVI=0.1
XPIDVD=0.0
YPIDVP=0.2
YPIDVI=0.1
YPIDVD=0.0

View File

@@ -20,10 +20,10 @@
#include <math.h> #include <math.h>
#define DEG2RAD(d) (d/180.*M_PI) #define DEG2RAD(d) ((d)/180.*M_PI)
#define ASEC2RAD(d) (d/180.*M_PI/3600.) #define ASEC2RAD(d) ((d)/180.*M_PI/3600.)
#define AMIN2RAD(d) (d/180.*M_PI/60.) #define AMIN2RAD(d) ((d)/180.*M_PI/60.)
#define RAD2DEG(r) (r/M_PI*180.) #define RAD2DEG(r) ((r)/M_PI*180.)
#define RAD2ASEC(r) (r/M_PI*180.*3600.) #define RAD2ASEC(r) ((r)/M_PI*180.*3600.)
#define RAD2AMIN(r) (r/M_PI*180.*60.) #define RAD2AMIN(r) ((r)/M_PI*180.*60.)

View File

@@ -28,12 +28,8 @@
static traectory_fn cur_traectory = NULL; static traectory_fn cur_traectory = NULL;
// starting point of traectory // starting point of traectory
static coords_t XYstart = {0}; static coordpair_t XYstart = {0};
static double tstart = 0.; static double tstart = 0.;
// convert Xe/Ye to approximate motor coordinates:
// Xnew = Xcor+Xe; Ynew = Ycor+Ye; as Ye goes backwards to Ym, we have
// Xcor = Xm0 - Xe0; Ycor = Xm0 + Ye0
static coords_t XYcor = {0};
/** /**
* @brief init_traectory - init traectory fn, sync starting positions of motor & encoders * @brief init_traectory - init traectory fn, sync starting positions of motor & encoders
@@ -41,20 +37,17 @@ static coords_t XYcor = {0};
* @param XY0 - starting point * @param XY0 - starting point
* @return FALSE if failed * @return FALSE if failed
*/ */
int init_traectory(traectory_fn f, coords_t *XY0){ int init_traectory(traectory_fn f, coordpair_t *XY0){
if(!f || !XY0) return FALSE; if(!f || !XY0) return FALSE;
cur_traectory = f; cur_traectory = f;
XYstart = *XY0; XYstart = *XY0;
tstart = sl_dtime(); tstart = Mount.timeFromStart();
mountdata_t mdata; mountdata_t mdata;
int ntries = 0; int ntries = 0;
for(; ntries < 10; ++ntries){ for(; ntries < 10; ++ntries){
if(MCC_E_OK == Mount.getMountData(&mdata)) break; if(MCC_E_OK == Mount.getMountData(&mdata)) break;
} }
if(ntries == 10) return FALSE; if(ntries == 10) return FALSE;
XYcor.X = mdata.motposition.X - mdata.encposition.X;
XYcor.Y = mdata.motposition.X + mdata.encposition.Y;
DBG("STARTING POINTS: x=%g, y=%g degrees", DEG2RAD(XYcor.X), DEG2RAD(XYcor.Y));
return TRUE; return TRUE;
} }
@@ -64,12 +57,10 @@ int init_traectory(traectory_fn f, coords_t *XY0){
* @param t - UNIX-time of event * @param t - UNIX-time of event
* @return FALSE if something wrong (e.g. X not in -90..90 or Y not in -180..180) * @return FALSE if something wrong (e.g. X not in -90..90 or Y not in -180..180)
*/ */
int traectory_point(coords_t *nextpt, double t){ int traectory_point(coordpair_t *nextpt, double t){
if(t < 0. || !cur_traectory) return FALSE; if(t < 0. || !cur_traectory) return FALSE;
coords_t pt; coordpair_t pt;
if(!cur_traectory(&pt, t)) return FALSE; if(!cur_traectory(&pt, t)) return FALSE;
pt.msrtime.tv_sec = floor(t);
pt.msrtime.tv_usec = (uint32_t) t - pt.msrtime.tv_sec;
if(nextpt) *nextpt = pt; if(nextpt) *nextpt = pt;
if(pt.X < -M_PI_2 || pt.X > M_PI_2 || pt.Y < -M_PI || pt.Y > M_PI) return FALSE; if(pt.X < -M_PI_2 || pt.X > M_PI_2 || pt.Y < -M_PI || pt.Y > M_PI) return FALSE;
return TRUE; return TRUE;
@@ -77,34 +68,36 @@ int traectory_point(coords_t *nextpt, double t){
// current telescope position according to starting motor coordinates // current telescope position according to starting motor coordinates
// @return FALSE if failed to get current coordinates // @return FALSE if failed to get current coordinates
int telpos(coords_t *curpos){ int telpos(coordval_pair_t *curpos){
mountdata_t mdata; mountdata_t mdata;
int ntries = 0; int ntries = 0;
for(; ntries < 10; ++ntries){ for(; ntries < 10; ++ntries){
if(MCC_E_OK == Mount.getMountData(&mdata)) break; if(MCC_E_OK == Mount.getMountData(&mdata)) break;
} }
if(ntries == 10) return FALSE; if(ntries == 10) return FALSE;
coords_t pt; coordval_pair_t pt;
pt.X = XYcor.X + mdata.encposition.X; //DBG("\n\nTELPOS: %g'/%g' measured @ %.6f", RAD2AMIN(mdata.encXposition.val), RAD2AMIN(mdata.encYposition.val), mdata.encXposition.t);
pt.Y = XYcor.Y + mdata.encposition.Y; pt.X.val = mdata.encXposition.val;
pt.msrtime = mdata.encposition.msrtime; pt.Y.val = mdata.encYposition.val;
pt.X.t = mdata.encXposition.t;
pt.Y.t = mdata.encYposition.t;
if(curpos) *curpos = pt; if(curpos) *curpos = pt;
return TRUE; return TRUE;
} }
// X=X0+1'/s, Y=Y0+15''/s // X=X0+1'/s, Y=Y0+15''/s
int Linear(coords_t *nextpt, double t){ int Linear(coordpair_t *nextpt, double t){
coords_t pt; coordpair_t pt;
pt.X = XYstart.X + ASEC2RAD(1.) * (t - tstart); pt.X = XYstart.X + ASEC2RAD(0.1) * (t - tstart);
pt.Y = XYstart.Y + ASEC2RAD(15.)* (t - tstart); pt.Y = XYstart.Y + ASEC2RAD(15.)* (t - tstart);
if(nextpt) *nextpt = pt; if(nextpt) *nextpt = pt;
return TRUE; return TRUE;
} }
// X=X0+5'*sin(t/30*2pi), Y=Y0+10'*cos(t/200*2pi) // X=X0+5'*sin(t/30*2pi), Y=Y0+10'*cos(t/200*2pi)
int SinCos(coords_t *nextpt, double t){ int SinCos(coordpair_t *nextpt, double t){
coords_t pt; coordpair_t pt;
pt.X = XYstart.X + AMIN2RAD(5.) * sin((t-tstart)/30.*2*M_PI); pt.X = XYstart.X + ASEC2RAD(5.) * sin((t-tstart)/30.*2*M_PI);
pt.Y = XYstart.Y + AMIN2RAD(10.)* cos((t-tstart)/200.*2*M_PI); pt.Y = XYstart.Y + AMIN2RAD(10.)* cos((t-tstart)/200.*2*M_PI);
if(nextpt) *nextpt = pt; if(nextpt) *nextpt = pt;
return TRUE; return TRUE;
@@ -117,8 +110,8 @@ typedef struct{
} tr_names; } tr_names;
static tr_names names[] = { static tr_names names[] = {
{Linear, "linear", "X=X0+1'/s, Y=Y0+15''/s"}, {Linear, "linear", "X=X0+0.1''/s, Y=Y0+15''/s"},
{SinCos, "sincos", "X=X0+5'*sin(t/30*2pi), Y=Y0+10'*cos(t/200*2pi)"}, {SinCos, "sincos", "X=X0+5''*sin(t/30*2pi), Y=Y0+10'*cos(t/200*2pi)"},
{NULL, NULL, NULL} {NULL, NULL, NULL}
}; };

View File

@@ -21,12 +21,12 @@
#include "sidservo.h" #include "sidservo.h"
// traectory // traectory
typedef int (*traectory_fn)(coords_t *, double); typedef int (*traectory_fn)(coordpair_t *, double);
int init_traectory(traectory_fn f, coords_t *XY0); int init_traectory(traectory_fn f, coordpair_t *XY0);
traectory_fn traectory_by_name(const char *name); traectory_fn traectory_by_name(const char *name);
void print_tr_names(); void print_tr_names();
int traectory_point(coords_t *nextpt, double t); int traectory_point(coordpair_t *nextpt, double t);
int telpos(coords_t *curpos); int telpos(coordval_pair_t *curpos);
int Linear(coords_t *nextpt, double t); int Linear(coordpair_t *nextpt, double t);
int SinCos(coords_t *nextpt, double t); int SinCos(coordpair_t *nextpt, double t);

View File

@@ -1,6 +1,7 @@
// Add predefined macros for your project here. For example: // Add predefined macros for your project here. For example:
// #define THE_ANSWER 42 // #define THE_ANSWER 42
#define EBUG #define EBUG
#define _POSIX_C_SOURCE #define _POSIX_C_SOURCE 11111111
#define PACKAGE_VERSION "0.0.1" #define PACKAGE_VERSION "0.0.1"
#define _XOPEN_SOURCE 666
#define _DEFAULT_SOURCE

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 15.0.1, 2025-02-27T22:45:06. --> <!-- Written by QtCreator 18.0.1, 2026-01-26T22:24:32. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>
@@ -13,8 +13,8 @@
<data> <data>
<variable>ProjectExplorer.Project.EditorSettings</variable> <variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value> <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value> <value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0"> <valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value> <value type="QString" key="language">Cpp</value>
@@ -75,8 +75,6 @@
<valuelist type="QVariantList" key="AutoTest.PathFilters"/> <valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value> <value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value> <value type="bool" key="AutoTest.UseGlobal">true</value>
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
<valuemap type="QVariantMap" key="ClangTools"> <valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value> <value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value> <value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
@@ -88,12 +86,14 @@
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/> <valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value> <value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap> </valuemap>
<value type="int" key="RcSync">0</value>
</valuemap> </valuemap>
</data> </data>
<data> <data>
<variable>ProjectExplorer.Project.Target.0</variable> <variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap"> <valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value> <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.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
@@ -101,7 +101,7 @@
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value> <value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value> <value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/Big/Data/00__Small_tel/C-sources/erfa</value> <value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/robo5/mountcontrol.git/LibSidServo</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0"> <valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets"> <valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
@@ -133,8 +133,44 @@
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value> <value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/> <valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Default</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.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> </valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value> <value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
@@ -153,7 +189,9 @@
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0"> <valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value> <value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value> <value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</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"/> <valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value> <value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/> <valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
@@ -163,8 +201,10 @@
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value> <value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value> <value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</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.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value> <value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">%{RunConfig:Executable:Path}</value>
</valuemap> </valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value> <value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap> </valuemap>
@@ -173,10 +213,6 @@
<variable>ProjectExplorer.Project.TargetCount</variable> <variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value> <value type="qlonglong">1</value>
</data> </data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data> <data>
<variable>Version</variable> <variable>Version</variable>
<value type="int">22</value> <value type="int">22</value>

View File

@@ -1,11 +1,13 @@
CMakeLists.txt CMakeLists.txt
dbg.h PID.c
PID.h
examples/SSIIconf.c examples/SSIIconf.c
examples/conf.c examples/conf.c
examples/conf.h examples/conf.h
examples/dump.c examples/dump.c
examples/dump.h examples/dump.h
examples/dumpmoving.c examples/dumpmoving.c
examples/dumpmoving_dragNtrack.c
examples/dumpmoving_scmd.c examples/dumpmoving_scmd.c
examples/dumpswing.c examples/dumpswing.c
examples/goto.c examples/goto.c
@@ -17,6 +19,12 @@ serial.c
examples/CMakeLists.txt examples/CMakeLists.txt
examples/traectories.c examples/traectories.c
examples/traectories.h examples/traectories.h
main.h
movingmodel.c
movingmodel.h
polltest/main.c
ramp.c
ramp.h
serial.h serial.h
ssii.c ssii.c
ssii.h ssii.h

View File

@@ -16,26 +16,169 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
* main functions to fill struct `mount_t`
*/
#include <inttypes.h> #include <inttypes.h>
#include <strings.h>
#include <time.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h>
#include "dbg.h" #include "main.h"
#include "movingmodel.h"
#include "serial.h" #include "serial.h"
#include "ssii.h" #include "ssii.h"
#include "PID.h"
// adder for monotonic time by realtime: inited any call of init()
static struct timespec timeadder = {0}, // adder of CLOCK_REALTIME to CLOCK_MONOTONIC
t0 = {0}, // curtime() for initstarttime() call
starttime = {0}; // starting time by monotonic (for timefromstart())
conf_t Conf = {0}; conf_t Conf = {0};
// parameters for model
static movemodel_t *Xmodel, *Ymodel;
// limits for model and/or real mount (in latter case data should be read from mount on init)
// radians, rad/sec, rad/sec^2
// max speeds (rad/s): xs=10 deg/s, ys=8 deg/s
// accelerations: xa=12.6 deg/s^2, ya= 9.5 deg/s^2
limits_t
Xlimits = {
.min = {.coord = -3.1241, .speed = 1e-10, .accel = 1e-6},
.max = {.coord = 3.1241, .speed = 0.174533, .accel = 0.219911}},
Ylimits = {
.min = {.coord = -3.1241, .speed = 1e-10, .accel = 1e-6},
.max = {.coord = 3.1241, .speed = 0.139626, .accel = 0.165806}}
;
static mcc_errcodes_t shortcmd(short_command_t *cmd);
static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig);
/**
* @brief curtime - monotonic time from first run
* @param t - struct timespec by CLOCK_MONOTONIC but with setpoint by CLOCK_REALTIME on observations start
* @return TRUE if all OK
* FIXME: double -> struct timespec; on init: init t0 by CLOCK_REALTIME
*/
int curtime(struct timespec *t){
struct timespec now;
if(clock_gettime(CLOCK_MONOTONIC, &now)) return FALSE;
now.tv_sec += timeadder.tv_sec;
now.tv_nsec += timeadder.tv_nsec;
if(now.tv_nsec > 999999999L){
++now.tv_sec;
now.tv_nsec -= 1000000000L;
}
if(t) *t = now;
return TRUE;
}
// init starttime; @return TRUE if all OK
static int initstarttime(){
struct timespec start;
if(clock_gettime(CLOCK_MONOTONIC, &starttime)) return FALSE;
if(clock_gettime(CLOCK_REALTIME, &start)) return FALSE;
timeadder.tv_sec = start.tv_sec - starttime.tv_sec;
timeadder.tv_nsec = start.tv_nsec - starttime.tv_nsec;
if(timeadder.tv_nsec < 0){
--timeadder.tv_sec;
timeadder.tv_nsec += 1000000000L;
}
curtime(&t0);
return TRUE;
}
// return difference (in seconds) between time1 and time0
double timediff(const struct timespec *time1, const struct timespec *time0){
if(!time1 || !time0) return -1.;
return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1e9;
}
// difference between given time and last initstarttime() call
double timediff0(const struct timespec *time1){
return timediff(time1, &t0);
}
// time from last initstarttime() call
double timefromstart(){
struct timespec now;
if(clock_gettime(CLOCK_MONOTONIC, &now)) return -1.;
return (now.tv_sec - starttime.tv_sec) + (now.tv_nsec - starttime.tv_nsec) / 1e9;
}
/** /**
* @brief quit - close all opened and return to default state * @brief quit - close all opened and return to default state
* TODO: close serial devices even in "model" mode
*/ */
static void quit(){ static void quit(){
DBG("Close serial devices"); if(Conf.RunModel) return;
for(int i = 0; i < 10; ++i) if(SSstop(TRUE)) break; for(int i = 0; i < 10; ++i) if(SSstop(TRUE)) break;
DBG("Close all serial devices");
closeSerial(); closeSerial();
DBG("Exit"); DBG("Exit");
} }
void getModData(coordpair_t *c, movestate_t *xst, movestate_t *yst){
if(!c || !Xmodel || !Ymodel) return;
double tnow = timefromstart();
moveparam_t Xp, Yp;
movestate_t Xst = Xmodel->get_state(Xmodel, &Xp);
//DBG("Xstate = %d", Xst);
if(Xst == ST_MOVE) Xst = Xmodel->proc_move(Xmodel, &Xp, tnow);
movestate_t Yst = Ymodel->get_state(Ymodel, &Yp);
if(Yst == ST_MOVE) Yst = Ymodel->proc_move(Ymodel, &Yp, tnow);
c->X = Xp.coord;
c->Y = Yp.coord;
if(xst) *xst = Xst;
if(yst) *yst = Yst;
}
/**
* less square calculations of speed
*/
less_square_t *LS_init(size_t Ndata){
if(Ndata < 5){
DBG("Ndata=%zd - TOO SMALL", Ndata);
return NULL;
}
DBG("Init less squares: %zd", Ndata);
less_square_t *l = calloc(1, sizeof(less_square_t));
l->x = calloc(Ndata, sizeof(double));
l->t2 = calloc(Ndata, sizeof(double));
l->t = calloc(Ndata, sizeof(double));
l->xt = calloc(Ndata, sizeof(double));
l->arraysz = Ndata;
return l;
}
void LS_delete(less_square_t **l){
if(!l || !*l) return;
free((*l)->x); free((*l)->t2); free((*l)->t); free((*l)->xt);
free(*l);
*l = NULL;
}
// add next data portion and calculate current slope
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];
double t2 = t * t, xt = x * t;
l->x[idx] = x; l->t2[idx] = t2;
l->t[idx] = t; l->xt[idx] = xt;
++idx;
l->idx = (idx >= l->arraysz) ? 0 : idx;
l->xsum += x - oldx;
l->t2sum += t2 - oldt2;
l->tsum += t - oldt;
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;
// point: (sum_x - slope * sum_t) / n;
return (numerator / denominator);
}
/** /**
* @brief init - open serial devices and do other job * @brief init - open serial devices and do other job
* @param c - initial configuration * @param c - initial configuration
@@ -44,55 +187,90 @@ static void quit(){
static mcc_errcodes_t init(conf_t *c){ static mcc_errcodes_t init(conf_t *c){
FNAME(); FNAME();
if(!c) return MCC_E_BADFORMAT; if(!c) return MCC_E_BADFORMAT;
if(!initstarttime()) return MCC_E_FAILED;
Conf = *c; Conf = *c;
mcc_errcodes_t ret = MCC_E_OK; mcc_errcodes_t ret = MCC_E_OK;
if(!Conf.MountDevPath || Conf.MountDevSpeed < 1200){ Xmodel = model_init(&Xlimits);
DBG("Define mount device path and speed"); Ymodel = model_init(&Ylimits);
ret = MCC_E_BADFORMAT;
}else if(!openMount(Conf.MountDevPath, Conf.MountDevSpeed)){
DBG("Can't open %s with speed %d", Conf.MountDevPath, Conf.MountDevSpeed);
ret = MCC_E_MOUNTDEV;
}
if(Conf.SepEncoder){
if(!Conf.EncoderDevPath || Conf.EncoderDevSpeed < 1200){
DBG("Define encoder device path and speed");
ret = MCC_E_BADFORMAT;
}else if(!openEncoder(Conf.EncoderDevPath, Conf.EncoderDevSpeed)){
DBG("Can't open %s with speed %d", Conf.EncoderDevPath, Conf.EncoderDevSpeed);
ret = MCC_E_ENCODERDEV;
}
}
if(Conf.MountReqInterval > 1. || Conf.MountReqInterval < 0.05){ if(Conf.MountReqInterval > 1. || Conf.MountReqInterval < 0.05){
DBG("Bad value of MountReqInterval"); DBG("Bad value of MountReqInterval");
ret = MCC_E_BADFORMAT; ret = MCC_E_BADFORMAT;
} }
uint8_t buf[1024]; if(Conf.RunModel){
data_t d = {.buf = buf, .len = 0, .maxlen = 1024}; if(!Xmodel || !Ymodel || !openMount()) return MCC_E_FAILED;
// read input data as there may be some trash on start return MCC_E_OK;
if(!SSrawcmd(CMD_EXITACM, &d)) ret = MCC_E_FAILED; }
if(ret != MCC_E_OK) quit(); if(!Conf.MountDevPath || Conf.MountDevSpeed < MOUNT_BAUDRATE_MIN){
return ret; DBG("Define mount device path and speed");
ret = MCC_E_BADFORMAT;
}else if(!openMount()){
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;
// read HW config to update constants
hardware_configuration_t HW;
if(MCC_E_OK != get_hwconf(&HW)) return MCC_E_FAILED;
// make a pause for actual encoder's values
double t0 = timefromstart();
while(timefromstart() - t0 < Conf.EncoderReqInterval) usleep(1000);
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);
return e;
} }
// check coordinates and speeds; return FALSE if failed // check coordinates (rad) and speeds (rad/s); return FALSE if failed
// TODO fix to real limits!!! // TODO fix to real limits!!!
static int chkX(double X){ static int chkX(double X){
if(X > 2.*M_PI || X < -2.*M_PI) return FALSE; if(X > Xlimits.max.coord || X < Xlimits.min.coord) return FALSE;
return TRUE; return TRUE;
} }
static int chkY(double Y){ static int chkY(double Y){
if(Y > 2.*M_PI || Y < -2.*M_PI) return FALSE; if(Y > Ylimits.max.coord || Y < Ylimits.min.coord) return FALSE;
return TRUE; return TRUE;
} }
static int chkXs(double s){ static int chkXs(double s){
if(s < 0. || s > X_SPEED_MAX) return FALSE; if(s < Xlimits.min.speed || s > Xlimits.max.speed) return FALSE;
return TRUE; return TRUE;
} }
static int chkYs(double s){ static int chkYs(double s){
if(s < 0. || s > Y_SPEED_MAX) return FALSE; if(s < Ylimits.min.speed || s > Ylimits.max.speed) return FALSE;
return TRUE; return TRUE;
} }
// set SLEWING state if axis was stopped
static void setslewingstate(){
//FNAME();
mountdata_t d;
if(MCC_E_OK == getMD(&d)){
axis_status_t newx = d.Xstate, newy = d.Ystate;
//DBG("old state: %d/%d", d.Xstate, d.Ystate);
if(d.Xstate == AXIS_STOPPED) newx = AXIS_SLEWING;
if(d.Ystate == AXIS_STOPPED) newy = AXIS_SLEWING;
if(newx != d.Xstate || newy != d.Ystate){
DBG("Started moving -> slew");
setStat(newx, newy);
}
}else DBG("CAN't GET MOUNT DATA!");
}
/** /**
* @brief move2 - simple move to given point and stop * @brief move2 - simple move to given point and stop
@@ -100,41 +278,36 @@ static int chkYs(double s){
* @param Y - new Y coordinate (radians: -pi..pi) or NULL * @param Y - new Y coordinate (radians: -pi..pi) or NULL
* @return error code * @return error code
*/ */
static mcc_errcodes_t move2(const double *X, const double *Y){ static mcc_errcodes_t move2(const coordpair_t *target){
if(!X && !Y) return MCC_E_BADFORMAT; if(!target) return MCC_E_BADFORMAT;
if(X){ if(!chkX(target->X) || !chkY(target->Y)) return MCC_E_BADFORMAT;
if(!chkX(*X)) return MCC_E_BADFORMAT; if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
int32_t tag = X_RAD2MOT(*X); short_command_t cmd = {0};
DBG("X: %g, tag: %d", *X, tag); DBG("x,y: %g, %g", target->X, target->Y);
if(!SSsetterI(CMD_MOTX, tag)) return MCC_E_FAILED; cmd.Xmot = target->X;
} cmd.Ymot = target->Y;
if(Y){ cmd.Xspeed = Xlimits.max.speed;
if(!chkY(*Y)) return MCC_E_BADFORMAT; cmd.Yspeed = Ylimits.max.speed;
int32_t tag = Y_RAD2MOT(*Y); /*mcc_errcodes_t r = shortcmd(&cmd);
DBG("Y: %g, tag: %d", *Y, tag); if(r != MCC_E_OK) return r;
if(!SSsetterI(CMD_MOTY, tag)) return MCC_E_FAILED; setslewingstate();
} return MCC_E_OK;*/
return MCC_E_OK; return shortcmd(&cmd);
} }
/** /**
* @brief setspeed - set maximal speed over axis * @brief setspeed - set maximal speed over axis by text command
* @param X (i) - max speed or NULL * @param X (i) - max speed or NULL
* @param Y (i) - -//- * @param Y (i) - -//-
* @return errcode * @return errcode
*/ */
static mcc_errcodes_t setspeed(const double *X, const double *Y){ static mcc_errcodes_t setspeed(const coordpair_t *tagspeed){
if(!X && !Y) return MCC_E_BADFORMAT; if(!tagspeed || !chkXs(tagspeed->X) || !chkYs(tagspeed->Y)) return MCC_E_BADFORMAT;
if(X){ if(Conf.RunModel) return MCC_E_FAILED;
if(!chkXs(*X)) return MCC_E_BADFORMAT; int32_t spd = X_RS2MOTSPD(tagspeed->X);
int32_t spd = X_RS2MOTSPD(*X);
if(!SSsetterI(CMD_SPEEDX, spd)) return MCC_E_FAILED; if(!SSsetterI(CMD_SPEEDX, spd)) return MCC_E_FAILED;
} spd = Y_RS2MOTSPD(tagspeed->Y);
if(Y){
if(!chkYs(*Y)) return MCC_E_BADFORMAT;
int32_t spd = Y_RS2MOTSPD(*Y);
if(!SSsetterI(CMD_SPEEDY, spd)) return MCC_E_FAILED; if(!SSsetterI(CMD_SPEEDY, spd)) return MCC_E_FAILED;
}
return MCC_E_OK; return MCC_E_OK;
} }
@@ -144,19 +317,20 @@ static mcc_errcodes_t setspeed(const double *X, const double *Y){
* @param speed (i) - speed or NULL * @param speed (i) - speed or NULL
* @return * @return
*/ */
static mcc_errcodes_t move2s(const coords_t *target, const coords_t *speed){ static mcc_errcodes_t move2s(const coordpair_t *target, const coordpair_t *speed){
if(!target && !speed) return MCC_E_BADFORMAT; if(!target || !speed) return MCC_E_BADFORMAT;
if(!target) return setspeed(&speed->X, &speed->Y); if(!chkX(target->X) || !chkY(target->Y)) return MCC_E_BADFORMAT;
if(!speed) return move2(&target->X, &target->Y); if(!chkXs(speed->X) || !chkYs(speed->Y)) return MCC_E_BADFORMAT;
if(!chkX(target->X) || !chkY(target->Y) || !chkXs(speed->X) || !chkYs(speed->Y)) // updateMotorPos() here can make a problem; TODO: remove?
return MCC_E_BADFORMAT; if(MCC_E_OK != updateMotorPos()) return MCC_E_FAILED;
char buf[128]; short_command_t cmd = {0};
int32_t spd = X_RS2MOTSPD(speed->X), tag = X_RAD2MOT(target->X); cmd.Xmot = target->X;
snprintf(buf, 127, "%s%" PRIi64 "%s%" PRIi64, CMD_MOTX, tag, CMD_MOTXYS, spd); cmd.Ymot = target->Y;
if(!SStextcmd(buf, NULL)) return MCC_E_FAILED; cmd.Xspeed = speed->X;
spd = Y_RS2MOTSPD(speed->Y); tag = Y_RAD2MOT(target->Y); cmd.Yspeed = speed->Y;
snprintf(buf, 127, "%s%" PRIi64 "%s%" PRIi64, CMD_MOTY, tag, CMD_MOTXYS, spd); mcc_errcodes_t r = shortcmd(&cmd);
if(!SStextcmd(buf, NULL)) return MCC_E_FAILED; if(r != MCC_E_OK) return r;
setslewingstate();
return MCC_E_OK; return MCC_E_OK;
} }
@@ -165,11 +339,25 @@ static mcc_errcodes_t move2s(const coords_t *target, const coords_t *speed){
* @return errcode * @return errcode
*/ */
static mcc_errcodes_t emstop(){ static mcc_errcodes_t emstop(){
FNAME();
if(Conf.RunModel){
double curt = timefromstart();
Xmodel->emergency_stop(Xmodel, curt);
Ymodel->emergency_stop(Ymodel, curt);
return MCC_E_OK;
}
if(!SSstop(TRUE)) return MCC_E_FAILED; if(!SSstop(TRUE)) return MCC_E_FAILED;
return MCC_E_OK; return MCC_E_OK;
} }
// normal stop // normal stop
static mcc_errcodes_t stop(){ static mcc_errcodes_t stop(){
FNAME();
if(Conf.RunModel){
double curt = timefromstart();
Xmodel->stop(Xmodel, curt);
Ymodel->stop(Ymodel,curt);
return MCC_E_OK;
}
if(!SSstop(FALSE)) return MCC_E_FAILED; if(!SSstop(FALSE)) return MCC_E_FAILED;
return MCC_E_OK; return MCC_E_OK;
} }
@@ -181,8 +369,18 @@ static mcc_errcodes_t stop(){
*/ */
static mcc_errcodes_t shortcmd(short_command_t *cmd){ static mcc_errcodes_t shortcmd(short_command_t *cmd){
if(!cmd) return MCC_E_BADFORMAT; if(!cmd) return MCC_E_BADFORMAT;
if(Conf.RunModel){
double curt = timefromstart();
moveparam_t param = {0};
param.coord = cmd->Xmot; param.speed = cmd->Xspeed;
if(!model_move2(Xmodel, &param, curt)) return MCC_E_FAILED;
param.coord = cmd->Ymot; param.speed = cmd->Yspeed;
if(!model_move2(Ymodel, &param, curt)) return MCC_E_FAILED;
setslewingstate();
return MCC_E_OK;
}
SSscmd s = {0}; SSscmd s = {0};
DBG("xmot=%g, ymot=%g", cmd->Xmot, cmd->Ymot); DBG("tag: xmot=%g rad, ymot=%g rad", cmd->Xmot, cmd->Ymot);
s.Xmot = X_RAD2MOT(cmd->Xmot); s.Xmot = X_RAD2MOT(cmd->Xmot);
s.Ymot = Y_RAD2MOT(cmd->Ymot); s.Ymot = Y_RAD2MOT(cmd->Ymot);
s.Xspeed = X_RS2MOTSPD(cmd->Xspeed); s.Xspeed = X_RS2MOTSPD(cmd->Xspeed);
@@ -192,23 +390,27 @@ static mcc_errcodes_t shortcmd(short_command_t *cmd){
s.YBits = cmd->YBits; s.YBits = cmd->YBits;
DBG("X->%d, Y->%d, Xs->%d, Ys->%d", s.Xmot, s.Ymot, s.Xspeed, s.Yspeed); DBG("X->%d, Y->%d, Xs->%d, Ys->%d", s.Xmot, s.Ymot, s.Xspeed, s.Yspeed);
if(!cmdS(&s)) return MCC_E_FAILED; if(!cmdS(&s)) return MCC_E_FAILED;
cmd->Xmot = X_MOT2RAD(s.Xmot); setslewingstate();
cmd->Ymot = Y_MOT2RAD(s.Ymot);
cmd->Xspeed = X_MOTSPD2RS(s.Xspeed);
cmd->Yspeed = Y_MOTSPD2RS(s.Yspeed);
cmd->xychange = s.xychange;
cmd->XBits = s.XBits;
cmd->YBits = s.YBits;
return MCC_E_OK; return MCC_E_OK;
} }
/** /**
* @brief shortcmd - send and receive long binary command * @brief longcmd - send and receive long binary command
* @param cmd (io) - command * @param cmd (io) - command
* @return errcode * @return errcode
*/ */
static mcc_errcodes_t longcmd(long_command_t *cmd){ static mcc_errcodes_t longcmd(long_command_t *cmd){
if(!cmd) return MCC_E_BADFORMAT; if(!cmd) return MCC_E_BADFORMAT;
if(Conf.RunModel){
double curt = timefromstart();
moveparam_t param = {0};
param.coord = cmd->Xmot; param.speed = cmd->Xspeed;
if(!model_move2(Xmodel, &param, curt)) return MCC_E_FAILED;
param.coord = cmd->Ymot; param.speed = cmd->Yspeed;
if(!model_move2(Ymodel, &param, curt)) return MCC_E_FAILED;
setslewingstate();
return MCC_E_OK;
}
SSlcmd l = {0}; SSlcmd l = {0};
l.Xmot = X_RAD2MOT(cmd->Xmot); l.Xmot = X_RAD2MOT(cmd->Xmot);
l.Ymot = Y_RAD2MOT(cmd->Ymot); l.Ymot = Y_RAD2MOT(cmd->Ymot);
@@ -219,31 +421,175 @@ static mcc_errcodes_t longcmd(long_command_t *cmd){
l.Xatime = S2ADDER(cmd->Xatime); l.Xatime = S2ADDER(cmd->Xatime);
l.Yatime = S2ADDER(cmd->Yatime); l.Yatime = S2ADDER(cmd->Yatime);
if(!cmdL(&l)) return MCC_E_FAILED; if(!cmdL(&l)) return MCC_E_FAILED;
cmd->Xmot = X_MOT2RAD(l.Xmot); setslewingstate();
cmd->Ymot = Y_MOT2RAD(l.Ymot);
cmd->Xspeed = X_MOTSPD2RS(l.Xspeed);
cmd->Yspeed = Y_MOTSPD2RS(l.Yspeed);
cmd->Xadder = X_MOTSPD2RS(l.Xadder);
cmd->Yadder = Y_MOTSPD2RS(l.Yadder);
cmd->Xatime = ADDER2S(l.Xatime);
cmd->Yatime = ADDER2S(l.Yatime);
return MCC_E_OK; return MCC_E_OK;
} }
mcc_errcodes_t get_hwconf(hardware_configuration_t *c){ static mcc_errcodes_t get_hwconf(hardware_configuration_t *hwConfig){
if(!c) return MCC_E_BADFORMAT; if(!hwConfig) return MCC_E_BADFORMAT;
SSconfig conf; if(Conf.RunModel) return MCC_E_FAILED;
if(!cmdC(&conf, FALSE)) return MCC_E_FAILED; SSconfig config;
// and bored transformations DBG("Read HW configuration");
DBG("Xacc=%u", conf.Xconf.accel); if(!cmdC(&config, FALSE)) return MCC_E_FAILED;
DBG("Yacc=%u", conf.Yconf.accel); // Convert acceleration (ticks per loop^2 to rad/s^2)
c->Xconf.accel = X_MOTACC2RS(conf.Xconf.accel); hwConfig->Xconf.accel = X_MOTACC2RS(config.Xconf.accel);
DBG("cacc: %g", c->Xconf.accel); hwConfig->Yconf.accel = Y_MOTACC2RS(config.Yconf.accel);
c->Xconf.backlash = conf.Xconf.backlash; // Convert backlash (ticks to radians)
// ... hwConfig->Xconf.backlash = X_MOT2RAD(config.Xconf.backlash);
c->Yconf.accel = X_MOTACC2RS(conf.Yconf.accel); hwConfig->Yconf.backlash = Y_MOT2RAD(config.Yconf.backlash);
c->Xconf.backlash = conf.Xconf.backlash; // Convert error limit (ticks to radians)
// ... hwConfig->Xconf.errlimit = X_MOT2RAD(config.Xconf.errlimit);
hwConfig->Yconf.errlimit = Y_MOT2RAD(config.Yconf.errlimit);
// Proportional, integral, and derivative gains are unitless, so no conversion needed
hwConfig->Xconf.propgain = (double)config.Xconf.propgain;
hwConfig->Yconf.propgain = (double)config.Yconf.propgain;
hwConfig->Xconf.intgain = (double)config.Xconf.intgain;
hwConfig->Yconf.intgain = (double)config.Yconf.intgain;
hwConfig->Xconf.derivgain = (double)config.Xconf.derivgain;
hwConfig->Yconf.derivgain = (double)config.Yconf.derivgain;
// Output limit is a percentage (0-100)
hwConfig->Xconf.outplimit = (double)config.Xconf.outplimit / 255.0 * 100.0;
hwConfig->Yconf.outplimit = (double)config.Yconf.outplimit / 255.0 * 100.0;
// Current limit in amps
hwConfig->Xconf.currlimit = (double)config.Xconf.currlimit / 100.0;
hwConfig->Yconf.currlimit = (double)config.Yconf.currlimit / 100.0;
// Integral limit is unitless
hwConfig->Xconf.intlimit = (double)config.Xconf.intlimit;
hwConfig->Yconf.intlimit = (double)config.Yconf.intlimit;
// Copy XBits and YBits (no conversion needed)
hwConfig->xbits = config.xbits;
hwConfig->ybits = config.ybits;
// Copy address
hwConfig->address = config.address;
// TODO: What to do with eqrate, eqadj and trackgoal?
config.latitude = __bswap_16(config.latitude);
// Convert latitude (degrees * 100 to radians)
hwConfig->latitude = ((double)config.latitude) / 100.0 * M_PI / 180.0;
// Copy ticks per revolution
hwConfig->Xsetpr = __bswap_32(config.Xsetpr);
hwConfig->Ysetpr = __bswap_32(config.Ysetpr);
hwConfig->Xmetpr = __bswap_32(config.Xmetpr); // as documentation said, real ticks are 4 times less
hwConfig->Ymetpr = __bswap_32(config.Ymetpr);
// Convert slew rates (ticks per loop to rad/s)
hwConfig->Xslewrate = X_MOTSPD2RS(config.Xslewrate);
hwConfig->Yslewrate = Y_MOTSPD2RS(config.Yslewrate);
// Convert pan rates (ticks per loop to rad/s)
hwConfig->Xpanrate = X_MOTSPD2RS(config.Xpanrate);
hwConfig->Ypanrate = Y_MOTSPD2RS(config.Ypanrate);
// Convert guide rates (ticks per loop to rad/s)
hwConfig->Xguiderate = X_MOTSPD2RS(config.Xguiderate);
hwConfig->Yguiderate = Y_MOTSPD2RS(config.Yguiderate);
// copy baudrate
hwConfig->baudrate = (uint32_t) config.baudrate;
// Convert local search degrees (degrees * 100 to radians)
hwConfig->locsdeg = (double)config.locsdeg / 100.0 * M_PI / 180.0;
// Convert local search speed (arcsec per second to rad/s)
hwConfig->locsspeed = (double)config.locsspeed * M_PI / (180.0 * 3600.0);
// Convert backlash speed (ticks per loop to rad/s)
hwConfig->backlspd = X_MOTSPD2RS(config.backlspd);
// now read text commands
int64_t i64;
double Xticks, Yticks;
DBG("SERIAL");
// motor's encoder ticks per rev
if(!SSgetint(CMD_MEPRX, &i64)) return MCC_E_FAILED;
Xticks = ((double) i64); // divide by 4 as these values stored ???
if(!SSgetint(CMD_MEPRY, &i64)) return MCC_E_FAILED;
Yticks = ((double) i64);
X_ENC_ZERO = Conf.XEncZero;
Y_ENC_ZERO = Conf.YEncZero;
DBG("xyrev: %d/%d", config.xbits.motrev, config.ybits.motrev);
X_MOT_STEPSPERREV = hwConfig->Xconf.motor_stepsperrev = Xticks; // (config.xbits.motrev) ? -Xticks : Xticks;
Y_MOT_STEPSPERREV = hwConfig->Yconf.motor_stepsperrev = Yticks; //(config.ybits.motrev) ? -Yticks : Yticks;
DBG("zero: %d/%d; motsteps: %.10g/%.10g", X_ENC_ZERO, Y_ENC_ZERO, X_MOT_STEPSPERREV, Y_MOT_STEPSPERREV);
// axis encoder ticks per rev
if(!SSgetint(CMD_AEPRX, &i64)) return MCC_E_FAILED;
Xticks = (double) i64;
if(!SSgetint(CMD_AEPRY, &i64)) return MCC_E_FAILED;
Yticks = (double) i64;
DBG("xyencrev: %d/%d", config.xbits.encrev, config.ybits.encrev);
X_ENC_STEPSPERREV = hwConfig->Xconf.axis_stepsperrev = (config.xbits.encrev) ? -Xticks : Xticks;
Y_ENC_STEPSPERREV = hwConfig->Yconf.axis_stepsperrev = (config.ybits.encrev) ? -Yticks : Yticks;
DBG("encsteps: %.10g/%.10g", X_ENC_STEPSPERREV, Y_ENC_STEPSPERREV);
return MCC_E_OK;
}
static mcc_errcodes_t write_hwconf(hardware_configuration_t *hwConfig){
SSconfig config;
if(Conf.RunModel) return MCC_E_FAILED;
// Convert acceleration (rad/s^2 to ticks per loop^2)
config.Xconf.accel = X_RS2MOTACC(hwConfig->Xconf.accel);
config.Yconf.accel = Y_RS2MOTACC(hwConfig->Yconf.accel);
// Convert backlash (radians to ticks)
config.Xconf.backlash = X_RAD2MOT(hwConfig->Xconf.backlash);
config.Yconf.backlash = Y_RAD2MOT(hwConfig->Yconf.backlash);
// Convert error limit (radians to ticks)
config.Xconf.errlimit = X_RAD2MOT(hwConfig->Xconf.errlimit);
config.Yconf.errlimit = Y_RAD2MOT(hwConfig->Yconf.errlimit);
// Proportional, integral, and derivative gains are unitless, so no conversion needed
config.Xconf.propgain = (uint16_t)hwConfig->Xconf.propgain;
config.Yconf.propgain = (uint16_t)hwConfig->Yconf.propgain;
config.Xconf.intgain = (uint16_t)hwConfig->Xconf.intgain;
config.Yconf.intgain = (uint16_t)hwConfig->Yconf.intgain;
config.Xconf.derivgain = (uint16_t)hwConfig->Xconf.derivgain;
config.Yconf.derivgain = (uint16_t)hwConfig->Yconf.derivgain;
// Output limit is a percentage (0-100), so convert back to 0-255
config.Xconf.outplimit = (uint8_t)(hwConfig->Xconf.outplimit / 100.0 * 255.0);
config.Yconf.outplimit = (uint8_t)(hwConfig->Yconf.outplimit / 100.0 * 255.0);
// Current limit is in amps (convert back to *100)
config.Xconf.currlimit = (uint16_t)(hwConfig->Xconf.currlimit * 100.0);
config.Yconf.currlimit = (uint16_t)(hwConfig->Yconf.currlimit * 100.0);
// Integral limit is unitless, so no conversion needed
config.Xconf.intlimit = (uint16_t)hwConfig->Xconf.intlimit;
config.Yconf.intlimit = (uint16_t)hwConfig->Yconf.intlimit;
// Copy XBits and YBits (no conversion needed)
config.xbits = hwConfig->xbits;
config.ybits = hwConfig->ybits;
// Convert latitude (radians to degrees * 100)
config.latitude = __bswap_16((uint16_t)(hwConfig->latitude * 180.0 / M_PI * 100.0));
// Convert slew rates (rad/s to ticks per loop)
config.Xslewrate = X_RS2MOTSPD(hwConfig->Xslewrate);
config.Yslewrate = Y_RS2MOTSPD(hwConfig->Yslewrate);
// Convert pan rates (rad/s to ticks per loop)
config.Xpanrate = X_RS2MOTSPD(hwConfig->Xpanrate);
config.Ypanrate = Y_RS2MOTSPD(hwConfig->Ypanrate);
// Convert guide rates (rad/s to ticks per loop)
config.Xguiderate = X_RS2MOTSPD(hwConfig->Xguiderate);
config.Yguiderate = Y_RS2MOTSPD(hwConfig->Yguiderate);
// Convert local search degrees (radians to degrees * 100)
config.locsdeg = (uint32_t)(hwConfig->locsdeg * 180.0 / M_PI * 100.0);
// Convert local search speed (rad/s to arcsec per second)
config.locsspeed = (uint32_t)(hwConfig->locsspeed * 180.0 * 3600.0 / M_PI);
// Convert backlash speed (rad/s to ticks per loop)
config.backlspd = X_RS2MOTSPD(hwConfig->backlspd);
config.Xsetpr = __bswap_32(hwConfig->Xsetpr);
config.Ysetpr = __bswap_32(hwConfig->Ysetpr);
config.Xmetpr = __bswap_32(hwConfig->Xmetpr);
config.Ymetpr = __bswap_32(hwConfig->Ymetpr);
// todo - also write text params
// TODO - next
(void) config;
return MCC_E_OK;
}
// getters of max/min speed and acceleration
mcc_errcodes_t maxspeed(coordpair_t *v){
if(!v) return MCC_E_BADFORMAT;
v->X = Xlimits.max.speed;
v->Y = Ylimits.max.speed;
return MCC_E_OK;
}
mcc_errcodes_t minspeed(coordpair_t *v){
if(!v) return MCC_E_BADFORMAT;
v->X = Xlimits.min.speed;
v->Y = Ylimits.min.speed;
return MCC_E_OK;
}
mcc_errcodes_t acceleration(coordpair_t *a){
if(!a) return MCC_E_BADFORMAT;
a->X = Xlimits.max.accel;
a->Y = Ylimits.max.accel;
return MCC_E_OK; return MCC_E_OK;
} }
@@ -260,4 +606,14 @@ mount_t Mount = {
.shortCmd = shortcmd, .shortCmd = shortcmd,
.longCmd = longcmd, .longCmd = longcmd,
.getHWconfig = get_hwconf, .getHWconfig = get_hwconf,
.saveHWconfig = write_hwconf,
.currentT = curtime,
.timeFromStart = timefromstart,
.timeDiff = timediff,
.timeDiff0 = timediff0,
.correctTo = correct2,
.getMaxSpeed = maxspeed,
.getMinSpeed = minspeed,
.getAcceleration = acceleration,
}; };

View File

@@ -0,0 +1,81 @@
/*
* This file is part of the libsidservo project.
* Copyright 2025 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/>.
*/
/*
* Almost all here used for debug purposes
*/
#pragma once
#include <stdlib.h>
#include "movingmodel.h"
#include "sidservo.h"
extern conf_t Conf;
extern limits_t Xlimits, Ylimits;
int curtime(struct timespec *t);
double timediff(const struct timespec *time1, const struct timespec *time0);
double timediff0(const struct timespec *time1);
double timefromstart();
void getModData(coordpair_t *c, movestate_t *xst, movestate_t *yst);
typedef struct{
double *x, *t, *t2, *xt; // arrays of coord/time and multiply
double xsum, tsum, t2sum, xtsum; // sums of coord/time and their multiply
size_t idx; // index of current data in array
size_t arraysz; // size of arrays
} less_square_t;
less_square_t *LS_init(size_t Ndata);
void LS_delete(less_square_t **ls);
double LS_calc_slope(less_square_t *l, double x, double t);
// unused arguments of functions
#define _U_ __attribute__((__unused__))
// weak functions
#define WEAK __attribute__ ((weak))
#ifndef DBL_EPSILON
#define DBL_EPSILON (2.2204460492503131e-16)
#endif
#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (1)
#endif
#ifdef EBUG
#include <stdio.h>
#define COLOR_RED "\033[1;31;40m"
#define COLOR_GREEN "\033[1;32;40m"
#define COLOR_OLD "\033[0;0;0m"
#define FNAME() do{ fprintf(stderr, COLOR_GREEN "\n%s " COLOR_OLD, __func__); \
fprintf(stderr, "(%s, line %d)\n", __FILE__, __LINE__);} while(0)
#define DBG(...) do{ fprintf(stderr, COLOR_RED "%s " COLOR_OLD, __func__); \
fprintf(stderr, "(%s, line %d): ", __FILE__, __LINE__); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n");} while(0)
#else // EBUG
#define FNAME()
#define DBG(...)
#endif // EBUG

View File

@@ -122,7 +122,10 @@ static int calc(moveparam_t *x, double t){
DBG("dx01=%g, dt01=%g", dx01, dt01); DBG("dx01=%g, dt01=%g", dx01, dt01);
}else{ // increase or decrease speed without stopping phase }else{ // increase or decrease speed without stopping phase
dt01 = fabs(sign*setspeed - curparams.speed) / Max.accel; dt01 = fabs(sign*setspeed - curparams.speed) / Max.accel;
double a = (curspeed > setspeed) ? -Max.accel : Max.accel; double a = sign * Max.accel;
if(sign * curparams.speed < 0.){DBG("change direction"); a = -a;}
else if(curspeed > setspeed){ DBG("lower speed @ this direction"); a = -a;}
//double a = (curspeed > setspeed) ? -Max.accel : Max.accel;
dx01 = curspeed * dt01 + a * dt01 * dt01 / 2.; dx01 = curspeed * dt01 + a * dt01 * dt01 / 2.;
DBG("dt01=%g, a=%g, dx01=%g", dt01, a, dx01); DBG("dt01=%g, a=%g, dx01=%g", dt01, a, dx01);
if(dx01 + dx23 > Dx){ // calculate max speed if(dx01 + dx23 > Dx){ // calculate max speed
@@ -150,7 +153,7 @@ static int calc(moveparam_t *x, double t){
dx23 = setspeed * dt23 / 2.; dx23 = setspeed * dt23 / 2.;
// calculate dx12 and dt12 // calculate dx12 and dt12
double dx12 = Dx - dx01 - dx23; double dx12 = Dx - dx01 - dx23;
if(dx12 < 0.){ if(dx12 < -coord_tolerance){
DBG("Oops, WTF?"); DBG("Oops, WTF?");
return FALSE; return FALSE;
} }

View File

@@ -38,12 +38,12 @@ static pars G = {
}; };
static limits_t limits = { static limits_t limits = {
.min = {.coord = -1e6, .speed = 0.1, .accel = 0.1}, .min = {.coord = -1e6, .speed = 0.01, .accel = 0.1},
.max = {.coord = 1e6, .speed = 1e3, .accel = 50.}, .max = {.coord = 1e6, .speed = 20., .accel = 9.53523},
.jerk = 10. .jerk = 10.
}; };
static myoption opts[] = { static sl_option_t opts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"ramp", NEED_ARG, NULL, 'r', arg_string, APTR(&G.ramptype), "ramp type: \"d\", \"t\" or \"s\" - dumb, trapezoid, s-type"}, {"ramp", NEED_ARG, NULL, 'r', arg_string, APTR(&G.ramptype), "ramp type: \"d\", \"t\" or \"s\" - dumb, trapezoid, s-type"},
{"deltat", NEED_ARG, NULL, 't', arg_int, APTR(&G.dT), "time interval for monitoring (microseconds, >0)"}, {"deltat", NEED_ARG, NULL, 't', arg_int, APTR(&G.dT), "time interval for monitoring (microseconds, >0)"},
@@ -84,9 +84,9 @@ static void monit(double tnext){
} }
int main(int argc, char **argv){ int main(int argc, char **argv){
initial_setup(); sl_init();
parseargs(&argc, &argv, opts); sl_parseargs(&argc, &argv, opts);
if(G.help) showhelp(-1, opts); if(G.help) sl_showhelp(-1, opts);
if(G.xlog){ if(G.xlog){
coordslog = fopen(G.xlog, "w"); coordslog = fopen(G.xlog, "w");
if(!coordslog) ERR("Can't open %s", G.xlog); if(!coordslog) ERR("Can't open %s", G.xlog);
@@ -101,8 +101,9 @@ int main(int argc, char **argv){
model = init_moving(ramp, &limits); model = init_moving(ramp, &limits);
if(!model) ERRX("Can't init moving model: check parameters"); if(!model) ERRX("Can't init moving model: check parameters");
Tstart = nanot(); Tstart = nanot();
moveparam_t target = {.speed = 10., .coord = 20.}; moveparam_t target = {.speed = 8.0, .coord = 90.};
if(move(&target)) monit(0.5); if(move(&target)) monit(60.);
/*
for(int i = 0; i < 10; ++i){ for(int i = 0; i < 10; ++i){
target.coord = -target.coord; target.coord = -target.coord;
if(move(&target)) monit(1.); if(move(&target)) monit(1.);
@@ -119,6 +120,7 @@ int main(int argc, char **argv){
target.coord = 0.; target.speed = 20.; target.coord = 0.; target.speed = 20.;
if(move(&target)) monit(1e6); if(move(&target)) monit(1e6);
usleep(5000); usleep(5000);
*/
fclose(coordslog); fclose(coordslog);
return 0; return 0;
} }

View File

@@ -0,0 +1,73 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <stdio.h>
#include <string.h>
#include <pthread.h>
#include "main.h"
#include "movingmodel.h"
#include "ramp.h"
extern movemodel_t trapez;
static void chkminmax(double *min, double *max){
if(*min <= *max) return;
double t = *min;
*min = *max;
*max = t;
}
movemodel_t *model_init(limits_t *l){
if(!l) return FALSE;
movemodel_t *m = calloc(1, sizeof(movemodel_t));
// we can't use memcpy or assign as Times/Params would be common for all
*m = trapez;
m->Times = calloc(STAGE_AMOUNT, sizeof(double));
m->Params = calloc(STAGE_AMOUNT, sizeof(moveparam_t));
moveparam_t *max = &l->max, *min = &l->min;
if(min->speed < 0.) min->speed = -min->speed;
if(max->speed < 0.) max->speed = -max->speed;
if(min->accel < 0.) min->accel = -min->accel;
if(max->accel < 0.) max->accel = -max->accel;
chkminmax(&min->coord, &max->coord);
chkminmax(&min->speed, &max->speed);
chkminmax(&min->accel, &max->accel);
m->Min = l->min;
m->Max = l->max;
m->movingstage = STAGE_STOPPED;
m->state = ST_STOP;
pthread_mutex_init(&m->mutex, NULL);
DBG("model inited");
return m;
}
int model_move2(movemodel_t *model, moveparam_t *target, double t){
if(!target || !model) return FALSE;
DBG("MOVE to %g (deg) at speed %g (deg/s)", target->coord/M_PI*180., target->speed/M_PI*180.);
// only positive velocity
if(target->speed < 0.) target->speed = -target->speed;
if(fabs(target->speed) < model->Min.speed){
DBG("STOP");
model->stop(model, t);
return TRUE;
}
// don't mind about acceleration - user cannot set it now
return model->calculate(model, target, t);
}

View File

@@ -0,0 +1,76 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 <pthread.h>
#include "sidservo.h"
// tolerance, time ticks
#define COORD_TOLERANCE_DEFAULT (1e-8)
#define COORD_TOLERANCE_MIN (1e-12)
#define COORD_TOLERANCE_MAX (10.)
#define TIME_TICK_DEFAULT (0.0001)
#define TIME_TICK_MIN (1e-9)
#define TIME_TICK_MAX (10.)
typedef enum{
ST_STOP, // stopped
ST_MOVE, // moving
ST_AMOUNT
} movestate_t;
typedef struct{
double coord;
double speed;
double accel;
} moveparam_t;
typedef struct{
moveparam_t min;
moveparam_t max;
//double acceleration;
} limits_t;
typedef enum{
STAGE_ACCEL, // start from last speed and accelerate/decelerate to target speed
STAGE_MAXSPEED, // go with target speed
STAGE_DECEL, // go from target speed to zero
STAGE_STOPPED, // stop
STAGE_AMOUNT
} movingstage_t;
typedef struct movemodel{
moveparam_t Min;
moveparam_t Max;
movingstage_t movingstage;
movestate_t state;
double *Times;
moveparam_t *Params;
moveparam_t curparams; // init values of limits, jerk
int (*calculate)(struct movemodel *m, moveparam_t *target, double t); // calculate stages of traectory beginning from t
movestate_t (*proc_move)(struct movemodel *m, moveparam_t *next, double t); // calculate next model point for time t
movestate_t (*get_state)(struct movemodel *m, moveparam_t *cur); // get current moving state
void (*stop)(struct movemodel *m, double t); // stop by ramp
void (*emergency_stop)(struct movemodel *m, double t); // stop with highest acceleration
double (*stoppedtime)(struct movemodel *m); // time when moving will ends
pthread_mutex_t mutex;
} movemodel_t;
movemodel_t *model_init(limits_t *l);
int model_move2(movemodel_t *model, moveparam_t *target, double t);

View File

@@ -0,0 +1,237 @@
/*
* 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 <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <usefull_macros.h>
#define XYBUFSZ (2048)
struct{
int help;
char *Xpath;
char *Ypath;
double dt;
} G = {
.Xpath = "/dev/encoder_X0",
.Ypath = "/dev/encoder_Y0",
.dt = 0.001,
};
sl_option_t options[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"Xpath", NEED_ARG, NULL, 'X', arg_string, APTR(&G.Xpath), "path to X encoder"},
{"Ypath", NEED_ARG, NULL, 'Y', arg_string, APTR(&G.Ypath), "path to Y encoder"},
{"dt", NEED_ARG, NULL, 'd', arg_double, APTR(&G.dt), "request interval (1e-4..10s)"},
};
typedef struct{
char buf[XYBUFSZ+1];
int len;
} buf_t;
static int Xfd = -1, Yfd = -1;
void signals(int sig){
if(sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
}
DBG("close");
if(Xfd > 0){ close(Xfd); Xfd = -1; }
if(Yfd > 0){ close(Yfd); Yfd = -1; }
exit(sig);
}
static int op(const char *nm){
int fd = open(nm, O_RDWR|O_NOCTTY|O_NONBLOCK);
if(fd < 0) ERR("Can't open %s", nm);
struct termios2 tty;
if(ioctl(fd, TCGETS2, &tty)) ERR("Can't read TTY settings");
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
tty.c_ispeed = 1000000;
tty.c_ospeed = 1000000;
if(ioctl(fd, TCSETS2, &tty)) ERR("Can't set TTY settings");
// try to set exclusive
if(ioctl(fd, TIOCEXCL)){DBG("Can't make exclusive");}
return fd;
}
static int eolcnt(buf_t *buf){
if(!buf) return -1;
int cnt = 0;
for(int i = 0; i < buf->len; ++i)
if(buf->buf[i] == '\n') ++cnt;
return cnt;
}
// move last record (if any) into head of buffer
static void movelast(buf_t *buf){
FNAME();
if(!buf) return;
DBG("buf was: %s", buf->buf);
int cnt = eolcnt(buf);
char *E = strrchr(buf->buf, '\n');
int idx = -1;
if(E){
idx = ++E - buf->buf; // position of symbol after '\n'
}else{
buf->len = strlen(buf->buf);
DBG("leave as is (%s)", buf->buf);
return;
}
DBG("cnt=%d, idx=%d", cnt, idx);
switch(cnt){
case 0: // EOL not found - clear buf
buf->len = 0;
break;
case 1: // only one record - move all after '\n'
if(idx > 0 && idx < XYBUFSZ){
buf->len = XYBUFSZ - idx;
memmove(buf->buf, E, buf->len);
}else buf->len = 0;
break;
default: // more than one record - move
{
int i = idx - 2;
for(; i > -1; --i)
if(buf->buf[i] == '\n') break;
++i;
buf->len = XYBUFSZ - i;
memmove(buf->buf, &buf->buf[i], buf->len);
}
}
buf->buf[buf->len] = 0;
DBG("MOVED; now buf[%d]=%s", buf->len, buf->buf);
}
// write to buffer next data portion; return FALSE in case of error
static int readstrings(buf_t *buf, int fd){
if(!buf){WARNX("Empty buffer"); return FALSE;}
int L = XYBUFSZ - buf->len;
if(L == 0){
DBG("len: %d", buf->len);
movelast(buf);
L = XYBUFSZ - buf->len;
}
int got = read(fd, &buf->buf[buf->len], L);
if(got < 0){
WARN("read()");
return FALSE;
}else if(got == 0) return TRUE;
buf->len += got;
buf->buf[buf->len] = 0;
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) return -1;
// read record between last '\n' and previous (or start of string)
int cnt = eolcnt(buf);
if(cnt < 1) return FALSE;
char *last = strrchr(buf->buf, '\n');
if(!last) return FALSE; // WTF?
*last = 0;
char *prev = buf->buf;
if(cnt > 1) prev = strrchr(buf->buf, '\n') + 1;
if(!prev) prev = buf->buf; // ??
if(out) *out = atol(prev);
int l = strlen(++last);
if(l < XYBUFSZ){
buf->len = l;
if(l){
memmove(buf->buf, last, l);
DBG("moved: %s", buf->buf);
}else DBG("empty line");
}else{
buf->len = 0;
DBG("buffer clear");
}
return TRUE;
}
// try to write '\n' asking new data portion; return FALSE if failed
static int asknext(int fd){
FNAME();
if(fd < 0) return FALSE;
int i = 0;
for(; i < 5; ++i){
int l = write(fd, "\n", 1);
DBG("l=%d", l);
if(1 == l) return TRUE;
usleep(100);
}
DBG("5 tries... failed!");
return FALSE;
}
int main(int argc, char **argv){
buf_t xbuf, ybuf;
long xlast, ylast;
double xtlast, ytlast;
sl_init();
sl_parseargs(&argc, &argv, options);
if(G.help) sl_showhelp(-1, options);
if(G.dt < 1e-4) ERRX("dx too small");
if(G.dt > 10.) ERRX("dx too big");
Xfd = op(G.Xpath);
Yfd = op(G.Ypath);
struct pollfd pfds[2];
pfds[0].fd = Xfd; pfds[0].events = POLLIN;
pfds[1].fd = Yfd; pfds[1].events = POLLIN;
double t0x, t0y, tstart;
asknext(Xfd); asknext(Yfd);
t0x = t0y = tstart = sl_dtime();
DBG("Start");
do{ // main cycle
if(poll(pfds, 2, 1) < 0){
WARN("poll()");
break;
}
if(pfds[0].revents && POLLIN){
if(!readstrings(&xbuf, Xfd)) break;
}
if(pfds[1].revents && POLLIN){
if(!readstrings(&ybuf, Yfd)) break;
}
double curt = sl_dtime();
if(getdata(&xbuf, &xlast)) xtlast = curt;
if(curt - t0x >= G.dt){ // get last records
if(curt - xtlast < 1.5*G.dt)
printf("%-14.4fX=%ld\n", xtlast-tstart, xlast);
if(!asknext(Xfd)) break;
t0x = (curt - t0x < 2.*G.dt) ? t0x + G.dt : curt;
}
curt = sl_dtime();
if(getdata(&ybuf, &ylast)) ytlast = curt;
if(curt - t0y >= G.dt){ // get last records
if(curt - ytlast < 1.5*G.dt)
printf("%-14.4fY=%ld\n", ytlast-tstart, ylast);
if(!asknext(Yfd)) break;
t0y = (curt - t0y < 2.*G.dt) ? t0y + G.dt : curt;
}
}while(Xfd > 0 && Yfd > 0);
DBG("OOps: disconnected");
signals(0);
return 0;
}

View File

@@ -0,0 +1,259 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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/>.
*/
// simplest trapezioidal ramp
#include <math.h>
#include <strings.h>
#include "main.h"
#include "ramp.h"
#ifdef EBUG
#undef DBG
#define DBG(...)
#undef FNAME
#define FNAME()
#endif
static double coord_tolerance = COORD_TOLERANCE_DEFAULT;
static void emstop(movemodel_t *m, double _U_ t){
FNAME();
pthread_mutex_lock(&m->mutex);
m->curparams.accel = 0.;
m->curparams.speed = 0.;
bzero(m->Times, sizeof(double) * STAGE_AMOUNT);
bzero(m->Params, sizeof(moveparam_t) * STAGE_AMOUNT);
m->state = ST_STOP;
m->movingstage = STAGE_STOPPED;
pthread_mutex_unlock(&m->mutex);
}
static void stop(movemodel_t *m, double t){
FNAME();
pthread_mutex_lock(&m->mutex);
if(m->state == ST_STOP || m->movingstage == STAGE_STOPPED) goto ret;
m->movingstage = STAGE_DECEL;
m->state = ST_MOVE;
m->Times[STAGE_DECEL] = t;
m->Params[STAGE_DECEL].speed = m->curparams.speed;
if(m->curparams.speed > 0.) m->Params[STAGE_DECEL].accel = -m->Max.accel;
else m->Params[STAGE_DECEL].accel = m->Max.accel;
m->Params[STAGE_DECEL].coord = m->curparams.coord;
// speed: v=v2+a2(t-t2), v2 and a2 have different signs; t3: v3=0 -> t3=t2-v2/a2
m->Times[STAGE_STOPPED] = t - m->curparams.speed / m->Params[STAGE_DECEL].accel;
// coordinate: x=x2+v2(t-t2)+a2(t-t2)^2/2 -> x3=x2+v2(t3-t2)+a2(t3-t2)^2/2
double dt = m->Times[STAGE_STOPPED] - t;
m->Params[STAGE_STOPPED].coord = m->curparams.coord + m->curparams.speed * dt +
m->Params[STAGE_DECEL].accel * dt * dt / 2.;
ret:
pthread_mutex_unlock(&m->mutex);
}
// inner part of `calc`, could be called recoursively for hard case
static void unlockedcalc(movemodel_t *m, moveparam_t *x, double t){
// signs
double sign_a01 = 0., sign_a23 = 0., sign_vset = 0.; // accelerations on stages ACCEL and DECEL, speed on maxspeed stage
// times
double dt01 = 0., dt12 = 0., dt23 = 0.;
// absolute speed at stage 23 (or in that point); absolute max acceleration
double abs_vset = x->speed, abs_a = m->Max.accel;
// absolute target movement
double abs_Dx = fabs(x->coord - m->curparams.coord);
if(m->state == ST_STOP && abs_Dx < coord_tolerance){
DBG("Movement too small -> stay at place");
return;
}
// signs of Dx and current speed
double sign_Dx = (x->coord > m->curparams.coord) ? 1. : -1.;
double v0 = m->curparams.speed;
double sign_v0 = v0 < 0. ? -1 : 1., abs_v0 = fabs(v0);
if(v0 == 0.) sign_v0 = 0.;
// preliminary calculations (vset and dependent values could be changed)
dt01 = fabs(abs_v0 - abs_vset) / abs_a;
double abs_dx23 = abs_vset * abs_vset / 2. / abs_a;
dt23 = abs_vset / abs_a;
double abs_dx_stop = abs_v0 * abs_v0 / 2. / abs_a;
if(sign_Dx * sign_v0 >= 0. && abs_dx_stop < abs_Dx){ // we shouldn't change speed direction
if(fabs(abs_dx_stop - abs_Dx) <= coord_tolerance){ // simplest case: just stop
//DBG("Simplest case: stop");
dt01 = dt12 = 0.;
sign_a23 = -sign_v0;
dt23 = abs_v0 / abs_a;
}else if(abs_vset < abs_v0){ // move with smaller speed than now: very simple case
//DBG("Move with smaller speed");
sign_a01 = sign_a23 = -sign_v0;
sign_vset = sign_v0;
double abs_dx01 = abs_v0 * dt01 - abs_a * dt01 * dt01 / 2.;
double abs_dx12 = abs_Dx - abs_dx01 - abs_dx23;
dt12 = abs_dx12 / abs_vset;
}else{// move with larget speed
//DBG("Move with larger speed");
double abs_dx01 = abs_v0 * dt01 + abs_a * dt01 * dt01 / 2.;
if(abs_Dx < abs_dx01 + abs_dx23){ // recalculate target speed and other
abs_vset = sqrt(abs_a * abs_Dx + abs_v0 * abs_v0 / 2.);
dt01 = fabs(abs_v0 - abs_vset) / abs_a;
abs_dx01 = abs_v0 * dt01 + abs_a * dt01 * dt01 / 2.;
dt23 = abs_vset / abs_a;
abs_dx23 = abs_vset * abs_vset / 2. / abs_a;
DBG("Can't reach target speed %g, take %g instead", x->speed, abs_vset);
}
sign_a01 = sign_Dx; // sign_v0 could be ZERO!!!
sign_a23 = -sign_Dx;
sign_vset = sign_Dx;
double abs_dx12 = abs_Dx - abs_dx01 - abs_dx23;
dt12 = abs_dx12 / abs_vset;
}
}else{
// if we are here, we have the worst case: change speed direction
// DBG("Hardest case: change speed direction");
// now we should calculate coordinate at which model stops and biuld new trapezium from that point
double x0 = m->curparams.coord, v0 = m->curparams.speed;
double xstop = x0 + sign_v0 * abs_dx_stop, tstop = t + abs_v0 / abs_a;
m->state = ST_STOP;
m->curparams.accel = 0.; m->curparams.coord = xstop; m->curparams.speed = 0.;
unlockedcalc(m, x, tstop); // calculate new ramp
// and change started conditions
m->curparams.coord = x0; m->curparams.speed = v0;
m->Times[STAGE_ACCEL] = t;
m->Params[STAGE_ACCEL].coord = x0;
m->Params[STAGE_ACCEL].speed = v0;
// DBG("NOW t[0]=%g, X[0]=%g, V[0]=%g", t, x0, v0);
return;
}
m->state = ST_MOVE;
m->movingstage = STAGE_ACCEL;
// some knot parameters
double a01 = sign_a01 * abs_a, a23 = sign_a23 * abs_a;
double v1, v2, x0, x1, x2;
v2 = v1 = sign_vset * abs_vset;
x0 = m->curparams.coord;
x1 = x0 + v0 * dt01 + a01 * dt01 * dt01 / 2.;
x2 = x1 + v1 * dt12;
// fill knot parameters
moveparam_t *p = &m->Params[STAGE_ACCEL]; // 0-1 - change started speed
p->accel = a01;
p->speed = m->curparams.speed;
p->coord = x0;
m->Times[STAGE_ACCEL] = t;
p = &m->Params[STAGE_MAXSPEED]; // 1-2 - constant speed
p->accel = 0.;
p->speed = v1;
p->coord = x1;
m->Times[STAGE_MAXSPEED] = m->Times[STAGE_ACCEL] + dt01;
p = &m->Params[STAGE_DECEL]; // 2-3 - decrease speed
p->accel = a23;
p->speed = v2;
p->coord = x2;
m->Times[STAGE_DECEL] = m->Times[STAGE_MAXSPEED] + dt12;
p = &m->Params[STAGE_STOPPED]; // 3 - stop at target
p->accel = p->speed = 0.;
p->coord = x->coord;
m->Times[STAGE_STOPPED] = m->Times[STAGE_DECEL] + dt23;
}
/**
* @brief calc - moving calculation
* @param x - using max speed (>0!!!) and coordinate
* @param t - current time value
* @return FALSE if can't move with given parameters
*/
static int calc(movemodel_t *m, moveparam_t *x, double t) {
//DBG("target coord/speed: %g/%g; current: %g/%g", x->coord, x->speed, m->curparams.coord, m->curparams.speed);
if (!x || !m) return FALSE;
pthread_mutex_lock(&m->mutex);
int ret = FALSE;
// Validate input parameters
if(x->coord < m->Min.coord || x->coord > m->Max.coord){
DBG("Wrong coordinate [%g, %g]", m->Min.coord, m->Max.coord);
goto ret;
}
if(x->speed < m->Min.speed || x->speed > m->Max.speed){
DBG("Wrong speed [%g, %g]", m->Min.speed, m->Max.speed);
goto ret;
}
ret = TRUE; // now there's no chanses to make error
unlockedcalc(m, x, t);
// Debug output
/*for(int i = 0; i < STAGE_AMOUNT; i++){
DBG("Stage %d: t=%.6f, coord=%.6f, speed=%.6f, accel=%.6f",
i, m->Times[i], m->Params[i].coord, m->Params[i].speed, m->Params[i].accel);
}*/
ret:
pthread_mutex_unlock(&m->mutex);
return ret;
}
static movestate_t proc(movemodel_t *m, moveparam_t *next, double t){
pthread_mutex_lock(&m->mutex);
if(m->state == ST_STOP) goto ret;
for(movingstage_t s = STAGE_STOPPED; s >= 0; --s){
if(m->Times[s] <= t){ // check time for current stage
m->movingstage = s;
break;
}
}
if(m->movingstage == STAGE_STOPPED){
m->curparams.coord = m->Params[STAGE_STOPPED].coord;
pthread_mutex_unlock(&m->mutex);
/* DBG("REACHED STOPping stage @ t=%g", t);
for(int s = STAGE_STOPPED; s >= 0; --s){
DBG("T[%d]=%g, ", s, m->Times[s]);
}*/
emstop(m, t);
goto ret;
}
// calculate current parameters
double dt = t - m->Times[m->movingstage];
double a = m->Params[m->movingstage].accel;
double v0 = m->Params[m->movingstage].speed;
double x0 = m->Params[m->movingstage].coord;
m->curparams.accel = a;
m->curparams.speed = v0 + a * dt;
m->curparams.coord = x0 + v0 * dt + a * dt * dt / 2.;
ret:
if(next) *next = m->curparams;
movestate_t st = m->state;
pthread_mutex_unlock(&m->mutex);
return st;
}
static movestate_t getst(movemodel_t *m, moveparam_t *cur){
pthread_mutex_lock(&m->mutex);
if(cur) *cur = m->curparams;
movestate_t st = m->state;
pthread_mutex_unlock(&m->mutex);
return st;
}
static double gettstop(movemodel_t *m){
pthread_mutex_lock(&m->mutex);
double r = m->Times[STAGE_STOPPED];
pthread_mutex_unlock(&m->mutex);
return r;
}
movemodel_t trapez = {
.stop = stop,
.emergency_stop = emstop,
.get_state = getst,
.calculate = calc,
.proc_move = proc,
.stoppedtime = gettstop,
};

View File

@@ -0,0 +1,23 @@
/*
* This file is part of the moving_model project.
* Copyright 2025 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 "movingmodel.h"
extern movemodel_t trapez;

View File

@@ -20,31 +20,36 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <math.h> #include <math.h>
#include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <signal.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <time.h>
#include <unistd.h> #include <unistd.h>
#include "dbg.h" #include "main.h"
#include "movingmodel.h"
#include "serial.h" #include "serial.h"
#include "ssii.h"
// serial devices FD // serial devices FD
static int encfd = -1, mntfd = -1; static int encfd[2] = {-1, -1}, mntfd = -1;
// main mount data // main mount data
static mountdata_t mountdata = {0}; static mountdata_t mountdata = {0};
// last encoders time and last encoders data - for speed measurement
//static coordval_t lastXenc = {0}, lastYenc = {0};
// mutexes for RW operations with mount device and data // mutexes for RW operations with mount device and data
static pthread_mutex_t mntmutex = PTHREAD_MUTEX_INITIALIZER, static pthread_mutex_t mntmutex = PTHREAD_MUTEX_INITIALIZER,
datamutex = PTHREAD_MUTEX_INITIALIZER; datamutex = PTHREAD_MUTEX_INITIALIZER;
// encoders thread and mount thread // encoders thread and mount thread
static pthread_t encthread, mntthread; static pthread_t encthread, mntthread;
// max timeout for 1.5 bytes of encoder and 2 bytes of mount // max timeout for 1.5 bytes of encoder and 2 bytes of mount - for `select`
static struct timeval encRtmout = {0}, mntRtmout = {0}; static struct timeval encRtmout = {.tv_sec = 0, .tv_usec = 100}, mntRtmout = {.tv_sec = 0, .tv_usec = 50000};
// encoders raw data // encoders raw data
typedef struct __attribute__((packed)){ typedef struct __attribute__((packed)){
uint8_t magick; uint8_t magick;
@@ -53,34 +58,42 @@ typedef struct __attribute__((packed)){
uint8_t CRC[4]; uint8_t CRC[4];
} enc_t; } enc_t;
/** // calculate current X/Y speeds
* @brief dtime - UNIX time with microsecond void getXspeed(){
* @return value static less_square_t *ls = NULL;
*/ if(!ls){
double dtime(){ ls = LS_init(Conf.EncoderSpeedInterval / Conf.EncoderReqInterval);
double t; if(!ls) return;
struct timeval tv; }
gettimeofday(&tv, NULL); double dt = timediff0(&mountdata.encXposition.t);
t = tv.tv_sec + ((double)tv.tv_usec)/1e6; double speed = LS_calc_slope(ls, mountdata.encXposition.val, dt);
return t; if(fabs(speed) < 1.5 * Xlimits.max.speed){
mountdata.encXspeed.val = speed;
mountdata.encXspeed.t = mountdata.encXposition.t;
}
//DBG("Xspeed=%g", mountdata.encXspeed.val);
}
void getYspeed(){
static less_square_t *ls = NULL;
if(!ls){
ls = LS_init(Conf.EncoderSpeedInterval / Conf.EncoderReqInterval);
if(!ls) return;
}
double dt = timediff0(&mountdata.encYposition.t);
double speed = LS_calc_slope(ls, mountdata.encYposition.val, dt);
if(fabs(speed) < 1.5 * Ylimits.max.speed){
mountdata.encYspeed.val = speed;
mountdata.encYspeed.t = mountdata.encYposition.t;
}
} }
#if 0
// init start time
static void gttime(){
struct timeval tv;
gettimeofday(&tv, NULL);
tv_sec_got = tv.tv_sec;
tv_usec_got = tv.tv_usec;
}
#endif
/** /**
* @brief parse_encbuf - check encoder buffer (for separate encoder) and fill fresh data * @brief parse_encbuf - check encoder buffer (for encoder data based on SSII proto) and fill fresh data
* @param databuf - input buffer with 13 bytes of data * @param databuf - input buffer with 13 bytes of data
* @param nexttime - time when databuf[0] got * @param t - time when databuf[0] got
*/ */
static void parse_encbuf(uint8_t databuf[ENC_DATALEN], struct timeval *tv){ static void parse_encbuf(uint8_t databuf[ENC_DATALEN], struct timespec *t){
if(!t) return;
enc_t *edata = (enc_t*) databuf; enc_t *edata = (enc_t*) databuf;
/* /*
#ifdef EBUG #ifdef EBUG
@@ -115,31 +128,104 @@ static void parse_encbuf(uint8_t databuf[ENC_DATALEN], struct timeval *tv){
return; return;
} }
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
mountdata.encposition.X = X_ENC2RAD(edata->encX); mountdata.encXposition.val = Xenc2rad(edata->encX);
mountdata.encposition.Y = Y_ENC2RAD(edata->encY); mountdata.encYposition.val = Yenc2rad(edata->encY);
mountdata.encposition.msrtime = *tv; DBG("Got positions X/Y= %.6g / %.6g", mountdata.encXposition.val, mountdata.encYposition.val);
mountdata.encXposition.t = *t;
mountdata.encYposition.t = *t;
getXspeed(); getYspeed();
pthread_mutex_unlock(&datamutex); pthread_mutex_unlock(&datamutex);
//DBG("time = %zd+%zd/1e6, X=%g deg, Y=%g deg", tv->tv_sec, tv->tv_usec, mountdata.encposition.X*180./M_PI, mountdata.encposition.Y*180./M_PI); //DBG("time = %zd+%zd/1e6, X=%g deg, Y=%g deg", tv->tv_sec, tv->tv_usec, mountdata.encposition.X*180./M_PI, mountdata.encposition.Y*180./M_PI);
} }
#if 0
/**
* @brief getencval - get uint64_t data from encoder
* @param fd - encoder fd
* @param val - value read
* @param t - measurement time
* @return amount of data read or 0 if problem
*/
static int getencval(int fd, double *val, struct timespec *t){
if(fd < 0){
DBG("Encoder fd < 0!");
return FALSE;
}
char buf[128];
int got = 0, Lmax = 127;
double t0 = timefromstart();
//DBG("start: %.6g", t0);
do{
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(fd, &rfds);
struct timeval tv = encRtmout;
int retval = select(fd + 1, &rfds, NULL, NULL, &tv);
if(!retval){
//DBG("select()==0 - timeout, %.6g", timefromstart());
break;
}
if(retval < 0){
if(errno == EINTR){
DBG("EINTR");
continue;
}
DBG("select() < 0");
return 0;
}
if(FD_ISSET(fd, &rfds)){
ssize_t l = read(fd, &buf[got], Lmax);
if(l < 1){
DBG("read() < 0");
return 0; // disconnected ??
}
got += l; Lmax -= l;
buf[got] = 0;
} else continue;
if(buf[got-1] == '\n') break; // got EOL as last symbol
}while(Lmax && timefromstart() - t0 < Conf.EncoderReqInterval / 5.);
if(got == 0){
//DBG("No data from encoder, tfs=%.6g", timefromstart());
return 0;
}
char *estr = strrchr(buf, '\n');
if(!estr){
DBG("No EOL");
return 0;
}
*estr = 0;
char *bgn = strrchr(buf, '\n');
if(bgn) ++bgn;
else bgn = buf;
char *eptr;
long data = strtol(bgn, &eptr, 10);
if(eptr != estr){
DBG("NAN");
return 0; // wrong number
}
if(val) *val = (double) data;
if(t){ if(!curtime(t)){ DBG("Can't get time"); return 0; }}
return got;
}
#endif
// try to read 1 byte from encoder; return -1 if nothing to read or -2 if device seems to be disconnected // try to read 1 byte from encoder; return -1 if nothing to read or -2 if device seems to be disconnected
static int getencbyte(){ static int getencbyte(){
if(encfd < 0) return -1; if(encfd[0] < 0) return -1;
uint8_t byte = 0; uint8_t byte = 0;
fd_set rfds; fd_set rfds;
struct timeval tv;
do{ do{
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(encfd, &rfds); FD_SET(encfd[0], &rfds);
tv = encRtmout; struct timeval tv = encRtmout;
int retval = select(encfd + 1, &rfds, NULL, NULL, &tv); int retval = select(encfd[0] + 1, &rfds, NULL, NULL, &tv);
if(!retval) break; if(!retval) break;
if(retval < 0){ if(retval < 0){
if(errno == EINTR) continue; if(errno == EINTR) continue;
return -1; return -1;
} }
if(FD_ISSET(encfd, &rfds)){ if(FD_ISSET(encfd[0], &rfds)){
ssize_t l = read(encfd, &byte, 1); ssize_t l = read(encfd[0], &byte, 1);
if(l != 1) return -2; // disconnected ?? if(l != 1) return -2; // disconnected ??
break; break;
} else return -1; } else return -1;
@@ -151,7 +237,6 @@ static int getmntbyte(){
if(mntfd < 0) return -1; if(mntfd < 0) return -1;
uint8_t byte; uint8_t byte;
fd_set rfds; fd_set rfds;
struct timeval tv;
/* ssize_t l = read(mntfd, &byte, 1); /* ssize_t l = read(mntfd, &byte, 1);
//DBG("MNT read=%zd byte=0x%X", l, byte); //DBG("MNT read=%zd byte=0x%X", l, byte);
if(l == 0) return -1; if(l == 0) return -1;
@@ -160,7 +245,7 @@ static int getmntbyte(){
do{ do{
FD_ZERO(&rfds); FD_ZERO(&rfds);
FD_SET(mntfd, &rfds); FD_SET(mntfd, &rfds);
tv = mntRtmout; struct timeval tv = mntRtmout;
int retval = select(mntfd + 1, &rfds, NULL, NULL, &tv); int retval = select(mntfd + 1, &rfds, NULL, NULL, &tv);
if(retval < 0){ if(retval < 0){
if(errno == EINTR) continue; if(errno == EINTR) continue;
@@ -171,19 +256,44 @@ static int getmntbyte(){
if(FD_ISSET(mntfd, &rfds)){ if(FD_ISSET(mntfd, &rfds)){
ssize_t l = read(mntfd, &byte, 1); ssize_t l = read(mntfd, &byte, 1);
//DBG("MNT read=%zd byte=0x%X", l, byte); //DBG("MNT read=%zd byte=0x%X", l, byte);
if(l != 1) return -2; // disconnected ?? if(l != 1){
DBG("Mount disconnected?");
return -2; // disconnected ??
}
break; break;
} else return -1; } else return -1;
}while(1); }while(1);
return (int)byte; return (int)byte;
} }
// clear data from input buffer
static void clrmntbuf(){
if(mntfd < 0) return;
uint8_t byte;
fd_set rfds;
do{
FD_ZERO(&rfds);
FD_SET(mntfd, &rfds);
struct timeval tv = {.tv_sec=0, .tv_usec=10};
int retval = select(mntfd + 1, &rfds, NULL, NULL, &tv);
if(retval < 0){
if(errno == EINTR) continue;
DBG("Error in select()");
break;
}
if(FD_ISSET(mntfd, &rfds)){
ssize_t l = read(mntfd, &byte, 1);
if(l != 1) break;
} else break;
}while(1);
}
// main encoder thread (for separate encoder): read next data and make parsing // main encoder thread (for separate encoder): read next data and make parsing
static void *encoderthread(void _U_ *u){ static void *encoderthread1(void _U_ *u){
if(Conf.SepEncoder != 1) return NULL;
uint8_t databuf[ENC_DATALEN]; uint8_t databuf[ENC_DATALEN];
int wridx = 0, errctr = 0; int wridx = 0, errctr = 0;
struct timeval tv; struct timespec tcur;
while(encfd > -1 && errctr < MAX_ERR_CTR){ while(encfd[0] > -1 && errctr < MAX_ERR_CTR){
int b = getencbyte(); int b = getencbyte();
if(b == -2) ++errctr; if(b == -2) ++errctr;
if(b < 0) continue; if(b < 0) continue;
@@ -193,18 +303,153 @@ static void *encoderthread(void _U_ *u){
if((uint8_t)b == ENC_MAGICK){ if((uint8_t)b == ENC_MAGICK){
// DBG("Got magic -> start filling packet"); // DBG("Got magic -> start filling packet");
databuf[wridx++] = (uint8_t) b; databuf[wridx++] = (uint8_t) b;
gettimeofday(&tv, NULL);
} }
continue; continue;
}else databuf[wridx++] = (uint8_t) b; }else databuf[wridx++] = (uint8_t) b;
if(wridx == ENC_DATALEN){ if(wridx == ENC_DATALEN){
parse_encbuf(databuf, &tv); if(curtime(&tcur)){
parse_encbuf(databuf, &tcur);
wridx = 0; wridx = 0;
} }
} }
if(encfd > -1){ }
close(encfd); if(encfd[0] > -1){
encfd = -1; close(encfd[0]);
encfd[0] = -1;
}
return NULL;
}
#define XYBUFSZ (128)
typedef struct{
char buf[XYBUFSZ+1];
int len;
} buf_t;
// 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("buffer overfull: %d!", buf->len);
char *lastn = strrchr(buf->buf, '\n');
if(lastn){
fprintf(stderr, "BUFOVR: _%s_", buf->buf);
++lastn;
buf->len = XYBUFSZ - (lastn - buf->buf);
DBG("Memmove %d", buf->len);
memmove(lastn, buf->buf, buf->len);
buf->buf[buf->len] = 0;
}else buf->len = 0;
L = XYBUFSZ - buf->len;
}
int got = read(fd, &buf->buf[buf->len], L);
if(got < 0){
DBG("read()");
return FALSE;
}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);
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;
// read record between last '\n' and previous (or start of string)
char *last = &buf->buf[buf->len - 1];
//DBG("buf: _%s_", buf->buf);
if(*last != '\n') return FALSE;
*last = 0;
//DBG("buf: _%s_", buf->buf);
char *prev = strrchr(buf->buf, '\n');
if(!prev) prev = buf->buf;
else{
fprintf(stderr, "MORETHANONE: _%s_", buf->buf);
++prev; // after last '\n'
}
if(out) *out = atol(prev);
// clear buffer
buf->len = 0;
return TRUE;
}
// try to write '\n' asking new data portion; return FALSE if failed
static int asknext(int fd){
FNAME();
if(fd < 0) return FALSE;
int i = 0;
for(; i < 5; ++i){
int l = write(fd, "\n", 1);
//DBG("l=%d", l);
if(1 == l) return TRUE;
usleep(100);
}
DBG("5 tries... failed!");
return FALSE;
}
// main encoder thread for separate encoders as USB devices /dev/encoder_X0 and /dev/encoder_Y0
static void *encoderthread2(void _U_ *u){
if(Conf.SepEncoder != 2) return NULL;
DBG("Thread started");
struct pollfd pfds[2];
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];
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;
do{ // main cycle
if(poll(pfds, 2, 0) < 0){
DBG("poll()");
break;
}
int got = 0;
for(int i = 0; i < 2; ++i){
if(pfds[i].revents && POLLIN){
if(!readstrings(&strbuf[i], encfd[i])){
++errctr;
break;
}
}
double curt = timefromstart();
if(getdata(&strbuf[i], &msrlast[i])) mtlast[i] = curt;
if(curt - t0[i] >= Conf.EncoderReqInterval){ // get last records
if(curt - mtlast[i] < 1.5*Conf.EncoderReqInterval){
pthread_mutex_lock(&datamutex);
if(i == 0){
mountdata.encXposition.val = Xenc2rad((double)msrlast[i]);
curtime(&mountdata.encXposition.t);
getXspeed();
}else{
mountdata.encYposition.val = Yenc2rad((double)msrlast[i]);
curtime(&mountdata.encYposition.t);
getYspeed();
}
pthread_mutex_unlock(&datamutex);
}
if(!asknext(encfd[i])){
++errctr;
break;
}
t0[i] = (curt - t0[i] < 2.*Conf.EncoderReqInterval) ? t0[i] + Conf.EncoderReqInterval : curt;
++got;
}
}
if(got == 2) errctr = 0;
}while(encfd[0] > -1 && encfd[1] > -1 && errctr < MAX_ERR_CTR);
DBG("\n\nEXIT: ERRCTR=%d", errctr);
for(int i = 0; i < 2; ++i){
if(encfd[i] > -1){
close(encfd[i]);
encfd[i] = -1;
}
} }
return NULL; return NULL;
} }
@@ -225,26 +470,78 @@ void data_free(data_t **x){
*x = NULL; *x = NULL;
} }
static void chkModStopped(double *prev, double cur, int *nstopped, axis_status_t *stat){
if(!prev || !nstopped || !stat) return;
if(isnan(*prev)){
*stat = AXIS_STOPPED;
DBG("START");
}else if(*stat != AXIS_STOPPED){
if(fabs(*prev - cur) < DBL_EPSILON && ++(*nstopped) > MOTOR_STOPPED_CNT){
*stat = AXIS_STOPPED;
DBG("AXIS stopped; prev=%g, cur=%g; nstopped=%d", *prev/M_PI*180., cur/M_PI*180., *nstopped);
}
}else if(*prev != cur){
DBG("AXIS moving");
*nstopped = 0;
}
*prev = cur;
}
// main mount thread // main mount thread
static void *mountthread(void _U_ *u){ static void *mountthread(void _U_ *u){
int errctr = 0; int errctr = 0;
uint8_t buf[2*sizeof(SSstat)]; uint8_t buf[2*sizeof(SSstat)];
SSstat *status = (SSstat*) buf; SSstat *status = (SSstat*) buf;
bzero(&mountdata, sizeof(mountdata));
double t0 = timefromstart(), tstart = t0, tcur = t0;
double oldmt = -100.; // old `millis measurement` time
static uint32_t oldmillis = 0;
if(Conf.RunModel){
double Xprev = NAN, Yprev = NAN; // previous coordinates
int xcnt = 0, ycnt = 0;
while(1){
coordpair_t c;
movestate_t xst, yst;
// now change data
getModData(&c, &xst, &yst);
struct timespec tnow;
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;
//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;
if(yst == ST_MOVE)
mountdata.motYposition.val = c.Y + (c.Y - mountdata.motYposition.val)*(drand48() - 0.5)/100.;
else
mountdata.motYposition.val = c.Y;
oldmt = tcur;
}else mountdata.millis = oldmillis;
chkModStopped(&Xprev, c.X, &xcnt, &mountdata.Xstate);
chkModStopped(&Yprev, c.Y, &ycnt, &mountdata.Ystate);
getXspeed(); getYspeed();
pthread_mutex_unlock(&datamutex);
while(timefromstart() - t0 < Conf.EncoderReqInterval) usleep(50);
t0 = timefromstart();
}
}
// data to get // data to get
data_t d = {.buf = buf, .maxlen = sizeof(buf)}; data_t d = {.buf = buf, .maxlen = sizeof(buf)};
// cmd to send // cmd to send
data_t *cmd_getstat = cmd2dat(CMD_GETSTAT); data_t *cmd_getstat = cmd2dat(CMD_GETSTAT);
if(!cmd_getstat) goto failed; if(!cmd_getstat) goto failed;
double t0 = dtime();
/*
#ifdef EBUG
double t00 = t0;
#endif
*/
while(mntfd > -1 && errctr < MAX_ERR_CTR){ while(mntfd > -1 && errctr < MAX_ERR_CTR){
// read data to status // read data to status
struct timeval tgot; struct timespec tcur;
if(0 != gettimeofday(&tgot, NULL)) continue; if(!curtime(&tcur)) continue;
// 80 milliseconds to get answer on GETSTAT
if(!MountWriteRead(cmd_getstat, &d) || d.len != sizeof(SSstat)){ if(!MountWriteRead(cmd_getstat, &d) || d.len != sizeof(SSstat)){
#ifdef EBUG #ifdef EBUG
DBG("Can't read SSstat, need %zd got %zd bytes", sizeof(SSstat), d.len); DBG("Can't read SSstat, need %zd got %zd bytes", sizeof(SSstat), d.len);
@@ -260,13 +557,13 @@ static void *mountthread(void _U_ *u){
errctr = 0; errctr = 0;
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
// now change data // now change data
SSconvstat(status, &mountdata, &tgot); SSconvstat(status, &mountdata, &tcur);
pthread_mutex_unlock(&datamutex); pthread_mutex_unlock(&datamutex);
// allow writing & getters // allow writing & getters
//DBG("t0=%g, tnow=%g", t0-t00, dtime()-t00); do{
if(dtime() - t0 >= Conf.MountReqInterval) usleep(50); usleep(500);
while(dtime() - t0 < Conf.MountReqInterval); }while(timefromstart() - t0 < Conf.MountReqInterval);
t0 = dtime(); t0 = timefromstart();
} }
data_free(&cmd_getstat); data_free(&cmd_getstat);
failed: failed:
@@ -282,8 +579,15 @@ static int ttyopen(const char *path, speed_t speed){
int fd = -1; int fd = -1;
struct termios2 tty; struct termios2 tty;
DBG("Try to open %s @ %d", path, speed); DBG("Try to open %s @ %d", path, speed);
if((fd = open(path, O_RDWR|O_NOCTTY)) < 0) return -1; if((fd = open(path, O_RDWR|O_NOCTTY)) < 0){
if(ioctl(fd, TCGETS2, &tty)){ close(fd); return -1; } DBG("Can't open device %s: %s", path, strerror(errno));
return -1;
}
if(ioctl(fd, TCGETS2, &tty)){
DBG("Can't read TTY settings");
close(fd);
return -1;
}
tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG) tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
tty.c_iflag = 0; // don't do any changes in input stream tty.c_iflag = 0; // don't do any changes in input stream
tty.c_oflag = 0; // don't do any changes in output stream tty.c_oflag = 0; // don't do any changes in output stream
@@ -292,8 +596,12 @@ static int ttyopen(const char *path, speed_t speed){
tty.c_ospeed = speed; tty.c_ospeed = speed;
//tty.c_cc[VMIN] = 0; // non-canonical mode //tty.c_cc[VMIN] = 0; // non-canonical mode
//tty.c_cc[VTIME] = 5; //tty.c_cc[VTIME] = 5;
if(ioctl(fd, TCSETS2, &tty)){ close(fd); return -1; } if(ioctl(fd, TCSETS2, &tty)){
DBG("Check speed"); DBG("Can't set TTY settings");
close(fd);
return -1;
}
DBG("Check speed: i=%d, o=%d", tty.c_ispeed, tty.c_ospeed);
if(tty.c_ispeed != (speed_t) speed || tty.c_ospeed != (speed_t)speed){ close(fd); return -1; } if(tty.c_ispeed != (speed_t) speed || tty.c_ospeed != (speed_t)speed){ close(fd); return -1; }
// try to set exclusive // try to set exclusive
if(ioctl(fd, TIOCEXCL)){DBG("Can't make exclusive");} if(ioctl(fd, TIOCEXCL)){DBG("Can't make exclusive");}
@@ -301,27 +609,49 @@ static int ttyopen(const char *path, speed_t speed){
} }
// return FALSE if failed // return FALSE if failed
int openEncoder(const char *path, int speed){ int openEncoder(){
if(Conf.RunModel) return TRUE;
if(!Conf.SepEncoder) return FALSE; // try to open separate encoder when it's absent if(!Conf.SepEncoder) return FALSE; // try to open separate encoder when it's absent
if(encfd > -1) close(encfd); if(Conf.SepEncoder == 1){ // only one device
encfd = ttyopen(path, (speed_t) speed); DBG("One device");
if(encfd < 0) return FALSE; 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_sec = 0;
encRtmout.tv_usec = 200000000 / speed; // 20 bytes encRtmout.tv_usec = 100000000 / Conf.EncoderDevSpeed; // 10 bytes
if(pthread_create(&encthread, NULL, encoderthread, NULL)){ if(pthread_create(&encthread, NULL, encoderthread1, NULL)){
close(encfd); close(encfd[0]);
encfd = -1; encfd[0] = -1;
return FALSE; return FALSE;
} }
}else if(Conf.SepEncoder == 2){
DBG("Two devices!");
const char* paths[2] = {Conf.EncoderXDevPath, Conf.EncoderYDevPath};
for(int i = 0; i < 2; ++i){
if(encfd[i] > -1) close(encfd[i]);
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]);
encfd[i] = -1;
}
return FALSE;
}
}else return FALSE;
DBG("Encoder opened, thread started"); DBG("Encoder opened, thread started");
return TRUE; return TRUE;
} }
// return FALSE if failed // return FALSE if failed
int openMount(const char *path, int speed){ int openMount(){
if(Conf.RunModel) goto create_thread;
if(mntfd > -1) close(mntfd); if(mntfd > -1) close(mntfd);
DBG("Open mount %s @ %d", path, speed); DBG("Open mount %s @ %d", Conf.MountDevPath, Conf.MountDevSpeed);
mntfd = ttyopen(path, (speed_t) speed); mntfd = ttyopen(Conf.MountDevPath, (speed_t) Conf.MountDevSpeed);
if(mntfd < 0) return FALSE; if(mntfd < 0) return FALSE;
DBG("mntfd=%d", mntfd); DBG("mntfd=%d", mntfd);
// clear buffer // clear buffer
@@ -334,11 +664,14 @@ int openMount(const char *path, int speed){
DBG("got %zd", l); DBG("got %zd", l);
}while(1);*/ }while(1);*/
mntRtmout.tv_sec = 0; mntRtmout.tv_sec = 0;
mntRtmout.tv_usec = 500000000 / speed; // 50 bytes mntRtmout.tv_usec = 500000000 / Conf.MountDevSpeed; // 50 bytes * 10bits / speed
create_thread:
if(pthread_create(&mntthread, NULL, mountthread, NULL)){ if(pthread_create(&mntthread, NULL, mountthread, NULL)){
DBG("Can't create thread"); DBG("Can't create mount thread");
if(!Conf.RunModel){
close(mntfd); close(mntfd);
mntfd = -1; mntfd = -1;
}
return FALSE; return FALSE;
} }
DBG("Mount opened, thread started"); DBG("Mount opened, thread started");
@@ -347,19 +680,29 @@ int openMount(const char *path, int speed){
// close all opened serial devices and quit threads // close all opened serial devices and quit threads
void closeSerial(){ void closeSerial(){
// TODO: close devices in "model" mode too!
if(Conf.RunModel) return;
if(mntfd > -1){ if(mntfd > -1){
DBG("Kill mount thread"); DBG("Cancel mount thread");
pthread_cancel(mntthread); pthread_cancel(mntthread);
DBG("close fd"); DBG("join mount thread");
pthread_join(mntthread, NULL);
DBG("close mount fd");
close(mntfd); close(mntfd);
mntfd = -1; mntfd = -1;
} }
if(encfd > -1){ if(encfd[0] > -1){
DBG("Kill encoder thread"); DBG("Cancel encoder thread");
pthread_cancel(encthread); pthread_cancel(encthread);
DBG("close fd"); DBG("join encoder thread");
close(encfd); pthread_join(encthread, NULL);
encfd = -1; DBG("close encoder's fd");
close(encfd[0]);
encfd[0] = -1;
if(Conf.SepEncoder == 2 && encfd[1] > -1){
close(encfd[1]);
encfd[1] = -1;
}
} }
} }
@@ -369,32 +712,45 @@ mcc_errcodes_t getMD(mountdata_t *d){
pthread_mutex_lock(&datamutex); pthread_mutex_lock(&datamutex);
*d = mountdata; *d = mountdata;
pthread_mutex_unlock(&datamutex); pthread_mutex_unlock(&datamutex);
//DBG("ENCpos: %.10g/%.10g", d->encXposition.val, d->encYposition.val);
//DBG("millis: %u, encxt: %zd (time: %zd)", d->millis, d->encXposition.t.tv_sec, time(NULL));
return MCC_E_OK; return MCC_E_OK;
} }
void setStat(axis_status_t Xstate, axis_status_t Ystate){
DBG("set x/y state to %d/%d", Xstate, Ystate);
pthread_mutex_lock(&datamutex);
mountdata.Xstate = Xstate;
mountdata.Ystate = Ystate;
pthread_mutex_unlock(&datamutex);
}
// write-read without locking mutex (to be used inside other functions) // write-read without locking mutex (to be used inside other functions)
static int wr(const data_t *out, data_t *in, int needeol){ static int wr(const data_t *out, data_t *in, int needeol){
if((!out && !in) || mntfd < 0) return FALSE; if((!out && !in) || mntfd < 0){
DBG("Wrong arguments or no mount fd");
return FALSE;
}
clrmntbuf();
if(out){ if(out){
if(out->len != (size_t)write(mntfd, out->buf, out->len)){ if(out->len != (size_t)write(mntfd, out->buf, out->len)){
DBG("written bytes not equal to need"); DBG("written bytes not equal to need");
return FALSE; return FALSE;
} }
//DBG("Send to mount %zd bytes: %s", out->len, out->buf);
if(needeol){ if(needeol){
int g = write(mntfd, "\r", 1); // add EOL int g = write(mntfd, "\r", 1); // add EOL
(void) g; (void) g;
} }
usleep(50000); // add little pause so that the idiot has time to swallow
} }
uint8_t buf[256]; if(!in) return TRUE;
data_t dumb = {.buf = buf, .maxlen = 256};
if(!in) in = &dumb; // even if user don't ask for answer, try to read to clear trash
in->len = 0; in->len = 0;
for(size_t i = 0; i < in->maxlen; ++i){ for(size_t i = 0; i < in->maxlen; ++i){
int b = getmntbyte(); int b = getmntbyte();
if(b < 0) break; // nothing to read -> go out if(b < 0) break; // nothing to read -> go out
in->buf[in->len++] = (uint8_t) b; in->buf[in->len++] = (uint8_t) b;
} }
while(getmntbyte() > -1);
return TRUE; return TRUE;
} }
@@ -405,6 +761,7 @@ static int wr(const data_t *out, data_t *in, int needeol){
* @return FALSE if failed * @return FALSE if failed
*/ */
int MountWriteRead(const data_t *out, data_t *in){ int MountWriteRead(const data_t *out, data_t *in){
if(Conf.RunModel) return -1;
pthread_mutex_lock(&mntmutex); pthread_mutex_lock(&mntmutex);
int ret = wr(out, in, 1); int ret = wr(out, in, 1);
pthread_mutex_unlock(&mntmutex); pthread_mutex_unlock(&mntmutex);
@@ -412,6 +769,7 @@ int MountWriteRead(const data_t *out, data_t *in){
} }
// send binary data - without EOL // send binary data - without EOL
int MountWriteReadRaw(const data_t *out, data_t *in){ int MountWriteReadRaw(const data_t *out, data_t *in){
if(Conf.RunModel) return -1;
pthread_mutex_lock(&mntmutex); pthread_mutex_lock(&mntmutex);
int ret = wr(out, in, 0); int ret = wr(out, in, 0);
pthread_mutex_unlock(&mntmutex); pthread_mutex_unlock(&mntmutex);
@@ -435,42 +793,43 @@ static void loglcmd(SSlcmd *c){
// send short/long binary command; return FALSE if failed // send short/long binary command; return FALSE if failed
static int bincmd(uint8_t *cmd, int len){ static int bincmd(uint8_t *cmd, int len){
if(Conf.RunModel) return FALSE;
static data_t *dscmd = NULL, *dlcmd = NULL; static data_t *dscmd = NULL, *dlcmd = NULL;
if(!dscmd) dscmd = cmd2dat(CMD_SHORTCMD); if(!dscmd) dscmd = cmd2dat(CMD_SHORTCMD);
if(!dlcmd) dlcmd = cmd2dat(CMD_LONGCMD); if(!dlcmd) dlcmd = cmd2dat(CMD_LONGCMD);
int ret = FALSE; int ret = FALSE;
pthread_mutex_lock(&mntmutex); pthread_mutex_lock(&mntmutex);
// dummy buffer to clear trash in input // dummy buffer to clear trash in input
char ans[300]; //char ans[300];
data_t a = {.buf = (uint8_t*)ans, .maxlen=299}; //data_t a = {.buf = (uint8_t*)ans, .maxlen=299};
if(len == sizeof(SSscmd)){ if(len == sizeof(SSscmd)){
((SSscmd*)cmd)->checksum = SScalcChecksum(cmd, len-2); ((SSscmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
DBG("Short command"); DBG("Short command");
#ifdef EBUG
logscmd((SSscmd*)cmd); logscmd((SSscmd*)cmd);
if(!wr(dscmd, &a, 1)) goto rtn; #endif
if(!wr(dscmd, NULL, 1)) goto rtn;
}else if(len == sizeof(SSlcmd)){ }else if(len == sizeof(SSlcmd)){
((SSlcmd*)cmd)->checksum = SScalcChecksum(cmd, len-2); ((SSlcmd*)cmd)->checksum = SScalcChecksum(cmd, len-2);
DBG("Long command"); DBG("Long command");
#ifdef EBUG
loglcmd((SSlcmd*)cmd); loglcmd((SSlcmd*)cmd);
if(!wr(dlcmd, &a, 1)) goto rtn; #endif
if(!wr(dlcmd, NULL, 1)) goto rtn;
}else{ }else{
goto rtn; goto rtn;
} }
DBG("Write %d bytes and wait for ans", len);
data_t d; data_t d;
d.buf = cmd; d.buf = cmd;
d.len = d.maxlen = len; d.len = d.maxlen = len;
ret = wr(&d, &d, 0); ret = wr(&d, NULL, 0);
#ifdef EBUG
if(len == sizeof(SSscmd)) logscmd((SSscmd*)cmd);
else loglcmd((SSlcmd*)cmd);
#endif
DBG("%s", ret ? "SUCCESS" : "FAIL"); DBG("%s", ret ? "SUCCESS" : "FAIL");
rtn: rtn:
pthread_mutex_unlock(&mntmutex); pthread_mutex_unlock(&mntmutex);
return ret; return ret;
} }
// short, long and config text-binary commands
// return TRUE if OK // return TRUE if OK
int cmdS(SSscmd *cmd){ int cmdS(SSscmd *cmd){
return bincmd((uint8_t *)cmd, sizeof(SSscmd)); return bincmd((uint8_t *)cmd, sizeof(SSscmd));
@@ -480,6 +839,7 @@ int cmdL(SSlcmd *cmd){
} }
// rw == 1 to write, 0 to read // rw == 1 to write, 0 to read
int cmdC(SSconfig *conf, int rw){ int cmdC(SSconfig *conf, int rw){
if(Conf.RunModel) return FALSE;
static data_t *wcmd = NULL, *rcmd = NULL; static data_t *wcmd = NULL, *rcmd = NULL;
int ret = FALSE; int ret = FALSE;
// dummy buffer to clear trash in input // dummy buffer to clear trash in input
@@ -493,16 +853,23 @@ int cmdC(SSconfig *conf, int rw){
}else{ // read }else{ // read
data_t d; data_t d;
d.buf = (uint8_t *) conf; d.buf = (uint8_t *) conf;
d.len = 0; d.maxlen = 0;
ret = wr(rcmd, &d, 1);
DBG("write command: %s", ret ? "TRUE" : "FALSE");
if(!ret) goto rtn;
// make a huge pause for stupid SSII
usleep(100000);
d.len = 0; d.maxlen = sizeof(SSconfig); d.len = 0; d.maxlen = sizeof(SSconfig);
ret = wr(rcmd, &d, 1); ret = wr(rcmd, &d, 1);
DBG("wr returned %s; got %zd bytes of %zd", ret ? "TRUE" : "FALSE", d.len, d.maxlen); DBG("wr returned %s; got %zd bytes of %zd", ret ? "TRUE" : "FALSE", d.len, d.maxlen);
if(d.len != d.maxlen) return FALSE; if(d.len != d.maxlen){ ret = FALSE; goto rtn; }
// simplest checksum // simplest checksum
uint16_t sum = 0; uint16_t sum = 0;
for(uint32_t i = 0; i < sizeof(SSconfig)-2; ++i) sum += d.buf[i]; for(uint32_t i = 0; i < sizeof(SSconfig)-2; ++i) sum += d.buf[i];
if(sum != conf->checksum){ if(sum != conf->checksum){
DBG("got sum: %u, need: %u", conf->checksum, sum); DBG("got sum: %u, need: %u", conf->checksum, sum);
return FALSE; ret = FALSE;
goto rtn;
} }
} }
rtn: rtn:

View File

@@ -28,15 +28,17 @@
// max error counter (when read() returns -1) // max error counter (when read() returns -1)
#define MAX_ERR_CTR (100) #define MAX_ERR_CTR (100)
double dtime();
data_t *cmd2dat(const char *cmd); data_t *cmd2dat(const char *cmd);
void data_free(data_t **x); void data_free(data_t **x);
int openEncoder(const char *path, int speed); int openEncoder();
int openMount(const char *path, int speed); int openMount();
void closeSerial(); void closeSerial();
mcc_errcodes_t getMD(mountdata_t *d); mcc_errcodes_t getMD(mountdata_t *d);
void setStat(axis_status_t Xstate, axis_status_t Ystate);
int MountWriteRead(const data_t *out, data_t *in); int MountWriteRead(const data_t *out, data_t *in);
int MountWriteReadRaw(const data_t *out, data_t *in); int MountWriteReadRaw(const data_t *out, data_t *in);
int cmdS(SSscmd *cmd); int cmdS(SSscmd *cmd);
int cmdL(SSlcmd *cmd); int cmdL(SSlcmd *cmd);
int cmdC(SSconfig *conf, int rw); int cmdC(SSconfig *conf, int rw);
void getXspeed();
void getYspeed();

View File

@@ -16,6 +16,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
* This file contains all need for external usage
*/
#pragma once #pragma once
#ifdef __cplusplus #ifdef __cplusplus
@@ -27,6 +32,14 @@ extern "C"
#include <stdint.h> #include <stdint.h>
#include <sys/time.h> #include <sys/time.h>
// minimal serial speed of mount device
#define MOUNT_BAUDRATE_MIN (1200)
// max speed interval, seconds
#define MCC_CONF_MAX_SPEEDINT (2.)
// minimal speed interval in parts of EncoderReqInterval
#define MCC_CONF_MIN_SPEEDC (3.)
// error codes // error codes
typedef enum{ typedef enum{
MCC_E_OK = 0, // all OK MCC_E_OK = 0, // all OK
@@ -35,22 +48,55 @@ typedef enum{
MCC_E_ENCODERDEV, // encoder device error or can't open MCC_E_ENCODERDEV, // encoder device error or can't open
MCC_E_MOUNTDEV, // mount device error or can't open MCC_E_MOUNTDEV, // mount device error or can't open
MCC_E_FAILED, // failed to run command - protocol error MCC_E_FAILED, // failed to run command - protocol error
MCC_E_AMOUNT // Just amount of errors
} mcc_errcodes_t; } mcc_errcodes_t;
typedef struct{
double P, I, D;
} PIDpar_t;
typedef struct{ typedef struct{
char* MountDevPath; // path to mount device char* MountDevPath; // path to mount device
int MountDevSpeed; // serial speed int MountDevSpeed; // serial speed
char* EncoderDevPath; // path to encoder device char* EncoderDevPath; // path to encoder device
int EncoderDevSpeed; // serial speed int EncoderDevSpeed; // serial speed
int SepEncoder; // ==1 if encoder works as separate serial device int SepEncoder; // ==1 if encoder works as separate serial device, ==2 if there's new version with two devices
double MountReqInterval; // maximal interval between subsequent mount requests (seconds) char* EncoderXDevPath; // paths to new controller devices
; char* EncoderYDevPath;
double EncodersDisagreement; // acceptable disagreement between motor and axis encoders
double MountReqInterval; // interval between subsequent mount requests (seconds)
double EncoderReqInterval; // interval between subsequent encoder requests (seconds)
double EncoderSpeedInterval; // interval between speed calculations
int RunModel; // == 1 if you want to use model instead of real mount
double PIDMaxDt; // maximal PID refresh time interval (if larger all old data will be cleared)
double PIDRefreshDt; // normal PID refresh interval
double PIDCycleDt; // PID I cycle time (analog of "RC" for PID on opamps)
PIDpar_t XPIDC; // gain parameters of PID for both axiss (C - coordinate driven, V - velocity driven)
PIDpar_t XPIDV;
PIDpar_t YPIDC;
PIDpar_t YPIDV;
double MaxPointingErr; // if angle < this, change state from "slewing" to "pointing" (coarse pointing): 8 degrees
double MaxFinePointingErr; // if angle < this, chane state from "pointing" to "guiding" (fine poinging): 1.5 deg
double MaxGuidingErr; // if error less than this value we suppose that target is captured and guiding is good (true guiding): 0.1''
int XEncZero; // encoders' zero position
int YEncZero;
} conf_t; } conf_t;
// coordinates in degrees: X, Y and time when they were reached // coordinates/speeds in degrees or d/s: X, Y
typedef struct{ typedef struct{
double X; double Y; struct timeval msrtime; double X; double Y;
} coords_t; } coordpair_t;
// coordinate/speed and time of last measurement
typedef struct{
double val;
struct timespec t;
} coordval_t;
typedef struct{
coordval_t X;
coordval_t Y;
} coordval_pair_t;
// data to read/write // data to read/write
typedef struct{ typedef struct{
@@ -89,25 +135,30 @@ typedef struct{
uint16_t ain1; uint16_t ain1;
} extradata_t; } extradata_t;
typedef enum{
AXIS_STOPPED,
AXIS_SLEWING,
AXIS_POINTING,
AXIS_GUIDING,
AXIS_ERROR,
} axis_status_t;
typedef struct{ typedef struct{
coords_t motposition; axis_status_t Xstate;
coords_t encposition; axis_status_t Ystate;
coords_t lastmotposition; coordval_t motXposition;
coordval_t motYposition;
coordval_t encXposition;
coordval_t encYposition;
coordval_t encXspeed; // once per <config> s
coordval_t encYspeed;
uint8_t keypad; uint8_t keypad;
extradata_t extradata; extradata_t extradata;
uint32_t millis; uint32_t millis;
double temperature; double temperature;
double voltage; double voltage;
int32_t XmotRaw;
int32_t YmotRaw;
int32_t XencRaw;
int32_t YencRaw;
} mountdata_t; } mountdata_t;
typedef struct{
;
} mountstat_t;
typedef struct{ typedef struct{
double Xmot; // 0 X motor position (rad) double Xmot; // 0 X motor position (rad)
double Xspeed; // 4 X speed (rad/s) double Xspeed; // 4 X speed (rad/s)
@@ -129,7 +180,7 @@ typedef struct{
double Yatime; // 28 double Yatime; // 28
} long_command_t; // long command } long_command_t; // long command
// hardware axe configuration // hardware axis configuration
typedef struct{ typedef struct{
double accel; // Default Acceleration, rad/s^2 double accel; // Default Acceleration, rad/s^2
double backlash; // Backlash (???) double backlash; // Backlash (???)
@@ -140,13 +191,16 @@ typedef struct{
double outplimit; // Output Limit, percent (0..100) double outplimit; // Output Limit, percent (0..100)
double currlimit; // Current Limit (A) double currlimit; // Current Limit (A)
double intlimit; // Integral Limit (???) double intlimit; // Integral Limit (???)
} __attribute__((packed)) axe_config_t; // these params are taken from mount by text commands (don't save negative values - better save these marks in xybits
double motor_stepsperrev;// encoder's steps per revolution: motor and axis
double axis_stepsperrev; // negative sign of these values means reverse direction
} __attribute__((packed)) axis_config_t;
// hardware configuration // hardware configuration
typedef struct{ typedef struct{
axe_config_t Xconf; axis_config_t Xconf;
xbits_t xbits; xbits_t xbits;
axe_config_t Yconf; axis_config_t Yconf;
ybits_t ybits; ybits_t ybits;
uint8_t address; uint8_t address;
double eqrate; // Equatorial Rate (???) double eqrate; // Equatorial Rate (???)
@@ -166,27 +220,42 @@ typedef struct{
uint32_t baudrate; // Baud Rate (baud) uint32_t baudrate; // Baud Rate (baud)
double locsdeg; // Local Search Degrees (rad) double locsdeg; // Local Search Degrees (rad)
double locsspeed; // Local Search Speed (rad/s) double locsspeed; // Local Search Speed (rad/s)
double backlspd; // Backlash speed (???) double backlspd; // Backlash speed (rad/s)
} hardware_configuration_t; } hardware_configuration_t;
/* flags for slew function
typedef struct{
uint32_t slewNguide : 1; // ==1 to guide after slewing
} slewflags_t;
*/
// mount class // mount class
typedef struct{ typedef struct{
// TODO: on init/quit clear all XY-bits to default`
mcc_errcodes_t (*init)(conf_t *c); // init device mcc_errcodes_t (*init)(conf_t *c); // init device
void (*quit)(); // deinit void (*quit)(); // deinit
mcc_errcodes_t (*getMountData)(mountdata_t *d); // get last data mcc_errcodes_t (*getMountData)(mountdata_t *d); // get last data
mcc_errcodes_t (*moveTo)(const double *X, const double *Y); // move to given position ans stop // mcc_errcodes_t (*slewTo)(const coordpair_t *target, slewflags_t flags);
mcc_errcodes_t (*moveWspeed)(const coords_t *target, const coords_t *speed); // move with given max speed mcc_errcodes_t (*correctTo)(const coordval_pair_t *target);
mcc_errcodes_t (*setSpeed)(const double *X, const double *Y); // set speed mcc_errcodes_t (*moveTo)(const coordpair_t *target); // move to given position and stop
mcc_errcodes_t (*moveWspeed)(const coordpair_t *target, const coordpair_t *speed); // move with given max speed
mcc_errcodes_t (*setSpeed)(const coordpair_t *tagspeed); // set speed
mcc_errcodes_t (*stop)(); // stop mcc_errcodes_t (*stop)(); // stop
mcc_errcodes_t (*emergStop)(); // emergency stop mcc_errcodes_t (*emergStop)(); // emergency stop
mcc_errcodes_t (*shortCmd)(short_command_t *cmd); // send/get short command mcc_errcodes_t (*shortCmd)(short_command_t *cmd); // send/get short command
mcc_errcodes_t (*longCmd)(long_command_t *cmd); // send/get long command mcc_errcodes_t (*longCmd)(long_command_t *cmd); // send/get long command
mcc_errcodes_t (*getHWconfig)(hardware_configuration_t *c); // get hardware configuration mcc_errcodes_t (*getHWconfig)(hardware_configuration_t *c); // get hardware configuration
mcc_errcodes_t (*saveHWconfig)(hardware_configuration_t *c); // save hardware configuration
int (*currentT)(struct timespec *t); // current time
double (*timeFromStart)(); // amount of seconds from last init
double (*timeDiff)(const struct timespec *time1, const struct timespec *time0); // difference of times
double (*timeDiff0)(const struct timespec *time1); // difference between current time and last init time
mcc_errcodes_t (*getMaxSpeed)(coordpair_t *v); // maximal speed by both axis
mcc_errcodes_t (*getMinSpeed)(coordpair_t *v); // minimal -//-
mcc_errcodes_t (*getAcceleration)(coordpair_t *a); // acceleration/deceleration
} mount_t; } mount_t;
extern mount_t Mount; extern mount_t Mount;
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -18,12 +18,17 @@
#include <ctype.h> #include <ctype.h>
#include <inttypes.h> #include <inttypes.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include "dbg.h" #include "main.h"
#include "serial.h" #include "serial.h"
#include "ssii.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.;
uint16_t SScalcChecksum(uint8_t *buf, int len){ uint16_t SScalcChecksum(uint8_t *buf, int len){
uint16_t checksum = 0; uint16_t checksum = 0;
for(int i = 0; i < len; i++){ for(int i = 0; i < len; i++){
@@ -35,32 +40,50 @@ uint16_t SScalcChecksum(uint8_t *buf, int len){
return checksum; return checksum;
} }
// Next three functions runs under locked mountdata_t mutex and shouldn't call locked it again!!
static void chkstopstat(int32_t *prev, int32_t cur, int *nstopped, axis_status_t *stat){
if(*prev == INT32_MAX){
*stat = AXIS_STOPPED;
DBG("START");
}else if(*stat != AXIS_STOPPED){
if(*prev == cur && ++(*nstopped) > MOTOR_STOPPED_CNT){
*stat = AXIS_STOPPED;
DBG("AXIS stopped");
}
}else if(*prev != cur){
DBG("AXIS moving");
*nstopped = 0;
}
*prev = cur;
}
// check for stopped/pointing states
static void ChkStopped(const SSstat *s, mountdata_t *m){
static int32_t Xmot_prev = INT32_MAX, Ymot_prev = INT32_MAX; // previous coordinates
static int Xnstopped = 0, Ynstopped = 0; // counters to get STOPPED state
chkstopstat(&Xmot_prev, s->Xmot, &Xnstopped, &m->Xstate);
chkstopstat(&Ymot_prev, s->Ymot, &Ynstopped, &m->Ystate);
}
/** /**
* @brief SSconvstat - convert stat from SSII format to human * @brief SSconvstat - convert stat from SSII format to human
* @param status (i) - just read data * @param s (i) - just read data
* @param mountdata (o) - output * @param m (o) - output
* @param t - measurement time
*/ */
void SSconvstat(const SSstat *s, mountdata_t *m, struct timeval *tdat){ void SSconvstat(const SSstat *s, mountdata_t *m, struct timespec *t){
if(!s || !m || !tdat) return; if(!s || !m || !t) return;
/* m->motXposition.val = X_MOT2RAD(s->Xmot);
#ifdef EBUG m->motYposition.val = Y_MOT2RAD(s->Ymot);
static double t0 = -1.; ChkStopped(s, m);
if(t0 < 0.) t0 = dtime(); m->motXposition.t = m->motYposition.t = *t;
#endif
DBG("Convert, t=%g", dtime()-t0);
*/
m->motposition.X = X_MOT2RAD(s->Xmot);
m->motposition.Y = Y_MOT2RAD(s->Ymot);
m->motposition.msrtime = *tdat;
// fill encoder data from here, as there's no separate enc thread // fill encoder data from here, as there's no separate enc thread
if(!Conf.SepEncoder){ if(!Conf.SepEncoder){
m->encposition.X = X_ENC2RAD(s->Xenc); m->encXposition.val = Xenc2rad(s->Xenc);
m->encposition.Y = Y_ENC2RAD(s->Yenc); DBG("encx: %g", m->encXposition.val);
m->encposition.msrtime = *tdat; m->encYposition.val = Yenc2rad(s->Yenc);
m->encXposition.t = m->encYposition.t = *t;
getXspeed(); getYspeed();
} }
m->lastmotposition.X = X_MOT2RAD(s->XLast);
m->lastmotposition.Y = Y_MOT2RAD(s->YLast);
m->lastmotposition.msrtime = *tdat;
m->keypad = s->keypad; m->keypad = s->keypad;
m->extradata.ExtraBits = s->ExtraBits; m->extradata.ExtraBits = s->ExtraBits;
m->extradata.ain0 = s->ain0; m->extradata.ain0 = s->ain0;
@@ -86,7 +109,7 @@ int SStextcmd(const char *cmd, data_t *answer){
data_t d; data_t d;
d.buf = (uint8_t*) cmd; d.buf = (uint8_t*) cmd;
d.len = d.maxlen = strlen(cmd); d.len = d.maxlen = strlen(cmd);
DBG("send %zd bytes: %s", d.len, d.buf); //DBG("send %zd bytes: %s", d.len, d.buf);
return MountWriteRead(&d, answer); return MountWriteRead(&d, answer);
} }
@@ -99,7 +122,7 @@ int SSrawcmd(const char *cmd, data_t *answer){
data_t d; data_t d;
d.buf = (uint8_t*) cmd; d.buf = (uint8_t*) cmd;
d.len = d.maxlen = strlen(cmd); d.len = d.maxlen = strlen(cmd);
DBG("send %zd bytes: %s", d.len, d.buf); //DBG("send %zd bytes: %s", d.len, d.buf);
return MountWriteReadRaw(&d, answer); return MountWriteReadRaw(&d, answer);
} }
@@ -153,3 +176,50 @@ int SSstop(int emerg){
return TRUE; return TRUE;
} }
// update motors' positions due to encoders'
mcc_errcodes_t updateMotorPos(){
mountdata_t md = {0};
if(Conf.RunModel) return MCC_E_OK;
double t0 = timefromstart(), t = 0.;
struct timespec curt;
DBG("start @ %g", t0);
do{
t = timefromstart();
if(!curtime(&curt)){
usleep(10000);
continue;
}
//DBG("XENC2RAD: %g (xez=%d, xesr=%.10g)", Xenc2rad(32424842), X_ENC_ZERO, X_ENC_STEPSPERREV);
if(MCC_E_OK == getMD(&md)){
if(md.encXposition.t.tv_sec == 0 || md.encYposition.t.tv_sec == 0){
DBG("Just started? t-t0 = %g!", t - t0);
usleep(10000);
continue;
}
if(md.Xstate != AXIS_STOPPED || md.Ystate != AXIS_STOPPED) return MCC_E_OK;
DBG("got; t pos x/y: %ld/%ld; tnow: %ld", md.encXposition.t.tv_sec, md.encYposition.t.tv_sec, curt.tv_sec);
mcc_errcodes_t OK = MCC_E_OK;
if(fabs(md.motXposition.val - md.encXposition.val) > Conf.EncodersDisagreement && md.Xstate == AXIS_STOPPED){
DBG("NEED to sync X: motors=%g, axis=%g", md.motXposition.val, md.encXposition.val);
DBG("new motsteps: %d", X_RAD2MOT(md.encXposition.val));
if(!SSsetterI(CMD_MOTXSET, X_RAD2MOT(md.encXposition.val))){
DBG("Xpos sync failed!");
OK = MCC_E_FAILED;
}else DBG("Xpos sync OK, Dt=%g", t - t0);
}
if(fabs(md.motYposition.val - md.encYposition.val) > Conf.EncodersDisagreement && md.Ystate == AXIS_STOPPED){
DBG("NEED to sync Y: motors=%g, axis=%g", md.motYposition.val, md.encYposition.val);
if(!SSsetterI(CMD_MOTYSET, Y_RAD2MOT(md.encYposition.val))){
DBG("Ypos sync failed!");
OK = MCC_E_FAILED;
}else DBG("Ypos sync OK, Dt=%g", t - t0);
}
if(MCC_E_OK == OK){
DBG("Encoders synced");
return OK;
}
}
DBG("NO DATA; dt = %g", t - t0);
}while(t - t0 < 2.);
return MCC_E_FATAL;
}

View File

@@ -16,6 +16,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
* This file contains stuff for sidereal-servo specific protocol
*/
#pragma once #pragma once
#include <math.h> #include <math.h>
@@ -165,44 +169,76 @@
// X,Y - motor, XZ,YZ - encoder, XC,YC - current*100, V - voltage*10, T - temp (F), XA,YA - mode (A[uto]/M[anual]), K - handpad status bits // X,Y - motor, XZ,YZ - encoder, XC,YC - current*100, V - voltage*10, T - temp (F), XA,YA - mode (A[uto]/M[anual]), K - handpad status bits
#define CMD_GETSTATTEXT "\r" #define CMD_GETSTATTEXT "\r"
// Loop freq
#define SITECH_LOOP_FREQUENCY (1953.)
// amount of consequent same coordinates to detect stop
#define MOTOR_STOPPED_CNT (19)
// steps per revolution // replace macros with global variables inited when config read
#define X_MOT_STEPSPERREV (3325952.) extern int X_ENC_ZERO, Y_ENC_ZERO;
#define Y_MOT_STEPSPERREV (4394960.) extern double X_MOT_STEPSPERREV, Y_MOT_STEPSPERREV, X_ENC_STEPSPERREV, Y_ENC_STEPSPERREV;
// maximal speeds in rad/s: 10deg/s by X and 8deg/s by Y // TODO: take it from settings?
#define X_SPEED_MAX (0.17453) // steps per revolution (SSI - x4 - for SSI)
#define Y_SPEED_MAX (0.13963) // -> hwconf.Xconf.mot/enc_stepsperrev
//#define X_MOT_STEPSPERREV_SSI (13312000.)
// motor position to radians and back // 13312000 / 4 = 3328000
#define X_MOT2RAD(n) (2.*M_PI * ((double)n) / X_MOT_STEPSPERREV) //#define X_MOT_STEPSPERREV (3328000.)
#define Y_MOT2RAD(n) (2.*M_PI * ((double)n) / Y_MOT_STEPSPERREV) //#define Y_MOT_STEPSPERREV_SSI (17578668.)
#define X_RAD2MOT(r) ((int32_t)((r) / 2./M_PI * X_MOT_STEPSPERREV)) // 17578668 / 4 = 4394667
#define Y_RAD2MOT(r) ((int32_t)((r) / 2./M_PI * Y_MOT_STEPSPERREV)) //#define Y_MOT_STEPSPERREV (4394667.)
// motor speed in rad/s and back
#define X_MOTSPD2RS(n) (X_MOT2RAD(n)/65536.*1953.)
#define X_RS2MOTSPD(r) ((int32_t)(X_RAD2MOT(r)*65536./1953.))
#define Y_MOTSPD2RS(n) (Y_MOT2RAD(n)/65536.*1953.)
#define Y_RS2MOTSPD(r) ((int32_t)(Y_RAD2MOT(r)*65536./1953.))
// motor acceleration -//-
#define X_MOTACC2RS(n) (X_MOT2RAD(n)/65536.*1953.*1953.)
#define X_RS2MOTACC(r) ((int32_t)(X_RAD2MOT(r)*65536./1953./1953.))
#define Y_MOTACC2RS(n) (Y_MOT2RAD(n)/65536.*1953.*1953.)
#define Y_RS2MOTACC(r) ((int32_t)(Y_RAD2MOT(r)*65536./1953./1953.))
// adder time to seconds vice versa
#define ADDER2S(a) ((a)*1953.)
#define S2ADDER(s) ((s)/1953.)
// encoder per revolution // encoder per revolution
#define X_ENC_STEPSPERREV (67108864.) //#define X_ENC_STEPSPERREV (67108864.)
#define Y_ENC_STEPSPERREV (67108864.) //#define Y_ENC_STEPSPERREV (67108864.)
// encoder zero position
// -> conf.XEncZero/YEncZero
//#define X_ENC_ZERO (61245239)
//#define Y_ENC_ZERO (36999830)
// encoder reversed (no: +1) -> sign of ...stepsperrev
//#define X_ENC_SIGN (-1.)
//#define Y_ENC_SIGN (-1.)
// encoder position to radians and back // encoder position to radians and back
#define X_ENC2RAD(n) (2.*M_PI * ((double)n) / X_ENC_STEPSPERREV) #define Xenc2rad(n) ang2half(2.*M_PI * ((double)((n)-(X_ENC_ZERO))) / (X_ENC_STEPSPERREV))
#define Y_ENC2RAD(n) (2.*M_PI * ((double)n) / Y_ENC_STEPSPERREV) #define Yenc2rad(n) ang2half(2.*M_PI * ((double)((n)-(Y_ENC_ZERO))) / (Y_ENC_STEPSPERREV))
#define X_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * X_ENC_STEPSPERREV)) #define Xrad2enc(r) ((uint32_t)((r) / 2./M_PI * (X_ENC_STEPSPERREV)))
#define Y_RAD2ENC(r) ((uint32_t)((r) / 2./M_PI * Y_ENC_STEPSPERREV)) #define Yrad2enc(r) ((uint32_t)((r) / 2./M_PI * (Y_ENC_STEPSPERREV)))
// convert angle in radians to +-pi
static inline __attribute__((always_inline)) double ang2half(double ang){
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){
if(ang < 0.) ang += 2.*M_PI;
else if(ang > 2.*M_PI) ang -= 2.*M_PI;
return ang;
}
// motor position to radians and back
#define X_MOT2RAD(n) ang2half(2. * M_PI * ((double)(n)) / (X_MOT_STEPSPERREV))
#define Y_MOT2RAD(n) ang2half(2. * M_PI * ((double)(n)) / (Y_MOT_STEPSPERREV))
#define X_RAD2MOT(r) ((int32_t)((r) / (2. * M_PI) * (X_MOT_STEPSPERREV)))
#define Y_RAD2MOT(r) ((int32_t)((r) / (2. * M_PI) * (Y_MOT_STEPSPERREV)))
// motor speed in rad/s and back
#define X_MOTSPD2RS(n) (X_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY))
#define Y_MOTSPD2RS(n) (Y_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY))
#define X_RS2MOTSPD(r) ((int32_t)(X_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY)))
#define Y_RS2MOTSPD(r) ((int32_t)(Y_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY)))
// motor acceleration -//-
#define X_MOTACC2RS(n) (X_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY) * (SITECH_LOOP_FREQUENCY))
#define Y_MOTACC2RS(n) (Y_MOT2RAD(n) / 65536. * (SITECH_LOOP_FREQUENCY) * (SITECH_LOOP_FREQUENCY))
#define X_RS2MOTACC(r) ((int32_t)(X_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY) / (SITECH_LOOP_FREQUENCY)))
#define Y_RS2MOTACC(r) ((int32_t)(Y_RAD2MOT(r) * 65536. / (SITECH_LOOP_FREQUENCY) / (SITECH_LOOP_FREQUENCY)))
// adder time to seconds vice versa
#define ADDER2S(a) ((a) / (SITECH_LOOP_FREQUENCY))
#define S2ADDER(s) ((s) * (SITECH_LOOP_FREQUENCY))
// encoder's tolerance (ticks) // encoder's tolerance (ticks)
#define YencTOL (25.) #define YencTOL (25.)
@@ -303,10 +339,10 @@ typedef struct{
} __attribute__((packed)) SSconfig; } __attribute__((packed)) SSconfig;
uint16_t SScalcChecksum(uint8_t *buf, int len); uint16_t SScalcChecksum(uint8_t *buf, int len);
void SSconvstat(const SSstat *status, mountdata_t *mountdata, struct timeval *tdat); void SSconvstat(const SSstat *status, mountdata_t *mountdata, struct timespec *t);
int SStextcmd(const char *cmd, data_t *answer); int SStextcmd(const char *cmd, data_t *answer);
int SSrawcmd(const char *cmd, data_t *answer); int SSrawcmd(const char *cmd, data_t *answer);
int SSgetint(const char *cmd, int64_t *ans); int SSgetint(const char *cmd, int64_t *ans);
int SSsetterI(const char *cmd, int32_t ival); int SSsetterI(const char *cmd, int32_t ival);
int SSstop(int emerg); int SSstop(int emerg);
int SSshortCmd(SSscmd *cmd); mcc_errcodes_t updateMotorPos();

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.30)
set(PROJ PCS_create) set(PROJ PCS_create)
set(MINOR_VERSION "0") set(MINOR_VERSION "0")
set(MID_VERSION "1") set(MID_VERSION "1")

View File

@@ -321,7 +321,7 @@ static void printheader(){
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
initial_setup(); sl_init();
G = parse_args(argc, argv); G = parse_args(argc, argv);
if(G->pressure < 0.) ERRX("Pressure should be greater than zero"); if(G->pressure < 0.) ERRX("Pressure should be greater than zero");
if(G->temperature < -100. || G->temperature > 100.) ERRX("Temperature over the range -100..+100"); if(G->temperature < -100. || G->temperature > 100.) ERRX("Temperature over the range -100..+100");

View File

@@ -40,7 +40,7 @@ glob_pars const Gdefault = {
* Define command line options by filling structure: * Define command line options by filling structure:
* name has_arg flag val type argptr help * name has_arg flag val type argptr help
*/ */
static myoption cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
// set 1 to param despite of its repeating number: // set 1 to param despite of its repeating number:
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")},
{"10m", NO_ARGS, NULL, 't', arg_int, APTR(&G.for10m), _("make output suitable for 10-micron mount")}, {"10m", NO_ARGS, NULL, 't', arg_int, APTR(&G.for10m), _("make output suitable for 10-micron mount")},
@@ -68,10 +68,10 @@ static myoption cmdlnopts[] = {
glob_pars *parse_args(int argc, char **argv){ glob_pars *parse_args(int argc, char **argv){
void *ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); void *ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr);
// format of help: "Usage: progname [args]\n" // format of help: "Usage: progname [args]\n"
change_helpstring(_("Version: " PACKAGE_VERSION "\nUsage: %s [args] FITS_files\nMake PCS list for equatorial mount\n\tWhere args are:\n")); sl_helpstring(_("Version: " PACKAGE_VERSION "\nUsage: %s [args] FITS_files\nMake PCS list for equatorial mount\n\tWhere args are:\n"));
// parse arguments // parse arguments
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts); if(help) sl_showhelp(-1, cmdlnopts);
G.nfiles = argc; G.nfiles = argc;
G.infiles = MALLOC(char*, argc); G.infiles = MALLOC(char*, argc);
for(int i = 0; i < argc; i++){ for(int i = 0; i < argc; i++){

View File

@@ -0,0 +1,59 @@
# run `make DEF=...` to add extra defines
PROGRAM := invertercmd
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
DEPS := $(OBJS:.o=.d)
TARGFILE := $(OBJDIR)/TARGET
CC = gcc
#TARGET := RELEASE
ifeq ($(shell test -e $(TARGFILE) && echo -n yes),yes)
TARGET := $(file < $(TARGFILE))
else
TARGET := RELEASE
endif
ifeq ($(TARGET), DEBUG)
.DEFAULT_GOAL := debug
endif
release: CFLAGS += -flto
release: LDFLAGS += -flto
release: $(PROGRAM)
debug: CFLAGS += -DEBUG -Werror
debug: TARGET := DEBUG
debug: $(PROGRAM)
$(TARGFILE): $(OBJDIR)
@echo -e "\t\tTARGET: $(TARGET)"
@echo "$(TARGET)" > $(TARGFILE)
$(PROGRAM) : $(TARGFILE) $(OBJS)
@echo -e "\t\tLD $(PROGRAM)"
$(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM)
$(OBJDIR):
@mkdir $(OBJDIR)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR)/%.o: %.c
@echo -e "\t\tCC $<"
$(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $<
clean:
@echo -e "\t\tCLEAN"
@rm -rf $(OBJDIR) 2>/dev/null || true
xclean: clean
@rm -f $(PROGRAM)
.PHONY: clean xclean

Binary file not shown.

View File

@@ -0,0 +1 @@
Serial communications with SILA inverters

View File

@@ -0,0 +1,414 @@
/*
* Copyright 2025 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 <ctype.h>
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
static sl_tty_t *dev = NULL;
static const char *wro = "(wrong parameter)";
typedef struct{
int help;
char *path;
char *customcmd;
char *getstatus;
int baudrate;
int status;
int settershelp;
} globopts;
typedef struct{
int all;
int help;
int rating;
int flag;
int status;
int mode;
int warning;
int deflt;
int bateq;
} statusinfo;
typedef void (*parsefn)(const char *answer);
static globopts G = {
.path = "/dev/ttyS0",
.baudrate = 2400,
};
static statusinfo S = {0};
static sl_option_t opts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"devpath", NEED_ARG, NULL, 'd', arg_string, APTR(&G.path), "path to device (default: /dev/ttyS0)"},
{"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.baudrate), "baudrate (default: 2400)"},
{"cmd", NEED_ARG, NULL, 'c', arg_string, APTR(&G.customcmd), "custom command to send"},
{"status", NEED_ARG, NULL, 's', arg_string, APTR(&G.getstatus), "get status: comma-separated options (type 'help' for options)"},
{"helpsetters",NO_ARGS, NULL, 0, arg_int, APTR(&G.settershelp),"show help about setters"},
end_option
};
static sl_suboption_t sopts[] = {
{"all", NO_ARGS, arg_int, APTR(&S.all)},
{"help", NO_ARGS, arg_int, APTR(&S.help)},
{"rating", NO_ARGS, arg_int, APTR(&S.rating)},
{"flag", NO_ARGS, arg_int, APTR(&S.flag)},
{"status", NO_ARGS, arg_int, APTR(&S.status)},
{"mode", NO_ARGS, arg_int, APTR(&S.mode)},
{"warning", NO_ARGS, arg_int, APTR(&S.warning)},
{"default", NO_ARGS, arg_int, APTR(&S.deflt)},
{"bateq", NO_ARGS, arg_int, APTR(&S.bateq)},
end_suboption
};
static void gettershelp(){
fprintf(stderr, "Status parameters:\n");
fprintf(stderr, "all - show all information available\n");
fprintf(stderr, "rating - device rating information (QPIRI)\n");
fprintf(stderr, "flag - device flag status (QFLAG)\n");
fprintf(stderr, "status - device general status parameters (QPIGS)\n");
fprintf(stderr, "mode - device mode (QMOD)\n");
fprintf(stderr, "warning - warning status (QPIWS)\n");
fprintf(stderr, "default - default settings (QDI)\n");
fprintf(stderr, "bateq - battery equalization parameters (QBEQI)\n");
// fprintf(stderr, "\n");
}
static uint8_t *cal_crc(const char *cmd, int len){
static uint8_t CRC[4]; // 0 - hi, 1 - low, four bytes for quick zeroing
if(!cmd || len < 1) return 0;
const uint16_t crc_table[16] = {
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef
};
*((uint32_t*)CRC) = 0;
uint16_t crc = 0;
uint8_t *ptr = (uint8_t*)cmd;
while(len--){
uint8_t da = ((uint8_t)(crc >> 8)) >> 4;
crc <<= 4;
crc ^= crc_table[da ^ (*ptr >> 4)];
da = ((uint8_t)(crc >> 8)) >> 4;
crc <<= 4;
crc ^= crc_table[da ^ (*ptr & 0x0f)];
ptr++;
}
uint8_t bCRCLow = crc;
uint8_t bCRCHign= (uint8_t)(crc >> 8);
if(bCRCLow==0x28 || bCRCLow==0x0d || bCRCLow==0x0a) ++bCRCLow;
if(bCRCHign==0x28 || bCRCHign==0x0d || bCRCHign==0x0a) ++bCRCHign;
CRC[0] = bCRCHign; CRC[1] = bCRCLow; CRC[2] = '\r';
DBG("CRC: 0x%02X 0x%02X 0x%02X", CRC[0], CRC[1], CRC[2]);
return CRC;
}
static int sendcmd(const char *cmd){
if(!cmd || !*cmd || !dev) return 0;
int len = strlen(cmd);
uint8_t *CRC = cal_crc(cmd, len);
#ifdef EBUG
printf("COMMAND: ");
for(int i = 0; i < len; ++i) printf("0x%02X ", cmd[i]);
printf("\n");
#endif
if(sl_tty_write(dev->comfd, cmd, len)){
WARN("Can't write command");
return 0;
}
if(sl_tty_write(dev->comfd, (const char*) CRC, 3)){
WARN("Can't write CRC & STREND");
return 0;
}
DBG("Command %s sent", cmd);
return 1;
}
static char *rd(){
if(!dev) return NULL;
int got = sl_tty_read(dev);
if(got < 0) ERR("Can't read");
DBG("got %d bytes, buflen: %zd", got, dev->buflen);
if(dev->buflen < 3) return NULL;
uint8_t *CRC = cal_crc(dev->buf, dev->buflen - 3);
DBG("GOT CRC: 0x%02X 0x%02X", (uint8_t)dev->buf[dev->buflen-3], (uint8_t)dev->buf[dev->buflen-2]);
if(CRC[0] != (uint8_t)dev->buf[dev->buflen-3] || CRC[1] != (uint8_t)dev->buf[dev->buflen-2]){
WARNX("Bad CRC");
return NULL;
}
char *r = dev->buf + 1;
dev->buf[dev->buflen-3] = 0;
return r;
}
// 230.0 13.0 230.0 50.0 13.0 3000 3000 24.0 23.0 21.0 28.2 27.0 0 25 50 0 0 2 - 01 1 0 27.0 0 0
// b c d e f h i j1 k1 j2 k2 l o1 p1q0 o2p2q2r s t u v w x
static void ratingparsing(const char *str){
float b, c, d, e, f, j1, k1, j2, k2, l, v;
int h, i, o1, p1, q, o2, p2, q2, s, t, u, w, x;
char r;
int N = sscanf(str, "%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d %c %d %d %d %f %d %d",
&b, &c, &d, &e, &f, &h, &i, &j1, &k1, &j2, &k2, &l, &o1, &p1, &q, &o2, &p2, &q2, &r, &s, &t,
&u, &v, &w, &x);
if(N != 25){ WARNX("Got not full answer (%d instead of 25): '%s'", N, str); return; }
printf("Grid rating voltage: %g\nGrid rating current: %g\nAC optuput rating voltage: %g\n", b, c, d);
printf("AC output rating frequency: %g\nAC output rating current: %g\nAC output rating apparent power: %d\n", e, f, h);
printf("AC output rating active power: %d\nBattery rating voltage: %g\nBattery recharge voltage: %g\n", i, j1, k1);
printf("Battery undervoltage: %g\nBattery bulk voltage: %g\nBattery float voltage: %g\n", j2, k2, l);
static const char *types[] = {"AGM", "Flooded", "User"};
printf("Battery type: %s\n", (o1 > -1 && o1 < 3) ? types[o1] : wro);
printf("Current max AC charging current: %d\nCurrent max charging current: %d\n", p1, q);
printf("Input voltage range: %s\n", o2 ? "UPS" : "Appliance");
static const char *oprio[] = {"Utility", "Solar", "SBU"};
printf("Output source priority: %s\n", (p2 > -1 && p2 < 3) ? oprio[p2] : wro);
static const char *sprio[] = {"Utility", "Solar", "Solar+Utility", "Only solar charging"};
printf("Charger source priority: %s\n", (q2 > -1 && q2 < 4) ? sprio[q2] : wro);
printf("Parallel max num: %c\n", r);
printf("Machine type: ");
switch(s){
case 0: printf("Grid tie"); break;
case 1: printf("Off grid"); break;
case 10: printf("Hybrid"); break;
default: printf("%s", wro);
}
printf("\nTopology: %s\n", (t) ? "transformer" : "transformerless");
printf("Output mode: %d\nBattery redischarge voltage: %g\n", u, v);
printf("PV OK condition for parallel: %s\n", (w) ? "Only all connected" : "At least one connected");
printf("PV power balance: %s\n", (x) ? "Sum of powers" : "Max charged current");
}
static const char *DEflags = "abjkuvxyz";
static const char *DEmeanings[] = {
"buzzer", "bypass", "power saving", "LCD display escape 1min", "overload restart",
"over temperature restart", "backlight on", "alarm on interrupt", "fault code record",
NULL
};
static void flagparsing(const char *str){
char c;
int first = 0;
while((c = *str++)){
if(c == 'D'){ green("\nDISABLED: "); first = 1; }
else if(c == 'E'){ red("ENABLED: "); first = 1; }
else{
const char *field = "unknown";
for(int i = 0; DEflags[i]; ++i){
if(DEflags[i] == c){ field = DEmeanings[i]; break; }
}
printf("%s%s", (first) ? "" : ", ", field);
first = 0;
}
}
printf("\n");
}
/**
* @brief showflags - display bitflags
* @param flags - string with flags ('1' / '0')
* @param meaning - array with meaning of each flag, starting from H bit, i.e. meaning[0] is lesser bit
* @param nfields - strlen of flags (or less), H first
*/
static void showflags(const char *flags, const char **meaning, int nfields){
for(int i = 0; i < nfields; ++i){
if(!meaning[i]) continue;
printf("\t%s: ", meaning[i]);
if(flags[i] == '1') red("on/yes\n");
else green("off/no\n");
}
}
// 230.0 50.0 232.0 50.0 0000 0000 000 409 26.99 000 100 0489 0000 000.0 00.00 00000 10011101 00 03 00000 100
// b c d e f g h i j k o t e1 u w p x [unknown shit]
static void statusparsing(const char *str){
float b, c, d, e, j, u, w;
int f, g, h, i, k, o, t, e1, p, S, H, I;
char x[9], T[4];
int N = sscanf(str, "%f %f %f %f %d %d %d %d %f %d %d %d %d %f %f %d %8s %d %d %d %3s",
&b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &o, &t, &e1, &u, &w, &p, x, &S, &H, &I, T);
DBG("N=%d", N);
if(N >= 17){
printf("Grid voltage: %g\nGrid frequency: %g\nAC output voltage: %g\nAC output frequency: %g\n",
b, c, d, e);
printf("AC output apparent power: %d\nAC output active power: %d\nOutput load percent: %d\n", f, g, h);
printf("Bus voltage: %d\nBattery voltage: %g\nBattery charging current: %d\nBattery capacity: %d\n", i, j, k, o);
printf("Inverter heat sink temperature: %d\nPV input current for battery: %d\nPV input voltage 1: %g\n", t, e1, u);
printf("Battery voltage from SCC: %g\nBattery discharge current: %d\nDevice status:\n", w, p);
static const char *sf[] = {"SBU priority version", "configuration changed", "SCC firmware updated",
"Load status", "Steady batt voltage while charging", "Charging", "SCC charging", "AC charging"};
showflags(x, sf, 8);
if(N > 17){
printf("Battery offset for fans on: %d\nEEPROM version: %d\nPV charging power: %d\n", S, H, I);
printf("Inverter status:\n");
static const char *is[] = {"Charging to floating mode", "Switch", "Dustproof installed"};
showflags(T, is, 3);
}
}else WARNX("Get not full answer: %d instead of 17", N);
}
static void modeparsing(const char *str){
printf("Device mode: ");
switch(*str){
case 'B': printf("Battery"); break;
case 'F': printf("Fault"); break;
case 'H': printf("Power saving"); break;
case 'L': printf("Line"); break;
case 'P': printf("Power on"); break;
case 'S': printf("Standby"); break;
default: printf("Unknown");
}
printf("\n");
}
static void warningparsing(const char *str){
int l = strlen(str);
if(l < 32) WARNX("Non-full status! Data could be wrong");
static const char *wmsgs[] = {
NULL, "Inverter fault", "Bus over", "Bus under", "Bus soft fail", "Line fail", "OPV short",
"Inv voltage too low", "Inv voltage too high", "Over temperature", "Fan locked", "Battery voltage high",
"Battery low alarm", "Overcharge", "Battery under shutdown", "Battery derating", "Overload", "EEPROM fault",
"Inverter overcurrent", "Inverter soft fail", "Self test fail", "OP DC voltage over", "Bat open",
"Current sensor fail", "Battery short", "Power limit", "PV voltage high", "MPPT overload fault",
"MPPT overload warning", "Battery too low to charge", NULL, NULL
};
printf("Warning status:\n");
showflags(str, wmsgs, l);
}
static const char *endis(int val){
return (val) ? COLOR_RED "enable" COLOR_OLD : COLOR_GREEN "disable" COLOR_OLD;
}
// 230.0 50.0 0025 21.0 27.0 28.2 23.0 50 0 0 2 0 1 0 0 0 1 1 1 0 1 0 27.0 0 0 ()
// b c d e f g h i j k l m n o p q r s t u v w y x z a
static void defaultparsing(const char *str){
float b, c, e, f, g, h, y;
int d, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, z, a;
int N = sscanf(str, "%f %f %d %f %f %f %f %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %f %d %d %d",
&b, &c, &d, &e, &f, &g, &h, &i, &j, &k, &l, &m, &n, &o, &p, &q, &r, &s, &t, &u, &v, &w, &y, &x, &z, &a);
if(N < 25){WARNX("Wrong data format: %d fields instead of 25", N); return;}
printf("AC output voltage: %g\nAC output frequency: %g\nMax AC charging current: %d\n", b, c, d);
printf("Battery undervoltage: %g\nCharging float voltage: %g\nCharging bulk voltage: %g\n", e, f, g);
printf("Battery default recharge voltage: %g\nMax charging current: %d\nAC input voltage range: %s\n", h, i, (j) ? "UPS" : "appliance");
printf("Output source priority: %s\nCharger source priority: %s\n", (k) ? "solar first": "utility first", (l) ? "solar first" : "utility first");
printf("Battery type: %s\nBuzzer: %s\nPover saving: %s\n", (m) ? "other" : "AGM", endis(n), endis(o));
printf("Overload restart: %s\nOver temperature restart: %s\n", endis(p), endis(q));
printf("Backlight: %s\nAlarm on interrupt: %s\nFault code record: %s\n", endis(r), endis(s), endis(t));
printf("Overload bypass: %s\nLCD timeout escape: %s\nOutput mode: %d\n", endis(u), endis(v), w);
printf("Battery re-discharge voltage: %g\nPV OK condition for parallel: %s\n", y, (x) ? "all" : "any");
printf("PV power balance: %s\n", (z) ? "?" : "PV max current is charged current");
if(N == 26) printf("Max charging time @ CV stage: %s\n", (a) ? "?" : "automatically");
}
// 0 060 030 050 030 29.20 000 120 0 0000
// b c d e f g h i j k
static void equparsing(const char *str){
float g;
int b, c, d, e, f, h, i, j, k;
int N = sscanf(str, "%d %d %d %d %d %f%d %d %d %d", &b, &c, &d, &e, &f, &g, &h, &i, &j, &k);
if(N != 10){ WARNX("Not enougn parameters: got %d instead of 10\n", N); return; }
printf("Equalization: %s\n", endis(b));
printf("Eq. time: %d minutes\nEq. period: %d days\nEq. max current: %d\n", c, d, e);
printf("Eq. voltage: %g\nEq. over time: %d minutes\nEq. active status: %d\n", g, i, j);
}
static void printpar(const char *str){
printf("%s\n", str);
}
static void runparsing(const char *cmd, parsefn f){
if(sendcmd(cmd)){
char *got = rd();
if(got && *got){
f(got);
printf("\n");
} else red("Can't get data\n\n");
}
}
static void showstatus(){
green("\nModel name: "); runparsing("QMN", printpar);
if(S.all || S.rating) runparsing("QPIRI", ratingparsing);
if(S.all || S.flag) runparsing("QFLAG", flagparsing);
if(S.all || S.status) runparsing("QPIGS", statusparsing);
if(S.all || S.mode) runparsing("QMOD", modeparsing);
if(S.all || S.warning) runparsing("QPIWS", warningparsing);
if(S.all || S.deflt) runparsing("QDI", defaultparsing);
if(S.all || S.bateq) runparsing("QBEQI", equparsing);
}
static void showsettershelp(){
printf("\n\n");
red("Be carefull with setters! Think twice before changing something!!!\n\n");
printf("Here are setters...\n");
printf("PEx - enable status / DEx - disable status, where 'x':\n");
for(int i = 0; DEflags[i]; ++i) printf("\t%c - %s\n", DEflags[i], DEmeanings[i]);
printf("PF - set all control parameters to default\n");
printf("MCHGCx - max charging current (Amps)\n");
printf("MUCHGCx - utility max charging current\n");
printf("Fx - invertere output frequency\n");
printf("POPx - output source priority (0 - utility, 1 - solar, 2 - SBU)\n");
printf("PBCVx - battery re-charge voltage\n");
printf("PBDVx - battery re-discharge voltage\n");
printf("PCPx - inverter charging priority (0-3: utility first/solar first/solar+utility/solar only\n");
printf("PGRx - inverter grid voltage range (0 - appliance, 1 - UPS)\n");
printf("PBTx - battery type (0 - AGM, 1 - flooded)\n");
printf("PSDVx - battery cut-off voltage\n");
printf("PCVVx - CV (constant voltage) charging voltage\n");
printf("PBFTx - battery float charging voltage\n");
printf("PBEQEx - enable (1) or disable (0) battery equalization\n");
printf("PBEQT x - battery equalization time (minutes)\n");
printf("PBEQPx - battery equalization period (days)\n");
printf("PBEQVx - battery equalization voltage\n");
printf("PBEQOTx - battery equalization overtime (minutes)\n");
printf("PBEQAx - activate (1) or disactivate (0) battery equalization now\n");
printf("PCVTx - max charging time an CV stage\n");
printf("\n\nAnd some getters that aren't in `status` variants:\n");
printf("QID - inverter's serial\nQVFW - firmware version\nQMCHGCR - max charging currents available\n");
printf("QMUCHGCR - max utility charging currents available\n");
printf("\n\n");
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, opts);
if(G.help) sl_showhelp(-1, opts);
if(!G.path || G.baudrate < 1) ERRX("Need device path and baudrate");
dev = sl_tty_new(G.path, G.baudrate, 128);
if(!dev) ERR("Can't init serial device");
dev = sl_tty_open(dev, 1);
if(!dev) ERR("Can't open %s", G.path);
if(sl_tty_tmout(100000)) WARNX("Can't set timeout");
DBG("bufsz: %zd", dev->bufsz);
if(G.getstatus){
if(!sl_get_suboption(G.getstatus, sopts) || S.help){
gettershelp();
return 1;
}
showstatus();
}
if(G.customcmd){
green("Try to send '%s'\n", G.customcmd);
if(sendcmd(G.customcmd)){
char *got = rd();
if(got) printf("Get data: '%s'\n", got);
}
}
sl_tty_close(&dev);
if(G.settershelp) showsettershelp();
return 0;
}

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.30)
set(PROJ chkweather) set(PROJ chkweather)
set(MINOR_VERSION "1") set(MINOR_VERSION "1")
set(MID_VERSION "0") set(MID_VERSION "0")

View File

@@ -40,7 +40,7 @@ glob_pars const Gdefault = {
* Define command line options by filling structure: * Define command line options by filling structure:
* name has_arg flag val type argptr help * name has_arg flag val type argptr help
*/ */
static myoption cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
// set 1 to param despite of its repeating number: // set 1 to param despite of its repeating number:
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")},
{"speed", NEED_ARG, NULL, 's', arg_int, APTR(&G.speed), _("baudrate (default: 9600)")}, {"speed", NEED_ARG, NULL, 's', arg_int, APTR(&G.speed), _("baudrate (default: 9600)")},
@@ -59,10 +59,10 @@ static myoption cmdlnopts[] = {
glob_pars *parse_args(int argc, char **argv){ glob_pars *parse_args(int argc, char **argv){
void *ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); void *ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr);
// format of help: "Usage: progname [args]\n" // format of help: "Usage: progname [args]\n"
change_helpstring(_("Usage: %s [args]\n\n\tWhere args are:\n")); sl_helpstring(_("Usage: %s [args]\n\n\tWhere args are:\n"));
// parse arguments // parse arguments
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts); if(help) sl_showhelp(-1, cmdlnopts);
if(argc > 0){ if(argc > 0){
WARNX("Wrong arguments:\n"); WARNX("Wrong arguments:\n");
for(int i = 0; i < argc; i++) for(int i = 0; i < argc; i++)

View File

@@ -51,28 +51,28 @@ static int getpar(char *string, double *Val, char *Name){
int main(int argc, char **argv){ int main(int argc, char **argv){
glob_pars *G = NULL; // default parameters see in cmdlnopts.c glob_pars *G = NULL; // default parameters see in cmdlnopts.c
initial_setup(); sl_init();
G = parse_args(argc, argv); G = parse_args(argc, argv);
TTY_descr *dev = new_tty(G->ttyname, G->speed, 64); sl_tty_t *dev = sl_tty_new(G->ttyname, G->speed, 64);
if(!dev) return 1; if(!dev) return 1;
size_t got, L = 0; size_t got, L = 0;
char buff[BUFLEN], *ptr = buff; char buff[BUFLEN], *ptr = buff;
int errctr = 0; int errctr = 0;
for(; errctr < ERRCTR_MAX; ++errctr){ for(; errctr < ERRCTR_MAX; ++errctr){
if(!tty_open(dev, 1)){ if(!sl_tty_open(dev, 1)){
sleep(1); sleep(1);
continue; continue;
} }
while(read_tty(dev)); // clear buffer while(sl_tty_read(dev)); // clear buffer
if(write_tty(dev->comfd, "?U\r\n", 3)){ if(sl_tty_write(dev->comfd, "?U\r\n", 3)){
WARNX("write_tty()"); WARNX("write_tty()");
continue; continue;
} }
double t0 = dtime(); double t0 = sl_dtime();
while(dtime() - t0 < 10.){ // timeout - 10s while(sl_dtime() - t0 < 10.){ // timeout - 10s
got = read_tty(dev); got = sl_tty_read(dev);
if(got == 0) continue; if(got == 0) continue;
t0 = dtime(); t0 = sl_dtime();
if(L + got > BUFLEN - 1) break; if(L + got > BUFLEN - 1) break;
L += got; L += got;
buff[L] = 0; buff[L] = 0;
@@ -92,8 +92,8 @@ int main(int argc, char **argv){
continue; continue;
}else break; }else break;
} }
while(read_tty(dev)); while(sl_tty_read(dev));
close_tty(&dev); sl_tty_close(&dev);
if(errctr == ERRCTR_MAX){ if(errctr == ERRCTR_MAX){
ERRX("No connection to meteostation"); ERRX("No connection to meteostation");
} }

View File

@@ -4,7 +4,7 @@ A="90:00:00"
H="45:00:00" H="45:00:00"
function sendcmd(){ function sendcmd(){
echo $1 | nc 192.168.70.33 10001 -q10 echo $1 | nc localhost 10001 -q10
} }
sendcmd ":Sz${A}#" sendcmd ":Sz${A}#"

View File

@@ -0,0 +1,20 @@
#!/bin/bash
#
# $1 - scaling factor (in percents) of the image
#
scale=50
if [ $# -gt 0 ]; then
scale=$1
fi
unset http_proxy
clear
while [[ 1 ]]; do
tput cup 0 0
curl -s http://zarch.sao.ru/webcam/mirat_allsky.cgi | magick - -colors 256 +dither -normalize -resize $scale% sixel:-
# sleep 30s
sleep 5s
done

View File

@@ -0,0 +1,114 @@
#!/bin/bash
#
# $1 - scaling factor (in percents) of the image
#
function get_val {
echo $(echo $1 | cut -d "=" -f 2 | cut -d "." -f 1)
}
white_col="\e[97m"
red_col="\e[1m\e[31m"
end_col="\e[0m"
last_row=0
scale=50
if [ $# -gt 0 ]; then
scale=$1
fi
unset http_proxy
im_sleep=20 # in secs
info_sleep=180 # in secs
n_info=$((info_sleep/im_sleep))
clear
while [[ 1 ]]; do
# weather info
m_old=($(curl 192.168.70.33:12345 2>/dev/null)// / )
rain=${m_old[0]}
clouds=${m_old[1]}
temp=${m_old[2]}
m_new=($(curl localhost:3333/stat3600 2>/dev/null | sed 's/[\x01-\x1F\x7F]//g')// / )
windmax=${m_new[0]}
m_new=($(curl localhost:3333 2>/dev/null | sed 's/[\x01-\x1F\x7F]//g')// / )
wind=${m_new[0]}
humi=${m_new[4]}
rain_col=$white_col
clouds_col=$white_col
humi_col=$white_col
wind_col=$white_col
wind_max_col=$white_col
let rain_flag=$(get_val $rain)
if [[ $rain_flag -eq 1 ]]; then
rain_col=$red_col
fi
let clouds_val=$(get_val $clouds)
if [[ $clouds_val -le 1500 ]]; then
clouds_col=$red_col
fi
let humi_val=$(get_val $humi)
if [[ $humi_val -ge 90 ]]; then
humi_col=$red_col
fi
let wind_val=$(get_val $wind)
if [[ $wind_val -ge 10 ]]; then
wind_col=$red_col
fi
let wind_max_val=$(get_val $windmax)
if [[ $wind_max_val -ge 10 ]]; then
wind_max_col=$red_col
fi
ncols=`tput cols`
start_col=$((ncols-25))
tput cup $last_row $start_col
# echo -e "\e[4m`date`:\e[0m"
echo -e "\e[4m`date +'%F %T'`:\e[0m"
((++last_row))
tput cup $last_row $start_col
echo -e "$clouds_col$clouds $rain_col($rain)$end_col"
((++last_row))
tput cup $last_row $start_col
# echo -e "$temp $humi_col($humi)$end_col"
temp_val=`printf "%.1f" ${temp#*=}`
echo -e "Temp=$temp_val, ${humi_col}Hum=$humi_val%$end_col"
((++last_row))
tput cup $last_row $start_col
# echo -e "$wind_col$wind $wind_max_col($windmax @hour)$end_col\n"
wind_max_val=`printf "%.1f" ${windmax#*=}`
echo -e "$wind_col$wind $wind_max_col(Max=$wind_max_val/hour)$end_col\n"
IFS='[;' read -p $'\e[6n' -d R -rs _ last_row col _
# allsky image
for i in `seq $n_info`; do
tput cup 0 0
curl -s http://zarch.sao.ru/webcam/omea_allsky.cgi | magick - -colors 256 -normalize +dither -resize $scale% sixel:-
# curl -s http://zarch.sao.ru/webcam/mirat_allsky.cgi | magick - -colors 256 -resize $scale% sixel:-
sleep ${im_sleep}s
done
done

View File

@@ -0,0 +1,18 @@
#!/bin/bash
#
# $1 - directory to be copied
#
RDIR="/home/obs/robotel1_2025"
# copy non-FITS files
tar c --exclude='*.fit' --exclude='*shit*' $1 | ssh obs@roboserv "tar x -C $RDIR"
#tar c --exclude='*.fit' --exclude='*shit*' $1 | ssh obs@roboserv "tar x -C /home/obs/robotel1_2023"
# copy FITS files and XZ-ing it on remote server
CMD='sh -c "xz -6e -T0 - > $TAR_FILENAME.xz"'
tar c $1/*.fit | ssh obs@roboserv "cd $RDIR; tar x --to-command='$CMD'"
#tar c $1/*.fit | ssh obs@roboserv "cd /home/obs/robotel1_2023; tar x --to-command='$CMD'"
ssh obs@roboserv "cd $RDIR; tar c $1 | ssh data@robostorage 'tar x -C /mnt/ARCHIVE/ROBOTEL1/'"

View File

@@ -0,0 +1,33 @@
#!/bin/bash
A="01:48:38"
H="01:13:29"
function sendcmd(){
echo $1 | nc localhost 10001 -q10 || (echo "Can't connect" >&2; exit 1)
}
#lower limit is 0
sendcmd ":So0#"
sendcmd ":Sz${A}#"
sendcmd ":Sa${H}#"
sendcmd ":MA#" # || exit 1
errctr=0
while true; do
sleep 2
ANS=$(sendcmd ":Gstat#")
if [ "x$ANS" == "x" ]; then
[[ $((++errctr)) < 5 ]] && continue
echo "No connection to server" >&2
exit 1
fi
echo $ANS
[ $ANS == "0#" -o $ANS == "7#" ] && break
done
# stop tracking
sendcmd ":AL#"
echo -e "\n\nTelescope parked\n"

View File

@@ -0,0 +1,33 @@
#!/bin/bash
A="00:00:00"
H="2:00:00"
function sendcmd(){
echo $1 | nc localhost 10001 -q10 || (echo "Can't connect" >&2; exit 1)
}
#lower limit is 0
sendcmd ":So0#"
sendcmd ":Sz${A}#"
sendcmd ":Sa${H}#"
sendcmd ":MA#" # || exit 1
errctr=0
while true; do
sleep 2
ANS=$(sendcmd ":Gstat#")
if [ "x$ANS" == "x" ]; then
[[ $((++errctr)) < 5 ]] && continue
echo "No connection to server" >&2
exit 1
fi
echo $ANS
[ $ANS == "0#" -o $ANS == "7#" ] && break
done
# stop tracking
sendcmd ":AL#"
echo -e "\n\nTelescope parked\n"

View File

@@ -0,0 +1,165 @@
#!/bin/bash
RA="17:17:08.86"
DEC="+67:57:11.4"
OBJ="GALEX171708.5"
EXPTIME=31000
FLATTIME=40000
OBS="Fatkhullin T.A."
BADWEATHER=1300
DATEEND=$(sunrise 14)
# focus each N seconds
FOCUST=7200
FNO=1
FDATE=0
#
# NOTE: THIS IS A NEW VERSION OF THE OBSERVATION SCRIPT!
# CHAGLELOG:
# Oct 2024: replace focussing algorithm (T. Fatkhullin)
# At the night start the first focussing run
# uses of two-step algorithm:
# 1) rough focus astimation along full season-to-season
# focus range (as it was implemented in the old obs. script)
# 2) precise focussing along narrow range computed from
# previous rough estimation
# Next focussing runs compute range from the current focus value.
# (new function 'focustel_new')
#
# May 2025: rewrite detection of running this script process (T. Fatkhullin)
# (use of '-c' commandline option for 'pgrep' command)
function focustel_new(){
rm focus*.fit 2>/dev/null
echo "Focussing..."
let fno=$1
if [[ $fno -gt 1 ]]; then
let curr_foc=`fli_control | tail -n 5 | head -n 1 | cut -d "=" -f 2`
let lowf=$curr_foc-1500
let highf=$curr_foc+1500
focus_seq_FLI.py -v -c focus$(printf "%02d" $1).jpg $lowf $highf 500
if [[ $? -ne 0 ]]; then
echo -e "\nFOCUSSING SCRIPT RETURNED: $?"
echo -e "SOMETHING WAS WRONG IN FOCUSSING SEQUENCE!!! SET FOCUS TO PREVIOUS VALUE!!\n"
fli_control -g $curr_foc
fi
else
focus_seq_FLI.py --guess -v -N 7 -c focus$(printf "%02d" $1).jpg 47000 60000 500
fi
FDATE=$(date +%s)
}
function sendcmd(){
echo $1 | nc 192.168.70.33 10001 -q10
}
function point_tel(){
touch lastpointing
send_coords -r $1 -d $2
}
#c=$(pgrep run_full | wc -l)
# $ will run another run_full, so c=2 for single run
#if [[ $c -gt 1 ]]; then
# echo "Another process is running; exiting"
# exit 1
#fi
if [[ $(pgrep -c run_full) -gt 1 ]]; then
echo "Another process is running! Exit!"
exit 1
fi
export http_proxy=""
# set lower limit to 5degr
send_command2mount ":So5#"
echo "Time diff: $(($DATEEND-$(date +%s)))"
if [ $(($DATEEND-$(date +%s))) -lt 3600 ]; then
echo "There's less an hour for observation!"
exit 2
fi
if [ $(($DATEEND-$(date +%s))) -gt 53200 ]; then
echo "There's more than 12 hours till closing, check script data!"
exit 3
fi
#STARTobs
#send_coords
#echo "Wait a little"
#sleep 10
echo "GoTo object: ${RA} ${DEC}"
send_coords -r${RA} -d${DEC}
echo "Start taking object"
badweather=0
while true; do
now=$(date +%s)
ANS=$(curl localhost:55555/status 2>/dev/null)
echo "Dome status: $ANS"
if [ $ANS != "opened" ]; then
echo "Closed"
curl localhost:55555/weather 2>/dev/null > DomeClosed
break;
fi
chkweather ${BADWEATHER} > lastweather && badweather=0 || badweather=$((badweather+1))
[ $badweather -gt 5 ] && break
[ -f stopobs ] && break
[ -f exitjob ] && exit 0
if [ "$now" -lt "$DATEEND" ]; then
est=$(sendcmd ":Gmte#"|sed -e 's/^0*//' -e 's/#//')
echo -e "\n\n\n\n\nEstimated time to flip: $est minutes"
if [[ ("x$est" == "x") || ($est -lt 3) ]]; then
point_tel "${RA}" "${DEC}"
continue
fi
ST=$(send_coords | awk '{print $4}')
[ "x$ST" == "x" ] && break
if [ $ST -ne "0" ]; then
point_tel "${RA}" "${DEC}"
continue
else
[ $(($(date +%s) - $FDATE)) -gt $FOCUST ] && focustel_new $((FNO++))
# [ $(($(date +%s) - $FDATE)) -gt $FOCUST ] && focustel $((FNO++))
preflash
fli_control -r /tmp/10micron.fitsheader -x $EXPTIME -N "${OBS}" -O "${OBJ}" "$OBJ"
fi
else
break
fi
done
echo "Dome closed @ $(date)" >> closed
curl localhost:55555/close
relay_manage -s1
park_telescope
for x in $(seq 1 10); do
preflash
fli_control -r /tmp/10micron.fitsheader -x1 -N "${OBS}" -O "bias" -d bias
preflash
fli_control -r /tmp/10micron.fitsheader -x $EXPTIME -N "${OBS}" -O "dark" -d dark
preflash
fli_control -r /tmp/10micron.fitsheader -n3 -x $FLATTIME -N "${OBS}" -O "flat" flat
done
relay_manage -r1
echo "Closed @ $(date)" >> closed
STOPobs || true
DIR=$(basename $PWD)
echo "TAR: $DIR"
cd ..
./copy_and_xz.sh $DIR
echo "Archived, end"
echo "$DIR archived @ $(date)" >> archived

View File

@@ -0,0 +1,44 @@
#!/bin/bash
#
# The script creates working directory,
# copies 'run' script into it and
# edits observer name according to
# the its argument
#
# Working directory name is formed from
# date of the script running and is computed
# as follows:
# now - 12hours
# i.e. the day starts from 12h not from 0h!
#
# $1 - observer name
#
if [[ $# -eq 0 ]]; then
obs_name="Fatkhullin T.A."
else
obs_name=$1
fi
# now - 12h
let now12=`date +%s`-12*3600
# working directory
wdir=/DATA/FITS/`date -d @$now12 +%y.%m.%d`
echo -n "Creating working directory: $wdir ..."
if [[ -d $wdir ]]; then
echo -e "\tFAILED! The directory already exists! Exit!"
exit 1
else
echo -e "\tOK!"
mkdir $wdir
cd $wdir
fi
cp ../run_full.new_foc .
# replace observer name
sed -i "0,/^OBS=\".*\"/ s//OBS=\"${obs_name}\"/" run_full.new_foc
#sed -i "0,/^OBS=\"[a-zA-Z \.]*\"/ s//OBS=\"${obs_name}\"/" run_full.new_foc

View File

@@ -0,0 +1,3 @@
#!/bin/bash
mpv -vo sixel --really-quiet=yes "rtsp://viewer:view25@192.168.70.25:554/axis-media/media.amp?videocodec=h264&resolution=640x480"

View File

@@ -41,7 +41,7 @@ static glob_pars G = {
* Define command line options by filling structure: * Define command line options by filling structure:
* name has_arg flag val type argptr help * name has_arg flag val type argptr help
*/ */
static myoption cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
// common options // common options
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
{"delimeter",NEED_ARG, NULL, 'd', arg_string, APTR(&G.delimeter), "coordinates delimeter string (default: ':')"}, {"delimeter",NEED_ARG, NULL, 'd', arg_string, APTR(&G.delimeter), "coordinates delimeter string (default: ':')"},
@@ -71,10 +71,10 @@ glob_pars *parse_args(int argc, char **argv){
char helpstring[1024], *hptr = helpstring; char helpstring[1024], *hptr = helpstring;
snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n");
// format of help: "Usage: progname [args]\n" // format of help: "Usage: progname [args]\n"
change_helpstring(helpstring); sl_helpstring(helpstring);
// parse arguments // parse arguments
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts); if(help) sl_showhelp(-1, cmdlnopts);
if(argc > 0){ if(argc > 0){
G.rest_pars_num = argc; G.rest_pars_num = argc;
G.rest_pars = MALLOC(char *, argc); G.rest_pars = MALLOC(char *, argc);

View File

@@ -100,7 +100,7 @@ static void savepoints(FILE *f, point *pts, int N, char *delim, int mask){
} }
int main(int argc, char **argv){ int main(int argc, char **argv){
initial_setup(); sl_init();
glob_pars *G = parse_args(argc, argv); glob_pars *G = parse_args(argc, argv);
FILE *f = NULL; FILE *f = NULL;
if(G->outfile){ if(G->outfile){

View File

@@ -32,7 +32,7 @@ typedef struct{
static glob_pars G = {.tolerance = 10.}; static glob_pars G = {.tolerance = 10.};
static myoption cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
// common options // common options
{"help", NO_ARGS, NULL, 'h', arg_none, APTR(&G.help), _("show this help")}, {"help", NO_ARGS, NULL, 'h', arg_none, APTR(&G.help), _("show this help")},
{"infile", NEED_ARG, NULL, 'i', arg_string, APTR(&G.input), _("input file name")}, {"infile", NEED_ARG, NULL, 'i', arg_string, APTR(&G.input), _("input file name")},
@@ -158,11 +158,11 @@ static double calcfocus(double coeffs[3]){
int main(int argc, char **argv){ int main(int argc, char **argv){
char helpstring[256]; char helpstring[256];
initial_setup(); sl_init();
snprintf(helpstring, 255, "Usage: `cat file | %%s` or with args; file format \"x y\\n..\"\n\tArgs:\n"); snprintf(helpstring, 255, "Usage: `cat file | %%s` or with args; file format \"x y\\n..\"\n\tArgs:\n");
change_helpstring(helpstring); sl_helpstring(helpstring);
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) showhelp(-1, cmdlnopts); if(G.help) sl_showhelp(-1, cmdlnopts);
if(G.tolerance <= 0.) ERRX("Tolerance should be > 0"); if(G.tolerance <= 0.) ERRX("Tolerance should be > 0");
FILE *f = stdin; FILE *f = stdin;
if(G.input){ if(G.input){

View File

@@ -36,7 +36,7 @@ static const char *radtodeg(double r){
int main(){ int main(){
initial_setup(); sl_init();
at_MJD_t mjd; at_MJD_t mjd;
if(!at_get_MJDu(time(NULL), &mjd)) ERRX("at_get_MJDu"); if(!at_get_MJDu(time(NULL), &mjd)) ERRX("at_get_MJDu");
printf("MJD=%g; TAI=%g/%g, TT=%g/%g, UTC=%g/%g\n", mjd.MJD, mjd.tai1, mjd.tai2, mjd.tt1, mjd.tt2, mjd.utc1, mjd.utc2); printf("MJD=%g; TAI=%g/%g, TT=%g/%g, UTC=%g/%g\n", mjd.MJD, mjd.tai1, mjd.tai2, mjd.tt1, mjd.tt2, mjd.utc1, mjd.utc2);

View File

@@ -56,7 +56,7 @@ static parameters G = {
.py = -10000. .py = -10000.
}; };
static myoption cmdlnopts[] = { static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"obsplace", NO_ARGS, NULL, 'O', arg_int, APTR(&G.obsplace), "input RA/Dec is observed place"}, {"obsplace", NO_ARGS, NULL, 'O', arg_int, APTR(&G.obsplace), "input RA/Dec is observed place"},
{"JD", NEED_ARG, NULL, 'J', arg_double, APTR(&G.JD), "Julian date"}, {"JD", NEED_ARG, NULL, 'J', arg_double, APTR(&G.JD), "Julian date"},
@@ -77,9 +77,9 @@ static myoption cmdlnopts[] = {
int main(int argc, char **argv){ int main(int argc, char **argv){
initial_setup(); sl_init();
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) showhelp(-1, cmdlnopts); if(G.help) sl_showhelp(-1, cmdlnopts);
at_MJD_t MJD; at_MJD_t MJD;
G.ra *= ERFA_DD2R; G.ra *= ERFA_DD2R;
G.dec *= ERFA_DD2R; G.dec *= ERFA_DD2R;

View File

@@ -226,6 +226,7 @@ int proc_data(uint8_t *data, ssize_t len){
// convert RA/DEC to hours/degrees // convert RA/DEC to hours/degrees
double tagRA = RA2HRS(ra), tagDec = DEC2DEG(dec); double tagRA = RA2HRS(ra), tagDec = DEC2DEG(dec);
DBG("RA: %u (%g), DEC: %d (%g)", ra, tagRA, dec, tagDec); DBG("RA: %u (%g), DEC: %d (%g)", ra, tagRA, dec, tagDec);
putlog("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec);
// check RA/DEC // check RA/DEC
horizCrds hnow; // without refraction horizCrds hnow; // without refraction
polarCrds p2000, pnow; polarCrds p2000, pnow;
@@ -236,7 +237,7 @@ int proc_data(uint8_t *data, ssize_t len){
WARNX("Can't convert coordinates to Jnow"); WARNX("Can't convert coordinates to Jnow");
return 0; return 0;
} }
#ifdef EBUG /*
int i[4], j[4]; char pm, pm1; int i[4], j[4]; char pm, pm1;
eraA2af(2, hnow.az, &pm, i); eraA2af(2, hnow.az, &pm, i);
eraA2af(2, hnow.zd, &pm1, j); eraA2af(2, hnow.zd, &pm1, j);
@@ -245,10 +246,10 @@ int proc_data(uint8_t *data, ssize_t len){
pm1,j[0],j[1],j[2],j[3]); pm1,j[0],j[1],j[2],j[3]);
eraA2af(2, M_PI_2 - hnow.zd, &pm, i); 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]); DBG("h: %c%02d %02d %02d.%2.d", pm, i[0],i[1],i[2],i[3]);
#endif */
if(hnow.zd > 80.*ERFA_DD2R){ if(hnow.zd > 80.*ERFA_DD2R){
WARNX("Z > 80degr, stop telescope"); WARNX("Z > 80degr (%g), stop telescope", hnow.zd * ERFA_DR2D);
putlog("Z>80 - stop!"); putlog("Z=%.1f > 80 - stop!", hnow.zd * ERFA_DR2D);
stop_telescope(); stop_telescope();
return 0; return 0;
} }

View File

@@ -8,3 +8,5 @@ Different daemons & tools
- *netsocket* - scripts for management of network 220V-socket - *netsocket* - scripts for management of network 220V-socket
- *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon - *send_coordinates* - get/send coordinates to 10-micron mount through stellarium daemon
- *teldaemon* - open/close Astrosib-500 scope covers by network query - *teldaemon* - open/close Astrosib-500 scope covers by network query
- *weatherdaemon* - weather daemon for old meteostation
- *weatherdaemon_newmeteo* - daemon for new (chinese) meteostation

View File

@@ -180,9 +180,10 @@ static void toomuch(int fd){
LOGWARN("Client fd=%d tried to connect after MAX reached", fd); LOGWARN("Client fd=%d tried to connect after MAX reached", fd);
} }
// new connections handler // new connections handler
static void connected(sl_sock_t *c){ static int connected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd); if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd);
else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP); else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP);
return TRUE;
} }
// disconnected handler // disconnected handler
static void disconnected(sl_sock_t *c){ static void disconnected(sl_sock_t *c){
@@ -196,12 +197,12 @@ void server_run(sl_socktype_e type, const char *node, sl_tty_t *serial){
ERRX("server_run(): wrong parameters"); ERRX("server_run(): wrong parameters");
} }
dome_serialdev(serial); dome_serialdev(serial);
sl_sock_changemaxclients(5);
sl_sock_maxclhandler(toomuch);
sl_sock_connhandler(connected);
sl_sock_dischandler(disconnected);
s = sl_sock_run_server(type, node, -1, handlers); s = sl_sock_run_server(type, node, -1, handlers);
if(!s) ERRX("Can't create socket and/or run threads"); if(!s) ERRX("Can't create socket and/or run threads");
sl_sock_changemaxclients(s, 5);
sl_sock_maxclhandler(s, toomuch);
sl_sock_connhandler(s, connected);
sl_sock_dischandler(s, disconnected);
while(s && s->connected){ while(s && s->connected){
if(!s->rthread){ if(!s->rthread){
LOGERR("Server handlers thread is dead"); LOGERR("Server handlers thread is dead");

View File

@@ -54,7 +54,7 @@ glob_pars const Gdefault = {
* Define command line options by filling structure: * Define command line options by filling structure:
* name has_arg flag val type argptr help * name has_arg flag val type argptr help
*/ */
myoption cmdlnopts[] = { sl_option_t cmdlnopts[] = {
// common options // common options
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")},
{"device", NEED_ARG, NULL, 'i', arg_string, APTR(&G.device), _("serial device name (default: none)")}, {"device", NEED_ARG, NULL, 'i', arg_string, APTR(&G.device), _("serial device name (default: none)")},
@@ -79,10 +79,10 @@ glob_pars *parse_args(int argc, char **argv){
void *ptr; void *ptr;
ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr);
// format of help: "Usage: progname [args]\n" // format of help: "Usage: progname [args]\n"
change_helpstring("Usage: %s [args]\n\n\tWhere args are:\n"); sl_helpstring("Usage: %s [args]\n\n\tWhere args are:\n");
// parse arguments // parse arguments
parseargs(&argc, &argv, cmdlnopts); sl_parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts); if(help) sl_showhelp(-1, cmdlnopts);
if(argc > 0){ if(argc > 0){
G.rest_pars_num = argc; G.rest_pars_num = argc;
G.rest_pars = calloc(argc, sizeof(char*)); G.rest_pars = calloc(argc, sizeof(char*));

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