diff --git a/Auxiliary_utils/bash_scripts/getflats b/Auxiliary_utils/bash_scripts/getflats old mode 100755 new mode 100644 diff --git a/Auxiliary_utils/bash_scripts/park_tel.sh b/Auxiliary_utils/bash_scripts/park_tel.sh old mode 100755 new mode 100644 diff --git a/Auxiliary_utils/bash_scripts/point_AH.sh b/Auxiliary_utils/bash_scripts/point_AH.sh old mode 100755 new mode 100644 diff --git a/Auxiliary_utils/bash_scripts/point_get_flat.sh b/Auxiliary_utils/bash_scripts/point_get_flat.sh old mode 100755 new mode 100644 diff --git a/Auxiliary_utils/bash_scripts/run b/Auxiliary_utils/bash_scripts/run old mode 100755 new mode 100644 diff --git a/Daemons/astrosib/HWoff b/Daemons/astrosib/HWoff old mode 100644 new mode 100755 diff --git a/Daemons/astrosib/HWon b/Daemons/astrosib/HWon old mode 100644 new mode 100755 diff --git a/Daemons/astrosib/STARTobs b/Daemons/astrosib/STARTobs old mode 100644 new mode 100755 diff --git a/Daemons/astrosib/STOPobs b/Daemons/astrosib/STOPobs old mode 100644 new mode 100755 diff --git a/Daemons/netdaemon.deprecated/Makefile b/Daemons/netdaemon.deprecated/Makefile new file mode 100644 index 0000000..bc11a20 --- /dev/null +++ b/Daemons/netdaemon.deprecated/Makefile @@ -0,0 +1,43 @@ +# run `make DEF=...` to add extra defines +PROGRAM := netdaemon +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 +# baudrate for USB<->UART converter +DEFINES += -DBAUD_RATE=B115200 +OBJDIR := mk +CFLAGS += -O2 -Wall -Werror -Wextra -Wno-trampolines +OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) +DEPS := $(OBJS:.o=.d) +CC = gcc + +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/Daemons/netdaemon.deprecated/Readme.md b/Daemons/netdaemon.deprecated/Readme.md new file mode 100644 index 0000000..24d6bef --- /dev/null +++ b/Daemons/netdaemon.deprecated/Readme.md @@ -0,0 +1,9 @@ +Network daemon snippet +================== + +This isn't an end-product, but just a template for different net-daemons. + +Open a socket at given port (default: 4444), works with http & direct requests. +Can read and send commands over serial interface. + +Pieces with user code marked as 'INSERT CODE HERE'. diff --git a/Daemons/netdaemon.deprecated/cmdlnopts.c b/Daemons/netdaemon.deprecated/cmdlnopts.c new file mode 100644 index 0000000..229b0d3 --- /dev/null +++ b/Daemons/netdaemon.deprecated/cmdlnopts.c @@ -0,0 +1,89 @@ +/* geany_encoding=koi8-r + * cmdlnopts.c - the only function that parse cmdln args and returns glob parameters + * + * Copyright 2018 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; +static glob_pars G; + +// default values for Gdefault & help +#define DEFAULT_PORT "4444" + +// DEFAULTS +// default global parameters +glob_pars const Gdefault = { + .device = NULL, + .port = DEFAULT_PORT, + .terminal = 0, + .echo = 0, + .logfile = NULL, + .rest_pars = NULL, + .rest_pars_num = 0 +}; + +/* + * 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")}, + {"device", NEED_ARG, NULL, 'i', arg_string, APTR(&G.device), _("serial device name (default: none)")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("network port to connect (default: " DEFAULT_PORT ")")}, + {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), _("save logs to file (default: none)")}, + {"terminal",NO_ARGS, NULL, 't', arg_int, APTR(&G.terminal), _("run as terminal")}, + {"echo", NO_ARGS, NULL, 'e', arg_int, APTR(&G.echo), _("echo users commands back")}, + 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]\n\n\tWhere args are:\n"); + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + G.rest_pars_num = argc; + G.rest_pars = calloc(argc, sizeof(char*)); + for (i = 0; i < argc; i++) + G.rest_pars[i] = strdup(argv[i]); + } + return &G; +} + diff --git a/Daemons/netdaemon.deprecated/cmdlnopts.h b/Daemons/netdaemon.deprecated/cmdlnopts.h new file mode 100644 index 0000000..712e140 --- /dev/null +++ b/Daemons/netdaemon.deprecated/cmdlnopts.h @@ -0,0 +1,44 @@ +/* geany_encoding=koi8-r + * cmdlnopts.h - comand line options for parceargs + * + * Copyright 2018 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 *device; // serial device name + char *port; // port to connect + char *logfile; // logfile name + int terminal; // run as terminal + int echo; // echo user commands back + int rest_pars_num; // number of rest parameters + char** rest_pars; // the rest parameters: array of char* (path to logfile and thrash) +} glob_pars; + + +glob_pars *parse_args(int argc, char **argv); +#endif // __CMDLNOPTS_H__ diff --git a/Daemons/netdaemon.deprecated/main.c b/Daemons/netdaemon.deprecated/main.c new file mode 100644 index 0000000..9ceae30 --- /dev/null +++ b/Daemons/netdaemon.deprecated/main.c @@ -0,0 +1,79 @@ +/* geany_encoding=koi8-r + * main.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 "usefull_macros.h" +#include +#include // wait +#include //prctl +#include "cmdlnopts.h" +#include "socket.h" + +glob_pars *GP; + +void signals(int signo){ + restore_console(); + restore_tty(); + LOG("exit with status %d", 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 + GP = parse_args(argc, argv); + if(GP->terminal){ + if(!GP->device) ERRX(_("Point serial device name")); + try_connect(GP->device); + run_terminal(); + signals(0); // never reached! + } + if(GP->logfile) + Cl_createlog(GP->logfile); + #ifndef EBUG + if(daemon(1, 0)){ + ERR("daemon()"); + } + while(1){ // guard for dead processes + pid_t childpid = fork(); + if(childpid){ + LOG("create child with PID %d\n", 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 + + if(GP->device) try_connect(GP->device); + /* + * INSERT CODE HERE + * connection check & device validation + */ + daemonize(GP->port); + return 0; +} diff --git a/Daemons/netdaemon/parseargs.c b/Daemons/netdaemon.deprecated/parseargs.c similarity index 100% rename from Daemons/netdaemon/parseargs.c rename to Daemons/netdaemon.deprecated/parseargs.c diff --git a/Daemons/netdaemon/parseargs.h b/Daemons/netdaemon.deprecated/parseargs.h similarity index 100% rename from Daemons/netdaemon/parseargs.h rename to Daemons/netdaemon.deprecated/parseargs.h diff --git a/Daemons/netdaemon.deprecated/socket.c b/Daemons/netdaemon.deprecated/socket.c new file mode 100644 index 0000000..4db0fc5 --- /dev/null +++ b/Daemons/netdaemon.deprecated/socket.c @@ -0,0 +1,307 @@ +/* + * geany_encoding=koi8-r + * socket.c - socket IO + * + * 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 "usefull_macros.h" +#include "socket.h" +#include "term.h" +#include // addrinfo +#include // inet_ntop +#include // INT_xxx +#include // pthread_kill +#include // daemon +#include // syscall + +#include "cmdlnopts.h" // glob_pars + +#define BUFLEN (10240) +// Max amount of connections +#define BACKLOG (30) + +extern glob_pars *GP; + +/* + * Define global data buffers here + */ + +/**************** COMMON FUNCTIONS ****************/ +/** + * 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 = 1; // wait not more than 1 second + timeout.tv_usec = 0; + 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; +} + +/**************** SERVER FUNCTIONS ****************/ +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +/** + * Send data over socket + * @param sock - socket fd + * @param webquery - ==1 if this is web query + * @param textbuf - zero-trailing buffer with data to send + * @return 1 if all OK + */ +static int send_data(int sock, int webquery, char *textbuf){ + ssize_t L, Len; + char tbuf[BUFLEN]; + Len = strlen(textbuf); + // OK buffer ready, prepare to send it + if(webquery){ + L = snprintf((char*)tbuf, 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: text/plain\r\nContent-Length: %zd\r\n\r\n", Len); + if(L < 0){ + WARN("sprintf()"); + return 0; + } + if(L != write(sock, tbuf, L)){ + WARN("write"); + return 0; + } + } + // send data + //DBG("send %zd bytes\nBUF: %s", Len, buf); + if(Len != write(sock, textbuf, Len)){ + WARN("write()"); + return 0; + } + return 1; +} + +// search a first word after needle without spaces +static 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 == '\r')) a++; + if(a >= end) return NULL; + e = strchr(a, ' '); + if(e) *e = 0; + return a; +} + +static void *handle_socket(void *asock){ + //LOG("handle_socket(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid)); + FNAME(); + int sock = *((int*)asock); + int webquery = 0; // whether query is web or regular + char buff[BUFLEN]; + ssize_t rd; + double t0 = dtime(); + /* + * INSERT CODE HERE + * change to while(1) if socket shouldn't be closed after data transmission + */ + while(dtime() - t0 < SOCKET_TIMEOUT){ + if(!waittoread(sock)){ // no data incoming + continue; + } + if(!(rd = read(sock, buff, BUFLEN-1))){ + //LOG("socket closed. Exit"); + break; + } + //LOG("client send %zd bytes", rd); + DBG("Got %zd bytes", rd); + if(rd < 0){ // error + //LOG("some error occured"); + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + break; + } + // add trailing zero to be on the safe side + 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; + char *slash = strchr(got, '/'); + if(slash) found = slash + 1; + // web query have format GET /some.resource + } + // here we can process user data + DBG("user send: %s\nfound=%s", buff, found); + if(GP->echo){ + if(!send_data(sock, webquery, found)){ + LOG("can't send data, some error occured"); + } + } + pthread_mutex_lock(&mutex); + /* + * INSERT CODE HERE + * Process user commands here & send him an answer + * remove trailing break if socket shouldn't be closed after server sent data + */ + pthread_mutex_unlock(&mutex); + break; + } + close(sock); + //DBG("closed"); + //LOG("socket closed, exit"); + pthread_exit(NULL); + return NULL; +} + +// main socket server +static void *server(void *asock){ + LOG("server(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid)); + int sock = *((int*)asock); + if(listen(sock, BACKLOG) == -1){ + LOG("listen() failed"); + WARN("listen"); + return NULL; + } + while(1){ + socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_in their_addr; + int newsock; + if(!waittoread(sock)) continue; + newsock = accept(sock, (struct sockaddr*)&their_addr, &size); + if(newsock <= 0){ + LOG("accept() failed"); + WARN("accept()"); + continue; + } + struct sockaddr_in* pV4Addr = (struct sockaddr_in*)&their_addr; + struct in_addr ipAddr = pV4Addr->sin_addr; + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN); + //LOG("get connection from %s", str); + DBG("Got connection from %s\n", str); + pthread_t handler_thread; + if(pthread_create(&handler_thread, NULL, handle_socket, (void*) &newsock)){ + LOG("server(): pthread_create() failed"); + WARN("pthread_create()"); + }else{ + DBG("Thread created, detouch"); + pthread_detach(handler_thread); // don't care about thread state + } + } + LOG("server(): UNREACHABLE CODE REACHED!"); +} + +// data gathering & socket management +static void daemon_(int sock){ + if(sock < 0) return; + pthread_t sock_thread; + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOG("daemon_(): pthread_create() failed"); + ERR("pthread_create()"); + } + double tgot = 0.; + do{ + if(pthread_kill(sock_thread, 0) == ESRCH){ // died + WARNX("Sockets thread died"); + LOG("Sockets thread died"); + pthread_join(sock_thread, NULL); + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOG("daemon_(): new pthread_create() failed"); + ERR("pthread_create()"); + } + } + usleep(1000); // sleep a little or thread's won't be able to lock mutex + if(dtime() - tgot < T_INTERVAL) continue; + tgot = dtime(); + /* + * INSERT CODE HERE + * Gather data (poll_device) + */ + // copy temporary buffers to main + pthread_mutex_lock(&mutex); + /* + * INSERT CODE HERE + * fill global data buffers + */ + pthread_mutex_unlock(&mutex); + }while(1); + LOG("daemon_(): UNREACHABLE CODE REACHED!"); +} + +/** + * Run daemon service + */ +void daemonize(char *port){ + FNAME(); + 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; + if(getaddrinfo(NULL, 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); + // 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; + } + int reuseaddr = 1; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ + ERR("setsockopt"); + } + if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ + close(sock); + WARN("bind"); + continue; + } + break; // if we get here, we have a successfull connection + } + if(p == NULL){ + LOG("failed to bind socket, exit"); + // looped off the end of the list with no successful bind + ERRX("failed to bind socket"); + } + freeaddrinfo(res); + daemon_(sock); + close(sock); + LOG("socket closed, exit"); + signals(0); +} + diff --git a/Daemons/netdaemon.deprecated/socket.h b/Daemons/netdaemon.deprecated/socket.h new file mode 100644 index 0000000..f8b1add --- /dev/null +++ b/Daemons/netdaemon.deprecated/socket.h @@ -0,0 +1,34 @@ +/* + * 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__ + +// timeout for socket closing +#define SOCKET_TIMEOUT (5.0) +// time interval for data polling (seconds) +#define T_INTERVAL (10.) + +void daemonize(char *port); + +#endif // __SOCKET_H__ diff --git a/Daemons/netdaemon.deprecated/term.c b/Daemons/netdaemon.deprecated/term.c new file mode 100644 index 0000000..8bf3894 --- /dev/null +++ b/Daemons/netdaemon.deprecated/term.c @@ -0,0 +1,118 @@ +/* geany_encoding=koi8-r + * client.c - terminal parser + * + * Copyright 2018 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" +#include "term.h" +#include // strncasecmp +#include // time(NULL) +#include // INT_MAX, INT_MIN + +#define BUFLEN 1024 + +static char buf[BUFLEN]; + +/** + * read strings from terminal (ending with '\n') with timeout + * @return NULL if nothing was read or pointer to static buffer + */ +static char *read_string(){ + size_t r = 0, l; + int LL = BUFLEN - 1; + char *ptr = NULL; + static char *optr = NULL; + if(optr && *optr){ + ptr = optr; + optr = strchr(optr, '\n'); + if(optr) ++optr; + //DBG("got data, roll to next; ptr=%s\noptr=%s",ptr,optr); + return ptr; + } + ptr = buf; + double d0 = dtime(); + do{ + if((l = read_tty(ptr, LL))){ + r += l; LL -= l; ptr += l; + if(ptr[-1] == '\n') break; + d0 = dtime(); + } + }while(dtime() - d0 < WAIT_TMOUT && LL); + if(r){ + buf[r] = 0; + //DBG("r=%zd, got string: %s", r, buf); + optr = strchr(buf, '\n'); + if(optr) ++optr; + return buf; + } + return NULL; +} + +/** + * Try to connect to `device` at BAUD_RATE speed + * @return connection speed if success or 0 + */ +void try_connect(char *device){ + if(!device) return; + char tmpbuf[4096]; + fflush(stdout); + tty_init(device); + while(read_tty(tmpbuf, 4096)); // clear rbuf + LOG("Connected to %s", device); +} + +/** + * run terminal emulation: send user's commands and show answers + */ +void run_terminal(){ + green(_("Work in terminal mode without echo\n")); + int rb; + char buf[BUFLEN]; + size_t l; + setup_con(); + while(1){ + if((l = read_tty(buf, BUFLEN - 1))){ + buf[l] = 0; + printf("%s", buf); + } + if((rb = read_console())){ + buf[0] = (char) rb; + write_tty(buf, 1); + } + } +} + +/** + * Poll serial port for new dataportion + * @return: NULL if no data received, pointer to string if valid data received + */ +char *poll_device(){ + char *ans; + double t0 = dtime(); + while(dtime() - t0 < T_POLLING_TMOUT){ + if((ans = read_string())){ // parse new data + DBG("got %s", ans); + /* + * INSERT CODE HERE + * (data validation) + */ + return ans; + } + } + return NULL; +} diff --git a/Daemons/netdaemon.deprecated/term.h b/Daemons/netdaemon.deprecated/term.h new file mode 100644 index 0000000..9c64ac8 --- /dev/null +++ b/Daemons/netdaemon.deprecated/term.h @@ -0,0 +1,36 @@ +/* geany_encoding=koi8-r + * term.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 __TERM_H__ +#define __TERM_H__ + +#define FRAME_MAX_LENGTH (300) +#define MAX_MEMORY_DUMP_SIZE (0x800 * 4) +// Terminal timeout (seconds) +#define WAIT_TMOUT (0.5) +// Terminal polling timeout - 1 second +#define T_POLLING_TMOUT (1.0) + +void run_terminal(); +void try_connect(char *device); +char *poll_device(); + +#endif // __TERM_H__ diff --git a/Daemons/netdaemon/usefull_macros.c b/Daemons/netdaemon.deprecated/usefull_macros.c similarity index 100% rename from Daemons/netdaemon/usefull_macros.c rename to Daemons/netdaemon.deprecated/usefull_macros.c diff --git a/Daemons/netdaemon/usefull_macros.h b/Daemons/netdaemon.deprecated/usefull_macros.h similarity index 100% rename from Daemons/netdaemon/usefull_macros.h rename to Daemons/netdaemon.deprecated/usefull_macros.h diff --git a/Daemons/netdaemon/Makefile b/Daemons/netdaemon/Makefile index bc11a20..8ae2622 100644 --- a/Daemons/netdaemon/Makefile +++ b/Daemons/netdaemon/Makefile @@ -1,11 +1,11 @@ # run `make DEF=...` to add extra defines PROGRAM := netdaemon LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all -pthread +LDFLAGS += -lusefull_macros SRCS := $(wildcard *.c) DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 DEFINES += -DEBUG # baudrate for USB<->UART converter -DEFINES += -DBAUD_RATE=B115200 OBJDIR := mk CFLAGS += -O2 -Wall -Werror -Wextra -Wno-trampolines OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) diff --git a/Daemons/netdaemon/Readme.md b/Daemons/netdaemon/Readme.md index 24d6bef..0234d43 100644 --- a/Daemons/netdaemon/Readme.md +++ b/Daemons/netdaemon/Readme.md @@ -1,9 +1,21 @@ Network daemon snippet ================== -This isn't an end-product, but just a template for different net-daemons. - Open a socket at given port (default: 4444), works with http & direct requests. Can read and send commands over serial interface. Pieces with user code marked as 'INSERT CODE HERE'. + + +Usage: netdaemon [args] + + Where args are: + + -b, --baudrate=arg serial terminal baudrate (default: 115200) + -e, --echo echo users commands back + -h, --help show this help + -i, --device=arg serial device name (default: none) + -l, --logfile=arg save logs to file (default: none) + -p, --port=arg network port to connect (default: 4444) + -t, --terminal run as terminal + -v, --verb logfile verbocity level (each -v increase it) diff --git a/Daemons/netdaemon/cmdlnopts.c b/Daemons/netdaemon/cmdlnopts.c index 229b0d3..315262a 100644 --- a/Daemons/netdaemon/cmdlnopts.c +++ b/Daemons/netdaemon/cmdlnopts.c @@ -23,8 +23,9 @@ #include #include #include +#include #include "cmdlnopts.h" -#include "usefull_macros.h" +#include "term.h" /* * here are global parameters initialisation @@ -43,6 +44,8 @@ glob_pars const Gdefault = { .terminal = 0, .echo = 0, .logfile = NULL, + .verb = 0, + .tty_speed = 115200, .rest_pars = NULL, .rest_pars_num = 0 }; @@ -59,6 +62,8 @@ myoption cmdlnopts[] = { {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), _("save logs to file (default: none)")}, {"terminal",NO_ARGS, NULL, 't', arg_int, APTR(&G.terminal), _("run as terminal")}, {"echo", NO_ARGS, NULL, 'e', arg_int, APTR(&G.echo), _("echo users commands back")}, + {"verb", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), _("logfile verbocity level (each -v increase it)")}, + {"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.tty_speed), _("serial terminal baudrate (default: 115200)")}, end_option }; diff --git a/Daemons/netdaemon/cmdlnopts.h b/Daemons/netdaemon/cmdlnopts.h index 712e140..48e3e08 100644 --- a/Daemons/netdaemon/cmdlnopts.h +++ b/Daemons/netdaemon/cmdlnopts.h @@ -23,9 +23,6 @@ #ifndef __CMDLNOPTS_H__ #define __CMDLNOPTS_H__ -#include "parseargs.h" -#include "term.h" - /* * here are some typedef's for global data */ @@ -35,6 +32,8 @@ typedef struct{ char *logfile; // logfile name int terminal; // run as terminal int echo; // echo user commands back + int verb; // verbocity level + int tty_speed; // serial terminal baudrate int rest_pars_num; // number of rest parameters char** rest_pars; // the rest parameters: array of char* (path to logfile and thrash) } glob_pars; diff --git a/Daemons/netdaemon/main.c b/Daemons/netdaemon/main.c index 9ceae30..018375a 100644 --- a/Daemons/netdaemon/main.c +++ b/Daemons/netdaemon/main.c @@ -18,19 +18,21 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ -#include "usefull_macros.h" #include +#include #include // wait #include //prctl +#include #include "cmdlnopts.h" #include "socket.h" +#include "term.h" glob_pars *GP; void signals(int signo){ restore_console(); - restore_tty(); - LOG("exit with status %d", signo); + if(ttydescr) close_tty(&ttydescr); + LOGERR("exit with status %d", signo); exit(signo); } @@ -44,12 +46,18 @@ int main(int argc, char **argv){ GP = parse_args(argc, argv); if(GP->terminal){ if(!GP->device) ERRX(_("Point serial device name")); - try_connect(GP->device); + if(!try_connect(GP->device, GP->tty_speed)) + ERRX("Can't connect to device"); run_terminal(); signals(0); // never reached! } - if(GP->logfile) - Cl_createlog(GP->logfile); + if(GP->logfile){ + sl_loglevel lvl = LOGLEVEL_ERR; + for(; GP->verb && lvl < LOGLEVEL_ANY; --GP->verb) ++lvl; + DBG("Loglevel: %d", lvl); + if(!OPENLOG(GP->logfile, lvl, 1)) ERRX("Can't open log file"); + LOGERR("Started"); + } #ifndef EBUG if(daemon(1, 0)){ ERR("daemon()"); @@ -57,10 +65,11 @@ int main(int argc, char **argv){ while(1){ // guard for dead processes pid_t childpid = fork(); if(childpid){ - LOG("create child with PID %d\n", childpid); + LOGDBG("create child with PID %d\n", childpid); DBG("Created child with PID %d\n", childpid); wait(NULL); WARNX("Child %d died\n", childpid); + LOGWARN("Child %d died\n", childpid); sleep(1); }else{ prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies @@ -69,7 +78,8 @@ int main(int argc, char **argv){ } #endif - if(GP->device) try_connect(GP->device); + if(GP->device) if(!try_connect(GP->device, GP->tty_speed)) + ERRX("Can't connect to device");; /* * INSERT CODE HERE * connection check & device validation diff --git a/Daemons/netdaemon/socket.c b/Daemons/netdaemon/socket.c index 4db0fc5..c07f68f 100644 --- a/Daemons/netdaemon/socket.c +++ b/Daemons/netdaemon/socket.c @@ -20,19 +20,24 @@ * MA 02110-1301, USA. * */ -#include "usefull_macros.h" -#include "socket.h" -#include "term.h" #include // addrinfo #include // inet_ntop #include // INT_xxx -#include // pthread_kill -#include // daemon +#include // poll +#include +#include // pthread_kill +#include +#include #include // syscall +#include // daemon +#include #include "cmdlnopts.h" // glob_pars +#include "socket.h" +#include "term.h" -#define BUFLEN (10240) +// temporary buffers +#define BUFLEN (1024) // Max amount of connections #define BACKLOG (30) @@ -42,37 +47,7 @@ extern glob_pars *GP; * Define global data buffers here */ -/**************** COMMON FUNCTIONS ****************/ -/** - * 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 = 1; // wait not more than 1 second - timeout.tv_usec = 0; - 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; -} - /**************** SERVER FUNCTIONS ****************/ -static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; /** * Send data over socket * @param sock - socket fd @@ -94,9 +69,11 @@ static int send_data(int sock, int webquery, char *textbuf){ "Content-type: text/plain\r\nContent-Length: %zd\r\n\r\n", Len); if(L < 0){ WARN("sprintf()"); + LOGWARN("sprintf()"); return 0; } if(L != write(sock, tbuf, L)){ + LOGWARN("Can't write header"); WARN("write"); return 0; } @@ -105,121 +82,162 @@ static int send_data(int sock, int webquery, char *textbuf){ //DBG("send %zd bytes\nBUF: %s", Len, buf); if(Len != write(sock, textbuf, Len)){ WARN("write()"); + LOGERR("send_data(): write() failed"); return 0; } + LOGDBG("fd %d, write %s", textbuf); return 1; } // search a first word after needle without spaces static char* stringscan(char *str, char *needle){ - char *a, *e; + 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 == '\r')) a++; if(a >= end) return NULL; - e = strchr(a, ' '); - if(e) *e = 0; return a; } -static void *handle_socket(void *asock){ - //LOG("handle_socket(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid)); +/** + * @brief handle_socket - read information from socket + * @param sock - socket fd + * @param chkheader - ==1 on first run + * @return 1 if socket closed + */ +static int handle_socket(int sock, int notchkhdr){ FNAME(); - int sock = *((int*)asock); int webquery = 0; // whether query is web or regular char buff[BUFLEN]; ssize_t rd; - double t0 = dtime(); - /* - * INSERT CODE HERE - * change to while(1) if socket shouldn't be closed after data transmission - */ - while(dtime() - t0 < SOCKET_TIMEOUT){ - if(!waittoread(sock)){ // no data incoming - continue; - } - if(!(rd = read(sock, buff, BUFLEN-1))){ - //LOG("socket closed. Exit"); - break; - } - //LOG("client send %zd bytes", rd); - DBG("Got %zd bytes", rd); - if(rd < 0){ // error - //LOG("some error occured"); - DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); - break; - } - // add trailing zero to be on the safe side - 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 + if(!(rd = read(sock, buff, BUFLEN-1))){ + LOGMSG("Client %d closed", sock); + return 1; + } + //LOG("client send %zd bytes", rd); + DBG("Got %zd bytes", rd); + if(rd < 0){ // error + LOGWARN("Client %d close socket on error", sock); + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + return 1; + } + // add trailing zero to be on the safe side + buff[rd] = 0; + // now we should check what do user want + char *found = buff; + DBG("user send: %s", buff); + if(!notchkhdr){ + if(0 == strncmp(buff, "GET", 3)){ + DBG("GET"); + // GET web query have format GET /some.resource webquery = 1; - char *slash = strchr(got, '/'); - if(slash) found = slash + 1; - // web query have format GET /some.resource - } - // here we can process user data - DBG("user send: %s\nfound=%s", buff, found); - if(GP->echo){ - if(!send_data(sock, webquery, found)){ - LOG("can't send data, some error occured"); + char *slash = strchr(buff, '/'); + if(slash){ + found = slash + 1; + char *eol = strstr(found, "HTTP"); + if(eol) *eol = 0; + } + }else if(0 == strncmp(buff, "POST", 4)){ + DBG("POST"); + webquery = 1; + // search content length of POST query + char *cl = stringscan(buff, "Content-Length:"); + if(cl){ + int contlen = atoi(cl); + int l = strlen(buff); + if(contlen && l > contlen) found = &buff[l - contlen]; } } - pthread_mutex_lock(&mutex); - /* - * INSERT CODE HERE - * Process user commands here & send him an answer - * remove trailing break if socket shouldn't be closed after server sent data - */ - pthread_mutex_unlock(&mutex); - break; } - close(sock); - //DBG("closed"); - //LOG("socket closed, exit"); - pthread_exit(NULL); - return NULL; + // here we can process user data + DBG("found=%s", found); + LOGDBG("sockfd=%d, got %s", sock, buff); + if(GP->echo){ + if(!send_data(sock, webquery, found)){ + LOGWARN("Can't send data, some error occured"); + return 1; + } + } + /* + * + * INSERT CODE HERE + * Process user commands here & send him an answer + * remove trailing break if socket shouldn't be closed after server sent data + * + */ + if(webquery) return 1; // close web query after message processing + return 0; } // main socket server static void *server(void *asock){ - LOG("server(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid)); + LOGMSG("server()"); int sock = *((int*)asock); if(listen(sock, BACKLOG) == -1){ - LOG("listen() failed"); + LOGERR("listen() failed"); WARN("listen"); return NULL; } + int nfd = 1; // current fd amount in poll_set + struct pollfd poll_set[MAX_FDS]; + int notchkhdr[MAX_FDS]; + memset(poll_set, 0, sizeof(poll_set)); + memset(notchkhdr, 0, sizeof(notchkhdr)); + poll_set[0].fd = sock; + poll_set[0].events = POLLIN; while(1){ - socklen_t size = sizeof(struct sockaddr_in); - struct sockaddr_in their_addr; - int newsock; - if(!waittoread(sock)) continue; - newsock = accept(sock, (struct sockaddr*)&their_addr, &size); - if(newsock <= 0){ - LOG("accept() failed"); - WARN("accept()"); - continue; - } - struct sockaddr_in* pV4Addr = (struct sockaddr_in*)&their_addr; - struct in_addr ipAddr = pV4Addr->sin_addr; - char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN); - //LOG("get connection from %s", str); - DBG("Got connection from %s\n", str); - pthread_t handler_thread; - if(pthread_create(&handler_thread, NULL, handle_socket, (void*) &newsock)){ - LOG("server(): pthread_create() failed"); - WARN("pthread_create()"); - }else{ - DBG("Thread created, detouch"); - pthread_detach(handler_thread); // don't care about thread state - } + poll(poll_set, nfd, 1); // poll for 1ms + for(int fdidx = 0; fdidx < nfd; ++fdidx){ // poll opened FDs + if((poll_set[fdidx].revents & POLLIN) == 0) continue; + poll_set[fdidx].revents = 0; + if(fdidx){ // client + int fd = poll_set[fdidx].fd; + if(handle_socket(fd, notchkhdr[fdidx])){ // socket closed - remove it from list + close(fd); + DBG("Client with fd %d closed", fd); + LOGMSG("Client %d disconnected", fd); + // move last to free space + poll_set[fdidx] = poll_set[nfd - 1]; + notchkhdr[fdidx] = notchkhdr[nfd - 1]; + --nfd; + }else notchkhdr[fdidx] = 1; + }else{ // server + socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_in their_addr; + int newsock = accept(sock, (struct sockaddr*)&their_addr, &size); + if(newsock <= 0){ + LOGERR("server(): accept() failed"); + WARN("accept()"); + continue; + } + struct in_addr ipAddr = their_addr.sin_addr; + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN); + DBG("Connection from %s, give fd=%d", str, newsock); + LOGMSG("Got connection from %s, fd=%d", str, newsock); + if(nfd == MAX_FDS){ + LOGWARN("Max amount of connections: disconnect %s (%d)", str, newsock); + send_data(newsock, 0, "Max amount of connections reached!\n"); + WARNX("Limit of connections reached"); + close(newsock); + }else{ + memset(&poll_set[nfd], 0, sizeof(struct pollfd)); + poll_set[nfd].fd = newsock; + poll_set[nfd].events = POLLIN; + notchkhdr[nfd] = 0; + ++nfd; + } + + } + } // endfor + /* + * INSERT CODE HERE + * Send broadcast messages + */ } - LOG("server(): UNREACHABLE CODE REACHED!"); + LOGERR("server(): UNREACHABLE CODE REACHED!"); } // data gathering & socket management @@ -227,36 +245,26 @@ static void daemon_(int sock){ if(sock < 0) return; pthread_t sock_thread; if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ - LOG("daemon_(): pthread_create() failed"); + LOGERR("daemon_(): pthread_create() failed"); ERR("pthread_create()"); } - double tgot = 0.; do{ if(pthread_kill(sock_thread, 0) == ESRCH){ // died WARNX("Sockets thread died"); - LOG("Sockets thread died"); + LOGWARN("Sockets thread died"); pthread_join(sock_thread, NULL); if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ - LOG("daemon_(): new pthread_create() failed"); + LOGERR("daemon_(): new pthread_create() failed"); ERR("pthread_create()"); } } - usleep(1000); // sleep a little or thread's won't be able to lock mutex - if(dtime() - tgot < T_INTERVAL) continue; - tgot = dtime(); + usleep(1000); // sleep a little /* * INSERT CODE HERE * Gather data (poll_device) */ - // copy temporary buffers to main - pthread_mutex_lock(&mutex); - /* - * INSERT CODE HERE - * fill global data buffers - */ - pthread_mutex_unlock(&mutex); }while(1); - LOG("daemon_(): UNREACHABLE CODE REACHED!"); + LOGERR("daemon_(): UNREACHABLE CODE REACHED!"); } /** @@ -270,7 +278,9 @@ void daemonize(char *port){ hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; + // To accept only local sockets replace NULL with "127.0.0.1" and remove AI_PASSIVE if(getaddrinfo(NULL, port, &hints, &res) != 0){ + LOGERR("getaddrinfo"); ERR("getaddrinfo"); } struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr; @@ -284,24 +294,26 @@ void daemonize(char *port){ } int reuseaddr = 1; if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ + LOGERR("setsockopt() error"); ERR("setsockopt"); } if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ close(sock); WARN("bind"); + LOGWARN("bind() error"); continue; } break; // if we get here, we have a successfull connection } if(p == NULL){ - LOG("failed to bind socket, exit"); + LOGERR("daemonize(): failed to bind socket, exit"); // looped off the end of the list with no successful bind ERRX("failed to bind socket"); } freeaddrinfo(res); daemon_(sock); close(sock); - LOG("socket closed, exit"); + LOGERR("daemonize(): UNREACHABLE CODE REACHED!"); signals(0); } diff --git a/Daemons/netdaemon/socket.h b/Daemons/netdaemon/socket.h index f8b1add..8db1614 100644 --- a/Daemons/netdaemon/socket.h +++ b/Daemons/netdaemon/socket.h @@ -24,10 +24,10 @@ #ifndef __SOCKET_H__ #define __SOCKET_H__ -// timeout for socket closing -#define SOCKET_TIMEOUT (5.0) // time interval for data polling (seconds) #define T_INTERVAL (10.) +// max amount of opened fd (+1 for server socket) +#define MAX_FDS (11) void daemonize(char *port); diff --git a/Daemons/netdaemon/term.c b/Daemons/netdaemon/term.c index 8bf3894..54ef9e1 100644 --- a/Daemons/netdaemon/term.c +++ b/Daemons/netdaemon/term.c @@ -18,14 +18,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ -#include "usefull_macros.h" -#include "term.h" + +#include +#include #include // strncasecmp #include // time(NULL) #include // INT_MAX, INT_MIN +#include "term.h" #define BUFLEN 1024 +TTY_descr *ttydescr = NULL; + static char buf[BUFLEN]; /** @@ -33,6 +37,7 @@ static char buf[BUFLEN]; * @return NULL if nothing was read or pointer to static buffer */ static char *read_string(){ + if(!ttydescr) ERRX("Serial device not initialized"); size_t r = 0, l; int LL = BUFLEN - 1; char *ptr = NULL; @@ -47,7 +52,7 @@ static char *read_string(){ ptr = buf; double d0 = dtime(); do{ - if((l = read_tty(ptr, LL))){ + if((l = read_tty(ttydescr))){ r += l; LL -= l; ptr += l; if(ptr[-1] == '\n') break; d0 = dtime(); @@ -64,35 +69,36 @@ static char *read_string(){ } /** - * Try to connect to `device` at BAUD_RATE speed - * @return connection speed if success or 0 + * Try to connect to `device` at baudrate speed + * @return 1 if OK */ -void try_connect(char *device){ - if(!device) return; - char tmpbuf[4096]; +int try_connect(char *device, int baudrate){ + if(!device) return 0; fflush(stdout); - tty_init(device); - while(read_tty(tmpbuf, 4096)); // clear rbuf - LOG("Connected to %s", device); + ttydescr = new_tty(device, baudrate, 1024); + if(ttydescr) ttydescr = tty_open(ttydescr, 1); // exclusive open + if(!ttydescr) return 0; + while(read_tty(ttydescr)); // clear rbuf + LOGMSG("Connected to %s", device); + return 1; } /** * run terminal emulation: send user's commands and show answers */ void run_terminal(){ + if(!ttydescr) ERRX("Terminal not connected"); green(_("Work in terminal mode without echo\n")); int rb; - char buf[BUFLEN]; size_t l; setup_con(); while(1){ - if((l = read_tty(buf, BUFLEN - 1))){ - buf[l] = 0; - printf("%s", buf); + if((l = read_tty(ttydescr))){ + printf("%s", ttydescr->buf); } if((rb = read_console())){ - buf[0] = (char) rb; - write_tty(buf, 1); + char c = (char) rb; + write_tty(ttydescr->comfd, &c, 1); } } } diff --git a/Daemons/netdaemon/term.h b/Daemons/netdaemon/term.h index 9c64ac8..85ee351 100644 --- a/Daemons/netdaemon/term.h +++ b/Daemons/netdaemon/term.h @@ -22,6 +22,8 @@ #ifndef __TERM_H__ #define __TERM_H__ +#include + #define FRAME_MAX_LENGTH (300) #define MAX_MEMORY_DUMP_SIZE (0x800 * 4) // Terminal timeout (seconds) @@ -29,8 +31,9 @@ // Terminal polling timeout - 1 second #define T_POLLING_TMOUT (1.0) +extern TTY_descr *ttydescr; void run_terminal(); -void try_connect(char *device); +int try_connect(char *device, int baudrate); char *poll_device(); #endif // __TERM_H__ diff --git a/Daemons/netsocket/HWpoweroff b/Daemons/netsocket/HWpoweroff old mode 100755 new mode 100644 diff --git a/Daemons/netsocket/HWpoweron b/Daemons/netsocket/HWpoweron old mode 100755 new mode 100644 diff --git a/Daemons/netsocket/MOUNTpoweronoff b/Daemons/netsocket/MOUNTpoweronoff old mode 100755 new mode 100644 diff --git a/Daemons/weatherdaemon/Makefile b/Daemons/weatherdaemon/Makefile new file mode 100644 index 0000000..46eccdc --- /dev/null +++ b/Daemons/weatherdaemon/Makefile @@ -0,0 +1,43 @@ +# run `make DEF=...` to add extra defines +PROGRAM := weatherdaemon +LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all -pthread +LDFLAGS += -lusefull_macros +SRCS := $(wildcard *.c) +DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 +DEFINES += -DEBUG +# baudrate for USB<->UART converter +OBJDIR := mk +CFLAGS += -O2 -Wall -Werror -Wextra -Wno-trampolines +OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) +DEPS := $(OBJS:.o=.d) +CC = gcc + +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/Daemons/weatherdaemon/Readme.md b/Daemons/weatherdaemon/Readme.md new file mode 100644 index 0000000..21e858f --- /dev/null +++ b/Daemons/weatherdaemon/Readme.md @@ -0,0 +1,19 @@ +Weather daemon +================== + +Open a socket at given port (default: 4444) +Parse weather data and send it to client + +Usage: weatherdaemon [args] + + Where args are: + + -b, --baudrate=arg serial terminal baudrate (default: 115200) + -e, --emulation emulate serial device + -h, --help show this help + -i, --device=arg serial device name (default: none) + -l, --logfile=arg save logs to file (default: none) + -p, --port=arg network port to connect (default: 4444) + -v, --verb logfile verbocity level (each -v increase it) + + diff --git a/Daemons/weatherdaemon/cmdlnopts.c b/Daemons/weatherdaemon/cmdlnopts.c new file mode 100644 index 0000000..b110918 --- /dev/null +++ b/Daemons/weatherdaemon/cmdlnopts.c @@ -0,0 +1,90 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 "cmdlnopts.h" +#include "term.h" + +/* + * here are global parameters initialisation + */ +int help; +static glob_pars G; + +// default values for Gdefault & help +#define DEFAULT_PORT "4444" + +// DEFAULTS +// default global parameters +glob_pars const Gdefault = { + .device = NULL, + .port = DEFAULT_PORT, + .logfile = NULL, + .verb = 0, + .tty_speed = 115200, + .rest_pars = NULL, + .rest_pars_num = 0, + .emul = 0 +}; + +/* + * 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")}, + {"device", NEED_ARG, NULL, 'i', arg_string, APTR(&G.device), _("serial device name (default: none)")}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), _("network port to connect (default: " DEFAULT_PORT ")")}, + {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), _("save logs to file (default: none)")}, + {"verb", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verb), _("logfile verbocity level (each -v increase it)")}, + {"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.tty_speed), _("serial terminal baudrate (default: 115200)")}, + {"emulation",NO_ARGS, NULL, 'e', arg_int, APTR(&G.emul), _("emulate serial device")}, + 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]\n\n\tWhere args are:\n"); + // parse arguments + parseargs(&argc, &argv, cmdlnopts); + if(help) showhelp(-1, cmdlnopts); + if(argc > 0){ + G.rest_pars_num = argc; + G.rest_pars = calloc(argc, sizeof(char*)); + for (i = 0; i < argc; i++) + G.rest_pars[i] = strdup(argv[i]); + } + return &G; +} + diff --git a/Daemons/weatherdaemon/cmdlnopts.h b/Daemons/weatherdaemon/cmdlnopts.h new file mode 100644 index 0000000..53328fc --- /dev/null +++ b/Daemons/weatherdaemon/cmdlnopts.h @@ -0,0 +1,43 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 +#ifndef __CMDLNOPTS_H__ +#define __CMDLNOPTS_H__ + +/* + * here are some typedef's for global data + */ +typedef struct{ + char *device; // serial device name + char *port; // port to connect + char *logfile; // logfile name + int terminal; // run as terminal + int echo; // echo user commands back + int verb; // verbocity level + int tty_speed; // serial terminal baudrate + int emul; // emulation of serial device + int rest_pars_num; // number of rest parameters + char** rest_pars; // the rest parameters: array of char* (path to logfile and thrash) +} glob_pars; + + +glob_pars *parse_args(int argc, char **argv); +#endif // __CMDLNOPTS_H__ diff --git a/Daemons/weatherdaemon/main.c b/Daemons/weatherdaemon/main.c new file mode 100644 index 0000000..140e4a2 --- /dev/null +++ b/Daemons/weatherdaemon/main.c @@ -0,0 +1,82 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 // wait +#include //prctl +#include +#include "cmdlnopts.h" +#include "socket.h" +#include "term.h" + +glob_pars *GP; + +void signals(int signo){ + restore_console(); + if(ttydescr) close_tty(&ttydescr); + LOGERR("exit with status %d", 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 + GP = parse_args(argc, argv); + if(GP->logfile){ + sl_loglevel lvl = LOGLEVEL_ERR; + for(; GP->verb && lvl < LOGLEVEL_ANY; --GP->verb) ++lvl; + DBG("Loglevel: %d", lvl); + if(!OPENLOG(GP->logfile, lvl, 1)) ERRX("Can't open log file"); + LOGERR("Started"); + } + #ifndef EBUG + if(daemon(1, 0)){ + ERR("daemon()"); + } + while(1){ // guard for dead processes + pid_t childpid = fork(); + if(childpid){ + LOGDBG("create child with PID %d\n", childpid); + DBG("Created child with PID %d\n", childpid); + wait(NULL); + WARNX("Child %d died\n", childpid); + LOGWARN("Child %d died\n", childpid); + sleep(1); + }else{ + prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies + break; // go out to normal functional + } + } + #endif + + if(GP->device) if(!try_connect(GP->device, GP->tty_speed)){ + LOGERR("Can't connect to device"); + ERRX("Can't connect to device"); + } + if(!GP->device && !GP->emul){ + LOGERR("Need serial device name or emulation flag"); + ERRX("Need serial device name or emulation flag"); + } + daemonize(GP->port); + return 0; +} diff --git a/Daemons/weatherdaemon/socket.c b/Daemons/weatherdaemon/socket.c new file mode 100644 index 0000000..453da91 --- /dev/null +++ b/Daemons/weatherdaemon/socket.c @@ -0,0 +1,347 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 // addrinfo +#include // inet_ntop +#include // INT_xxx +#include // poll +#include +#include // pthread_kill +#include +#include +#include // syscall +#include // daemon +#include + +#include "cmdlnopts.h" // glob_pars +#include "socket.h" +#include "term.h" + +// temporary buffers +#define BUFLEN (1024) +// Max amount of connections +#define BACKLOG (30) + +extern glob_pars *GP; + +static char *answer = NULL; +static int freshdata = 0; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + +/**************** SERVER FUNCTIONS ****************/ +/** + * Send data over socket + * @param sock - socket fd + * @param webquery - ==1 if this is web query + * @param textbuf - zero-trailing buffer with data to send + * @return 1 if all OK + */ +static int send_data(int sock, int webquery, char *textbuf){ + ssize_t L, Len; + char tbuf[BUFLEN]; + Len = strlen(textbuf); + // OK buffer ready, prepare to send it + if(webquery){ + L = snprintf((char*)tbuf, 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: text/plain\r\nContent-Length: %zd\r\n\r\n", Len); + if(L < 0){ + WARN("sprintf()"); + LOGWARN("sprintf()"); + return 0; + } + if(L != write(sock, tbuf, L)){ + LOGWARN("Can't write header"); + WARN("write"); + return 0; + } + } + // send data + //DBG("send %zd bytes\nBUF: %s", Len, buf); + if(Len != write(sock, textbuf, Len)){ + WARN("write()"); + LOGERR("send_data(): write() failed"); + return 0; + } + LOGDBG("fd %d, write %s", textbuf); + return 1; +} + +// search a first word after needle without spaces +static 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 == '\r')) a++; + if(a >= end) return NULL; + return a; +} + +/** + * @brief handle_socket - read information from socket + * @param sock - socket fd + * @param chkheader - ==1 on first run + * @return 1 if socket closed + */ +static int handle_socket(int sock, int notchkhdr){ + FNAME(); + int webquery = 0; // whether query is web or regular + char buff[BUFLEN]; + ssize_t rd; + if(!(rd = read(sock, buff, BUFLEN-1))){ + LOGMSG("Client %d closed", sock); + return 1; + } + //LOG("client send %zd bytes", rd); + DBG("Got %zd bytes", rd); + if(rd < 0){ // error + LOGWARN("Client %d close socket on error", sock); + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + return 1; + } + // add trailing zero to be on the safe side + buff[rd] = 0; + // now we should check what do user want + char *found = buff; + DBG("user send: %s", buff); + if(!notchkhdr){ + if(0 == strncmp(buff, "GET", 3)){ + DBG("GET"); + // GET web query have format GET /some.resource + webquery = 1; + char *slash = strchr(buff, '/'); + if(slash){ + found = slash + 1; + char *eol = strstr(found, "HTTP"); + if(eol) *eol = 0; + } + }else if(0 == strncmp(buff, "POST", 4)){ + DBG("POST"); + webquery = 1; + // search content length of POST query + char *cl = stringscan(buff, "Content-Length:"); + if(cl){ + int contlen = atoi(cl); + int l = strlen(buff); + if(contlen && l > contlen) found = &buff[l - contlen]; + } + } + } + // here we can process user data + DBG("found=%s", found); + LOGDBG("sockfd=%d, got %s", sock, buff); + if(GP->echo){ + if(!send_data(sock, webquery, found)){ + LOGWARN("Can't send data, some error occured"); + return 1; + } + } + if(answer) send_data(sock, webquery, answer); + else send_data(sock, webquery, "No data\n"); + if(webquery) return 1; // close web query after message processing + return 0; +} + +// main socket server +static void *server(void *asock){ + LOGMSG("server()"); + int sock = *((int*)asock); + if(listen(sock, BACKLOG) == -1){ + LOGERR("listen() failed"); + WARN("listen"); + return NULL; + } + int nfd = 1; // current fd amount in poll_set + struct pollfd poll_set[MAX_FDS]; + int notchkhdr[MAX_FDS]; + memset(poll_set, 0, sizeof(poll_set)); + memset(notchkhdr, 0, sizeof(notchkhdr)); + poll_set[0].fd = sock; + poll_set[0].events = POLLIN; + double lastdatat = dtime(); + while(1){ + poll(poll_set, nfd, 1); // poll for 1ms + for(int fdidx = 0; fdidx < nfd; ++fdidx){ // poll opened FDs + if((poll_set[fdidx].revents & POLLIN) == 0) continue; + poll_set[fdidx].revents = 0; + if(fdidx){ // client + int fd = poll_set[fdidx].fd; + if(handle_socket(fd, notchkhdr[fdidx])){ // socket closed - remove it from list + close(fd); + DBG("Client with fd %d closed", fd); + LOGMSG("Client %d disconnected", fd); + // move last to free space + poll_set[fdidx] = poll_set[nfd - 1]; + notchkhdr[fdidx] = notchkhdr[nfd - 1]; + --nfd; + }else notchkhdr[fdidx] = 1; + }else{ // server + socklen_t size = sizeof(struct sockaddr_in); + struct sockaddr_in their_addr; + int newsock = accept(sock, (struct sockaddr*)&their_addr, &size); + if(newsock <= 0){ + LOGERR("server(): accept() failed"); + WARN("accept()"); + continue; + } + struct in_addr ipAddr = their_addr.sin_addr; + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN); + DBG("Connection from %s, give fd=%d", str, newsock); + LOGMSG("Got connection from %s, fd=%d", str, newsock); + if(nfd == MAX_FDS){ + LOGWARN("Max amount of connections: disconnect %s (%d)", str, newsock); + send_data(newsock, 0, "Max amount of connections reached!\n"); + WARNX("Limit of connections reached"); + close(newsock); + }else{ + memset(&poll_set[nfd], 0, sizeof(struct pollfd)); + poll_set[nfd].fd = newsock; + poll_set[nfd].events = POLLIN; + notchkhdr[nfd] = 0; + ++nfd; + } + } + } // endfor + if(freshdata && answer){ // send new data to all + freshdata = 0; + lastdatat = dtime(); + for(int fdidx = 1; fdidx < nfd; ++fdidx){ + if(notchkhdr[fdidx]) + send_data(poll_set[fdidx].fd, 0, answer); + } + } + if(dtime() - lastdatat > NODATA_TMOUT){ + LOGERR("No data timeout"); + ERRX("No data timeout"); + } + } + LOGERR("server(): UNREACHABLE CODE REACHED!"); +} + +static void *ttyparser(_U_ void *notused){ + double tlast = 0; + while(1){ + if(dtime() - tlast > T_INTERVAL){ + char *got = poll_device(); + if(got){ + if (0 == pthread_mutex_lock(&mutex)){ + FREE(answer); + answer = strdup(got); + freshdata = 1; + pthread_mutex_unlock(&mutex); + } + tlast = dtime(); + } + } + sleep(1); + } + return NULL; +} + +// data gathering & socket management +static void daemon_(int sock){ + if(sock < 0) return; + pthread_t sock_thread, parser_thread; + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOGERR("daemon_(): pthread_create(sock_thread) failed"); + ERR("pthread_create()"); + } + if(pthread_create(&parser_thread, NULL, ttyparser, NULL)){ + LOGERR("daemon_(): pthread_create(parser_thread) failed"); + ERR("pthread_create()"); + } + do{ + if(pthread_kill(sock_thread, 0) == ESRCH){ // died + WARNX("Sockets thread died"); + LOGWARN("Sockets thread died"); + pthread_join(sock_thread, NULL); + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOGERR("daemon_(): new pthread_create() failed"); + ERR("pthread_create()"); + } + } + if(pthread_kill(parser_thread, 0) == ESRCH){ // died + WARNX("TTY thread died"); + LOGWARN("TTY thread died"); + pthread_join(parser_thread, NULL); + if(pthread_create(&parser_thread, NULL, ttyparser, NULL)){ + LOGERR("daemon_(): new pthread_create(parser_thread) failed"); + ERR("pthread_create()"); + } + } + usleep(1000); // sleep a little + }while(1); + LOGERR("daemon_(): UNREACHABLE CODE REACHED!"); +} + +/** + * Run daemon service + */ +void daemonize(char *port){ + FNAME(); + 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; + // To accept only local sockets replace NULL with "127.0.0.1" and remove AI_PASSIVE + if(getaddrinfo(NULL, port, &hints, &res) != 0){ + LOGERR("getaddrinfo"); + 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); + // 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; + } + int reuseaddr = 1; + if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ + LOGERR("setsockopt() error"); + ERR("setsockopt"); + } + if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ + close(sock); + WARN("bind"); + LOGWARN("bind() error"); + continue; + } + break; // if we get here, we have a successfull connection + } + if(p == NULL){ + LOGERR("daemonize(): failed to bind socket, exit"); + // looped off the end of the list with no successful bind + ERRX("failed to bind socket"); + } + freeaddrinfo(res); + daemon_(sock); + close(sock); + LOGERR("daemonize(): UNREACHABLE CODE REACHED!"); + signals(0); +} + diff --git a/Daemons/weatherdaemon/socket.h b/Daemons/weatherdaemon/socket.h new file mode 100644 index 0000000..189aabc --- /dev/null +++ b/Daemons/weatherdaemon/socket.h @@ -0,0 +1,32 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +// time interval for data polling (seconds) +#define T_INTERVAL (10.) +// max amount of opened fd (+1 for server socket) +#define MAX_FDS (11) +// no data timeout +#define NODATA_TMOUT (90.) + +void daemonize(char *port); + +#endif // __SOCKET_H__ diff --git a/Daemons/weatherdaemon/term.c b/Daemons/weatherdaemon/term.c new file mode 100644 index 0000000..adb6584 --- /dev/null +++ b/Daemons/weatherdaemon/term.c @@ -0,0 +1,168 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 // isspace +#include +#include +#include // strncasecmp +#include // time(NULL) +#include // INT_MAX, INT_MIN +#include "term.h" +#include "cmdlnopts.h" + +#define BUFLEN 1024 + +TTY_descr *ttydescr = NULL; +extern glob_pars *GP; + +static char buf[BUFLEN]; +static const char *emultemplate = " 06:50:36, 20.01.00, TE-2.20, DR1405.50, WU2057.68, RT0.00, WK1.00, WR177.80, WT-2.20, FE0.69, RE0.00, WG7.36, WV260.03, TI0.00, FI0.00,"; + +/** + * read strings from terminal (ending with '\n') with timeout + * @return NULL if nothing was read or pointer to static buffer + */ +static char *read_string(){ + //static int done = 0; + if(GP->emul){ + //if(done) return NULL; + strncpy(buf, emultemplate, BUFLEN); + //done = 1; + return buf; + } + if(!ttydescr) ERRX("Serial device not initialized"); + size_t r = 0, l; + int LL = BUFLEN - 1; + char *ptr = buf; + double d0 = dtime(); + do{ + if((l = read_tty(ttydescr))){ + r += l; LL -= l; ptr += l; + d0 = dtime(); + } + }while(dtime() - d0 < WAIT_TMOUT && LL); + if(r){ + buf[r] = 0; + return buf; + } + return NULL; +} + +/** + * Try to connect to `device` at baudrate speed + * @return 1 if OK + */ +int try_connect(char *device, int baudrate){ + if(!device) return 0; + fflush(stdout); + ttydescr = new_tty(device, baudrate, 1024); + if(ttydescr) ttydescr = tty_open(ttydescr, 1); // exclusive open + if(!ttydescr) return 0; + while(read_tty(ttydescr)); // clear rbuf + LOGMSG("Connected to %s", device); + return 1; +} + +/** + * @brief getpar - get parameter value + * @param string (i) - string where to search + * @param Val (o) - value found + * @param Name - parameter name + * @return 0 if found + */ +static int getpar(char *string, double *Val, char *Name){ + char *p = strstr(string, Name); + if(!p) return 1; + p += strlen(Name); + DBG("search %s", Name); + if(!Val) return 0; + char *endptr; + *Val = strtod(p, &endptr); + DBG("eptr=%s, val=%g", endptr, *Val); + if(endptr == string){ + WARNX("Double value not found"); + return 2; + } + return 0; +} + + +/** + * Poll serial port for new dataportion + * @return: NULL if no data received, pointer to string if valid data received + */ +char *poll_device(){ + static char ans[BUFLEN]; + char *ptr = ans, *r = NULL; + if(!GP->emul){ + if(write_tty(ttydescr->comfd, "?U\r\n", 4)) + return NULL; + } + double t0 = dtime(); + while(dtime() - t0 < T_POLLING_TMOUT){ + if((r = read_string())){ // parse new data + DBG("got %s", r); + if(strncmp(r, "", 4)){ + WARNX("Wrong answer"); + LOGWARN("poll_device() get wrong answer: %s", r); + return NULL; + } + r += 4; + DBG("R=%s", r); + while(*r){if(isspace(*r)) ++r; else break;} + DBG("R=%s", r); + char *eol = strchr(r, '\n'); + if(eol) *eol = 0; + double d; + size_t L = BUFLEN, l; + if(!getpar(r, &d, "RT")){ + l = snprintf(ptr, L, "Rain=%g\n", d); + if(l > 0){ + L -= l; + ptr += l; + } + } + if(!getpar(r, &d, "WU")){ + l = snprintf(ptr, L, "Clouds=%g\n", d); + if(l > 0){ + L -= l; + ptr += l; + } + } + if(!getpar(r, &d, "TE")){ + l = snprintf(ptr, L, "Exttemp=%g\n", d); + if(l > 0){ + L -= l; + ptr += l; + } + } + if(!getpar(r, &d, "WG")){ + l = snprintf(ptr, L, "Wind=%g\n", d/3.6); + if(l > 0){ + L -= l; + ptr += l; + } + } + snprintf(ptr, L, "Time=%lld\n", (long long)time(NULL)); + DBG("Buffer: %s", ans); + return ans; + } + } + return NULL; +} + diff --git a/Daemons/weatherdaemon/term.h b/Daemons/weatherdaemon/term.h new file mode 100644 index 0000000..dff6928 --- /dev/null +++ b/Daemons/weatherdaemon/term.h @@ -0,0 +1,37 @@ +/* + * This file is part of the weatherdaemon project. + * Copyright 2021 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 +#ifndef __TERM_H__ +#define __TERM_H__ + +#include + +#define FRAME_MAX_LENGTH (300) +#define MAX_MEMORY_DUMP_SIZE (0x800 * 4) +// Terminal timeout (seconds) +#define WAIT_TMOUT (0.5) +// Terminal polling timeout - 1 second +#define T_POLLING_TMOUT (1.0) + +extern TTY_descr *ttydescr; +void run_terminal(); +int try_connect(char *device, int baudrate); +char *poll_device(); + +#endif // __TERM_H__ diff --git a/Docs/Alignment/makelist b/Docs/Alignment/makelist old mode 100755 new mode 100644 diff --git a/Docs/focus b/Docs/focus old mode 100755 new mode 100644