From b85b89c468817e283d06d30f5c55d5bbc0b51f26 Mon Sep 17 00:00:00 2001 From: eddyem Date: Fri, 17 Mar 2017 19:12:56 +0300 Subject: [PATCH] Big job done. Add common (with SBIG all-sky) FITS file storage. --- Makefile | 4 +- allsky_logger/Makefile | 45 +++ allsky_logger/Readme.md | 11 + allsky_logger/cmdlnopts.c | 86 ++++++ allsky_logger/cmdlnopts.h | 41 +++ allsky_logger/imfunctions.c | 233 ++++++++++++++++ allsky_logger/imfunctions.h | 32 +++ allsky_logger/main.c | 45 +++ allsky_logger/parseargs.c | 497 +++++++++++++++++++++++++++++++++ allsky_logger/parseargs.h | 124 ++++++++ allsky_logger/socket.c | 349 +++++++++++++++++++++++ allsky_logger/socket.h | 51 ++++ allsky_logger/usefull_macros.c | 385 +++++++++++++++++++++++++ allsky_logger/usefull_macros.h | 140 ++++++++++ cmdlnopts.c | 6 +- cmdlnopts.h | 1 + main.c | 6 +- socket.c | 155 +++++----- term.c | 171 ++++++++++-- term.h | 44 ++- usefull_macros.c | 17 +- 21 files changed, 2313 insertions(+), 130 deletions(-) create mode 100644 allsky_logger/Makefile create mode 100644 allsky_logger/Readme.md create mode 100644 allsky_logger/cmdlnopts.c create mode 100644 allsky_logger/cmdlnopts.h create mode 100644 allsky_logger/imfunctions.c create mode 100644 allsky_logger/imfunctions.h create mode 100644 allsky_logger/main.c create mode 100644 allsky_logger/parseargs.c create mode 100644 allsky_logger/parseargs.h create mode 100644 allsky_logger/socket.c create mode 100644 allsky_logger/socket.h create mode 100644 allsky_logger/usefull_macros.c create mode 100644 allsky_logger/usefull_macros.h diff --git a/Makefile b/Makefile index a2b0c92..76649eb 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,9 @@ # run `make DEF=...` to add extra defines PROGRAM := boltwood -LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all +LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all -pthread SRCS := $(wildcard *.c) DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 -DEFINES += -DEBUG +#DEFINES += -DEBUG OBJDIR := mk CFLAGS += -O2 -Wall -Werror -Wextra -Wno-trampolines -std=gnu99 OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) diff --git a/allsky_logger/Makefile b/allsky_logger/Makefile new file mode 100644 index 0000000..09aba76 --- /dev/null +++ b/allsky_logger/Makefile @@ -0,0 +1,45 @@ +# run `make DEF=...` to add extra defines +PROGRAM := allsky_logger +LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all +LDFLAGS += -lcfitsio +SRCS := $(wildcard *.c) +DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 +OBJDIR := mk +CFLAGS += -O2 -Wall -Wextra -Wno-trampolines -std=gnu99 +DEFINES += -DEBUG +#CFLAGS += -Werror +OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) +DEPS := $(OBJS:.o=.d) +CC = gcc +#CXX = g++ + + +all : $(OBJDIR) $(PROGRAM) + +$(PROGRAM) : $(OBJS) + @echo -e "\t\tLD $(PROGRAM)" + $(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM) + +$(OBJDIR): + mkdir $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +-include $(DEPS) +endif + +$(OBJDIR)/%.o: %.c + @echo -e "\t\tCC $<" + $(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $< + +clean: + @echo -e "\t\tCLEAN" + @rm -f $(OBJS) $(DEPS) + @rmdir $(OBJDIR) 2>/dev/null || true + +xclean: clean + @rm -f $(PROGRAM) + +gentags: + CFLAGS="$(CFLAGS) $(DEFINES)" geany -g $(PROGRAM).c.tags *[hc] 2>/dev/null + +.PHONY: gentags clean xclean diff --git a/allsky_logger/Readme.md b/allsky_logger/Readme.md new file mode 100644 index 0000000..f6c3147 --- /dev/null +++ b/allsky_logger/Readme.md @@ -0,0 +1,11 @@ +Logger for SBIG all-sky +==================== + +Store raw FITS images from SBIG FITS dumps adding to headers cloud sensor +information. + +Files stored in subdirectories as ${PWD}/YYYY/MM/DD/hhmmss.fits + +The original FITS file should be rewrited by SBIG daemon, this daemon watch +for changes using inotify and as only file changed, store it appending boltwood +data. diff --git a/allsky_logger/cmdlnopts.c b/allsky_logger/cmdlnopts.c new file mode 100644 index 0000000..89e785c --- /dev/null +++ b/allsky_logger/cmdlnopts.c @@ -0,0 +1,86 @@ +/* geany_encoding=koi8-r + * cmdlnopts.c - the only function that parse cmdln args and returns glob parameters + * + * Copyright 2013 Edward V. Emelianoff + * + * 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 "cmdlnopts.h" +#include "usefull_macros.h" + +/* + * here are global parameters initialisation + */ +int help; +glob_pars G; + +#define DEFAULT_COMDEV "/dev/ttyUSB0" +// DEFAULTS +// default global parameters +glob_pars const Gdefault = { + .cwd = NULL, // current working directory + .port = "55555", // port to listen boltwood data + .server = NULL, // server name + .filename = NULL, // input file name +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +myoption cmdlnopts[] = { +// common options + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("port to connect (default: 55555)")}, + {"address", NEED_ARG, NULL, 'a', arg_string, APTR(&G.server), _("server name")}, + {"filename",NEED_ARG, NULL, 'i', arg_string, APTR(&G.filename), _("input FITS file name")}, + end_option +}; + +/** + * Parse command line options and return dynamically allocated structure + * to global parameters + * @param argc - copy of argc from main + * @param argv - copy of argv from main + * @return allocated structure with global parameters + */ +glob_pars *parse_args(int argc, char **argv){ + int i; + void *ptr; + ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); + // format of help: "Usage: progname [args]\n" + change_helpstring(_("Usage: %s [args] [working dir]\n\n\tWhere args are:\n")); + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ // the rest should be working directory + if(argc != 1){ + red(_("Too many arguments:\n")); + for (i = 0; i < argc; i++) + printf("\t%s\n", argv[i]); + printf(_("Should be path to archive directory\n")); + signals(1); + } + G.cwd = argv[0]; + } + return &G; +} + diff --git a/allsky_logger/cmdlnopts.h b/allsky_logger/cmdlnopts.h new file mode 100644 index 0000000..ab802ce --- /dev/null +++ b/allsky_logger/cmdlnopts.h @@ -0,0 +1,41 @@ +/* geany_encoding=koi8-r + * cmdlnopts.h - comand line options for parceargs + * + * Copyright 2013 Edward V. Emelianoff + * + * 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 __CMDLNOPTS_H__ +#define __CMDLNOPTS_H__ + +#include "parseargs.h" +#include "term.h" + +/* + * here are some typedef's for global data + */ +typedef struct{ + char *port; // port to connect + char *cwd; // change working directory to given + char *server; // server name + char *filename; // input file name +} glob_pars; + + +glob_pars *parse_args(int argc, char **argv); +#endif // __CMDLNOPTS_H__ diff --git a/allsky_logger/imfunctions.c b/allsky_logger/imfunctions.c new file mode 100644 index 0000000..031c072 --- /dev/null +++ b/allsky_logger/imfunctions.c @@ -0,0 +1,233 @@ +/* + * geany_encoding=koi8-r + * imfunctions.c - functions to work with image + * + * Copyright 2017 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 // strncasecmp +#include // sqrt +#include // time, gmtime etc +#include // save fits +#include // basename +#include // utimensat +#include // AT_... +#include +#include // sendfile + +#include "usefull_macros.h" +#include "imfunctions.h" +#include "term.h" +#include "socket.h" + +/** + * All image-storing functions modify ctime of saved files to be the time of + * exposition start! + */ +void modifytimestamp(char *filename, time_t tv_sec){ + if(!filename) return; + struct timespec times[2]; + memset(times, 0, 2*sizeof(struct timespec)); + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = tv_sec; // change mtime + if(utimensat(AT_FDCWD, filename, times, 0)) WARN(_("Can't change timestamp for %s"), filename); +} + +/** + * Test if `name` is a fits file with 2D image + * @return 1 if all OK + */ +int test_fits(char *name){ + fitsfile *fptr; + int status = 0, nfound, ret = 0; + long naxes[2]; + if(fits_open_file(&fptr, name, READONLY, &status)){ + goto retn; + } + // test image size + if(fits_read_keys_lng(fptr, "NAXIS", 1, 2, naxes, &nfound, &status)){ + goto retn; + } + if(naxes[0] > 0 && naxes[1] > 0) ret = 1; +retn: + if(status) fits_report_error(stderr, status); + fits_close_file(fptr, &status); + return ret; +} + + +/** + * copy FITS file with absolute path 'oldname' to 'newname' in current dir (using sendfile) + * @return 0 if all OK + * +static int copyfile(char *oldname, char *newname){ + struct stat st; + int ret = 1, ifd = -1, ofd = -1; + if(stat(oldname, &st) || !S_ISREG(st.st_mode)){ + WARN("stat()"); + goto rtn; + } + ifd = open(oldname, O_RDONLY); + ofd = open(newname, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); // rewrite existing files + if(ifd < 0 || ofd < 0){ + WARN("open()"); + goto rtn; + } + posix_fadvise(ifd, 0, 0, POSIX_FADV_SEQUENTIAL); // tell kernel that we want to copy file -> sequential + posix_fadvise(ofd, 0, 0, POSIX_FADV_SEQUENTIAL); + ssize_t bytes = sendfile(ofd, ifd, NULL, st.st_size); + if(bytes < 0){ + WARN("sendfile()"); + goto rtn; + } + if(bytes == st.st_size) ret = 0; + DBG("copy %s -> %s %ssuccesfull", oldname, newname, ret ? "un" : ""); +rtn: + if(ifd > 0) close(ifd); + if(ofd > 0) close(ofd); + return ret; +}*/ + +static void mymkdir(char *name){ + if(mkdir(name, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)){ + if(errno != EEXIST) + ERR("mkdir()"); + } + DBG("mkdir(%s)", name); +} + +static void gotodir(char *relpath){ // create directory structure + if(chdir(relpath)){ // can't chdir -> test + if(errno != ENOENT) ERR("Can't chdir"); + }else return; + // no such directory -> create it + char *p = relpath; + for(; *p; ++p){ + if(*p == '/'){ + *p = 0; + mymkdir(relpath); + *p = '/'; + } + } + mymkdir(relpath); + if(-1 == chdir(relpath)) ERR("chdir()"); +} + +#define TRYFITS(f, ...) \ +do{ int status = 0; \ + f(__VA_ARGS__, &status); \ + if (status){ \ + fits_report_error(stderr, status); \ + goto ret;} \ +}while(0) +#define WRITEIREC(k, v, c) \ +do{ int status = 0; \ + snprintf(record, FLEN_CARD, "%-8s=%21ld / %s", k, v, c); \ + fits_write_record(ofptr, record, &status); \ +}while(0) +#define WRITEDREC(k, v, c) \ +do{ int status = 0; \ + snprintf(record, FLEN_CARD, "%-8s=%21g / %s", k, v, c); \ + fits_write_record(ofptr, record, &status); \ +}while(0) + + +/** + * Try to store given FITS file adding boltwood's header + * @param name (i) - full path to FITS file + * @param data (i) - boltwood's header data + * file stored in current directory under YYYY/MM/DD/hh:mm:ss.fits.gz + * @return file descriptor for new notifications + */ +int store_fits(char *name, datarecord *data){ + DBG("Try to store %s", name); + double t0 = dtime(); + long modtm = 0; // modification time - to change later + char oldwd[PATH_MAX]; + fitsfile *fptr, *ofptr; + int nkeys, bitpix, naxis; + long naxes[2]; + uint16_t *imdat = NULL; + TRYFITS(fits_open_file, &fptr, name, READONLY); + TRYFITS(fits_get_img_param, fptr, 2, &bitpix, &naxis, naxes); + TRYFITS(fits_get_hdrspace, fptr, &nkeys, NULL); + DBG("%d keys", nkeys); + green("open & get hdr: %gs\n", dtime() - t0); + char dateobs[FLEN_KEYWORD], timeobs[FLEN_KEYWORD]; + TRYFITS(fits_read_key, fptr, TSTRING, "DATE-OBS", dateobs, NULL); + DBG("DATE-OBS: %s", dateobs); + TRYFITS(fits_read_key, fptr, TSTRING, "START", timeobs, NULL); + DBG("START: %s", timeobs); + TRYFITS(fits_read_key, fptr, TLONG, "UNIXTIME", &modtm, NULL); + DBG("MODTM: %ld", modtm); + if(!getcwd(oldwd, PATH_MAX)) ERR("getcwd()"); + DBG("Current working directory: %s", oldwd); + green("+ got headers: %gs\n", dtime() - t0); + gotodir(dateobs); + char newname[PATH_MAX]; + snprintf(newname, PATH_MAX, "%s.fits.gz", timeobs); + // remove if exists + unlink(newname); + TRYFITS(fits_create_file, &ofptr, newname); + TRYFITS(fits_create_img, ofptr, bitpix, naxis, naxes); + // copy all keywords + int i, status; + char record[81]; + // copy all the user keywords (not the structural keywords) + DBG("got %d user keys, copy them", nkeys); + for(i = 1; i <= nkeys; ++i){ + status = 0; + fits_read_record(fptr, i, record, &status); + DBG("status: %d", status); + if(!status && fits_get_keyclass(record) > TYP_CMPRS_KEY) + fits_write_record(ofptr, record, &status); + else + fits_report_error(stderr, status); + } + DBG("Boltwood data"); + // now the file copied -> add header from Boltwood's sensor + while(data->varname){ + if(data->type == INTEGR){ + WRITEIREC(data->keyname, data->data.i, data->comment); + }else{ + WRITEDREC(data->keyname, data->data.d, data->comment); + } + ++data; + } + long npix = naxes[0]*naxes[1]; + imdat = MALLOC(uint16_t, npix); + int anynul = 0; + TRYFITS(fits_read_img, fptr, TUSHORT, 1, npix, NULL, imdat, &anynul); + DBG("read, anynul = %d", anynul); + TRYFITS(fits_write_img, ofptr, TUSHORT, 1, npix, imdat); +ret: + FREE(imdat); + status = 0; + if(fptr) fits_close_file(fptr, &status); + status = 0; + if(ofptr){ + fits_close_file(ofptr, &status); + modifytimestamp(newname, (time_t)modtm); + } + // as cfitsio removes old file instead of trunkate it, we need to refresh inotify every time! + int fd = watch_fits(name); + if(chdir(oldwd)){ // return back to BD root directory + ERR("Can't chdir"); + }; + return fd; +} diff --git a/allsky_logger/imfunctions.h b/allsky_logger/imfunctions.h new file mode 100644 index 0000000..6c96f20 --- /dev/null +++ b/allsky_logger/imfunctions.h @@ -0,0 +1,32 @@ +/* + * geany_encoding=koi8-r + * imfunctions.h + * + * Copyright 2017 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 __IMFUNCTIONS_H__ +#define __IMFUNCTIONS_H__ +#include "socket.h" +#include "cmdlnopts.h" + +int test_fits(char *name); +int store_fits(char *name, datarecord *data); + +#endif // __IMFUNCTIONS_H__ diff --git a/allsky_logger/main.c b/allsky_logger/main.c new file mode 100644 index 0000000..0278c19 --- /dev/null +++ b/allsky_logger/main.c @@ -0,0 +1,45 @@ +/* geany_encoding=koi8-r + * main.c + * + * Copyright 2017 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 "usefull_macros.h" +#include +#include +#include +#include "cmdlnopts.h" +#include "imfunctions.h" +#include "socket.h" + +void signals(int signo){ + exit(signo); +} + +int main(int argc, char **argv){ + initial_setup(); + signal(SIGTERM, signals); // kill (-15) - quit + signal(SIGHUP, SIG_IGN); // hup - ignore + signal(SIGINT, signals); // ctrl+C - quit + signal(SIGQUIT, signals); // ctrl+\ - quit + signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z + glob_pars *G = parse_args(argc, argv); + if(!G->server) ERRX(_("Please, specify server name")); + if(!G->filename) ERRX(_("Please, specify the name of input FITS file")); + daemonize(G); + return 0; +} diff --git a/allsky_logger/parseargs.c b/allsky_logger/parseargs.c new file mode 100644 index 0000000..b235752 --- /dev/null +++ b/allsky_logger/parseargs.c @@ -0,0 +1,497 @@ +/* geany_encoding=koi8-r + * parseargs.c - parsing command line arguments & print help + * + * Copyright 2013 Edward V. Emelianoff + * + * 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 // printf +#include // getopt_long +#include // calloc, exit, strtoll +#include // assert +#include // strdup, strchr, strlen +#include // strcasecmp +#include // INT_MAX & so on +#include // gettext +#include // isalpha +#include "parseargs.h" +#include "usefull_macros.h" + +char *helpstring = "%s\n"; + +/** + * Change standard help header + * MAY consist ONE "%s" for progname + * @param str (i) - new format + */ +void change_helpstring(char *s){ + int pcount = 0, scount = 0; + char *str = s; + // check `helpstring` and set it to default in case of error + for(; pcount < 2; str += 2){ + if(!(str = strchr(str, '%'))) break; + if(str[1] != '%') pcount++; // increment '%' counter if it isn't "%%" + else{ + str += 2; // pass next '%' + continue; + } + if(str[1] == 's') scount++; // increment "%s" counter + }; + if(pcount > 1 || pcount != scount){ // amount of pcount and/or scount wrong + /// "Неправильный формат строки помощи" + ERRX(_("Wrong helpstring!")); + } + helpstring = s; +} + +/** + * Carefull atoll/atoi + * @param num (o) - returning value (or NULL if you wish only check number) - allocated by user + * @param str (i) - string with number must not be NULL + * @param t (i) - T_INT for integer or T_LLONG for long long (if argtype would be wided, may add more) + * @return TRUE if conversion sone without errors, FALSE otherwise + */ +static bool myatoll(void *num, char *str, argtype t){ + long long tmp, *llptr; + int *iptr; + char *endptr; + assert(str); + assert(num); + tmp = strtoll(str, &endptr, 0); + if(endptr == str || *str == '\0' || *endptr != '\0') + return FALSE; + switch(t){ + case arg_longlong: + llptr = (long long*) num; + *llptr = tmp; + break; + case arg_int: + default: + if(tmp < INT_MIN || tmp > INT_MAX){ + /// "Целое вне допустимого диапазона" + WARNX(_("Integer out of range")); + return FALSE; + } + iptr = (int*)num; + *iptr = (int)tmp; + } + return TRUE; +} + +// the same as myatoll but for double +// There's no NAN & INF checking here (what if they would be needed?) +static bool myatod(void *num, const char *str, argtype t){ + double tmp, *dptr; + float *fptr; + char *endptr; + assert(str); + tmp = strtod(str, &endptr); + if(endptr == str || *str == '\0' || *endptr != '\0') + return FALSE; + switch(t){ + case arg_double: + dptr = (double *) num; + *dptr = tmp; + break; + case arg_float: + default: + fptr = (float *) num; + *fptr = (float)tmp; + break; + } + return TRUE; +} + +/** + * Get index of current option in array options + * @param opt (i) - returning val of getopt_long + * @param options (i) - array of options + * @return index in array + */ +static int get_optind(int opt, myoption *options){ + int oind; + myoption *opts = options; + assert(opts); + for(oind = 0; opts->name && opts->val != opt; oind++, opts++); + if(!opts->name || opts->val != opt) // no such parameter + showhelp(-1, options); + return oind; +} + +/** + * reallocate new value in array of multiple repeating arguments + * @arg paptr - address of pointer to array (**void) + * @arg type - its type (for realloc) + * @return pointer to new (next) value + */ +void *get_aptr(void *paptr, argtype type){ + int i = 1; + void **aptr = *((void***)paptr); + if(aptr){ // there's something in array + void **p = aptr; + while(*p++) ++i; + } + size_t sz = 0; + switch(type){ + default: + case arg_none: + /// "Не могу использовать несколько параметров без аргументов!" + ERRX("Can't use multiple args with arg_none!"); + break; + case arg_int: + sz = sizeof(int); + break; + case arg_longlong: + sz = sizeof(long long); + break; + case arg_double: + sz = sizeof(double); + break; + case arg_float: + sz = sizeof(float); + break; + case arg_string: + sz = 0; + break; + /* case arg_function: + sz = sizeof(argfn *); + break;*/ + } + aptr = realloc(aptr, (i + 1) * sizeof(void*)); + *((void***)paptr) = aptr; + aptr[i] = NULL; + if(sz){ + aptr[i - 1] = malloc(sz); + }else + aptr[i - 1] = &aptr[i - 1]; + return aptr[i - 1]; +} + + +/** + * Parse command line arguments + * ! If arg is string, then value will be strdup'ed! + * + * @param argc (io) - address of argc of main(), return value of argc stay after `getopt` + * @param argv (io) - address of argv of main(), return pointer to argv stay after `getopt` + * BE CAREFUL! if you wanna use full argc & argv, save their original values before + * calling this function + * @param options (i) - array of `myoption` for arguments parcing + * + * @exit: in case of error this function show help & make `exit(-1)` + */ +void parseargs(int *argc, char ***argv, myoption *options){ + char *short_options, *soptr; + struct option *long_options, *loptr; + size_t optsize, i; + myoption *opts = options; + // check whether there is at least one options + assert(opts); + assert(opts[0].name); + // first we count how much values are in opts + for(optsize = 0; opts->name; optsize++, opts++); + // now we can allocate memory + short_options = calloc(optsize * 3 + 1, 1); // multiply by three for '::' in case of args in opts + long_options = calloc(optsize + 1, sizeof(struct option)); + opts = options; loptr = long_options; soptr = short_options; + // in debug mode check the parameters are not repeated +#ifdef EBUG + char **longlist = MALLOC(char*, optsize); + char *shortlist = MALLOC(char, optsize); +#endif + // fill short/long parameters and make a simple checking + for(i = 0; i < optsize; i++, loptr++, opts++){ + // check + assert(opts->name); // check name +#ifdef EBUG + longlist[i] = strdup(opts->name); +#endif + if(opts->has_arg){ + assert(opts->type != arg_none); // check error with arg type + assert(opts->argptr); // check pointer + } + if(opts->type != arg_none) // if there is a flag without arg, check its pointer + assert(opts->argptr); + // fill long_options + // don't do memcmp: what if there would be different alignment? + loptr->name = opts->name; + loptr->has_arg = (opts->has_arg < MULT_PAR) ? opts->has_arg : 1; + loptr->flag = opts->flag; + loptr->val = opts->val; + // fill short options if they are: + if(!opts->flag && opts->val){ +#ifdef EBUG + shortlist[i] = (char) opts->val; +#endif + *soptr++ = opts->val; + if(loptr->has_arg) // add ':' if option has required argument + *soptr++ = ':'; + if(loptr->has_arg == 2) // add '::' if option has optional argument + *soptr++ = ':'; + } + } + // sort all lists & check for repeating +#ifdef EBUG + int cmpstringp(const void *p1, const void *p2){ + return strcmp(* (char * const *) p1, * (char * const *) p2); + } + int cmpcharp(const void *p1, const void *p2){ + return (int)(*(char * const)p1 - *(char *const)p2); + } + qsort(longlist, optsize, sizeof(char *), cmpstringp); + qsort(shortlist,optsize, sizeof(char), cmpcharp); + char *prevl = longlist[0], prevshrt = shortlist[0]; + for(i = 1; i < optsize; ++i){ + if(longlist[i]){ + if(prevl){ + if(strcmp(prevl, longlist[i]) == 0) ERRX("double long arguments: --%s", prevl); + } + prevl = longlist[i]; + } + if(shortlist[i]){ + if(prevshrt){ + if(prevshrt == shortlist[i]) ERRX("double short arguments: -%c", prevshrt); + } + prevshrt = shortlist[i]; + } + } +#endif + // now we have both long_options & short_options and can parse `getopt_long` + while(1){ + int opt; + int oindex = 0, optind = 0; // oindex - number of option in argv, optind - number in options[] + if((opt = getopt_long(*argc, *argv, short_options, long_options, &oindex)) == -1) break; + if(opt == '?'){ + opt = optopt; + optind = get_optind(opt, options); + if(options[optind].has_arg == NEED_ARG || options[optind].has_arg == MULT_PAR) + showhelp(optind, options); // need argument + } + else{ + if(opt == 0 || oindex > 0) optind = oindex; + else optind = get_optind(opt, options); + } + opts = &options[optind]; + // if(opt == 0 && opts->has_arg == NO_ARGS) continue; // only long option changing integer flag + // now check option + if(opts->has_arg == NEED_ARG || opts->has_arg == MULT_PAR) + if(!optarg) showhelp(optind, options); // need argument + void *aptr; + if(opts->has_arg == MULT_PAR){ + aptr = get_aptr(opts->argptr, opts->type); + }else + aptr = opts->argptr; + bool result = TRUE; + // even if there is no argument, but argptr != NULL, think that optarg = "1" + if(!optarg) optarg = "1"; + switch(opts->type){ + default: + case arg_none: + if(opts->argptr) *((int*)aptr) += 1; // increment value + break; + case arg_int: + result = myatoll(aptr, optarg, arg_int); + break; + case arg_longlong: + result = myatoll(aptr, optarg, arg_longlong); + break; + case arg_double: + result = myatod(aptr, optarg, arg_double); + break; + case arg_float: + result = myatod(aptr, optarg, arg_float); + break; + case arg_string: + result = (*((void**)aptr) = (void*)strdup(optarg)); + break; + case arg_function: + result = ((argfn)aptr)(optarg); + break; + } + if(!result){ + showhelp(optind, options); + } + } + *argc -= optind; + *argv += optind; +} + +/** + * compare function for qsort + * first - sort by short options; second - sort arguments without sort opts (by long options) + */ +static int argsort(const void *a1, const void *a2){ + const myoption *o1 = (myoption*)a1, *o2 = (myoption*)a2; + const char *l1 = o1->name, *l2 = o2->name; + int s1 = o1->val, s2 = o2->val; + int *f1 = o1->flag, *f2 = o2->flag; + // check if both options has short arg + if(f1 == NULL && f2 == NULL && s1 && s2){ // both have short arg + return (s1 - s2); + }else if((f1 != NULL || !s1) && (f2 != NULL || !s2)){ // both don't have short arg - sort by long + return strcmp(l1, l2); + }else{ // only one have short arg -- return it + if(f2 || !s2) return -1; // a1 have short - it is 'lesser' + else return 1; + } +} + +/** + * Show help information based on myoption->help values + * @param oindex (i) - if non-negative, show only help by myoption[oindex].help + * @param options (i) - array of `myoption` + * + * @exit: run `exit(-1)` !!! + */ +void showhelp(int oindex, myoption *options){ + int max_opt_len = 0; // max len of options substring - for right indentation + const int bufsz = 255; + char buf[bufsz+1]; + myoption *opts = options; + assert(opts); + assert(opts[0].name); // check whether there is at least one options + if(oindex > -1){ // print only one message + opts = &options[oindex]; + printf(" "); + if(!opts->flag && isalpha(opts->val)) printf("-%c, ", opts->val); + printf("--%s", opts->name); + if(opts->has_arg == 1) printf("=arg"); + else if(opts->has_arg == 2) printf("[=arg]"); + printf(" %s\n", _(opts->help)); + exit(-1); + } + // header, by default is just "progname\n" + printf("\n"); + if(strstr(helpstring, "%s")) // print progname + printf(helpstring, __progname); + else // only text + printf("%s", helpstring); + printf("\n"); + // count max_opt_len + do{ + int L = strlen(opts->name); + if(max_opt_len < L) max_opt_len = L; + }while((++opts)->name); + max_opt_len += 14; // format: '-S , --long[=arg]' - get addition 13 symbols + opts = options; + // count amount of options + int N; for(N = 0; opts->name; ++N, ++opts); + if(N == 0) exit(-2); + // Now print all help (sorted) + opts = options; + qsort(opts, N, sizeof(myoption), argsort); + do{ + int p = sprintf(buf, " "); // a little indent + if(!opts->flag && opts->val) // .val is short argument + p += snprintf(buf+p, bufsz-p, "-%c, ", opts->val); + p += snprintf(buf+p, bufsz-p, "--%s", opts->name); + if(opts->has_arg == 1) // required argument + p += snprintf(buf+p, bufsz-p, "=arg"); + else if(opts->has_arg == 2) // optional argument + p += snprintf(buf+p, bufsz-p, "[=arg]"); + assert(p < max_opt_len); // there would be magic if p >= max_opt_len + printf("%-*s%s\n", max_opt_len+1, buf, _(opts->help)); // write options & at least 2 spaces after + ++opts; + }while(--N); + printf("\n\n"); + exit(-1); +} + +/** + * get suboptions from parameter string + * @param str - parameter string + * @param opt - pointer to suboptions structure + * @return TRUE if all OK + */ +bool get_suboption(char *str, mysuboption *opt){ + int findsubopt(char *par, mysuboption *so){ + int idx = 0; + if(!par) return -1; + while(so[idx].name){ + if(strcasecmp(par, so[idx].name) == 0) return idx; + ++idx; + } + return -1; // badarg + } + bool opt_setarg(mysuboption *so, int idx, char *val){ + mysuboption *soptr = &so[idx]; + bool result = FALSE; + void *aptr = soptr->argptr; + switch(soptr->type){ + default: + case arg_none: + if(soptr->argptr) *((int*)aptr) += 1; // increment value + result = TRUE; + break; + case arg_int: + result = myatoll(aptr, val, arg_int); + break; + case arg_longlong: + result = myatoll(aptr, val, arg_longlong); + break; + case arg_double: + result = myatod(aptr, val, arg_double); + break; + case arg_float: + result = myatod(aptr, val, arg_float); + break; + case arg_string: + result = (*((void**)aptr) = (void*)strdup(val)); + break; + case arg_function: + result = ((argfn)aptr)(val); + break; + } + return result; + } + char *tok; + bool ret = FALSE; + char *tmpbuf; + tok = strtok_r(str, ":,", &tmpbuf); + do{ + char *val = strchr(tok, '='); + int noarg = 0; + if(val == NULL){ // no args + val = "1"; + noarg = 1; + }else{ + *val++ = '\0'; + if(!*val || *val == ':' || *val == ','){ // no argument - delimeter after = + val = "1"; noarg = 1; + } + } + int idx = findsubopt(tok, opt); + if(idx < 0){ + /// "Неправильный параметр: %s" + WARNX(_("Wrong parameter: %s"), tok); + goto returning; + } + if(noarg && opt[idx].has_arg == NEED_ARG){ + /// "%s: необходим аргумент!" + WARNX(_("%s: argument needed!"), tok); + goto returning; + } + if(!opt_setarg(opt, idx, val)){ + /// "Неправильный аргумент \"%s\" параметра \"%s\"" + WARNX(_("Wrong argument \"%s\" of parameter \"%s\""), val, tok); + goto returning; + } + }while((tok = strtok_r(NULL, ":,", &tmpbuf))); + ret = TRUE; +returning: + return ret; +} diff --git a/allsky_logger/parseargs.h b/allsky_logger/parseargs.h new file mode 100644 index 0000000..537fc5b --- /dev/null +++ b/allsky_logger/parseargs.h @@ -0,0 +1,124 @@ +/* geany_encoding=koi8-r + * parseargs.h - headers for parsing command line arguments + * + * Copyright 2013 Edward V. Emelianoff + * + * 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 __PARSEARGS_H__ +#define __PARSEARGS_H__ + +#include // bool +#include + +#ifndef TRUE + #define TRUE true +#endif + +#ifndef FALSE + #define FALSE false +#endif + +// macro for argptr +#define APTR(x) ((void*)x) + +// if argptr is a function: +typedef bool(*argfn)(void *arg); + +/* + * type of getopt's argument + * WARNING! + * My function change value of flags by pointer, so if you want to use another type + * make a latter conversion, example: + * char charg; + * int iarg; + * myoption opts[] = { + * {"value", 1, NULL, 'v', arg_int, &iarg, "char val"}, ..., end_option}; + * ..(parse args).. + * charg = (char) iarg; + */ +typedef enum { + arg_none = 0, // no arg + arg_int, // integer + arg_longlong, // long long + arg_double, // double + arg_float, // float + arg_string, // char * + arg_function // parse_args will run function `bool (*fn)(char *optarg, int N)` +} argtype; + +/* + * Structure for getopt_long & help + * BE CAREFUL: .argptr is pointer to data or pointer to function, + * conversion depends on .type + * + * ATTENTION: string `help` prints through macro PRNT(), bu default it is gettext, + * but you can redefine it before `#include "parseargs.h"` + * + * if arg is string, then value wil be strdup'ed like that: + * char *str; + * myoption opts[] = {{"string", 1, NULL, 's', arg_string, &str, "string val"}, ..., end_option}; + * *(opts[1].str) = strdup(optarg); + * in other cases argptr should be address of some variable (or pointer to allocated memory) + * + * NON-NULL argptr should be written inside macro APTR(argptr) or directly: (void*)argptr + * + * !!!LAST VALUE OF ARRAY SHOULD BE `end_option` or ZEROS !!! + * + */ +typedef enum{ + NO_ARGS = 0, // first three are the same as in getopt_long + NEED_ARG = 1, + OPT_ARG = 2, + MULT_PAR +} hasarg; + +typedef struct{ + // these are from struct option: + const char *name; // long option's name + hasarg has_arg; // 0 - no args, 1 - nesessary arg, 2 - optionally arg, 4 - need arg & key can repeat (args are stored in null-terminated array) + int *flag; // NULL to return val, pointer to int - to set its value of val (function returns 0) + int val; // short opt name (if flag == NULL) or flag's value + // and these are mine: + argtype type; // type of argument + void *argptr; // pointer to variable to assign optarg value or function `bool (*fn)(char *optarg, int N)` + const char *help; // help string which would be shown in function `showhelp` or NULL +} myoption; + +/* + * Suboptions structure, almost the same like myoption + * used in parse_subopts() + */ +typedef struct{ + const char *name; + hasarg has_arg; + argtype type; + void *argptr; +} mysuboption; + +// last string of array (all zeros) +#define end_option {0,0,0,0,0,0,0} +#define end_suboption {0,0,0,0} + +extern const char *__progname; + +void showhelp(int oindex, myoption *options); +void parseargs(int *argc, char ***argv, myoption *options); +void change_helpstring(char *s); +bool get_suboption(char *str, mysuboption *opt); + +#endif // __PARSEARGS_H__ diff --git a/allsky_logger/socket.c b/allsky_logger/socket.c new file mode 100644 index 0000000..904c474 --- /dev/null +++ b/allsky_logger/socket.c @@ -0,0 +1,349 @@ +/* + * geany_encoding=koi8-r + * socket.c - socket IO + * + * Copyright 2017 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 // addrinfo +#include // inet_ntop +#include // INT_xxx +#include // pthread_kill +#include // daemon +#include // wait +#include // prctl +#include // inotify_init1 +#include // pollfd + +#include "usefull_macros.h" +#include "socket.h" +#include "imfunctions.h" + +#define BUFLEN (1024) +// Max amount of connections +#define BACKLOG (30) + +// keyname, comment, type, data +static datarecord boltwood_data[] = { + {"humidstatTempCode" , "HSCODE" , "hum. and ambient temp. sensor code (0 - OK)" , INTEGR, {0}}, + {"rainCond" , "RAINCOND", "rain sensor conditions (1 - no, 2 - rain)" , INTEGR, {0}}, + {"skyMinusAmbientTemperature", "TSKYAMB" , "Tsky-Tamb (degrC)" , DOUBLE, {0}}, + {"ambientTemperature" , "TAMB" , "ambient temperature (degrC)" , DOUBLE, {0}}, + {"windSpeed" , "WIND" , "wind speed (m/s)" , DOUBLE, {0}}, + {"wetState" , "WETSTATE", "wet sensor value (0 - dry, 1 - wet)" , INTEGR, {0}}, + {"relHumid" , "HUMIDITY", "relative humidity in %" , INTEGR, {0}}, + {"dewPointTemperature" , "DEWPOINT", "dew point temperature (degrC)" , DOUBLE, {0}}, + {"caseTemperature" , "TCASE" , "thermopile case temperature (degrC)" , DOUBLE, {0}}, + {"rainHeaterState" , "RHTRSTAT", "state of rain sensor heater (1 - OK)" , INTEGR, {0}}, + {"powerVoltage" , "PWRVOLT" , "power voltage value (V)" , DOUBLE, {0}}, + {"anemometerTemeratureDiff" , "ANEMTDIF", "anemometer tip temperature difference(degrC)" , DOUBLE, {0}}, + {"wetnessDrop" , "WETDROP" , "rain drops count for this cycle" , INTEGR, {0}}, + {"wetnessAvg" , "WETAWG" , "wetness osc. count diff. from base dry value" , INTEGR, {0}}, + {"wetnessDry" , "WETDRY" , "wetness osc. dry count diff. from base value" , INTEGR, {0}}, + {"daylightADC" , "DAYADC" , "daylight photodiode raw A/D output" , INTEGR, {0}}, + {"tmsrment" , "TBOLTWD" , "Boltwood's sensor measurement time (UNIX TIME)" , INTEGR, {0}}, + {NULL, NULL, NULL, 0, {0}} +}; + +/** + * Find parameter `par` in pairs "parameter=value" + * if found, return pointer to `value` + */ +static char *findpar(const char *str, const char *par){ + size_t L = strlen(par); + char *f = strstr((char*)str, par); + if(!f) return NULL; + f += L; + if(*f != '=') return NULL; + return (f + 1); +} +/** + * get integer & double `parameter` value from string `str`, put value to `ret` + * @return 1 if all OK + */ +static int getintpar(const char *str, const char *parameter, int64_t *ret){ + int64_t tmp; + char *endptr; + if(!(str = findpar(str, parameter))) return 0; + tmp = strtol(str, &endptr, 0); + if(endptr == str || *str == 0 ) + return 0; + if(ret) *ret = tmp; + return 1; +} +static int getdpar(const char *str, const char *parameter, double *ret){ + double tmp; + char *endptr; + if(!(str = findpar(str, parameter))) return 0; + tmp = strtod(str, &endptr); + if(endptr == str || *str == 0) + return 0; + if(ret) *ret = tmp; + return 1; +} + +/** + * Boltwood's sensor data parser + * @param buf (i) - zero-terminated text buffer with data received + * @param data (o) - boltwood data (allocated here if *data == NULL) - unchanged if input buffer wrong + */ +static void parse_data(char *buf, datarecord **data){ + if(!data) return; + if(!*data){ // copy boltwood_data[] array + *data = MALLOC(datarecord, sizeof(boltwood_data)); + datarecord *rec = boltwood_data, *optr = *data; + while(rec->varname){ + optr->varname = strdup(rec->varname); + optr->keyname = strdup(rec->keyname); + optr->comment = strdup(rec->comment); + optr->type = rec->type; + ++rec; ++optr; + } + } + datarecord *rec = boltwood_data; + int status = 1; // 1 - good, 0 - bad + while(rec->varname){ + switch(rec->type){ + case INTEGR: + if(!getintpar(buf, rec->varname, &rec->data.i)){ + status = 0; + goto ewhile; + } + break; + case DOUBLE: + default: + if(!getdpar(buf, rec->varname, &rec->data.d)){ + status = 0; + goto ewhile; + } + } + ++rec; + } +ewhile: + if(status){ // all fields received - copy them + rec = boltwood_data; + datarecord *optr = *data; + while(rec->varname){ + optr->data = rec->data; + ++optr; ++rec; + } + } +} + +/** + * wait for answer from socket + * @param sock - socket fd + * @return 0 in case of error or timeout, 1 in case of socket ready + */ +static int waittoread(int sock){ + fd_set fds; + struct timeval timeout; + int rc; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; // wait not more than 100ms + FD_ZERO(&fds); + FD_SET(sock, &fds); + do{ + rc = select(sock+1, &fds, NULL, NULL, &timeout); + if(rc < 0){ + if(errno != EINTR){ + WARN("select()"); + return 0; + } + continue; + } + break; + }while(1); + if(FD_ISSET(sock, &fds)) return 1; + return 0; +} + +/** + * Open given FITS file, check it and add to inotify + * THREAD UNSAFE!!! + * @return inotify file descriptor + */ +int watch_fits(char *name){ + static int oldfd = -1, oldwd = -1; + if(oldfd > 0 && oldwd > 0){ + inotify_rm_watch(oldfd, oldwd); + oldfd = -1, oldwd = -1; + } + if(!test_fits(name)) ERRX(_("File %s is not FITS file with 2D image!"), name); + int fd; + fd = inotify_init1(IN_NONBLOCK); + if(fd == -1) ERR("inotify_init1()"); + oldfd = fd; + FILE* f = fopen(name, "r"); + if(!f) ERR("fopen()"); // WTF??? + fclose(f); + if((oldwd = inotify_add_watch(fd, name, IN_CLOSE_WRITE)) < 0) + ERR("inotify_add_watch()"); + DBG("file %s added to inotify", name); + return fd; +} + +/** + * test if user can write something to path & make CWD + */ +static void test_path(char *path){ + if(path){ + if(chdir(path)) ERR("Can't chdir(%s)", path); + } + if(access("./", W_OK)) ERR("access()"); + DBG("OK, user can write to given path"); +} + +/** + * Client daemon itself + * @param G - global parameters + * @param infd - inotify file descriptor + * @param sock - socket's file descriptor + */ +static void client_(char *FITSpath, int infd, int sock){ + if(sock < 0) return; + size_t Bufsiz = BUFLEN; + char *recvBuff = MALLOC(char, Bufsiz); + datarecord *last_good_msrment = NULL; + int poll_num; + struct pollfd fds; + fds.fd = infd; + fds.events = POLLIN; + char buf[256]; + void rlc(size_t newsz){ + if(newsz >= Bufsiz){ + Bufsiz += 1024; + recvBuff = realloc(recvBuff, Bufsiz); + if(!recvBuff){ + WARN("realloc()"); + return; + } + DBG("Buffer reallocated, new size: %zd\n", Bufsiz); + } + } + DBG("Start polling"); + while(1){ + poll_num = poll(&fds, 1, 1); + if(poll_num == -1){ + if (errno == EINTR) + continue; + ERR("poll()"); + return; + } + if(poll_num > 0){ + DBG("changed?"); + if(fds.revents & POLLIN){ + ssize_t len = read(infd, &buf, sizeof(buf)); + if (len == -1 && errno != EAGAIN) { + ERR("read"); + return; + }else{ + DBG("file changed"); + usleep(100000); // wait a little for file changes + fds.fd = store_fits(FITSpath, last_good_msrment); + } + } + } + if(!waittoread(sock)) continue; + size_t offset = 0; + do{ + rlc(offset); + ssize_t n = read(sock, &recvBuff[offset], Bufsiz - offset); + if(!n) break; + if(n < 0){ + WARN("read"); + break; + } + offset += n; + }while(waittoread(sock)); + if(!offset){ + WARN("Socket closed\n"); + return; + } + rlc(offset); + recvBuff[offset] = 0; + DBG("read %zd bytes", offset); + parse_data(recvBuff, &last_good_msrment); + } +} + +/** + * Connect to socket and run daemon service + */ +void daemonize(glob_pars *G){ + char resolved_path[PATH_MAX]; + // get full path to FITS file + if(!realpath(G->filename, resolved_path)) ERR("realpath()"); + // open FITS file & add it to inotify + int fd = watch_fits(G->filename); + // CD to archive directory if user wants + test_path(G->cwd); + // run fork before socket opening to prevent daemon's death if there's no network + #ifndef EBUG + green("Daemonize\n"); + if(daemon(1, 0)){ + ERR("daemon()"); + while(1){ // guard for dead processes + pid_t childpid = fork(); + if(childpid){ + DBG("Created child with PID %d\n", childpid); + wait(NULL); + WARNX("Child %d died\n", childpid); + sleep(1); + }else{ + prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies + break; // go out to normal functional + } + }} + #endif + int sock = -1; + 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("connect to %s:%s", G->server, G->port); + if(getaddrinfo(G->server, G->port, &hints, &res) != 0){ + ERR("getaddrinfo"); + } + struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr; + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &(ia->sin_addr), str, INET_ADDRSTRLEN); + DBG("canonname: %s, port: %u, addr: %s\n", res->ai_canonname, ntohs(ia->sin_port), str); + // 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(connect(sock, p->ai_addr, p->ai_addrlen) == -1){ + WARN("connect()"); + close(sock); + continue; + } + break; // if we get here, we have a successfull connection + } + if(p == NULL){ + // looped off the end of the list with no successful bind + ERRX("failed to bind socket"); + } + freeaddrinfo(res); + client_(resolved_path, fd, sock); + close(sock); + signals(0); +} diff --git a/allsky_logger/socket.h b/allsky_logger/socket.h new file mode 100644 index 0000000..a8c6923 --- /dev/null +++ b/allsky_logger/socket.h @@ -0,0 +1,51 @@ +/* + * geany_encoding=koi8-r + * socket.h + * + * Copyright 2017 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 __SOCKET_H__ +#define __SOCKET_H__ + +#include "usefull_macros.h" +#include "cmdlnopts.h" + +typedef enum{ + INTEGR, // int64_t + DOUBLE // double +} datatype; + +typedef union{ + double d; + int64_t i; +} bdata; + +typedef struct{ + const char *varname; // variable name as it comes from socket + const char *keyname; // FITS keyword name + const char *comment; // comment to FITS file + datatype type; // type of data + bdata data; // data itself +} datarecord; + +void daemonize(glob_pars *G); +int watch_fits(char *name); + +#endif // __SOCKET_H__ diff --git a/allsky_logger/usefull_macros.c b/allsky_logger/usefull_macros.c new file mode 100644 index 0000000..334be15 --- /dev/null +++ b/allsky_logger/usefull_macros.c @@ -0,0 +1,385 @@ +/* geany_encoding=koi8-r + * usefull_macros.h - a set of usefull functions: memory, color etc + * + * Copyright 2013 Edward V. Emelianoff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include "usefull_macros.h" + +/** + * function for different purposes that need to know time intervals + * @return double value: time in seconds + */ +double dtime(){ + double t; + struct timeval tv; + gettimeofday(&tv, NULL); + t = tv.tv_sec + ((double)tv.tv_usec)/1e6; + return t; +} + +/******************************************************************************\ + * Coloured terminal +\******************************************************************************/ +int globErr = 0; // errno for WARN/ERR + +// pointers to coloured output printf +int (*red)(const char *fmt, ...); +int (*green)(const char *fmt, ...); +int (*_WARN)(const char *fmt, ...); + +/* + * format red / green messages + * name: r_pr_, g_pr_ + * @param fmt ... - printf-like format + * @return number of printed symbols + */ +int r_pr_(const char *fmt, ...){ + va_list ar; int i; + printf(RED); + va_start(ar, fmt); + i = vprintf(fmt, ar); + va_end(ar); + printf(OLDCOLOR); + return i; +} +int g_pr_(const char *fmt, ...){ + va_list ar; int i; + printf(GREEN); + va_start(ar, fmt); + i = vprintf(fmt, ar); + va_end(ar); + printf(OLDCOLOR); + return i; +} +/* + * print red error/warning messages (if output is a tty) + * @param fmt ... - printf-like format + * @return number of printed symbols + */ +int r_WARN(const char *fmt, ...){ + va_list ar; int i = 1; + fprintf(stderr, RED); + va_start(ar, fmt); + if(globErr){ + errno = globErr; + vwarn(fmt, ar); + errno = 0; + }else + i = vfprintf(stderr, fmt, ar); + va_end(ar); + i++; + fprintf(stderr, OLDCOLOR "\n"); + return i; +} + +static const char stars[] = "****************************************"; +/* + * notty variants of coloured printf + * name: s_WARN, r_pr_notty + * @param fmt ... - printf-like format + * @return number of printed symbols + */ +int s_WARN(const char *fmt, ...){ + va_list ar; int i; + i = fprintf(stderr, "\n%s\n", stars); + va_start(ar, fmt); + if(globErr){ + errno = globErr; + vwarn(fmt, ar); + errno = 0; + }else + i = +vfprintf(stderr, fmt, ar); + va_end(ar); + i += fprintf(stderr, "\n%s\n", stars); + i += fprintf(stderr, "\n"); + return i; +} +int r_pr_notty(const char *fmt, ...){ + va_list ar; int i; + i = printf("\n%s\n", stars); + va_start(ar, fmt); + i += vprintf(fmt, ar); + va_end(ar); + i += printf("\n%s\n", stars); + return i; +} + +/** + * Run this function in the beginning of main() to setup locale & coloured output + */ +void initial_setup(){ + // setup coloured output + if(isatty(STDOUT_FILENO)){ // make color output in tty + red = r_pr_; green = g_pr_; + }else{ // no colors in case of pipe + red = r_pr_notty; green = printf; + } + if(isatty(STDERR_FILENO)) _WARN = r_WARN; + else _WARN = s_WARN; + // Setup locale + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); +#if defined GETTEXT_PACKAGE && defined LOCALEDIR + bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); + textdomain(GETTEXT_PACKAGE); +#endif +} + +/******************************************************************************\ + * Memory +\******************************************************************************/ +/* + * safe memory allocation for macro ALLOC + * @param N - number of elements to allocate + * @param S - size of single element (typically sizeof) + * @return pointer to allocated memory area + */ +void *my_alloc(size_t N, size_t S){ + void *p = calloc(N, S); + if(!p) ERR("malloc"); + //assert(p); + return p; +} + +/** + * Mmap file to a memory area + * + * @param filename (i) - name of file to mmap + * @return stuct with mmap'ed file or die + */ +mmapbuf *My_mmap(char *filename){ + int fd; + char *ptr; + size_t Mlen; + struct stat statbuf; + /// "Не задано имя файла!" + if(!filename){ + WARNX(_("No filename given!")); + return NULL; + } + if((fd = open(filename, O_RDONLY)) < 0){ + /// "Не могу открыть %s для чтения" + WARN(_("Can't open %s for reading"), filename); + return NULL; + } + if(fstat (fd, &statbuf) < 0){ + /// "Не могу выполнить stat %s" + WARN(_("Can't stat %s"), filename); + close(fd); + return NULL; + } + Mlen = statbuf.st_size; + if((ptr = mmap (0, Mlen, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED){ + /// "Ошибка mmap" + WARN(_("Mmap error for input")); + close(fd); + return NULL; + } + /// "Не могу закрыть mmap'нутый файл" + if(close(fd)) WARN(_("Can't close mmap'ed file")); + mmapbuf *ret = MALLOC(mmapbuf, 1); + ret->data = ptr; + ret->len = Mlen; + return ret; +} + +void My_munmap(mmapbuf *b){ + if(munmap(b->data, b->len)){ + /// "Не могу munmap" + ERR(_("Can't munmap")); + } + FREE(b); +} + + +/******************************************************************************\ + * Terminal in no-echo mode +\******************************************************************************/ +static struct termios oldt, newt; // terminal flags +static int console_changed = 0; +// run on exit: +void restore_console(){ + if(console_changed) + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); // return terminal to previous state + console_changed = 0; +} + +// initial setup: +void setup_con(){ + if(console_changed) return; + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + if(tcsetattr(STDIN_FILENO, TCSANOW, &newt) < 0){ + /// "Не могу настроить консоль" + WARN(_("Can't setup console")); + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + signals(0); //quit? + } + console_changed = 1; +} + +/** + * Read character from console without echo + * @return char readed + */ +int read_console(){ + int rb; + struct timeval tv; + int retval; + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(STDIN_FILENO, &rfds); + tv.tv_sec = 0; tv.tv_usec = 10000; + retval = select(1, &rfds, NULL, NULL, &tv); + if(!retval) rb = 0; + else { + if(FD_ISSET(STDIN_FILENO, &rfds)) rb = getchar(); + else rb = 0; + } + return rb; +} + +/** + * getchar() without echo + * wait until at least one character pressed + * @return character readed + */ +int mygetchar(){ // getchar() without need of pressing ENTER + int ret; + do ret = read_console(); + while(ret == 0); + return ret; +} + + +/******************************************************************************\ + * TTY with select() +\******************************************************************************/ +static struct termio oldtty, tty; // TTY flags +static int comfd = -1; // TTY fd + +// run on exit: +void restore_tty(){ + if(comfd == -1) return; + ioctl(comfd, TCSANOW, &oldtty ); // return TTY to previous state + close(comfd); + comfd = -1; +} + +#ifndef BAUD_RATE +#define BAUD_RATE B9600 +#endif +// init: (speed = B9600 etc) +void tty_init(char *comdev, tcflag_t speed){ + if(comfd == -1){ // not opened + if(!comdev){ + WARNX("comdev == NULL"); + signals(11); + } + DBG("Open port..."); + do{ + comfd = open(comdev,O_RDWR|O_NOCTTY|O_NONBLOCK); + }while (comfd == -1 && errno == EINTR); + if(comfd < 0){ + WARN(_("Can't open port %s"),comdev); + signals(2); + } + DBG("OK\nGet current settings..."); + if(ioctl(comfd, TCGETA, &oldtty) < 0){ // Get settings + /// "Не могу получить настройки" + WARN(_("Can't get settings")); + signals(2); + } + DBG("Make exclusive"); + // make exclusive open + if(ioctl(comfd, TIOCEXCL)){ + WARN(_("Can't do exclusive open")); + close(comfd); + signals(2); + } + } + tty = oldtty; + tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG) + tty.c_oflag = 0; + tty.c_cflag = speed|CS8|CREAD|CLOCAL; // 9.6k, 8N1, RW, ignore line ctrl + tty.c_cc[VMIN] = 0; // non-canonical mode + tty.c_cc[VTIME] = 5; + if(ioctl(comfd, TCSETA, &tty) < 0){ + /// "Не могу установить настройки" + WARN(_("Can't set settings")); + signals(0); + } + DBG("OK"); +} +/** + * Read data from TTY + * @param buff (o) - buffer for data read + * @param length - buffer len + * @return amount of readed bytes + */ +size_t read_tty(uint8_t *buff, size_t length){ + if(comfd < 0) return 0; + ssize_t L = 0; + fd_set rfds; + struct timeval tv; + int retval; + FD_ZERO(&rfds); + FD_SET(comfd, &rfds); + tv.tv_sec = 0; tv.tv_usec = 50000; // wait for 50ms + retval = select(comfd + 1, &rfds, NULL, NULL, &tv); + if (!retval) return 0; + if(FD_ISSET(comfd, &rfds)){ + if((L = read(comfd, buff, length)) < 1) return 0; + } + return (size_t)L; +} + +int write_tty(const uint8_t *buff, size_t length){ + if(comfd < 0) return 1; + ssize_t L = write(comfd, buff, length); + if((size_t)L != length){ + /// "Ошибка записи!" + WARN("Write error!"); + return 1; + } + return 0; +} + + +/** + * Safely convert data from string to double + * + * @param num (o) - double number read from string + * @param str (i) - input string + * @return 1 if success, 0 if fails + */ +int str2double(double *num, const char *str){ + double res; + char *endptr; + if(!str) return 0; + res = strtod(str, &endptr); + if(endptr == str || *str == '\0' || *endptr != '\0'){ + /// "Неправильный формат числа double!" + WARNX("Wrong double number format!"); + return FALSE; + } + if(num) *num = res; // you may run it like myatod(NULL, str) to test wether str is double number + return TRUE; +} diff --git a/allsky_logger/usefull_macros.h b/allsky_logger/usefull_macros.h new file mode 100644 index 0000000..1e2f325 --- /dev/null +++ b/allsky_logger/usefull_macros.h @@ -0,0 +1,140 @@ +/* geany_encoding=koi8-r + * usefull_macros.h - a set of usefull macros: memory, color etc + * + * Copyright 2013 Edward V. Emelianoff + * + * 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 __USEFULL_MACROS_H__ +#define __USEFULL_MACROS_H__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined GETTEXT_PACKAGE && defined LOCALEDIR +/* + * GETTEXT + */ +#include +#define _(String) gettext(String) +#define gettext_noop(String) String +#define N_(String) gettext_noop(String) +#else +#define _(String) (String) +#define N_(String) (String) +#endif +#include +#include +#include +#include +#include +#include + + +// unused arguments with -Wall -Werror +#define _U_ __attribute__((__unused__)) + +/* + * Coloured messages output + */ +#define RED "\033[1;31;40m" +#define GREEN "\033[1;32;40m" +#define OLDCOLOR "\033[0;0;0m" + +#ifndef FALSE +#define FALSE (0) +#endif + +#ifndef TRUE +#define TRUE (1) +#endif + +/* + * ERROR/WARNING messages + */ +extern int globErr; +extern void signals(int sig); +#define ERR(...) do{globErr=errno; _WARN(__VA_ARGS__); signals(9);}while(0) +#define ERRX(...) do{globErr=0; _WARN(__VA_ARGS__); signals(9);}while(0) +#define WARN(...) do{globErr=errno; _WARN(__VA_ARGS__);}while(0) +#define WARNX(...) do{globErr=0; _WARN(__VA_ARGS__);}while(0) + +/* + * print function name, debug messages + * debug mode, -DEBUG + */ +#ifdef EBUG + #define FNAME() do{ fprintf(stderr, OLDCOLOR); \ + fprintf(stderr, "\n%s (%s, line %d)\n", __func__, __FILE__, __LINE__);} while(0) + #define DBG(...) do{ fprintf(stderr, OLDCOLOR); \ + fprintf(stderr, "%s (%s, line %d): ", __func__, __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n");} while(0) +#else + #define FNAME() do{}while(0) + #define DBG(...) do{}while(0) +#endif //EBUG + +/* + * Memory allocation + */ +#define ALLOC(type, var, size) type * var = ((type *)my_alloc(size, sizeof(type))) +#define MALLOC(type, size) ((type *)my_alloc(size, sizeof(type))) +#define FREE(ptr) do{if(ptr){free(ptr); ptr = NULL;}}while(0) + +#ifndef DBL_EPSILON +#define DBL_EPSILON (2.2204460492503131e-16) +#endif + +double dtime(); + +// functions for color output in tty & no-color in pipes +extern int (*red)(const char *fmt, ...); +extern int (*_WARN)(const char *fmt, ...); +extern int (*green)(const char *fmt, ...); +void * my_alloc(size_t N, size_t S); +void initial_setup(); + +// mmap file +typedef struct{ + char *data; + size_t len; +} mmapbuf; +mmapbuf *My_mmap(char *filename); +void My_munmap(mmapbuf *b); + +void restore_console(); +void setup_con(); +int read_console(); +int mygetchar(); + +void restore_tty(); +void tty_init(char *comdev, tcflag_t speed); +size_t read_tty(uint8_t *buff, size_t length); +int write_tty(const uint8_t *buff, size_t length); + +int str2double(double *num, const char *str); + +#endif // __USEFULL_MACROS_H__ diff --git a/cmdlnopts.c b/cmdlnopts.c index 9c22f92..d15a20a 100644 --- a/cmdlnopts.c +++ b/cmdlnopts.c @@ -38,8 +38,9 @@ glob_pars G; glob_pars const Gdefault = { .device = DEFAULT_COMDEV, .port = "55555", + .terminal = 0, .rest_pars = NULL, - .rest_pars_num = 0, + .rest_pars_num = 0 }; /* @@ -50,7 +51,8 @@ myoption cmdlnopts[] = { // common options {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), _("show this help")}, {"device", NEED_ARG, NULL, 'i', arg_string, APTR(&G.device), _("serial device name (default: " DEFAULT_COMDEV ")")}, - {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("port to connect (default: 4444)")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("port to connect (default: 55555)")}, + {"terminal",NO_ARGS, NULL, 't', arg_int, APTR(&G.terminal), _("run as terminal")}, end_option }; diff --git a/cmdlnopts.h b/cmdlnopts.h index acbac34..f6adc4f 100644 --- a/cmdlnopts.h +++ b/cmdlnopts.h @@ -32,6 +32,7 @@ typedef struct{ char *device; // serial device name char *port; // port to connect + int terminal; // run as terminal int rest_pars_num; // number of rest parameters char** rest_pars; // the rest parameters: array of char* } glob_pars; diff --git a/main.c b/main.c index a7d4fb4..2e1c362 100644 --- a/main.c +++ b/main.c @@ -21,6 +21,7 @@ #include "usefull_macros.h" #include #include "cmdlnopts.h" +#include "socket.h" void signals(int signo){ restore_console(); @@ -38,7 +39,8 @@ int main(int argc, char **argv){ glob_pars *G = parse_args(argc, argv); try_connect(G->device); - //daemonize(G->hostname, G->port); - run_terminal(); + if(check_sensor()) signals(15); // there's not Boltwood sensor connected + if(G->terminal) run_terminal(); + else daemonize(G->port); return 0; } diff --git a/socket.c b/socket.c index c988d98..b6a262a 100644 --- a/socket.c +++ b/socket.c @@ -37,6 +37,9 @@ // Max amount of connections #define BACKLOG (30) +static uint64_t datctr = 0; // data portions counter +static boltwood_data boltwood; // global structure with last data + /**************** COMMON FUNCTIONS ****************/ /** * wait for answer from socket @@ -66,100 +69,63 @@ static int waittoread(int sock){ return 0; } -static uint8_t *findpar(uint8_t *str, char *par){ - size_t L = strlen(par); - char *f = strstr((char*)str, par); - if(!f) return NULL; - f += L; - if(*f != '=') return NULL; - return (uint8_t*)(f + 1); -} -/** - * get integer & double `parameter` value from string `str`, put value to `ret` - * @return 1 if all OK - */ -static long getintpar(uint8_t *str, char *parameter, long *ret){ - long tmp; - char *endptr; - if(!(str = findpar(str, parameter))) return 0; - tmp = strtol((char*)str, &endptr, 0); - if(endptr == (char*)str || *str == 0 ) - return 0; - if(ret) *ret = tmp; - DBG("get par: %s = %ld", parameter, tmp); - return 1; -} -static int getdpar(uint8_t *str, char *parameter, double *ret){ - double tmp; - char *endptr; - if(!(str = findpar(str, parameter))) return 0; - tmp = strtod((char*)str, &endptr); - if(endptr == (char*)str || *str == 0) - return 0; - if(ret) *ret = tmp; - DBG("get par: %s = %g", parameter, tmp); - return 1; -} - /**************** SERVER FUNCTIONS ****************/ pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; -/* -int send_ima(int sock, int webquery){ +int send_data(int sock, int webquery){ + DBG("webq=%d", webquery); uint8_t buf[BUFLEN10], *bptr = buf, obuff[BUFLEN]; - ssize_t Len; - size_t rest = BUFLEN10, imS = storedima->W * storedima->H * sizeof(uint16_t); - #define PUT(key, val) do{Len = snprintf((char*)bptr, rest, "%s=%i\n", key, (int)storedima->val); \ - if(Len > 0){rest -= Len; bptr += Len;}}while(0) - PUT("binning", binning); - if(storedima->binning == 0xff){ - PUT("subX", subframe->Xstart); - PUT("subY", subframe->Xstart); - PUT("subS", subframe->size); - } - Len = snprintf((char*)bptr, rest, "%s=%g\n", "exptime", storedima->exptime); - if(Len > 0){rest -= Len; bptr += Len;} - PUT("imtype", imtype); - PUT("imW", W); - PUT("imH", H); - PUT("exposetime", exposetime); - Len = snprintf((char*)bptr, rest, "imdata="); - if(Len){rest -= Len; bptr += Len;} - if(rest < imS){ - red("rest = %zd, need %zd\n", rest, imS); - return 0; // not enough memory - HOW??? - } - if(!memcpy(bptr, storedima->imdata, imS)){ - WARN("memcpy()"); - return 0; - } - rest -= imS; + ssize_t L; + size_t rest = BUFLEN10, Len = 0; + #define PUTI(val) do{L = snprintf((char*)bptr, rest, "%s=%d\n", #val, boltwood.val); \ + if(L > 0){rest -= L; bptr += L; Len += L;}}while(0) + #define PUTD(val) do{L = snprintf((char*)bptr, rest, "%s=%.1f\n", #val, boltwood.val); \ + if(L > 0){rest -= L; bptr += L; Len += L;}}while(0) + PUTI(humidstatTempCode); + PUTI(rainCond); + PUTD(skyMinusAmbientTemperature); + PUTD(ambientTemperature); + PUTD(windSpeed); + PUTI(wetState); + PUTI(relHumid); + PUTD(dewPointTemperature); + PUTD(caseTemperature); + PUTI(rainHeaterState); + PUTD(powerVoltage); + PUTD(anemometerTemeratureDiff); + PUTI(wetnessDrop); + PUTI(wetnessAvg); + PUTI(wetnessDry); + PUTI(daylightADC); + L = snprintf((char*)bptr, rest, "tmsrment=%ld\n", boltwood.tmsrment); + if(L > 0){rest -= L; bptr += L; Len += L;} + // OK buffer ready, prepare to send it if(webquery){ - Len = snprintf((char*)obuff, BUFLEN, + L = snprintf((char*)obuff, BUFLEN, "HTTP/2.0 200 OK\r\n" "Access-Control-Allow-Origin: *\r\n" "Access-Control-Allow-Methods: GET, POST\r\n" "Access-Control-Allow-Credentials: true\r\n" - "Content-type: multipart/form-data\r\nContent-Length: %zd\r\n\r\n", Len); - if(Len < 0){ + "Content-type: text/plain\r\nContent-Length: %zd\r\n\r\n", Len); + if(L < 0){ WARN("sprintf()"); return 0; } - if(Len != write(sock, obuff, Len)){ + if(L != write(sock, obuff, L)){ WARN("write"); return 0; } DBG("%s", obuff); } // send data - size_t send = BUFLEN10 - rest; - red("send %zd bytes\n", send); - if(send != (size_t)write(sock, buf, send)){ + red("send %zd bytes\n", Len); + DBG("Buf: %s", buf); + if(Len != (size_t)write(sock, buf, Len)){ WARN("write()"); return 0; } return 1; -}*/ +} // search a first word after needle without spaces char* stringscan(char *str, char *needle){ @@ -178,44 +144,46 @@ char* stringscan(char *str, char *needle){ void *handle_socket(void *asock){ FNAME(); int sock = *((int*)asock); - //int webquery = 0; // whether query is web or regular + int webquery = 0; // whether query is web or regular char buff[BUFLEN]; - ssize_t readed; + uint64_t locctr = 0; + ssize_t rd; while(1){ if(!waittoread(sock)){ // no data incoming - /* pthread_mutex_lock(&mutex); - red("Send image, imctr = %ld, locctr = %ld\n", imctr, locctr); - if(send_ima(sock, webquery)){ - locctr = imctr; + DBG("datctr:%lu, locctr: %lu", datctr, locctr); + pthread_mutex_lock(&mutex); + DBG("datctr:%lu, locctr: %lu", datctr, locctr); + if(datctr != locctr){ + red("Send data, datctr = %ld, locctr = %ld\n", datctr, locctr); + if(send_data(sock, webquery)){ + locctr = datctr; if(webquery){ pthread_mutex_unlock(&mutex); break; // end of transmission } + } } - pthread_mutex_unlock(&mutex);*/ + pthread_mutex_unlock(&mutex); continue; } - if(!(readed = read(sock, buff, BUFLEN))) continue; - DBG("Got %zd bytes", readed); - if(readed < 0){ // error or disconnect - DBG("Nothing to read from fd %d (ret: %zd)", sock, readed); + if(!(rd = read(sock, buff, BUFLEN-1))) continue; + DBG("Got %zd bytes", rd); + if(rd < 0){ // error or disconnect + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); break; } // add trailing zero to be on the safe side - buff[readed] = 0; + buff[rd] = 0; // now we should check what do user want char *got, *found = buff; if((got = stringscan(buff, "GET")) || (got = stringscan(buff, "POST"))){ // web query - //webquery = 1; + webquery = 1; char *slash = strchr(got, '/'); if(slash) found = slash + 1; // web query have format GET /some.resource } // here we can process user data - printf("user send: %s\n", found); - long ii; double dd; - if(getdpar((uint8_t*)found, "exptime", &dd)) printf("exptime: %g\n", dd); - if(getintpar((uint8_t*)found, "somepar", &ii)) printf("somepar: %ld\n", ii); + printf("user send: %s\nfound=%s", buff,found); } close(sock); //DBG("closed"); @@ -263,6 +231,15 @@ static void daemon_(int sock){ if(pthread_create(&sock_thread, NULL, server, (void*) &sock)) ERR("pthread_create()"); } + // get data + boltwood_data b; + if(poll_sensor(&b) == 1){ + pthread_mutex_lock(&mutex); + memcpy(&boltwood, &b, sizeof(boltwood_data)); + ++datctr; + pthread_mutex_unlock(&mutex); + } + usleep(1000); // sleep a little or thread's won't be able to lock mutex }while(1); } diff --git a/term.c b/term.c index 875db9b..70ae783 100644 --- a/term.c +++ b/term.c @@ -71,24 +71,6 @@ static uint16_t calcCRC16(const uint8_t *buf, int nBytes){ return crc; } -/** - * Send command by serial port, return 0 if all OK - *static uint8_t last_chksum = 0; -int send_data(uint8_t *buf, int len){ - if(len < 1) return 1; - uint8_t chksum = 0, *ptr = buf; - int l; - for(l = 0; l < len; ++l) - chksum ^= ~(*ptr++) & 0x7f; - DBG("send: %s (chksum: 0x%X)", buf, chksum); - if(write_tty(buf, len)) return 1; - DBG("cmd sent"); - if(write_tty(&chksum, 1)) return 1; - DBG("checksum sent"); - last_chksum = chksum; - return 0; -}*/ - // send single symbol without CRC int send_symbol(uint8_t cmd){ uint8_t s[5] = {FRAME_START, cmd, '\n', REQUEST_POLL, 0}; @@ -150,8 +132,8 @@ int send_cmd(uint8_t cmd){ * Try to connect to `device` at given speed (or try all speeds, if speed == 0) * @return connection speed if success or 0 */ -int try_connect(char *device){ - if(!device) return 0; +void try_connect(char *device){ + if(!device) return; uint8_t tmpbuf[4096]; green(_("Connecting to %s... "), device); fflush(stdout); @@ -159,7 +141,6 @@ int try_connect(char *device){ read_tty(tmpbuf, 4096); // clear rbuf green("Ok!"); printf("\n\n"); - return 0; } // check CRC and return 0 if all OK @@ -234,3 +215,151 @@ void run_terminal(){ } } +/** + * parser + * @param buf (i) - data frame from sensor + * @param L - its length + * @param d - structure to fill + * 1. humidstatTempCode + * 2. cloudCond + * 3. windCond + * 4. rainCond + * 5. skyCond + * 6. roofCloseRequested + * 7. skyMinusAmbientTemperature + * 8. ambientTemperature + * 9. windSpeed + * 10. wetSensor + * 11. rainSensor + * 12. relativeHumidityPercentage + * 13. dewPointTemperature + * 14. caseTemperature + * 15. rainHeaterPercentage + * 16. blackBodyTemperature + * 17. rainHeaterState + * 18. powerVoltage + * 19. anemometerTemeratureDiff + * 20. wetnessDrop + * 21. wetnessAvg + * 22. wetnessDry + * 23. rainHeaterPWM + * 24. anemometerHeaterPWM + * 25. thermopileADC + * 26. thermistorADC + * 27. powerADC + * 28. blockADC + * 29. anemometerThermistorADC + * 30. davisVaneADC + * 31. dkMPH + * 32. extAnemometerDirection + * 33. rawWetnessOsc + * 34. dayCond + * 35. daylightADC + */ +static void parse_answer(uint8_t *buf, size_t L, boltwood_data *d){ + if(!buf || !L || !d) return; + char *endptr; + int getint(){ + if(!buf || !*buf) return -999; + long l = strtol((char*)buf, &endptr, 10); + if(endptr == (char*)buf) endptr = strchr((char*)buf, ' '); // omit non-number + buf = (uint8_t*)endptr; + while(*buf == ' ') ++buf; + return (int)l; + } + double getdbl(){ + if(!buf || !*buf) return -999.; + double d = strtod((char*)buf, &endptr); + if(endptr == (char*)buf) endptr = strchr((char*)buf, ' '); // omit non-number + buf = (uint8_t*)endptr; + while(*buf == ' ') ++buf; + return d; + } + d->tmsrment = time(NULL); + d->humidstatTempCode = getint(); // 1. humidstatTempCode + DBG("buf: %s", buf); + getint(); getint(); + d->rainCond = getint(); // 4. rainCond + DBG("buf: %s", buf); + getint(); getint(); + DBG("buf: %s", buf); + d->skyMinusAmbientTemperature = getdbl(); // 7. skyMinusAmbientTemperature + d->ambientTemperature = getdbl(); // 8. ambientTemperature + d->windSpeed = getdbl(); // 9. windSpeed + switch(*buf){ // 10. wetSensor + case 'N': + d->wetState = 0; + break; + case 'W': + d->wetState = 1; + break; + case 'w': + d->wetState = -1; + break; + default: + d->wetState = -2; + } + getint(); // go to pos 11 + getint(); d->relHumid = getint(); // 12. relativeHumidityPercentage + d->dewPointTemperature = getdbl(); // 13. dewPointTemperature + d->caseTemperature = getdbl(); // 14. caseTemperature + getint(); getdbl(); + d->rainHeaterState = getint(); // 17. rainHeaterState + d->powerVoltage = getdbl(); // 18. powerVoltage + d->anemometerTemeratureDiff = getdbl(); // 19. anemometerTemeratureDiff + d->wetnessDrop = getint(); // 20. wetnessDrop + d->wetnessAvg = getint(); // 21. wetnessAvg + d->wetnessDry = getint(); // 22. wetnessDry + while(*buf) ++buf; // roll to end + while(*buf != ' ') --buf; ++buf; + d->daylightADC = getint(); // 35. daylightADC +} + +/** + * Poll sensor for new dataportion + * @return: 0 if no data received, -1 if invalid data received, 1 if valid data received + */ +int poll_sensor(boltwood_data *d){ + if(!d) return -1; + uint8_t buf[BUFLEN], cmd; + size_t L; + cmd = REQUEST_POLL; + if(write_tty(&cmd, 1)) // start polling + WARNX(_("can't request poll")); + if((L = read_string(buf, BUFLEN))){ + if(L > 5 && buf[1] == ANS_DATA_FRAME){ + if(chk_crc(&buf[2])){ + WARNX(_("Bad CRC in input data")); + send_symbol(CMD_NACK); + return -1; + } + buf[L-5] = 0; + send_symbol(CMD_ACK); + printf("got: %s\n", &buf[4]); + if(buf[2] == DATAFRAME_SENSOR_REPORT){ + parse_answer(&buf[3], L, d); + return 1; + } + }else if(L > 1){ + if(buf[1] == ANS_POLLING_FRAME || buf[2] == ANS_POLLING_FRAME){ // polling - send command or ack + send_symbol(CMD_ACK); + } + } + } + return 0; +} + +/** + * check whether connected device is boltwood sensor + * @return 1 if NO + */ +int check_sensor(){ + double t0 = dtime(); + boltwood_data b; + green(_("Check if there's a sensor...\n")); + while(dtime() - t0 < POLLING_TMOUT){ + if(poll_sensor(&b) == 1) return 0; + } + WARNX(_("No sensor detected!")); + return 1; +} diff --git a/term.h b/term.h index 6e90fdb..0231344 100644 --- a/term.h +++ b/term.h @@ -22,6 +22,14 @@ #ifndef __TERM_H__ #define __TERM_H__ +#define FRAME_MAX_LENGTH (300) +#define MAX_MEMORY_DUMP_SIZE (0x800 * 4) +// terminal timeout (seconds) +#define WAIT_TMOUT (0.2) +// boltwood polling timeout - 15 seconds +#define POLLING_TMOUT (15.0) + + // communication errors typedef enum{ TRANS_SUCCEED = 0, // no errors @@ -41,26 +49,44 @@ typedef enum{ FRAME_END = '\n', } term_symbols; -#define FRAME_MAX_LENGTH (300) -#define MAX_MEMORY_DUMP_SIZE (0x800 * 4) +// main structure with useful data from cloud sensor +typedef struct{ + int humidstatTempCode; + int rainCond; + double skyMinusAmbientTemperature; + double ambientTemperature; + double windSpeed; + int wetState; // 0 - dry, 1 - wet now, -1 - wet recently, -2 - unknown + int relHumid; + double dewPointTemperature; + double caseTemperature; + int rainHeaterState; + double powerVoltage; + double anemometerTemeratureDiff; + int wetnessDrop; + int wetnessAvg; + int wetnessDry; + int daylightADC; + time_t tmsrment; // measurement time +} boltwood_data; -// terminal timeout (seconds) -#define WAIT_TMOUT (0.2) - /******************************** Commands definition ********************************/ #define CMD_ACK 'a' #define CMD_NACK 'n' - - - /******************************** Answers definition ********************************/ #define ANS_COMMAND_ACKED 'A' #define ANS_COMMAND_NACKED 'N' #define ANS_DATA_FRAME 'M' #define ANS_POLLING_FRAME 'P' +/******************************** Data frame types ********************************/ +#define DATAFRAME_SENSOR_REPORT 'D' + void run_terminal(); -int try_connect(char *device); +void try_connect(char *device); +int poll_sensor(boltwood_data *d); +int check_sensor(); + #endif // __TERM_H__ diff --git a/usefull_macros.c b/usefull_macros.c index a56c8b8..a332e5a 100644 --- a/usefull_macros.c +++ b/usefull_macros.c @@ -289,17 +289,24 @@ void restore_tty(){ // init: void tty_init(char *comdev){ DBG("\nOpen port...\n"); - if ((comfd = open(comdev,O_RDWR|O_NOCTTY|O_NONBLOCK)) < 0){ + do{ + comfd = open(comdev,O_RDWR|O_NOCTTY|O_NONBLOCK); + }while (comfd == -1 && errno == EINTR); + if(comfd < 0){ WARN("Can't use port %s\n",comdev); - ioctl(comfd, TCSANOW, &oldtty); // return TTY to previous state + signals(-1); // quit? + } + // make exclusive open + if(ioctl(comfd, TIOCEXCL)){ + WARN(_("Can't do exclusive open")); close(comfd); - signals(0); // quit? + signals(2); } DBG(" OK\nGet current settings... "); if(ioctl(comfd,TCGETA,&oldtty) < 0){ // Get settings /// "Не могу получить настройки" WARN(_("Can't get settings")); - signals(0); + signals(-1); } tty = oldtty; tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG) @@ -310,7 +317,7 @@ void tty_init(char *comdev){ if(ioctl(comfd,TCSETA,&tty) < 0){ /// "Не могу установить настройки" WARN(_("Can't set settings")); - signals(0); + signals(-1); } DBG(" OK\n"); }