diff --git a/Daemons/10micron_stellarium/CMakeLists.txt b/Daemons/10micron_stellarium/CMakeLists.txt new file mode 100644 index 0000000..45b7019 --- /dev/null +++ b/Daemons/10micron_stellarium/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 4.0) + +set(PROJ mountdaemon_10micron) +project(${PROJ}) + +set(MAJOR_VERSION "0") +set(MID_VERSION "1") +set(MINOR_VERSION "1") +set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") + +enable_language(C) +message("VER: ${VERSION}") + +# options +option(DEBUG "Compile in debug mode" OFF) + +# default flags +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -W -Wextra -std=c23") +if(DEBUG) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -fno-builtin-strlen -Werror") + add_definitions(-DEBUG) + set(CMAKE_BUILD_TYPE DEBUG) + set(CMAKE_VERBOSE_MAKEFILE "ON") +else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto=auto") + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto=auto") + set(CMAKE_BUILD_TYPE RELEASE) +endif() + +message("Build type: ${CMAKE_BUILD_TYPE}") + +set(CMAKE_COLOR_MAKEFILE ON) + +# here is one of two variants: all .c in directory or .c files in list +aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES) + +###### pkgconfig ###### +find_package(PkgConfig REQUIRED) +pkg_check_modules(MODULES REQUIRED usefull_macros>=0.3.2 erfa>=2.0.0) + +# change wrong behaviour with install prefix +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT AND CMAKE_INSTALL_PREFIX MATCHES "/usr/local") +else() + message("Change default install path to /usr/local") + set(CMAKE_INSTALL_PREFIX "/usr/local") +endif() +message("Install dir prefix: ${CMAKE_INSTALL_PREFIX}") + +# executable file +add_executable(${PROJ} ${SOURCES}) +# -I +target_include_directories(${PROJ} PUBLIC ${MODULES_INCLUDE_DIRS}) +# -L +target_link_directories(${PROJ} PUBLIC ${MODULES_LIBRARY_DIRS}) +# -l +target_link_libraries(${PROJ} ${MODULES_LIBRARIES} -lweather -lm) +# -D +add_definitions(-D_XOPEN_SOURCE=1234 -D_DEFAULT_SOURCE -D_GNU_SOURCE + -DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" + -DMID_VERSION=\"${MID_VERSION}\" -DMAJOR_VERSION=\"${MAJOR_VESION}\" + ) + +# Installation of the program +INSTALL(TARGETS ${PROJ} DESTINATION "bin") diff --git a/Daemons/10micron_stellarium/angles.c b/Daemons/10micron_stellarium/angles.c new file mode 100644 index 0000000..043757c --- /dev/null +++ b/Daemons/10micron_stellarium/angles.c @@ -0,0 +1,343 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#include +#include +#include +#include +#include + +#include "angles.h" + +static placeData_t place = {.slong = 0.7232763200, .slat = 0.7618977414, .salt = 2070.}; +static almDut_t AlmDut = {0}; + +// set/get polar/DUT1 data +bool setDUT(almDut_t *D){ + if(!D){ + WARNX("setDUT(): no args"); + return false; + } + if(fabs(D->DUT1) > 1.){ + WARNX("setDUT(): DUT1 too large (%g)", D->DUT1); + return false; + } + if(fabs(D->px) > 1000. || fabs(D->py) > 1000.){ + WARNX("setDUT(): bad polar coordinates (%g, %g)", D->px, D->py); + return false; + } + AlmDut = *D; + return true; +} + +void getDUT(almDut_t *D){ + if(!D) return; + *D = AlmDut; +} + + +/** + * @brief setPlaceData - correct place data to given values + * @param longitude - degrees, (-180, 180], minus to west + * @param latitude - degrees, [-90, 90], minus to south + * @param altitude - meters + * @return false if some of data wrong + */ +bool setPlaceData(double longitude, double latitude, double altitude){ + if(longitude <= -180. || longitude > 180.){ + WARNX("setPlaceData(): bad longitude (%g)", longitude); + return false; + } + if(latitude < -90. || latitude > 90.){ + WARNX("setPlaceData(): bad latitude (%g)", latitude); + return false; + } + if(altitude < -500. || altitude > 9000.){ + WARNX("setPlaceData(): bad altitude (%g)", altitude); + return false; + } + place.salt = altitude; + place.slat = latitude; + place.slong = longitude; + return true; +} + +void getPlaceData(placeData_t *pd){ + if(!pd) return; + *pd = place; +} + +/** + * convert RA/DEC to string in forman RA: HH:MM:SS.SS, DEC: DD:MM:SS.S + */ +char *radec2str(double ra, double dec, char buf[RADEC_STR_MAXLEN]){ + char sign = '+'; + if(dec < 0){ + sign = '-'; + dec = -dec; + } + + int h = (int)ra; + ra -= h; ra *= 60.; + int m = (int)ra; + ra -= m; ra *= 60.; + + int d = (int) dec; + dec -= d; dec *= 60.; + int dm = (int)dec; + dec -= dm; dec *= 60.; + snprintf(buf, RADEC_STR_MAXLEN, "%d:%d:%.2f %c%d:%d:%.1f", h,m,ra, sign,d,dm,dec); + buf[RADEC_STR_MAXLEN-1] = 0; + return buf; +} + +// normalize RA/DEC to [0..24) for RA and [-90, 90] for DEC +void norm_RA(double *ra){ + if(!ra) return; + if(*ra >= 0. && *ra < 24.) return; + double r = fmod(*ra, 24.); + if(r < 0.) r += 24.; + *ra = r; +} +void norm_RADEC(double *ra, double *dec){ + if(!ra || !dec) return; + if(*dec >= -90. && *dec <= 90.){ // need only check RA + norm_RA(ra); + return; + } + // 1: convert to (-180..+180) + norm_angle180(dec); + // 2: fix dec & ra together + if(*dec > 90.) *dec = 180. - *dec; + else *dec = -180. - *dec; + *ra += 12.; + norm_RA(ra); +} +// normalize angle to (-180, 180] +void norm_angle180(double *a){ + if(!a) return; + double d = fmod(*a + 180.0, 360.0); + if(d < 0.) d += 360.0; + else d -= 180; + *a = d; +} + +/** + * @brief hor2eq - convert horizontal coordinates to polar + * @param h (i) - horizontal coordinates + * @param pc (o) - polar coordinates + * @param sidTime - sidereal time + */ +void hor2eq(horizCrds_t *h, polarCrds_t *pc, double sidTime){ + if(!h || !pc) return; + eraAe2hd(h->az, ERFA_DPI/2. - h->zd, place.slat, &pc->ha, &pc->dec); // A,H -> HA,DEC; phi - site latitude + pc->ra = sidTime - pc->ha; + pc->eo = 0.; +} + +/** + * @brief eq2horH - convert polar coordinates to horizontal + * @param pc (i) - polar coordinates (only HA used) + * @param h (o) - horizontal coordinates + * @param sidTime - sidereal time + */ +void eq2horH(polarCrds_t *pc, horizCrds_t *h){ + if(!h || !pc) return; + double alt; + eraHd2ae(pc->ha, pc->dec, place.slat, &h->az, &alt); + h->zd = ERFA_DPI/2. - alt; +} + +/** + * @brief eq2hor - convert polar coordinates to horizontal + * @param pc (i) - polar coordinates (only RA used) + * @param h (o) - horizontal coordinates + * @param sidTime - sidereal time + */ +void eq2hor(polarCrds_t *pc, horizCrds_t *h, double sidTime){ + if(!h || !pc) return; + double ha = sidTime - pc->ra + pc->eo; + double alt; + eraHd2ae(ha, pc->dec, place.slat, &h->az, &alt); + h->zd = ERFA_DPI/2. - alt; +} + +/** + * @brief r2sHMS - convert angle in radians into string "'HH:MM:SS.SS'" + * @param radians - angle + * @param hms (o) - string + * @param len - length of hms + */ +void r2sHMS(double radians, char *hms, int len){ + char pm; + int i[4]; + eraA2tf(2, radians, &pm, i); + snprintf(hms, len, "'%c%02d:%02d:%02d.%02d'", pm, i[0],i[1],i[2],i[3]); +} + +/** + * @brief r2sDMS - convert angle in radians into string "'DD:MM:SS.S'" + * @param radians - angle + * @param dms (o) - string + * @param len - length of hms + */ +void r2sDMS(double radians, char *dms, int len){ + char pm; + int i[4]; + eraA2af(1, radians, &pm, i); + snprintf(dms, len, "'%c%02d:%02d:%02d.%d'", pm, i[0],i[1],i[2],i[3]); +} + +/** + * @brief get_MJDt - calculate MJD of date from argument + * @param tval (i) - given date (or NULL for current) + * @param MJD (o) - time (or NULL just to check) + * @return true if all OK + */ +bool get_MJDt(struct timeval *tval, sMJD_t *MJD){ + struct tm tms; + double tSeconds; + if(!MJD) return false; + if(!tval){ + struct timespec ts; + if(clock_gettime(CLOCK_REALTIME, &ts)){ + WARN("clock_gettime()"); + return false; + } + gmtime_r(&ts.tv_sec, &tms); + tSeconds = tms.tm_sec + ((double)ts.tv_nsec)/1e9; + }else{ + gmtime_r(&tval->tv_sec, &tms); + tSeconds = tms.tm_sec + ((double)tval->tv_usec)/1e6; + } + int y, m, d; + y = 1900 + tms.tm_year; + m = tms.tm_mon + 1; + d = tms.tm_mday; + double utc1, utc2; + /* UTC date. */ + if(eraDtf2d("UTC", y, m, d, tms.tm_hour, tms.tm_min, tSeconds, &utc1, &utc2) < 0) return false; + if(!MJD) return false; + MJD->MJD = utc1 - ERFA_DJM0 + utc2; + MJD->utc1 = utc1; + MJD->utc2 = utc2; + //DBG("UTC(m): %g, %.8f\n", utc1 - 2400000.5, utc2); + if(eraUtctai(utc1, utc2, &MJD->tai1, &MJD->tai2)) return false; + //DBG("TAI"); + if(eraTaitt(MJD->tai1, MJD->tai2, &MJD->tt1, &MJD->tt2)) return false; + //DBG("TT"); + return true; +} + +/** + * @brief get_LST - calculate local siderial time + * @param mjd (i) - date/time for LST (utc1 & tt used) + * @param dUT1 - (UT1-UTC) + * @param slong - site longitude (radians) + * @param LST (o) - local sidereal time (radians) + * @return true if all OK + */ +bool get_LST(sMJD_t *mjd, double dUT1, double slong, double *LST){ + double ut11, ut12; + sMJD_t Mjd; + if(!LST) return false; + if(!mjd){ + if(!get_MJDt(NULL, &Mjd)) return false; + }else Mjd = *mjd; + if(eraUtcut1(Mjd.utc1, Mjd.utc2, dUT1, &ut11, &ut12)) return false; + double ST = eraGst06a(ut11, ut12, Mjd.tt1, Mjd.tt2); + ST += slong; + if(ST > ERFA_D2PI) ST -= ERFA_D2PI; + else if(ST < 0.) ST += ERFA_D2PI; + *LST = ST; + return 0; +} + +/** + * @brief get_ObsPlace - calculate observed place (without PM etc) for given date @550nm + * @param tval (i) - time + * @param p2000 (i) - polar coordinates for J2000 (only ra/dec used), ICRS (catalog) + * @param pnow (o) - polar coordinates for given epoch (or NULL) + * @param hnow (o) - horizontal coordinates for given epoch (or NULL) + * @return true if all OK + */ +bool get_ObsPlace(struct timeval *tval, polarCrds_t *p2000, polarCrds_t *pnow, horizCrds_t *hnow){ + double pr = 0.0; // RA proper motion (radians/year; Note 2) + double pd = 0.0; // Dec proper motion (radians/year) + double px = 0.0; // parallax (arcsec) + double rv = 0.0; // radial velocity (km/s, positive if receding) + sMJD_t MJD; + if(!tval || !p2000) return false; + if(get_MJDt(tval, &MJD)) return false; + /* Effective wavelength (microns) */ + double wl = 0.55; + /* ICRS to observed. */ + double aob, zob, hob, dob, rob, eo; + double p = 1000., t = 0., h = place.salt; + weather_data_t weath; + if(get_weather_data(&weath)){ + p = 1.3332239 * weath.pressure; + t = weath.exttemp; + }else WARNX("Can't get weather - use default values"); + if(eraAtco13(p2000->ra, p2000->dec, + pr, pd, px, rv, + MJD.utc1, MJD.utc2, + AlmDut.DUT1, + place.slong, place.slat, place.salt, + AlmDut.px, AlmDut.py, + p, t, h, + wl, + &aob, &zob, + &hob, &dob, &rob, &eo)) return false; + if(pnow){ + pnow->eo = eo; + pnow->ha = hob; + pnow->ra = rob; + pnow->dec = dob; + } + if(hnow){ + hnow->az = aob; + hnow->zd = zob; + } + return true; +} + + +#if 0 +typedef enum { + WEATHER_GOOD = 0, // may start observations + WEATHER_BAD = 1, // cannot start but can continue if want + WEATHER_TERRIBLE = 2, // close & park: wind, precipitation, humidity etc. + WEATHER_PROHIBITED = 3, // force closing & parking; power off equipment, ready to power off computer +} weather_condition_t; + +typedef struct { + weather_condition_t weather; // conditions: field "WEATHER" + float windmax; // maximal wind for last hour, m/s: "WINDMAX1" + float wind; // current wind speed, m/s: "WIND" + float clouds; // sky "quality" (>2500 - OK): "CLOUDS" + float exttemp; // external temperature, degC: "EXTTEMP" + float pressure; // atm. pressure, mmHg: "PRESSURE" + float humidity; // humidity, percents: "HUMIDITY" + int rain; // ==1 when rainy: "PRECIP" + int forceoff; // force power off (AC power lost or lightning) + time_t last_update; // value of "TMEAS" +} weather_data_t; + +int get_weather_data(weather_data_t *data); +#endif diff --git a/Daemons/10micron_stellarium/angles.h b/Daemons/10micron_stellarium/angles.h new file mode 100644 index 0000000..daa5c98 --- /dev/null +++ b/Daemons/10micron_stellarium/angles.h @@ -0,0 +1,89 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once + +#include +#include + +#define RADEC_STR_MAXLEN 72 + +#define RAD2DEG(angle) (angle * ERFA_DR2D) +#define DEG2RAD(angle) (angle * ERFA_DD2R) +#define RAD2HRS(angle) (angle * 24. / ERFA_DPI) +#define HRS2RAD(hour) (hour * ERFA_DPI / 24.) + +typedef struct{ + double utc1; double utc2; // UTC JD, commonly used MJD = utc1+utc2-2400000.5 + double MJD; + double tai1; double tai2; // TAI JD + double tt1; double tt2; // TT JD +} sMJD_t; + +// polar coordinates & equation of origins (all in radians) +typedef struct{ + double ha; // hour angle + double dec; // declination + double ra; // right ascension + double eo; // equation of origins +} polarCrds_t; + +// horizontal coordinates (all in radians) +typedef struct{ + double az; // azimuth, 0 @ south, positive clockwise + double zd; // zenith distance +} horizCrds_t; + +// observational place coordinates and altitude; all coordinates are in radians! +typedef struct{ + double slong; // longitude, radians + double slat; // lattitude, radians + double salt; // altitude, m +} placeData_t; + +// DUT/polar almanach data +// TODO: add function to read this data from almanach +typedef struct{ + double DUT1; // UT1-UTC, sec + double px; // polar coordinates, arcsec + double py; +} almDut_t; + +char *radec2str(double ra, double dec, char buf[RADEC_STR_MAXLEN]); +void norm_RA(double *ra); +void norm_RADEC(double *ra, double *dec); +void norm_angle180(double *a); + +void hor2eq(horizCrds_t *h, polarCrds_t *pc, double sidTime); +void eq2horH(polarCrds_t *pc, horizCrds_t *h); +void eq2hor(polarCrds_t *pc, horizCrds_t *h, double sidTime); + +void r2sHMS(double radians, char *hms, int len); +void r2sDMS(double radians, char *hms, int len); +void hor2eq(horizCrds_t *h, polarCrds_t *pc, double sidTime); +void eq2horH(polarCrds_t *pc, horizCrds_t *h); +void eq2hor(polarCrds_t *pc, horizCrds_t *h, double sidTime); +bool get_MJDt(struct timeval *tval, sMJD_t *MJD); +bool get_LST(sMJD_t *mjd, double dUT1, double slong, double *LST); +bool get_ObsPlace(struct timeval *tval, polarCrds_t *p2000, polarCrds_t *pnow, horizCrds_t *hnow); + +bool setDUT(almDut_t *D); +void getDUT(almDut_t *D); + +bool setPlaceData(double longitude, double latitude, double altitude); +void getPlaceData(placeData_t *pd); diff --git a/Daemons/10micron_stellarium/args.c b/Daemons/10micron_stellarium/args.c new file mode 100644 index 0000000..4e5529a --- /dev/null +++ b/Daemons/10micron_stellarium/args.c @@ -0,0 +1,140 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#include +#include + +#include "args.h" +#include "server.h" + +#define DEFAULT_COMDEV "/dev/ttyUSB0" +// TCP socket port +#define DEFAULT_PORT ":10000" +#define DEFAULT_CMDNODE "localhost:10001" +// default PID filename: +#define DEFAULT_PIDFILE "/tmp/mountdaemon_10micron.pid" +// default file with headers +#define DEFAULT_FITSHDR "/tmp/10micron.fitsheader" +// baudrate - 115200 +#define DEFAULT_SERSPEED 115200 +// serial polling timeout - 100ms +#define DEFAULT_SERTMOUT 100000 +// mount name +#define DEFAULT_MOUNT_NAME "10Micron GM4000HPS" + +// non-config file options +static int help = 0; +static char *conffile = NULL; + +// default command line options +static parameters_t G = {0}; +// default config options +static parameters_t Conf = {0}; + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +#define OPTIONS(Stor) \ + {"device", NEED_ARG, NULL, 'd', arg_string, APTR(&Stor.device), "serial device name"}, \ + {"emulation",NO_ARGS, NULL, 'e', arg_int, APTR(&Stor.emulation), "run in emulation mode"}, \ + {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&Stor.logfile), "file to save logs"}, \ + {"hdrfile", NEED_ARG, NULL, 'o', arg_string, APTR(&Stor.crdsfile), "file to save FITS-header with coordinates and time (default: " DEFAULT_FITSHDR ")"}, \ + {"pidfile", NEED_ARG, NULL, 0, arg_string, APTR(&Stor.pidfile), "pidfile (default: " DEFAULT_PIDFILE ")"}, \ + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&Stor.port), "port for stellaruim's connect (default: " DEFAULT_PORT ")"}, \ + {"cmdport", NEED_ARG, NULL, 'P', arg_string, APTR(&Stor.cmdnode), "port or UNIX-socket path to connect for command console (default: " DEFAULT_CMDNODE ")"}, \ + {"sleept", NEED_ARG, NULL, 't', arg_int, APTR(&Stor.sleept), "time of servers' sleeping in main cycle, us (default: " STR(DEFAULT_SLEEP_T) " us)"}, \ + {"isunix", NO_ARGS, NULL, 'U', arg_int, APTR(&Stor.isunix), "use UNIX-socket for cmdport"}, \ + {"sertmout",NEED_ARG, NULL, 'T', arg_int, APTR(&Stor.sertmout), "serial timeout, us (default: " STR(DEFAULT_SERTMOUT) " us)"}, \ + {"serspeed",NEED_ARG, NULL, 'S', arg_int, APTR(&Stor.serspeed), "serial speed (default: " STR(DEFAULT_SERSPEED) ")"}, \ + {"maxclients",NEED_ARG, NULL, 0, arg_int, APTR(&Stor.maxclients),"max amount of clients connected to one socket (default: " STR(DEFAULT_MAXCLIENTS) ")"}, \ + {"mountname",NEED_ARG, NULL, 0, arg_string, APTR(&Stor.mountname), "mount name (default: " DEFAULT_MOUNT_NAME ")"}, \ + {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&Stor.verbose), "verbose level (each -v increases it)"}, \ + + +static sl_option_t cmdlnopts[] = { + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"}, + {"config", NEED_ARG, NULL, 'c', arg_string, APTR(&conffile), "configuration file (command line options are in advantage)"}, + OPTIONS(G) + end_option +}; + +// --help and --conf - options that should be parsed first +static sl_option_t confopts[] = { + OPTIONS(Conf) + end_option +}; + +static void chkstr(char **conf, char **cmd, const char *dflt){ + if(!*conf){ + if(!*cmd && dflt){ + DBG("Set default parameter: %s", dflt); + *cmd = strdup(dflt); + } + return; + } + if(!*cmd){ + DBG("Conf-only parameter %s", *conf); + *cmd = strdup(*conf); + } + FREE(*conf); // don't forget to clear +} + +static void chkint(int *conf, int *cmd, int dflt){ + if(!*conf){ + if(!*cmd) *cmd = dflt; + return; + } + if(!*cmd){ + DBG("Conf-only parameter %d", *conf); + *cmd = *conf; + } +} + + +#define STRCHK(field) chkstr(&Conf.field, &G.field, NULL) +#define STRCHKD(field, def) chkstr(&Conf.field, &G.field, def) +#define INTCHK(field) chkint(&Conf.field, &G.field, 0) +#define INTCHKD(field, def) chkint(&Conf.field, &G.field, def) + +parameters_t *parse_cmdline(int *argc, char ***argv){ + sl_parseargs(argc, argv, cmdlnopts); + if(help) sl_showhelp(-1, cmdlnopts); + if(!conffile) return &G; + // fix for conffile + if(!sl_conf_readopts(conffile, confopts)){ // show conf help + sl_conf_showhelp(-1, confopts); + return NULL; + } + // now fix for command line and check all: + DBG("Check all args"); + STRCHKD(device, DEFAULT_COMDEV); + STRCHKD(port, DEFAULT_PORT); + STRCHKD(cmdnode, DEFAULT_CMDNODE); + STRCHKD(pidfile, DEFAULT_PIDFILE); + STRCHK(logfile); + STRCHKD(crdsfile, DEFAULT_FITSHDR); + STRCHKD(mountname, DEFAULT_MOUNT_NAME); + INTCHK(emulation); + INTCHK(verbose); + INTCHKD(sleept, DEFAULT_SLEEP_T); + INTCHK(isunix); + INTCHKD(sertmout, DEFAULT_SERTMOUT); + INTCHKD(serspeed, DEFAULT_SERSPEED); + INTCHKD(maxclients, DEFAULT_MAXCLIENTS); + return &G; +} diff --git a/Daemons/10micron_stellarium/args.h b/Daemons/10micron_stellarium/args.h new file mode 100644 index 0000000..2f59a05 --- /dev/null +++ b/Daemons/10micron_stellarium/args.h @@ -0,0 +1,38 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once + +typedef struct{ + char *device; // serial device name + char *port; // port to connect + char *cmdnode; // port for commands sending + char *pidfile; // name of PID file + char *logfile; // logging to this file + char *crdsfile; // file where FITS-header should be written + char *mountname; // set mount name for FITS-header + int emulation; // run in emulation mode + int verbose; // verbose level + int sleept; // server's sleeping in main cycle time + int isunix; // use UNIX-socket for command port + int sertmout; // serial timeout, us + int serspeed; // serial speed, baud + int maxclients; // max amount of clients connected to one socket +} parameters_t; + +parameters_t *parse_cmdline(int *argc, char ***argv); diff --git a/Daemons/10micron_stellarium/emulation.c b/Daemons/10micron_stellarium/emulation.c index 81ebc1f..edc2b01 100644 --- a/Daemons/10micron_stellarium/emulation.c +++ b/Daemons/10micron_stellarium/emulation.c @@ -1,12 +1,10 @@ /* - * geany_encoding=koi8-r - * emulation.c + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . * - * Copyright 2018 Edward V. Emelianov - * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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, @@ -15,18 +13,18 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * + * along with this program. If not, see . */ -#include "math.h" -#include "emulation.h" -#include "usefull_macro.h" -// emulation speed over RA & DEC (0.5 degr per sec) -#define RA_SPEED (0.033) -#define DECL_SPEED (0.5) +#include +#include + +#include "angles.h" +#include "emulation.h" + +// emulation speed over RA & DEC (degr per sec) +#define RA_SPEED (5.) +#define DECL_SPEED (11.) // current coordinates static double RA = 0., DECL = 0.; @@ -35,8 +33,6 @@ static double RAtarg = 0., DECLtarg = 0.; // coordinates @ guiding start static double RA0 = 0., DECL0 = 0.; static double raspeed = 0.; -// ==1 if pointing -static int pointing = 0; // pointing start time static double tstart = -1.; @@ -44,20 +40,20 @@ static double tstart = -1.; * send coordinates to telescope emulation * @param ra - right ascention (hours) * @param decl - declination (degrees) - * @return 1 if all OK */ -int point_emulation(double ra, double decl){ +bool point_emulation(double ra, double decl){ + norm_RADEC(&ra, &decl); DBG("(emul) Send ra=%g, decl=%g", ra, decl); - putlog("(emul) Send ra=%g, decl=%g", ra, decl); + LOGMSG("(emul) Send ra=%g, decl=%g", ra, decl); + // TODO: check that Z<90! RAtarg = ra; DECLtarg = decl; RA0 = RA; DECL0 = DECL; raspeed = (RAtarg > RA) ? RA_SPEED : -RA_SPEED; if(fabs(RAtarg - RA) > 12.){ // go to opposite direction raspeed = -raspeed; } - tstart = dtime(); - pointing = 1; - return 0; + tstart = sl_dtime(); + return true; } static double getradiff(){ @@ -70,20 +66,19 @@ static double getradiff(){ /** * get coordinates (emulation) - * @return 1 if all OK */ -int get_emul_coords(double *ra, double *decl){ - if(pointing){ +void get_emul_coords(double *ra, double *decl){ + if(tstart > 0.){ DBG("RA/DEC: targ: %g/%g, cur: %g/%g, start: %g/%g", RAtarg, DECLtarg, RA, DECL, RA0, DECL0); // diff < speed? stop if((fabs(RAtarg - RA) < RA_SPEED && fabs(DECLtarg - DECL) < DECL_SPEED)){ RA = RAtarg; DECL = DECLtarg; - pointing = 0; // guiding + tstart = -1.; // "guiding" DBG("@ target"); }else{ // calculate new coordinates double radiff = getradiff(), decldiff = fabs(DECLtarg - DECL); - double tdiff = dtime() - tstart; + double tdiff = sl_dtime() - tstart; RA = RA0 + raspeed * tdiff; DBG("RA=%g", RA); if(getradiff() > radiff) RA = RAtarg; @@ -99,5 +94,5 @@ int get_emul_coords(double *ra, double *decl){ } if(ra) *ra = RA; if(decl) *decl = DECL; - return 1; } + diff --git a/Daemons/10micron_stellarium/emulation.h b/Daemons/10micron_stellarium/emulation.h index c15fa71..6bced1f 100644 --- a/Daemons/10micron_stellarium/emulation.h +++ b/Daemons/10micron_stellarium/emulation.h @@ -1,12 +1,10 @@ /* - * geany_encoding=koi8-r - * emulation.h + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . * - * Copyright 2018 Edward V. Emelianov - * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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, @@ -15,16 +13,12 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - * + * along with this program. If not, see . */ + + #pragma once -#ifndef __EMULATION_H__ -#define __EMULATION_H__ -int point_emulation(double ra, double decl); -int get_emul_coords(double *ra, double *decl); +bool point_emulation(double ra, double decl); +void get_emul_coords(double *ra, double *decl); -#endif // __EMULATION_H__ diff --git a/Daemons/10micron_stellarium/main.c b/Daemons/10micron_stellarium/main.c index 9b7a19e..25e7764 100644 --- a/Daemons/10micron_stellarium/main.c +++ b/Daemons/10micron_stellarium/main.c @@ -1,11 +1,10 @@ /* - * main.c + * This file is part of the Snippets project. + * Copyright 2024 Edward V. Emelianov . * - * Copyright 2014 Edward V. Emelianov - * - * This program is free software; you can redistribute it and/or modify + * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or + * 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, @@ -14,509 +13,106 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. + * along with this program. If not, see . */ -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include -#include +#include +#include +#include // wait +#include //prctl +#include -#include "emulation.h" -#include "libsofa.h" -#include "main.h" -#include "socket.h" -#include "telescope.h" +#include "args.h" +#include "server.h" +#include "mount.h" -// daemon.c -extern void check4running(char *self, char *pidfilename, void (*iffound)(pid_t pid)); +static pid_t childpid = -1; // PID of child process +static const char *pidfile = NULL; -// Max amount of connections -#define BACKLOG (10) -#define BUFLEN (1024) -// pause for incoming message waiting (out coordinates sent after that timeout) -#define SOCK_TMOUT (1) - -static pid_t childpid = 1; // PID of child process -volatile int global_quit = 0; -// quit by signal void signals(int sig){ - signal(sig, SIG_IGN); - if(childpid){ // parent process - restore_tty(); // restore all parameters - unlink(GP->pidfile); // and remove pidfile - weatherserver_disconnect(); - } - DBG("Get signal %d, quit.\n", sig); - global_quit = 1; - if(childpid) putlog("PID %d exit with status %d after child's %d death", getpid(), sig, childpid); - else WARNX("Child %d died with %d", getpid(), sig); - sleep(1); - exit(sig); -} - -// search a first word after needle without spaces -char* stringscan(char *str, char *needle){ - char *a, *e; - char *end = str + strlen(str); - a = strstr(str, needle); - if(!a) return NULL; - a += strlen(needle); - while (a < end && (*a == ' ' || *a == '\r' || *a == '\t')) a++; - if(a >= end) return NULL; - e = strchr(a, ' '); - if(e) *e = 0; - return a; -} - -/** - * Send data to user - * @param data - data to send - * @param dlen - data length - * @param sockfd - socket fd for sending data - * @return 0 if failed - */ -int send_data(uint8_t *data, size_t dlen, int sockfd){ - size_t sent = write(sockfd, data, dlen); - if(sent != dlen){ - WARN("write()"); - return 0; + if(childpid){ // single or parent process + if(pidfile) unlink(pidfile); // remove pidfile + if(childpid > 0) kill(childpid, SIGTERM); } - return 1; -} - -//read: 0x14 0x0 0x0 0x0 0x5b 0x5a 0x2e 0xc6 0x8c 0x23 0x5 0x0 0x23 0x9 0xe5 0xaf 0x23 0x2e 0x34 0xed -// command: goto 16h29 24.45 -26d25 55.62 -/* - LITTLE-ENDIAN!!! - from client: -LENGTH (2 bytes, integer): length of the message -TYPE (2 bytes, integer): 0 -TIME (8 bytes, integer): current time on the server computer in microseconds - since 1970.01.01 UT. Currently unused. -RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) - a value of 0x100000000 = 0x0 means 24h=0h, - a value of 0x80000000 means 12h -DEC (4 bytes, signed integer): declination of the telescope (J2000) - a value of -0x40000000 means -90degrees, - a value of 0x0 means 0degrees, - a value of 0x40000000 means 90degrees - -to client: -LENGTH (2 bytes, integer): length of the message -TYPE (2 bytes, integer): 0 -TIME (8 bytes, integer): current time on the server computer in microseconds - since 1970.01.01 UT. Currently unused. -RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) - a value of 0x100000000 = 0x0 means 24h=0h, - a value of 0x80000000 means 12h -DEC (4 bytes, signed integer): declination of the telescope (J2000) - a value of -0x40000000 means -90degrees, - a value of 0x0 means 0degrees, - a value of 0x40000000 means 90degrees -STATUS (4 bytes, signed integer): status of the telescope, currently unused. - status=0 means ok, status<0 means some error -*/ - -#define DEG2DEC(degr) ((int32_t)(degr / 90. * ((double)0x40000000))) -#define HRS2RA(hrs) ((uint32_t)(hrs / 12. * ((double)0x80000000))) -#define DEC2DEG(i32) (((double)i32)*90./((double)0x40000000)) -#define RA2HRS(u32) (((double)u32)*12. /((double)0x80000000)) - -typedef struct __attribute__((__packed__)){ - uint16_t len; - uint16_t type; - uint64_t time; - uint32_t ra; - int32_t dec; -} indata; - -typedef struct __attribute__((__packed__)){ - uint16_t len; - uint16_t type; - uint64_t time; - uint32_t ra; - int32_t dec; - int32_t status; -} outdata; - -/** - * convert RA/DEC to string in forman RA: HH:MM:SS.SS, DEC: DD:MM:SS.S - */ -char *radec2str(double ra, double dec){ - static char buf[1024]; - char sign = '+'; - if(dec < 0){ - sign = '-'; - dec = -dec; + if(sig){ + signal(sig, SIG_IGN); + DBG("Get signal %d, quit.\n", sig); } - - int h = (int)ra; - ra -= h; ra *= 60.; - int m = (int)ra; - ra -= m; ra *= 60.; - - int d = (int) dec; - dec -= d; dec *= 60.; - int dm = (int)dec; - dec -= dm; dec *= 60.; - snprintf(buf, 1024, "%d:%d:%.2f %c%d:%d:%.1f", h,m,ra, sign,d,dm,dec); - return buf; -} - -/** - * send input RA/Decl (j2000!) coordinates to tel - * ra in hours (0..24), decl in degrees (-90..90) - * @return 1 if all OK - */ -int setCoords(double ra, double dec){ - char *radec = radec2str(ra, dec); - DBG("Set RA/Decl to %s", radec); - putlog("Try to set RA/Decl to %s", radec); - int (*pointfunction)(double, double) = point_telescope; - if(GP->emulation) pointfunction = point_emulation; - return pointfunction(ra, dec); -} - -/** - * @brief proc_data - process data received from Stellarium - * @param data - raw data - * @param len - its length - * @return 1 if all OK - */ -int proc_data(uint8_t *data, ssize_t len){ - FNAME(); - if(len != sizeof(indata)){ - WARNX("Bad data size: got %zd instead of %zd!", len, sizeof(indata)); - return 0; + if(childpid > 0){ // parent process + LOGERR("PID %d exit with status %d", getpid(), sig); + }else{ + LOGWARN("Child %d died with %d", getpid(), sig); + server_stop(); } - indata *dat = (indata*)data; - uint16_t L, T; - //uint64_t tim; - uint32_t ra; - int32_t dec; -#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ - L = le16toh(dat->len); T = le16toh(dat->type); - //tim = le64toh(dat->time); - ra = le32toh(dat->ra); - dec = (int32_t)le32toh((uint32_t)dat->dec); -#else - L = dat->len; T = dat->type; - //tim = dat->time; - ra = dat->ra; dec = dat->dec; -#endif - DBG("got message with len %u & type %u", L, T); - if(L != len){ - WARNX("Length of message != msg->len"); - return 0; - } - if(T){ - WARNX("Wrong message type"); - return 0; - } - // convert RA/DEC to hours/degrees - double tagRA = RA2HRS(ra), tagDec = DEC2DEG(dec); - DBG("RA: %u (%g), DEC: %d (%g)", ra, tagRA, dec, tagDec); - putlog("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec); - // check RA/DEC - horizCrds hnow; // without refraction - polarCrds p2000, pnow; - p2000.ra = tagRA/12. * M_PI; - p2000.dec = tagDec * ERFA_DD2R; - // now J2000 obs Jnow - if(get_ObsPlace(NULL, &p2000, NULL, &pnow, &hnow)){ - WARNX("Can't convert coordinates to Jnow"); - return 0; - } -/* - int i[4], j[4]; char pm, pm1; - eraA2af(2, hnow.az, &pm, i); - eraA2af(2, hnow.zd, &pm1, j); - DBG("az: %c%02d %02d %02d.%2.d, zd: %c%02d %02d %02d.%2.d", - pm, i[0],i[1],i[2],i[3], - pm1,j[0],j[1],j[2],j[3]); - eraA2af(2, M_PI_2 - hnow.zd, &pm, i); - DBG("h: %c%02d %02d %02d.%2.d", pm, i[0],i[1],i[2],i[3]); -*/ - if(hnow.zd > 80.*ERFA_DD2R){ - WARNX("Z > 80degr (%g), stop telescope", hnow.zd * ERFA_DR2D); - putlog("Z=%.1f > 80 - stop!", hnow.zd * ERFA_DR2D); - stop_telescope(); - return 0; - } - tagRA = (pnow.ra - pnow.eo) / M_PI * 12.; - tagDec = pnow.dec / ERFA_DD2R; - if(!setCoords(tagRA, tagDec)) return 0; - return 1; -} - -/** - * main socket service procedure - */ -void *handle_socket(void *sockd){ - FNAME(); - if(global_quit) return NULL; - outdata dout; - int sock = *(int*)sockd; - dout.len = htole16(sizeof(outdata)); - dout.type = 0; - int (*getcoords)(double*, double*) = get_telescope_coords; - if(GP->emulation) getcoords = get_emul_coords; - while(!global_quit){ - // get coordinates - double RA = 0., Decl = 0.; - if((dout.status = getcoords(&RA, &Decl)) < 0){ - WARNX("Error: can't get coordinates"); - sleep(1); - continue; - } - //DBG("got : %g/%g", RA, Decl); - dout.ra = htole32(HRS2RA(RA)); - dout.dec = (int32_t)htole32(DEG2DEC(Decl)); - if(!send_data((uint8_t*)&dout, sizeof(outdata), sock)) break; - //DBG("sent ra = %g, dec = %g", RA2HRS(dout.ra), DEC2DEG(dout.dec)); - fd_set readfds; - struct timeval timeout; - FD_ZERO(&readfds); - FD_SET(sock, &readfds); - timeout.tv_sec = SOCK_TMOUT; // wait not more than SOCK_TMOUT second - timeout.tv_usec = 0; - int sel = select(sock + 1 , &readfds , NULL , NULL , &timeout); - if(sel < 0){ - if(errno != EINTR) - WARN("select()"); - continue; - } - if(!(FD_ISSET(sock, &readfds))) continue; - // fill incoming buffer - uint8_t buff[BUFLEN+1]; - ssize_t rd = read(sock, buff, BUFLEN); - buff[rd] = 0; - DBG("read %zd (%s)", rd, buff); - if(rd <= 0){ // error or disconnect - DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); - break; - } - /************************************** - * DO SOMETHING WITH DATA * - **************************************/ - if(!proc_data(buff, rd)) dout.status = -1; - else dout.status = 0; - } - close(sock); - return NULL; -} - -// thread writing FITS-header file -static void *hdrthread(_U_ void *buf){ - // write FITS-header at most once per second - while(!global_quit){ - wrhdr(); - usleep(100000); // give a chance to write/read for others - } - return NULL; -} - -/** - * @brief opensocket - open socket to port `port` - * @return socket fd or <0 if failed - */ -static int opensocket(char *port){ - if(!port) return -1; - int reuseaddr = 1; - int sock; - struct addrinfo hints, *res, *p; - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - DBG("try to open port %s", port); - if(getaddrinfo(NULL, port, &hints, &res) != 0){ - WARN("getaddrinfo()"); - return 0; - } - /* - struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr; - char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &(ia->sin_addr), str, INET_ADDRSTRLEN); - */ - // loop through all the results and bind to the first we can - for(p = res; p != NULL; p = p->ai_next){ - if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){ - WARN("socket()"); - continue; - } - if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ - WARN("setsockopt()"); - close(sock); - continue; - } - if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ - WARN("bind()"); - close(sock); - continue; - } - break; // if we get here, we must have connected successfully - } - freeaddrinfo(res); - // Listen - if(listen(sock, BACKLOG) == -1){ - WARN("listen"); - putlog("listen() error"); - } - DBG("listen at %s", port); - putlog("listen at %s", port); - return sock; -} - -/** - * @brief waitconn - * @param sock - socket fd to accept() - * @param connthread - thread which to run when connection accepted (it's parameter - socket fd) - */ -static void waitconn(int sock, void *(*connthread)(void*)){ - // Main loop - while(!global_quit){ - socklen_t size = sizeof(struct sockaddr_in); - struct sockaddr_in myaddr; - int newsock; - newsock = accept(sock, (struct sockaddr*)&myaddr, &size); - if(newsock <= 0){ - WARN("accept()"); - sleep(1); - continue; - } - struct sockaddr_in peer; - socklen_t peer_len = sizeof(peer); - if(getpeername(newsock, (struct sockaddr*)&peer, &peer_len) == -1){ - WARN("getpeername()"); - close(newsock); - continue; - } - int sockport = -1; - if(getsockname(newsock, (struct sockaddr*)&peer, &peer_len) == 0){ - sockport = ntohs(peer.sin_port); - } - char *peerIP = inet_ntoa(peer.sin_addr); - putlog("Got connection from %s @ %d", peerIP, sockport); - DBG("Peer's IP address is: %s (@port %d)\n", peerIP, sockport); - /*if(strcmp(peerIP, ACCEPT_IP) && strcmp(peerIP, "127.0.0.1")){ - WARNX("Wrong IP"); - close(newsock); - continue; - }*/ - pthread_t rthrd; - if(pthread_create(&rthrd, NULL, connthread, (void*)&newsock)){ - putlog("Error creating listen thread"); - ERR(_("Can't create socket thread")); - }else{ - DBG("Thread created, detouch"); - pthread_detach(rthrd); // don't care about thread state - } - } - close(sock); -} - -// thread working with terminal -static void *termthread(_U_ void *buf){ - int sock = opensocket(GP->dbgport); - if(sock < 0){ - putlog("Can't open debugging socket @ port %s", GP->dbgport); - ERRX("Can't open debug socket"); - } - waitconn(sock, term_thread); - return NULL; -} - -static inline void main_proc(){ - pthread_t hthrd, termthrd; - // connect to telescope - if(!GP->emulation){ - if(!connect_telescope(GP->device, GP->crdsfile)){ - ERRX(_("Can't connect to telescope device")); - } - if(pthread_create(&hthrd, NULL, hdrthread, NULL)) - ERR(_("Can't create writing thread")); - if(pthread_create(&termthrd, NULL, termthread, NULL)) - ERR(_("Can't create terminal thread")); - } - // connect to weather daemon - if(!weatherserver_connect()){ - DBG("Can't connect to weather server, will try later"); - } - // open socket - int sock = opensocket(GP->port); - if(sock < 0){ - putlog("Can't open socket @ port %s", GP->port); - ERRX("Can't open stellarium socket"); - } - waitconn(sock, handle_socket); - usleep(10000); - pthread_cancel(hthrd); // cancel reading thread - pthread_cancel(termthrd); - pthread_join(hthrd, NULL); - pthread_join(termthrd, NULL); + DBG("EXIT"); } int main(int argc, char **argv){ - char *self = strdup(argv[0]); - GP = parse_args(argc, argv); - initial_setup(); - - signal(SIGTERM, signals); // kill (-15) - quit - signal(SIGKILL, signals); // kill (-9) - quit - signal(SIGHUP, SIG_IGN); // hup - ignore - signal(SIGINT, signals); // ctrl+C - quit - signal(SIGQUIT, signals); // ctrl+\ - quit - signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z - + sl_init(); + parameters_t *G = parse_cmdline(&argc, &argv); + if(!G) return 1; + sl_loglevel_e lvl = G->verbose + LOGLEVEL_ERR; + if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1; + DBG("verb: %d, level: %d", G->verbose, lvl); int fd; - if((fd = open(GP->crdsfile, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) // test FITS-header file for writing - ERR(_("Can't open %s for writing"), GP->crdsfile); + if((fd = open(G->crdsfile, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) // test FITS-header file for writing + ERR(_("Can't open %s for writing"), G->crdsfile); close(fd); - - printf("Daemonize\n"); -#ifndef EBUG // daemonize only in release mode - if(daemon(1, 0)){ - putlog("Err: daemon()"); - ERR("daemon()"); - } -#endif // EBUG - check4running(self, GP->pidfile, NULL); - if(GP->logfile) openlogfile(GP->logfile); - putlog("Starting, master PID=%d", getpid()); - + if(G->sleept < 1) ERRX("Sleeping time sould be positive value"); + if(!server_setsleept(G->sleept)) ERRX("Can't set sleep time to %d", G->sleept); + if(G->emulation) set_emulation_mode(); + else if(!mount_set_dev(G->device, G->serspeed, G->sertmout)) + ERRX("Can't open device %s @ %d", G->device, G->serspeed); + if(!mount_set_name(G->mountname)) + ERRX("Can't set mount name to %s", G->mountname); + signal(SIGTERM, signals); + signal(SIGINT, signals); + signal(SIGQUIT, signals); + signal(SIGTSTP, SIG_IGN); + signal(SIGHUP, signals); + sl_check4running((char*)__progname, G->pidfile); + if(G->logfile) OPENLOG(G->logfile, lvl, 1); + DBG("Started"); + LOGMSG("Started, master PID=%d", getpid()); #ifndef EBUG - while(1){ + time_t lastd = 0; + while(1){ // guard for dead processes childpid = fork(); if(childpid < 0){ - putlog("fork() error"); - ERR("ERROR on fork"); + LOGERR("fork() returns error"); + sleep(5); + continue; } if(childpid){ - WARNX(_("Created child with PID %d\n"), childpid); DBG("Created child with PID %d\n", childpid); wait(NULL); - WARNX(_("Child %d died\n"), childpid); - DBG("Child %d died\n", childpid); + time_t t = time(NULL); + if(t - lastd > 600){ // at least 10 minutes of work + LOGERR("Child %d died\n", childpid); + } + lastd = t; + WARNX("Child %d died\n", childpid); + sleep(1); }else{ prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies - main_proc(); - return 0; + break; // go out to normal functional } } -#else - main_proc(); #endif - - return 0; + server_sock_t sockt = { + .cmd_isunix = G->isunix, + .cmdnode = G->cmdnode, + .stellport = G->port, + .maxclients = G->maxclients, + }; + if(!server_check(&sockt)){ + LOGERR("Can't run servers"); + ERRX("Can't run servers"); + } + DBG("Run server"); + server_run(); + DBG("Server died"); + return 1; } diff --git a/Daemons/10micron_stellarium/mount.c b/Daemons/10micron_stellarium/mount.c new file mode 100644 index 0000000..e332002 --- /dev/null +++ b/Daemons/10micron_stellarium/mount.c @@ -0,0 +1,288 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#include +#include +#include +#include + +#include "angles.h" +#include "emulation.h" +#include "mount.h" + + +#define MNAME_LEN 31 + +static bool isemulated = false; // ==true for emulation mode + +static char mount_name[MNAME_LEN+3] = "'noname'"; +static sl_tty_t *mount_dev = NULL; +// device mutex, blocking only in non-local functions +static pthread_mutex_t mntdev_mutex = PTHREAD_MUTEX_INITIALIZER; +// ring buffer for data incoming from serial port +static sl_ringbuffer_t *RBin = NULL; +// status +static atomic_int mountstatus = MNT_S_ERROR; + +// input and current target coordinates +polarCrds_t InpCoords = {0}, TagCoords = {0}; +horizCrds_t InpHoriz = {0}; + +// change input coordinates +/** + * @brief mount_setInpHA - set hour angle + * @param ha (HOURS!!) + * @return false if `ha` isn't in [0,24) + */ +bool mount_setInpHA(double ha){ + if(ha < 0. || ha >= 24.) return false; + InpCoords.ha = HRS2RAD(ha); + return true; +} +/** + * @brief mount_setInpRA - set right ascension + * @param ra (DEGREES!) + * @return fale if `ra` isn't in [0, 360) + */ +bool mount_setInpRA(double ra){ + if(ra < 0. || ra >= 360.) return false; + InpCoords.ra = DEG2RAD(ra); + return true; +} +/** + * @brief mount_setInpDec - set declination + * @param dec (DEGREES!) + * @return false if `dec` isn't in [-90, 90] + */ +bool mount_setInpDec(double dec){ + if(dec < -90. || dec > 90.) return false; + InpCoords.dec = DEG2RAD(dec); + return true; +} +/** + * @brief mount_setInpA - set azimuth + * @param A (DEGREES) + * @return false if A isn't in [0, 360) + */ +bool mount_setInpA(double A){ + if(A < 0. || A >= 360.) return false; + InpHoriz.az = DEG2RAD(A); + return true; +} +/** + * @brief mount_setInpZ - set zenith distance + * @param Z (DEGREES) + * @return false if Z isn't in [0, 90] + */ +bool mount_setInpZ(double Z){ + if(Z < 0 || Z > 90) return false; + InpHoriz.zd = DEG2RAD(Z); + return true; +} + +/** + * @brief mount_set_name - set mount name for FITS header + * @param name - new string with name + * @return false if failed + */ +bool mount_set_name(const char *name){ + if(!name || !*name) return false; + int l = strlen(name); + if(l > MNAME_LEN) return false; + sprintf(mount_name, "'%s'", name); + return true; +} + +/** + * @brief mount_set_dev - open mount device without checking that mount is alive + * @param dev - path to device + * @param speed - baudrate + * @return false if failed to open device `dev` + */ +bool mount_set_dev(char *dev, int speed, int timeout){ + pthread_mutex_lock(&mntdev_mutex); + if(mount_dev) sl_tty_close(&mount_dev); + mount_dev = sl_tty_new(dev, speed, 4096); + if(mount_dev) mount_dev = sl_tty_open(mount_dev, 1); + if(!mount_dev){ + pthread_mutex_unlock(&mntdev_mutex); + return false; + } + sl_tty_tmout(timeout); + if(!RBin) RBin = sl_RB_new(BUFSIZ); + else sl_RB_clearbuf(RBin); + pthread_mutex_unlock(&mntdev_mutex); + return true; +} + +static const char *statuses[MNT_S_STATAMOUNT] = { + [MNT_S_TRACKING] = "'Tracking'", + [MNT_S_STOPHOM] = "'Stopped or homing'", + [MNT_S_PARKING] = "'Slewing to park'", + [MNT_S_UNPARKING] = "'Unparking'", + [MNT_S_HOMING] = "'Slewing to home'", + [MNT_S_PARKED] = "'Parked'", + [MNT_S_SLEWING] = "'Slewing or going to stop'", + [MNT_S_STOPPED] = "'Stopped'", + [MNT_S_INHIBITED] = "'Motors inhibited, T too low'", + [MNT_S_OUTLIMIT] = "'Outside tracking limit'", + [MNT_S_FOLSAT]= "'Following satellite'", + [MNT_S_DATINCOSIST]= "'Data inconsistency'", + [MNT_S_ERROR] = "'Error (disconnected?)'" +}; + +/** + * @brief strstatus - return string explanation of mount status + * @param status - integer status code + * @return statically allocated string with explanation + */ +const char* mount_status_str(){ + int curst = atomic_load(&mountstatus); + if(curst > -1 && curst < MNT_S_STATAMOUNT) return statuses[curst]; + return "'Unknown status'"; +} +// return current mount status +mount_status_t mount_status(){ + return (mount_status_t)atomic_load(&mountstatus); +} + +/** + * @brief write_cmd - try to write command to mount + * @param cmd - string with command or NULL just to clear all incoming data + * @return false on write/read error; all read information is in RBin (even if `false` returned, you SHOULD read all available strings from it) + */ +static bool write_cmd(const char *cmd){ + bool ret = true; + if(cmd){ + size_t l = strlen(cmd); + if(sl_tty_write(mount_dev->comfd, cmd, l)) ret = false; + } + int got = 0; + while((got = sl_tty_read(mount_dev)) > 0){ + if(sl_RB_write(RBin, (uint8_t*) mount_dev->buf, got)){ + got = -1; + break; + } + } + if(got < 0) return false; + return ret; +} + +// check if mount connected +static bool chkconn(){ + int r = 0; + do{ // clear incoming buffer @ start + r = sl_tty_read(mount_dev); + }while(r > 0); + if(r < 0) return false; + write_cmd("#"); // clear cmd buffer + sl_RB_clearbuf(RBin); + bool w = write_cmd(":SB0#"); + sl_RB_clearbuf(RBin); + return w; +} + + +// try to guess serial speed & set 115200 +static bool guess_speed(){ + if(!mount_dev) return false; + close(mount_dev->comfd); +#define SPDBUFSZ 7 + const int speeds[SPDBUFSZ] = {57600, 38400, 19200, 9600, 4800, 2400, 1200}; + int idx = 0; + for(; idx < SPDBUFSZ; ++idx){ + mount_dev->speed = speeds[idx]; + sl_tty_t *trydev = sl_tty_open(mount_dev, 1); + if(!trydev) continue; + if(chkconn()) break; + close(mount_dev->comfd); + } + if(idx == SPDBUFSZ) return false; // device not responding + close(mount_dev->comfd); + mount_dev->speed = 115200; + if(!sl_tty_open(mount_dev, 1)) return false; +#undef SPDBUFSZ + return true; +} + +// connect to mount +bool mount_connect(){ + if(isemulated){ + atomic_store(&mountstatus, MNT_S_STOPPED); + return true; + } + if(!mount_dev) return false; + pthread_mutex_lock(&mntdev_mutex); + if(!chkconn() && !guess_speed()) return false; + bool ret = true; + if(!write_cmd(":STOP#")) ret = false; // stop tracking after poweron + sl_RB_clearbuf(RBin); + if(!write_cmd(":U2#")) ret = false; // set high precision + sl_RB_clearbuf(RBin); + if(!write_cmd(":So10#")) ret = false; // set minimum altitude to 10 degrees + sl_RB_clearbuf(RBin); + pthread_mutex_unlock(&mntdev_mutex); + if(ret) LOGMSG("Connected to %s@115200", mount_dev->portname); + else LOGERR("Can't write commands to mount"); + return ret; +} + +void mount_disconnect(){ + if(isemulated) return; + pthread_mutex_trylock(&mntdev_mutex); // at least, try + if(mount_dev) close(mount_dev->comfd); + pthread_mutex_unlock(&mntdev_mutex); +} + +// point to ra/dec over serial +static bool mount_pointto(double ra, double dec){ + (void) ra; (void) dec; + ; + return true; +} + +/** + * send input RA/Decl (j2000!) coordinates to tel + * ra in hours (0..24), decl in degrees (-90..90) + * @return true if all OK + */ +bool mount_point(double ra, double dec){ + char buf[RADEC_STR_MAXLEN]; + radec2str(ra, dec, buf); + DBG("Set RA/Decl to %s", buf); + LOGMSG("Try to set RA/Decl to %s", buf); + norm_RADEC(&ra, &dec); + bool (*pointfunction)(double, double) = mount_pointto; + if(isemulated) pointfunction = point_emulation; + return pointfunction(ra, dec); +} + +void set_emulation_mode(){ + isemulated = true; +} + +mount_status_t mount_getcoords(double *ra, double *dec){ + if(!ra || !dec) return MNT_S_ERROR; + if(isemulated){ + get_emul_coords(ra, dec); + DBG("Emulated coordinates: %g, %g", *ra, *dec); + }else{ + ; // get real coordinates + } + return mount_status(); +} diff --git a/Daemons/10micron_stellarium/mount.h b/Daemons/10micron_stellarium/mount.h new file mode 100644 index 0000000..65eb135 --- /dev/null +++ b/Daemons/10micron_stellarium/mount.h @@ -0,0 +1,57 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once + +#include + +// mount statuses +typedef enum{ + MNT_S_TRACKING = 0, + MNT_S_STOPHOM = 1, + MNT_S_PARKING = 2, + MNT_S_UNPARKING = 3, + MNT_S_HOMING = 4, + MNT_S_PARKED = 5, + MNT_S_SLEWING = 6, + MNT_S_STOPPED = 7, + MNT_S_INHIBITED = 8, + MNT_S_OUTLIMIT = 9, + MNT_S_FOLSAT = 10, + MNT_S_DATINCOSIST = 11, + MNT_S_ERROR = 12, // my status + MNT_S_STATAMOUNT = 13 // number of statuses +} mount_status_t; + +void set_emulation_mode(); + +bool mount_setInpHA(double ha); +bool mount_setInpRA(double ra); +bool mount_setInpDec(double dec); +bool mount_setInpA(double A); +bool mount_setInpZ(double Z); + +bool mount_set_name(const char *name); +bool mount_set_dev(char *dev, int speed, int timeout); +const char* mount_status_str(); +mount_status_t mount_status(); +bool mount_connect(); +void mount_disconnect(); + +mount_status_t mount_getcoords(double *ra, double *dec); +bool mount_point(double ra, double dec); diff --git a/Daemons/10micron_stellarium/mountdaemon_10micron.cflags b/Daemons/10micron_stellarium/mountdaemon_10micron.cflags new file mode 100644 index 0000000..a07354f --- /dev/null +++ b/Daemons/10micron_stellarium/mountdaemon_10micron.cflags @@ -0,0 +1 @@ +-std=c23 \ No newline at end of file diff --git a/Daemons/10micron_stellarium/mountdaemon_10micron.config b/Daemons/10micron_stellarium/mountdaemon_10micron.config new file mode 100644 index 0000000..ec616c8 --- /dev/null +++ b/Daemons/10micron_stellarium/mountdaemon_10micron.config @@ -0,0 +1,7 @@ +// Add predefined macros for your project here. For example: +// #define THE_ANSWER 42 +#define _XOPEN_SOURCE 1234 +#define _DEFAULT_SOURCE +#define _GNU_SOURCE +#define EBUG + diff --git a/Daemons/10micron_stellarium/StelD.creator b/Daemons/10micron_stellarium/mountdaemon_10micron.creator similarity index 100% rename from Daemons/10micron_stellarium/StelD.creator rename to Daemons/10micron_stellarium/mountdaemon_10micron.creator diff --git a/Daemons/10micron_stellarium/mountdaemon_10micron.creator.user b/Daemons/10micron_stellarium/mountdaemon_10micron.creator.user new file mode 100644 index 0000000..0cf5bde --- /dev/null +++ b/Daemons/10micron_stellarium/mountdaemon_10micron.creator.user @@ -0,0 +1,220 @@ + + + + + + EnvironmentId + {cf63021e-ef53-49b0-b03b-2f2570cdf3b6} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + KOI8-R + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + false + false + 1 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + true + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 4 + true + + + + true + + 0 + + + + ProjectExplorer.Project.Target.0 + + Desktop + true + Desktop + Desktop + {91347f2c-5221-46a7-80b1-0a054ca02f79} + 0 + 0 + 0 + + /home/eddy/Docs/SAO/10micron/C-sources/mountdaemon_10micron + + + + all + + true + GenericProjectManager.GenericMakeStep + + 1 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + GenericProjectManager.GenericMakeStep + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + По умолчанию + GenericProjectManager.GenericBuildConfiguration + 0 + 0 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + + + 2 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + + ProjectExplorer.CustomExecutableRunConfiguration + + false + + true + true + + 1 + + 1 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + + + 2 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + + ProjectExplorer.CustomExecutableRunConfiguration + + false + + true + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + Version + 22 + + diff --git a/Daemons/10micron_stellarium/StelD.cxxflags b/Daemons/10micron_stellarium/mountdaemon_10micron.cxxflags similarity index 100% rename from Daemons/10micron_stellarium/StelD.cxxflags rename to Daemons/10micron_stellarium/mountdaemon_10micron.cxxflags diff --git a/Daemons/10micron_stellarium/mountdaemon_10micron.files b/Daemons/10micron_stellarium/mountdaemon_10micron.files new file mode 100644 index 0000000..2eccabf --- /dev/null +++ b/Daemons/10micron_stellarium/mountdaemon_10micron.files @@ -0,0 +1,16 @@ +angles.c +angles.h +args.c +args.h +astrosib_proto.h +dome.c +dome.h +emulation.c +emulation.h +main.c +server.c +server.h +mount.c +mount.h +stellarium.c +stellarium.h diff --git a/Daemons/10micron_stellarium/mountdaemon_10micron.includes b/Daemons/10micron_stellarium/mountdaemon_10micron.includes new file mode 100644 index 0000000..e69de29 diff --git a/Daemons/10micron_stellarium/server.c b/Daemons/10micron_stellarium/server.c new file mode 100644 index 0000000..460eb0b --- /dev/null +++ b/Daemons/10micron_stellarium/server.c @@ -0,0 +1,186 @@ +/* + * This file is part of the Snippets project. + * Copyright 2024 Edward V. Emelianov . + * + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mount.h" +#include "server.h" +#include "stellarium.h" + +// max age time of last status - 30s +#define STATUS_MAX_AGE (30.) + +// commands +#define CMD_UNIXT "unixt" +#define CMD_STATUS "status" +#define CMD_RELAY "relay" +#define CMD_OPEN "open" +#define CMD_CLOSE "close" +#define CMD_STOP "stop" +#define CMD_HALF "half" + +// main command socket +static sl_sock_t *cmd_socket = NULL; +// socket for stellarium purposes +static int stellarium_sockfd = -1; +// sleep time (us) +static unsigned int sleept = DEFAULT_SLEEP_T; +// running flag +static volatile bool isrunning = false; + +unsigned int server_getsleept(){ return sleept; } +bool server_setsleept(unsigned int t){ + if(t == 0 || t > MAX_SLEEP_T) return false; + sleept = t; + return true; +} + +/////// handlers +// unixt - send to ALL clients +static sl_sock_hresult_e dtimeh(sl_sock_t *c, _U_ sl_sock_hitem_t *item, _U_ const char *req){ + char buf[32]; + snprintf(buf, 31, "%s=%.2f\n", item->key, sl_dtime()); + LOGDBG("Client %d asks time: %s", c->fd, buf); + sl_sock_sendstrmessage(c, buf); + return RESULT_SILENCE; +} + +// statust - text format status +static sl_sock_hresult_e status(sl_sock_t *c, sl_sock_hitem_t *item, _U_ const char *req){ + char buf[BUFSIZ]; + snprintf(buf, BUFSIZ-1, "%s=%s\n", item->key, mount_status_str()); + LOGDBG("Client %d asks status: %s", c->fd, buf); + sl_sock_sendstrmessage(c, buf); + return RESULT_SILENCE; +} + +// and all handlers collection +static sl_sock_hitem_t handlers[] = { + {dtimeh, CMD_UNIXT, "get server's UNIX time", NULL}, + {status, CMD_STATUS, "get mount status", NULL}, + {NULL, NULL, NULL, NULL} +}; + +// Too much clients handler +static void toomuch(int fd){ + const char m[] = "Try later: too much clients connected\n"; + send(fd, m, sizeof(m)-1, MSG_NOSIGNAL); + shutdown(fd, SHUT_WR); + DBG("shutdown, wait"); + double t0 = sl_dtime(); + uint8_t buf[8]; + while(sl_dtime() - t0 < 11.){ + if(sl_canread(fd)){ + ssize_t got = read(fd, buf, 8); + DBG("Got=%zd", got); + if(got < 1) break; + } + } + DBG("Disc after %gs", sl_dtime() - t0); + LOGWARN("Client fd=%d tried to connect after MAX reached", fd); +} +// new connections handler: can check IP and reject client by returning FALSE +static int connected(sl_sock_t *c){ + 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); + return TRUE; +} +// disconnected handler +static void disconnected(sl_sock_t *c){ + if(c->type == SOCKT_UNIX) LOGMSG("Disconnected client fd=%d", c->fd); + else LOGMSG("Disconnected client fd=%d, IP=%s", c->fd, c->IP); +} + +bool server_check(server_sock_t *sockt){ + sl_socktype_e type = (sockt->cmd_isunix) ? SOCKT_UNIX : SOCKT_NETLOCAL; + cmd_socket = sl_sock_run_server(type, sockt->cmdnode, BUFSIZ, handlers); + if(!cmd_socket){ + LOGERR("Can't start main server"); + return false; + } + LOGMSG("Main server started: %s", sockt->cmdnode); + sl_sock_changemaxclients(cmd_socket, sockt->maxclients); + sl_sock_maxclhandler(cmd_socket, toomuch); + sl_sock_connhandler(cmd_socket, connected); + sl_sock_dischandler(cmd_socket, disconnected); + stellarium_sockfd = sl_sock_open(SOCKT_NET, sockt->stellport, 1, 0); + if(stellarium_sockfd < 0){ + LOGERR("Can't start stellarium socket"); + sl_sock_delete(&cmd_socket); + return false; + } + if(listen(stellarium_sockfd, sockt->maxclients) == -1){ + WARN("listen() for stellarium socket"); + LOGERR("Can't run listen() for stellarium socket"); + sl_sock_delete(&cmd_socket); + close(stellarium_sockfd); + return false; + } + DBG("stellarium_sockfd=%d", stellarium_sockfd); + LOGMSG("Prepared stellarium socket: %s", sockt->stellport); + DBG("Prepared stellarium socket: %s", sockt->stellport); + return true; +} + +void server_run(){ + if(isrunning){ + LOGERR("server_run(): still running!"); + return; + } + if(stellarium_sockfd == -1 || !cmd_socket){ + LOGERR("server_run(): not initialized"); + if(cmd_socket) sl_sock_delete(&cmd_socket); + ERRX("server_run(): not initialized"); + } + if(!stellarium_start(stellarium_sockfd)){ + LOGERR("Can't start stellarium server"); + return; + } + if(!mount_connect()){ + LOGERR("Can't connect to mount"); + sl_sock_delete(&cmd_socket); + return; + } + isrunning = true; + DBG("While"); + while(isrunning && cmd_socket && cmd_socket->connected){ + usleep(sleept); + if(!cmd_socket->rthread){ + LOGERR("Server handlers thread is dead"); + break; + } + // finite state machine polling + //dome_poll(DOME_POLL, 0); + } + DBG("Stop command socket"); + sl_sock_delete(&cmd_socket); + WARNX("Server is dead"); + LOGERR("Server is dead"); + isrunning = false; +} + +void server_stop(){ + isrunning = false; +} diff --git a/Daemons/10micron_stellarium/server.h b/Daemons/10micron_stellarium/server.h new file mode 100644 index 0000000..0daae2a --- /dev/null +++ b/Daemons/10micron_stellarium/server.h @@ -0,0 +1,39 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2025 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once + +#include + +// default and max available time for "usleep" +#define DEFAULT_SLEEP_T 100 +#define MAX_SLEEP_T 10000 +#define DEFAULT_MAXCLIENTS 5 + +typedef struct{ + int cmd_isunix; // UNIX-socket instead of INET for `cmdnode` + const char *stellport; // port of stellarium server; could be "localhost:port" for local-only work + const char *cmdnode; // node of command socket + int maxclients; // maximal amount of clients connected +} server_sock_t; + +bool server_check(server_sock_t *sockt); +void server_run(); +void server_stop(); +unsigned int server_getsleept(); +bool server_setsleept(unsigned int t); diff --git a/Daemons/10micron_stellarium/stellarium.c b/Daemons/10micron_stellarium/stellarium.c new file mode 100644 index 0000000..9411721 --- /dev/null +++ b/Daemons/10micron_stellarium/stellarium.c @@ -0,0 +1,284 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +// base functions for sending/receiving data in stellarium protocol + +#include +#include +#include +#include +#include + +#include "angles.h" +#include "mount.h" +#include "stellarium.h" + +#define BUFLEN 256 + +// running flag +static volatile bool isrunning = false; +static pthread_t mainthread; + +//read: 0x14 0x0 0x0 0x0 0x5b 0x5a 0x2e 0xc6 0x8c 0x23 0x5 0x0 0x23 0x9 0xe5 0xaf 0x23 0x2e 0x34 0xed +// command: goto 16h29 24.45 -26d25 55.62 +/* + LITTLE-ENDIAN!!! + from client: +LENGTH (2 bytes, integer): length of the message +TYPE (2 bytes, integer): 0 +TIME (8 bytes, integer): current time on the server computer in microseconds + since 1970.01.01 UT. Currently unused. +RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) + a value of 0x100000000 = 0x0 means 24h=0h, + a value of 0x80000000 means 12h +DEC (4 bytes, signed integer): declination of the telescope (J2000) + a value of -0x40000000 means -90degrees, + a value of 0x0 means 0degrees, + a value of 0x40000000 means 90degrees + +to client: +LENGTH (2 bytes, integer): length of the message +TYPE (2 bytes, integer): 0 +TIME (8 bytes, integer): current time on the server computer in microseconds + since 1970.01.01 UT. Currently unused. +RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) + a value of 0x100000000 = 0x0 means 24h=0h, + a value of 0x80000000 means 12h +DEC (4 bytes, signed integer): declination of the telescope (J2000) + a value of -0x40000000 means -90degrees, + a value of 0x0 means 0degrees, + a value of 0x40000000 means 90degrees +STATUS (4 bytes, signed integer): status of the telescope, currently unused. + status=0 means ok, status<0 means some error +*/ + + +#define DEG2DEC(degr) ((int32_t)(degr / 90. * ((double)0x40000000))) +#define DEG2RA(degr) ((uint32_t)(degr / 180. * ((double)0x80000000))) +#define HRS2RA(degr) ((uint32_t)(degr / 12. * ((double)0x80000000))) +#define DEC2DEG(i32) (((double)i32)*90./((double)0x40000000)) +#define RA2DEG(u32) (((double)u32)*180. /((double)0x80000000)) +#define RA2HRS(u32) (((double)u32)*12. /((double)0x80000000)) + +typedef struct __attribute__((__packed__)){ + uint16_t len; + uint16_t type; + uint64_t time; + uint32_t ra; + int32_t dec; +} indata; + +typedef struct __attribute__((__packed__)){ + uint16_t len; + uint16_t type; + uint64_t time; + uint32_t ra; + int32_t dec; + int32_t status; +} outdata; + +/** + * @brief proc_data - process data received from Stellarium + * @param data - raw data + * @param len - its length + * @return true if all OK + */ +static bool proc_data(uint8_t *data, ssize_t len){ + FNAME(); + if(len != sizeof(indata)){ + WARNX("Bad data size: got %zd instead of %zd!", len, sizeof(indata)); + return false; + } + indata *dat = (indata*)data; + uint16_t L, T; + uint32_t ra; + int32_t dec; +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ + L = le16toh(dat->len); T = le16toh(dat->type); + ra = le32toh(dat->ra); + dec = (int32_t)le32toh((uint32_t)dat->dec); +#else + L = dat->len; T = dat->type; + ra = dat->ra; dec = dat->dec; +#endif + DBG("got message with len %u & type %u", L, T); + if(L != len){ + WARNX("Length of message != msg->len"); + return false; + } + if(T){ + WARNX("Wrong message type"); + return false; + } + // convert RA/DEC to degrees + double tagRA = RA2DEG(ra), tagDec = DEC2DEG(dec); + DBG("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec); + LOGMSG("(stellarium) RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec); + // check RA/DEC + horizCrds_t hnow; // without refraction + polarCrds_t p2000, pnow; + p2000.ra = DEG2RAD(tagRA); + p2000.dec = DEG2RAD(tagDec); + // now J2000 obs Jnow + if(!get_ObsPlace(NULL, &p2000, &pnow, &hnow)){ + WARNX("Can't convert coordinates to Jnow"); + return false; + } + tagRA = RAD2DEG(pnow.ra - pnow.eo); + tagDec = RAD2DEG(pnow.dec); + return (mount_setInpRA(tagRA) && mount_setInpDec(tagDec)); +} + +/** + * Send data to user + * @param data - data to send + * @param dlen - data length + * @param sockfd - socket fd for sending data + * @return false if client disconnected + */ +float send_data(uint8_t *data, size_t dlen, int sockfd){ + ssize_t sent = send(sockfd, data, dlen, MSG_NOSIGNAL); + if(sent != (ssize_t)dlen){ + if(sent == -1 && errno != EINTR){ + WARN("Disconnected?"); + return false; + } + WARN("write()"); + } + return true; +} + +/** + * main socket service procedure + */ +static void *handle_socket(void *sockd){ + FNAME(); + if(!isrunning) return NULL; + outdata dout; + int sock = *(int*)sockd; + dout.len = htole16(sizeof(outdata)); + dout.type = 0; + while(isrunning){ + // get coordinates + double RA = 0., Decl = 0.; + if((dout.status = mount_getcoords(&RA, &Decl)) == MNT_S_ERROR){ + WARNX("Error: can't get coordinates"); + sleep(1); + continue; + } + //DBG("got : %g/%g", RA, Decl); + dout.ra = htole32(HRS2RA(RA)); + dout.dec = (int32_t)htole32(DEG2DEC(Decl)); + if(!send_data((uint8_t*)&dout, sizeof(outdata), sock)) break; + //DBG("sent ra = %g, dec = %g", RA2HRS(dout.ra), DEC2DEG(dout.dec)); + if(!sl_canread(sock)) continue; + // fill incoming buffer + uint8_t buff[BUFLEN]; + ssize_t rd = read(sock, buff, BUFLEN-1); + if(rd <= 0){ // error or disconnect + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + WARNX("Client disconnected?"); + break; + } + buff[rd] = 0; + DBG("read %zd (%s)", rd, buff); + if(!proc_data(buff, rd)) dout.status = -1; + else dout.status = 0; + } + close(sock); + return NULL; +} + +// Main loop thread: wait connections over socket and create one thread for each +static void* start(void *F){ + if(isrunning){ + WARNX("already running"); + return NULL; + } + if(!F){ + WARNX("start(): No arg"); + return NULL; + } + int sockfd = *((int*)F); + if(sockfd < 0){ + WARNX("start(): sockfd=%d", sockfd); + return NULL; + } + DBG("Start main loop, sockfd=%d", sockfd); + isrunning = true; + // Main loop + while(isrunning){ + socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_in myaddr; + int newsock; + newsock = accept(sockfd, (struct sockaddr*)&myaddr, &size); + if(newsock <= 0){ + if(errno == EAGAIN) continue; // nothing available + WARN("accept()"); + LOGWARN("Stellarium socket error in accept()"); + sleep(1); + continue; + } + struct sockaddr_in peer; + socklen_t peer_len = sizeof(peer); + if(getpeername(newsock, (struct sockaddr*)&peer, &peer_len) == -1){ + LOGWARN("Stellarium socket error in getpeername()"); + WARN("getpeername()"); + close(newsock); + continue; + } + int sockport = -1; + if(getsockname(newsock, (struct sockaddr*)&peer, &peer_len) == 0){ + sockport = ntohs(peer.sin_port); + } + char *peerIP = inet_ntoa(peer.sin_addr); + LOGMSG("Got connection from %s @ %d", peerIP, sockport); + DBG("Peer's IP address is: %s (@port %d)\n", peerIP, sockport); + /*if(strcmp(peerIP, ACCEPT_IP) && strcmp(peerIP, "127.0.0.1")){ + WARNX("Wrong IP"); + close(newsock); + continue; + }*/ + pthread_t rthrd; + if(pthread_create(&rthrd, NULL, handle_socket, (void*)&newsock)){ + WARN("Can't create socket thread"); + LOGERR("Stellarium socket: error creating listen thread"); + }else{ + DBG("Thread created, detouch"); + pthread_detach(rthrd); // don't care about thread state + } + } + close(sockfd); + return NULL; +} + +bool stellarium_start(int sockfd){ + int fd = sockfd; + if(pthread_create(&mainthread, NULL, start, (void*)&fd)){ + WARN("pthread_create()"); + return false; + } + DBG("Stellarium server started"); + return true; +} + +void stellarium_stop(){ + if(!isrunning) return; + isrunning = false; + pthread_join(mainthread, NULL); +} diff --git a/Daemons/10micron_stellarium/stellarium.h b/Daemons/10micron_stellarium/stellarium.h new file mode 100644 index 0000000..43e799e --- /dev/null +++ b/Daemons/10micron_stellarium/stellarium.h @@ -0,0 +1,22 @@ +/* + * This file is part of the mountdaemon_10micron project. + * Copyright 2026 Edward V. Emelianov . + * + * 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 . + */ + +#pragma once + +bool stellarium_start(int sockfd); +void stellarium_stop(); diff --git a/Daemons/10micron_stellarium/Makefile b/Daemons/deprecated/10micron_stellarium/Makefile similarity index 100% rename from Daemons/10micron_stellarium/Makefile rename to Daemons/deprecated/10micron_stellarium/Makefile diff --git a/Daemons/10micron_stellarium/Readme b/Daemons/deprecated/10micron_stellarium/Readme similarity index 100% rename from Daemons/10micron_stellarium/Readme rename to Daemons/deprecated/10micron_stellarium/Readme diff --git a/Daemons/10micron_stellarium/StelD.cflags b/Daemons/deprecated/10micron_stellarium/StelD.cflags similarity index 100% rename from Daemons/10micron_stellarium/StelD.cflags rename to Daemons/deprecated/10micron_stellarium/StelD.cflags diff --git a/Daemons/10micron_stellarium/StelD.config b/Daemons/deprecated/10micron_stellarium/StelD.config similarity index 100% rename from Daemons/10micron_stellarium/StelD.config rename to Daemons/deprecated/10micron_stellarium/StelD.config diff --git a/Daemons/deprecated/10micron_stellarium/StelD.creator b/Daemons/deprecated/10micron_stellarium/StelD.creator new file mode 100644 index 0000000..e94cbbd --- /dev/null +++ b/Daemons/deprecated/10micron_stellarium/StelD.creator @@ -0,0 +1 @@ +[General] diff --git a/Daemons/10micron_stellarium/StelD.creator.user b/Daemons/deprecated/10micron_stellarium/StelD.creator.user similarity index 100% rename from Daemons/10micron_stellarium/StelD.creator.user rename to Daemons/deprecated/10micron_stellarium/StelD.creator.user diff --git a/Daemons/10micron_stellarium/StelD.creator.user.7bd84e3.22 b/Daemons/deprecated/10micron_stellarium/StelD.creator.user.7bd84e3.22 similarity index 100% rename from Daemons/10micron_stellarium/StelD.creator.user.7bd84e3.22 rename to Daemons/deprecated/10micron_stellarium/StelD.creator.user.7bd84e3.22 diff --git a/Daemons/10micron_stellarium/StelD.creator.user.cf63021.4.9-pre1 b/Daemons/deprecated/10micron_stellarium/StelD.creator.user.cf63021.4.9-pre1 similarity index 100% rename from Daemons/10micron_stellarium/StelD.creator.user.cf63021.4.9-pre1 rename to Daemons/deprecated/10micron_stellarium/StelD.creator.user.cf63021.4.9-pre1 diff --git a/Daemons/deprecated/10micron_stellarium/StelD.cxxflags b/Daemons/deprecated/10micron_stellarium/StelD.cxxflags new file mode 100644 index 0000000..6435dfc --- /dev/null +++ b/Daemons/deprecated/10micron_stellarium/StelD.cxxflags @@ -0,0 +1 @@ +-std=c++17 \ No newline at end of file diff --git a/Daemons/10micron_stellarium/StelD.files b/Daemons/deprecated/10micron_stellarium/StelD.files similarity index 100% rename from Daemons/10micron_stellarium/StelD.files rename to Daemons/deprecated/10micron_stellarium/StelD.files diff --git a/Daemons/10micron_stellarium/StelD.includes b/Daemons/deprecated/10micron_stellarium/StelD.includes similarity index 100% rename from Daemons/10micron_stellarium/StelD.includes rename to Daemons/deprecated/10micron_stellarium/StelD.includes diff --git a/Daemons/10micron_stellarium/cmdlnopts.c b/Daemons/deprecated/10micron_stellarium/cmdlnopts.c similarity index 100% rename from Daemons/10micron_stellarium/cmdlnopts.c rename to Daemons/deprecated/10micron_stellarium/cmdlnopts.c diff --git a/Daemons/10micron_stellarium/cmdlnopts.h b/Daemons/deprecated/10micron_stellarium/cmdlnopts.h similarity index 100% rename from Daemons/10micron_stellarium/cmdlnopts.h rename to Daemons/deprecated/10micron_stellarium/cmdlnopts.h diff --git a/Daemons/10micron_stellarium/daemon.c b/Daemons/deprecated/10micron_stellarium/daemon.c similarity index 100% rename from Daemons/10micron_stellarium/daemon.c rename to Daemons/deprecated/10micron_stellarium/daemon.c diff --git a/Daemons/deprecated/10micron_stellarium/emulation.c b/Daemons/deprecated/10micron_stellarium/emulation.c new file mode 100644 index 0000000..81ebc1f --- /dev/null +++ b/Daemons/deprecated/10micron_stellarium/emulation.c @@ -0,0 +1,103 @@ +/* + * geany_encoding=koi8-r + * emulation.c + * + * Copyright 2018 Edward V. Emelianov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ +#include "math.h" +#include "emulation.h" +#include "usefull_macro.h" + +// emulation speed over RA & DEC (0.5 degr per sec) +#define RA_SPEED (0.033) +#define DECL_SPEED (0.5) + +// current coordinates +static double RA = 0., DECL = 0.; +// target coordinates +static double RAtarg = 0., DECLtarg = 0.; +// coordinates @ guiding start +static double RA0 = 0., DECL0 = 0.; +static double raspeed = 0.; +// ==1 if pointing +static int pointing = 0; +// pointing start time +static double tstart = -1.; + +/** + * send coordinates to telescope emulation + * @param ra - right ascention (hours) + * @param decl - declination (degrees) + * @return 1 if all OK + */ +int point_emulation(double ra, double decl){ + DBG("(emul) Send ra=%g, decl=%g", ra, decl); + putlog("(emul) Send ra=%g, decl=%g", ra, decl); + RAtarg = ra; DECLtarg = decl; + RA0 = RA; DECL0 = DECL; + raspeed = (RAtarg > RA) ? RA_SPEED : -RA_SPEED; + if(fabs(RAtarg - RA) > 12.){ // go to opposite direction + raspeed = -raspeed; + } + tstart = dtime(); + pointing = 1; + return 0; +} + +static double getradiff(){ + double diff = RAtarg - RA; + if(raspeed < 0.) diff = -diff; + if(diff > 12.) diff -= 24.; + else if(diff < -12.) diff += 24.; + return fabs(diff); +} + +/** + * get coordinates (emulation) + * @return 1 if all OK + */ +int get_emul_coords(double *ra, double *decl){ + if(pointing){ + DBG("RA/DEC: targ: %g/%g, cur: %g/%g, start: %g/%g", RAtarg, DECLtarg, RA, DECL, RA0, DECL0); + // diff < speed? stop + if((fabs(RAtarg - RA) < RA_SPEED && fabs(DECLtarg - DECL) < DECL_SPEED)){ + RA = RAtarg; + DECL = DECLtarg; + pointing = 0; // guiding + DBG("@ target"); + }else{ // calculate new coordinates + double radiff = getradiff(), decldiff = fabs(DECLtarg - DECL); + double tdiff = dtime() - tstart; + RA = RA0 + raspeed * tdiff; + DBG("RA=%g", RA); + if(getradiff() > radiff) RA = RAtarg; + DBG("RA=%g", RA); + if(RA < 0.) RA += 24.; + else if(RA > 24.) RA -= 24.; + DBG("RA=%g", RA); + double sign = (DECLtarg > DECL) ? 1. : -1.; + DECL = DECL0 + sign * DECL_SPEED * tdiff; + if(fabs(DECLtarg - DECL) > decldiff) DECL = DECLtarg; + DBG("RA/DEC: targ: %g/%g, cur: %g/%g, start: %g/%g", RAtarg, DECLtarg, RA, DECL, RA0, DECL0); + } + } + if(ra) *ra = RA; + if(decl) *decl = DECL; + return 1; +} diff --git a/Daemons/deprecated/10micron_stellarium/emulation.h b/Daemons/deprecated/10micron_stellarium/emulation.h new file mode 100644 index 0000000..c15fa71 --- /dev/null +++ b/Daemons/deprecated/10micron_stellarium/emulation.h @@ -0,0 +1,30 @@ +/* + * geany_encoding=koi8-r + * emulation.h + * + * Copyright 2018 Edward V. Emelianov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + */ +#pragma once +#ifndef __EMULATION_H__ +#define __EMULATION_H__ + +int point_emulation(double ra, double decl); +int get_emul_coords(double *ra, double *decl); + +#endif // __EMULATION_H__ diff --git a/Daemons/10micron_stellarium/libsofa.c b/Daemons/deprecated/10micron_stellarium/libsofa.c similarity index 100% rename from Daemons/10micron_stellarium/libsofa.c rename to Daemons/deprecated/10micron_stellarium/libsofa.c diff --git a/Daemons/10micron_stellarium/libsofa.h b/Daemons/deprecated/10micron_stellarium/libsofa.h similarity index 100% rename from Daemons/10micron_stellarium/libsofa.h rename to Daemons/deprecated/10micron_stellarium/libsofa.h diff --git a/Daemons/deprecated/10micron_stellarium/main.c b/Daemons/deprecated/10micron_stellarium/main.c new file mode 100644 index 0000000..9b7a19e --- /dev/null +++ b/Daemons/deprecated/10micron_stellarium/main.c @@ -0,0 +1,522 @@ +/* + * main.c + * + * Copyright 2014 Edward V. Emelianov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "emulation.h" +#include "libsofa.h" +#include "main.h" +#include "socket.h" +#include "telescope.h" + +// daemon.c +extern void check4running(char *self, char *pidfilename, void (*iffound)(pid_t pid)); + +// Max amount of connections +#define BACKLOG (10) +#define BUFLEN (1024) +// pause for incoming message waiting (out coordinates sent after that timeout) +#define SOCK_TMOUT (1) + +static pid_t childpid = 1; // PID of child process +volatile int global_quit = 0; +// quit by signal +void signals(int sig){ + signal(sig, SIG_IGN); + if(childpid){ // parent process + restore_tty(); // restore all parameters + unlink(GP->pidfile); // and remove pidfile + weatherserver_disconnect(); + } + DBG("Get signal %d, quit.\n", sig); + global_quit = 1; + if(childpid) putlog("PID %d exit with status %d after child's %d death", getpid(), sig, childpid); + else WARNX("Child %d died with %d", getpid(), sig); + sleep(1); + exit(sig); +} + +// search a first word after needle without spaces +char* stringscan(char *str, char *needle){ + char *a, *e; + char *end = str + strlen(str); + a = strstr(str, needle); + if(!a) return NULL; + a += strlen(needle); + while (a < end && (*a == ' ' || *a == '\r' || *a == '\t')) a++; + if(a >= end) return NULL; + e = strchr(a, ' '); + if(e) *e = 0; + return a; +} + +/** + * Send data to user + * @param data - data to send + * @param dlen - data length + * @param sockfd - socket fd for sending data + * @return 0 if failed + */ +int send_data(uint8_t *data, size_t dlen, int sockfd){ + size_t sent = write(sockfd, data, dlen); + if(sent != dlen){ + WARN("write()"); + return 0; + } + return 1; +} + +//read: 0x14 0x0 0x0 0x0 0x5b 0x5a 0x2e 0xc6 0x8c 0x23 0x5 0x0 0x23 0x9 0xe5 0xaf 0x23 0x2e 0x34 0xed +// command: goto 16h29 24.45 -26d25 55.62 +/* + LITTLE-ENDIAN!!! + from client: +LENGTH (2 bytes, integer): length of the message +TYPE (2 bytes, integer): 0 +TIME (8 bytes, integer): current time on the server computer in microseconds + since 1970.01.01 UT. Currently unused. +RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) + a value of 0x100000000 = 0x0 means 24h=0h, + a value of 0x80000000 means 12h +DEC (4 bytes, signed integer): declination of the telescope (J2000) + a value of -0x40000000 means -90degrees, + a value of 0x0 means 0degrees, + a value of 0x40000000 means 90degrees + +to client: +LENGTH (2 bytes, integer): length of the message +TYPE (2 bytes, integer): 0 +TIME (8 bytes, integer): current time on the server computer in microseconds + since 1970.01.01 UT. Currently unused. +RA (4 bytes, unsigned integer): right ascension of the telescope (J2000) + a value of 0x100000000 = 0x0 means 24h=0h, + a value of 0x80000000 means 12h +DEC (4 bytes, signed integer): declination of the telescope (J2000) + a value of -0x40000000 means -90degrees, + a value of 0x0 means 0degrees, + a value of 0x40000000 means 90degrees +STATUS (4 bytes, signed integer): status of the telescope, currently unused. + status=0 means ok, status<0 means some error +*/ + +#define DEG2DEC(degr) ((int32_t)(degr / 90. * ((double)0x40000000))) +#define HRS2RA(hrs) ((uint32_t)(hrs / 12. * ((double)0x80000000))) +#define DEC2DEG(i32) (((double)i32)*90./((double)0x40000000)) +#define RA2HRS(u32) (((double)u32)*12. /((double)0x80000000)) + +typedef struct __attribute__((__packed__)){ + uint16_t len; + uint16_t type; + uint64_t time; + uint32_t ra; + int32_t dec; +} indata; + +typedef struct __attribute__((__packed__)){ + uint16_t len; + uint16_t type; + uint64_t time; + uint32_t ra; + int32_t dec; + int32_t status; +} outdata; + +/** + * convert RA/DEC to string in forman RA: HH:MM:SS.SS, DEC: DD:MM:SS.S + */ +char *radec2str(double ra, double dec){ + static char buf[1024]; + char sign = '+'; + if(dec < 0){ + sign = '-'; + dec = -dec; + } + + int h = (int)ra; + ra -= h; ra *= 60.; + int m = (int)ra; + ra -= m; ra *= 60.; + + int d = (int) dec; + dec -= d; dec *= 60.; + int dm = (int)dec; + dec -= dm; dec *= 60.; + snprintf(buf, 1024, "%d:%d:%.2f %c%d:%d:%.1f", h,m,ra, sign,d,dm,dec); + return buf; +} + +/** + * send input RA/Decl (j2000!) coordinates to tel + * ra in hours (0..24), decl in degrees (-90..90) + * @return 1 if all OK + */ +int setCoords(double ra, double dec){ + char *radec = radec2str(ra, dec); + DBG("Set RA/Decl to %s", radec); + putlog("Try to set RA/Decl to %s", radec); + int (*pointfunction)(double, double) = point_telescope; + if(GP->emulation) pointfunction = point_emulation; + return pointfunction(ra, dec); +} + +/** + * @brief proc_data - process data received from Stellarium + * @param data - raw data + * @param len - its length + * @return 1 if all OK + */ +int proc_data(uint8_t *data, ssize_t len){ + FNAME(); + if(len != sizeof(indata)){ + WARNX("Bad data size: got %zd instead of %zd!", len, sizeof(indata)); + return 0; + } + indata *dat = (indata*)data; + uint16_t L, T; + //uint64_t tim; + uint32_t ra; + int32_t dec; +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ + L = le16toh(dat->len); T = le16toh(dat->type); + //tim = le64toh(dat->time); + ra = le32toh(dat->ra); + dec = (int32_t)le32toh((uint32_t)dat->dec); +#else + L = dat->len; T = dat->type; + //tim = dat->time; + ra = dat->ra; dec = dat->dec; +#endif + DBG("got message with len %u & type %u", L, T); + if(L != len){ + WARNX("Length of message != msg->len"); + return 0; + } + if(T){ + WARNX("Wrong message type"); + return 0; + } + // convert RA/DEC to hours/degrees + double tagRA = RA2HRS(ra), tagDec = DEC2DEG(dec); + DBG("RA: %u (%g), DEC: %d (%g)", ra, tagRA, dec, tagDec); + putlog("RA: %u (%g degr), DEC: %d (%g degr)", ra, tagRA, dec, tagDec); + // check RA/DEC + horizCrds hnow; // without refraction + polarCrds p2000, pnow; + p2000.ra = tagRA/12. * M_PI; + p2000.dec = tagDec * ERFA_DD2R; + // now J2000 obs Jnow + if(get_ObsPlace(NULL, &p2000, NULL, &pnow, &hnow)){ + WARNX("Can't convert coordinates to Jnow"); + return 0; + } +/* + int i[4], j[4]; char pm, pm1; + eraA2af(2, hnow.az, &pm, i); + eraA2af(2, hnow.zd, &pm1, j); + DBG("az: %c%02d %02d %02d.%2.d, zd: %c%02d %02d %02d.%2.d", + pm, i[0],i[1],i[2],i[3], + pm1,j[0],j[1],j[2],j[3]); + eraA2af(2, M_PI_2 - hnow.zd, &pm, i); + DBG("h: %c%02d %02d %02d.%2.d", pm, i[0],i[1],i[2],i[3]); +*/ + if(hnow.zd > 80.*ERFA_DD2R){ + WARNX("Z > 80degr (%g), stop telescope", hnow.zd * ERFA_DR2D); + putlog("Z=%.1f > 80 - stop!", hnow.zd * ERFA_DR2D); + stop_telescope(); + return 0; + } + tagRA = (pnow.ra - pnow.eo) / M_PI * 12.; + tagDec = pnow.dec / ERFA_DD2R; + if(!setCoords(tagRA, tagDec)) return 0; + return 1; +} + +/** + * main socket service procedure + */ +void *handle_socket(void *sockd){ + FNAME(); + if(global_quit) return NULL; + outdata dout; + int sock = *(int*)sockd; + dout.len = htole16(sizeof(outdata)); + dout.type = 0; + int (*getcoords)(double*, double*) = get_telescope_coords; + if(GP->emulation) getcoords = get_emul_coords; + while(!global_quit){ + // get coordinates + double RA = 0., Decl = 0.; + if((dout.status = getcoords(&RA, &Decl)) < 0){ + WARNX("Error: can't get coordinates"); + sleep(1); + continue; + } + //DBG("got : %g/%g", RA, Decl); + dout.ra = htole32(HRS2RA(RA)); + dout.dec = (int32_t)htole32(DEG2DEC(Decl)); + if(!send_data((uint8_t*)&dout, sizeof(outdata), sock)) break; + //DBG("sent ra = %g, dec = %g", RA2HRS(dout.ra), DEC2DEG(dout.dec)); + fd_set readfds; + struct timeval timeout; + FD_ZERO(&readfds); + FD_SET(sock, &readfds); + timeout.tv_sec = SOCK_TMOUT; // wait not more than SOCK_TMOUT second + timeout.tv_usec = 0; + int sel = select(sock + 1 , &readfds , NULL , NULL , &timeout); + if(sel < 0){ + if(errno != EINTR) + WARN("select()"); + continue; + } + if(!(FD_ISSET(sock, &readfds))) continue; + // fill incoming buffer + uint8_t buff[BUFLEN+1]; + ssize_t rd = read(sock, buff, BUFLEN); + buff[rd] = 0; + DBG("read %zd (%s)", rd, buff); + if(rd <= 0){ // error or disconnect + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + break; + } + /************************************** + * DO SOMETHING WITH DATA * + **************************************/ + if(!proc_data(buff, rd)) dout.status = -1; + else dout.status = 0; + } + close(sock); + return NULL; +} + +// thread writing FITS-header file +static void *hdrthread(_U_ void *buf){ + // write FITS-header at most once per second + while(!global_quit){ + wrhdr(); + usleep(100000); // give a chance to write/read for others + } + return NULL; +} + +/** + * @brief opensocket - open socket to port `port` + * @return socket fd or <0 if failed + */ +static int opensocket(char *port){ + if(!port) return -1; + int reuseaddr = 1; + int sock; + struct addrinfo hints, *res, *p; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + DBG("try to open port %s", port); + if(getaddrinfo(NULL, port, &hints, &res) != 0){ + WARN("getaddrinfo()"); + return 0; + } + /* + struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr; + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(ia->sin_addr), str, INET_ADDRSTRLEN); + */ + // loop through all the results and bind to the first we can + for(p = res; p != NULL; p = p->ai_next){ + if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){ + WARN("socket()"); + continue; + } + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ + WARN("setsockopt()"); + close(sock); + continue; + } + if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ + WARN("bind()"); + close(sock); + continue; + } + break; // if we get here, we must have connected successfully + } + freeaddrinfo(res); + // Listen + if(listen(sock, BACKLOG) == -1){ + WARN("listen"); + putlog("listen() error"); + } + DBG("listen at %s", port); + putlog("listen at %s", port); + return sock; +} + +/** + * @brief waitconn + * @param sock - socket fd to accept() + * @param connthread - thread which to run when connection accepted (it's parameter - socket fd) + */ +static void waitconn(int sock, void *(*connthread)(void*)){ + // Main loop + while(!global_quit){ + socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_in myaddr; + int newsock; + newsock = accept(sock, (struct sockaddr*)&myaddr, &size); + if(newsock <= 0){ + WARN("accept()"); + sleep(1); + continue; + } + struct sockaddr_in peer; + socklen_t peer_len = sizeof(peer); + if(getpeername(newsock, (struct sockaddr*)&peer, &peer_len) == -1){ + WARN("getpeername()"); + close(newsock); + continue; + } + int sockport = -1; + if(getsockname(newsock, (struct sockaddr*)&peer, &peer_len) == 0){ + sockport = ntohs(peer.sin_port); + } + char *peerIP = inet_ntoa(peer.sin_addr); + putlog("Got connection from %s @ %d", peerIP, sockport); + DBG("Peer's IP address is: %s (@port %d)\n", peerIP, sockport); + /*if(strcmp(peerIP, ACCEPT_IP) && strcmp(peerIP, "127.0.0.1")){ + WARNX("Wrong IP"); + close(newsock); + continue; + }*/ + pthread_t rthrd; + if(pthread_create(&rthrd, NULL, connthread, (void*)&newsock)){ + putlog("Error creating listen thread"); + ERR(_("Can't create socket thread")); + }else{ + DBG("Thread created, detouch"); + pthread_detach(rthrd); // don't care about thread state + } + } + close(sock); +} + +// thread working with terminal +static void *termthread(_U_ void *buf){ + int sock = opensocket(GP->dbgport); + if(sock < 0){ + putlog("Can't open debugging socket @ port %s", GP->dbgport); + ERRX("Can't open debug socket"); + } + waitconn(sock, term_thread); + return NULL; +} + +static inline void main_proc(){ + pthread_t hthrd, termthrd; + // connect to telescope + if(!GP->emulation){ + if(!connect_telescope(GP->device, GP->crdsfile)){ + ERRX(_("Can't connect to telescope device")); + } + if(pthread_create(&hthrd, NULL, hdrthread, NULL)) + ERR(_("Can't create writing thread")); + if(pthread_create(&termthrd, NULL, termthread, NULL)) + ERR(_("Can't create terminal thread")); + } + // connect to weather daemon + if(!weatherserver_connect()){ + DBG("Can't connect to weather server, will try later"); + } + // open socket + int sock = opensocket(GP->port); + if(sock < 0){ + putlog("Can't open socket @ port %s", GP->port); + ERRX("Can't open stellarium socket"); + } + waitconn(sock, handle_socket); + usleep(10000); + pthread_cancel(hthrd); // cancel reading thread + pthread_cancel(termthrd); + pthread_join(hthrd, NULL); + pthread_join(termthrd, NULL); +} + +int main(int argc, char **argv){ + char *self = strdup(argv[0]); + GP = parse_args(argc, argv); + initial_setup(); + + signal(SIGTERM, signals); // kill (-15) - quit + signal(SIGKILL, signals); // kill (-9) - quit + signal(SIGHUP, SIG_IGN); // hup - ignore + signal(SIGINT, signals); // ctrl+C - quit + signal(SIGQUIT, signals); // ctrl+\ - quit + signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z + + int fd; + if((fd = open(GP->crdsfile, O_WRONLY | O_TRUNC | O_CREAT, 0644)) < 0) // test FITS-header file for writing + ERR(_("Can't open %s for writing"), GP->crdsfile); + close(fd); + + printf("Daemonize\n"); +#ifndef EBUG // daemonize only in release mode + if(daemon(1, 0)){ + putlog("Err: daemon()"); + ERR("daemon()"); + } +#endif // EBUG + check4running(self, GP->pidfile, NULL); + if(GP->logfile) openlogfile(GP->logfile); + putlog("Starting, master PID=%d", getpid()); + +#ifndef EBUG + while(1){ + childpid = fork(); + if(childpid < 0){ + putlog("fork() error"); + ERR("ERROR on fork"); + } + if(childpid){ + WARNX(_("Created child with PID %d\n"), childpid); + DBG("Created child with PID %d\n", childpid); + wait(NULL); + WARNX(_("Child %d died\n"), childpid); + DBG("Child %d died\n", childpid); + }else{ + prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies + main_proc(); + return 0; + } + } +#else + main_proc(); +#endif + + return 0; +} diff --git a/Daemons/10micron_stellarium/main.h b/Daemons/deprecated/10micron_stellarium/main.h similarity index 100% rename from Daemons/10micron_stellarium/main.h rename to Daemons/deprecated/10micron_stellarium/main.h diff --git a/Daemons/10micron_stellarium/parseargs.c b/Daemons/deprecated/10micron_stellarium/parseargs.c similarity index 100% rename from Daemons/10micron_stellarium/parseargs.c rename to Daemons/deprecated/10micron_stellarium/parseargs.c diff --git a/Daemons/10micron_stellarium/parseargs.h b/Daemons/deprecated/10micron_stellarium/parseargs.h similarity index 100% rename from Daemons/10micron_stellarium/parseargs.h rename to Daemons/deprecated/10micron_stellarium/parseargs.h diff --git a/Daemons/10micron_stellarium/socket.c b/Daemons/deprecated/10micron_stellarium/socket.c similarity index 100% rename from Daemons/10micron_stellarium/socket.c rename to Daemons/deprecated/10micron_stellarium/socket.c diff --git a/Daemons/10micron_stellarium/socket.h b/Daemons/deprecated/10micron_stellarium/socket.h similarity index 100% rename from Daemons/10micron_stellarium/socket.h rename to Daemons/deprecated/10micron_stellarium/socket.h diff --git a/Daemons/10micron_stellarium/telescope.c b/Daemons/deprecated/10micron_stellarium/telescope.c similarity index 100% rename from Daemons/10micron_stellarium/telescope.c rename to Daemons/deprecated/10micron_stellarium/telescope.c diff --git a/Daemons/10micron_stellarium/telescope.h b/Daemons/deprecated/10micron_stellarium/telescope.h similarity index 100% rename from Daemons/10micron_stellarium/telescope.h rename to Daemons/deprecated/10micron_stellarium/telescope.h diff --git a/Daemons/10micron_stellarium/usefull_macro.c b/Daemons/deprecated/10micron_stellarium/usefull_macro.c similarity index 100% rename from Daemons/10micron_stellarium/usefull_macro.c rename to Daemons/deprecated/10micron_stellarium/usefull_macro.c diff --git a/Daemons/10micron_stellarium/usefull_macro.h b/Daemons/deprecated/10micron_stellarium/usefull_macro.h similarity index 100% rename from Daemons/10micron_stellarium/usefull_macro.h rename to Daemons/deprecated/10micron_stellarium/usefull_macro.h