Change netdaemon to poll & work with libusefull_macros, add weatherdaemon

This commit is contained in:
Edward Emelianov 2021-01-02 20:04:20 +03:00
parent dff0f9e43f
commit 0028ba8a4b
45 changed files with 1835 additions and 168 deletions

0
Auxiliary_utils/bash_scripts/getflats Executable file → Normal file
View File

0
Auxiliary_utils/bash_scripts/park_tel.sh Executable file → Normal file
View File

0
Auxiliary_utils/bash_scripts/point_AH.sh Executable file → Normal file
View File

0
Auxiliary_utils/bash_scripts/point_get_flat.sh Executable file → Normal file
View File

0
Auxiliary_utils/bash_scripts/run Executable file → Normal file
View File

0
Daemons/astrosib/HWoff Normal file → Executable file
View File

0
Daemons/astrosib/HWon Normal file → Executable file
View File

0
Daemons/astrosib/STARTobs Normal file → Executable file
View File

0
Daemons/astrosib/STOPobs Normal file → Executable file
View File

View File

@ -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

View File

@ -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'.

View File

@ -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 <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include "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;
}

View File

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

View File

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

View File

@ -0,0 +1,307 @@
/*
* geany_encoding=koi8-r
* socket.c - socket IO
*
* Copyright 2018 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#include "usefull_macros.h"
#include "socket.h"
#include "term.h"
#include <netdb.h> // addrinfo
#include <arpa/inet.h> // inet_ntop
#include <limits.h> // INT_xxx
#include <signal.h> // pthread_kill
#include <unistd.h> // daemon
#include <sys/syscall.h> // 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);
}

View File

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

View File

@ -0,0 +1,118 @@
/* geany_encoding=koi8-r
* client.c - terminal parser
*
* Copyright 2018 Edward V. Emelianoff <eddy@sao.ru>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "usefull_macros.h"
#include "term.h"
#include <strings.h> // strncasecmp
#include <time.h> // time(NULL)
#include <limits.h> // 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;
}

View File

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

View File

@ -1,11 +1,11 @@
# run `make DEF=...` to add extra defines # run `make DEF=...` to add extra defines
PROGRAM := netdaemon PROGRAM := netdaemon
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all -pthread LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all -pthread
LDFLAGS += -lusefull_macros
SRCS := $(wildcard *.c) SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
DEFINES += -DEBUG DEFINES += -DEBUG
# baudrate for USB<->UART converter # baudrate for USB<->UART converter
DEFINES += -DBAUD_RATE=B115200
OBJDIR := mk OBJDIR := mk
CFLAGS += -O2 -Wall -Werror -Wextra -Wno-trampolines CFLAGS += -O2 -Wall -Werror -Wextra -Wno-trampolines
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))

View File

@ -1,9 +1,21 @@
Network daemon snippet 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. Open a socket at given port (default: 4444), works with http & direct requests.
Can read and send commands over serial interface. Can read and send commands over serial interface.
Pieces with user code marked as 'INSERT CODE HERE'. 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)

View File

@ -23,8 +23,9 @@
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
#include <math.h> #include <math.h>
#include <usefull_macros.h>
#include "cmdlnopts.h" #include "cmdlnopts.h"
#include "usefull_macros.h" #include "term.h"
/* /*
* here are global parameters initialisation * here are global parameters initialisation
@ -43,6 +44,8 @@ glob_pars const Gdefault = {
.terminal = 0, .terminal = 0,
.echo = 0, .echo = 0,
.logfile = NULL, .logfile = NULL,
.verb = 0,
.tty_speed = 115200,
.rest_pars = NULL, .rest_pars = NULL,
.rest_pars_num = 0 .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)")}, {"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")}, {"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")}, {"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 end_option
}; };

View File

@ -23,9 +23,6 @@
#ifndef __CMDLNOPTS_H__ #ifndef __CMDLNOPTS_H__
#define __CMDLNOPTS_H__ #define __CMDLNOPTS_H__
#include "parseargs.h"
#include "term.h"
/* /*
* here are some typedef's for global data * here are some typedef's for global data
*/ */
@ -35,6 +32,8 @@ typedef struct{
char *logfile; // logfile name char *logfile; // logfile name
int terminal; // run as terminal int terminal; // run as terminal
int echo; // echo user commands back int echo; // echo user commands back
int verb; // verbocity level
int tty_speed; // serial terminal baudrate
int rest_pars_num; // number of rest parameters int rest_pars_num; // number of rest parameters
char** rest_pars; // the rest parameters: array of char* (path to logfile and thrash) char** rest_pars; // the rest parameters: array of char* (path to logfile and thrash)
} glob_pars; } glob_pars;

View File

@ -18,19 +18,21 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA. * MA 02110-1301, USA.
*/ */
#include "usefull_macros.h"
#include <signal.h> #include <signal.h>
#include <stdio.h>
#include <sys/wait.h> // wait #include <sys/wait.h> // wait
#include <sys/prctl.h> //prctl #include <sys/prctl.h> //prctl
#include <usefull_macros.h>
#include "cmdlnopts.h" #include "cmdlnopts.h"
#include "socket.h" #include "socket.h"
#include "term.h"
glob_pars *GP; glob_pars *GP;
void signals(int signo){ void signals(int signo){
restore_console(); restore_console();
restore_tty(); if(ttydescr) close_tty(&ttydescr);
LOG("exit with status %d", signo); LOGERR("exit with status %d", signo);
exit(signo); exit(signo);
} }
@ -44,12 +46,18 @@ int main(int argc, char **argv){
GP = parse_args(argc, argv); GP = parse_args(argc, argv);
if(GP->terminal){ if(GP->terminal){
if(!GP->device) ERRX(_("Point serial device name")); 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(); run_terminal();
signals(0); // never reached! signals(0); // never reached!
} }
if(GP->logfile) if(GP->logfile){
Cl_createlog(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 #ifndef EBUG
if(daemon(1, 0)){ if(daemon(1, 0)){
ERR("daemon()"); ERR("daemon()");
@ -57,10 +65,11 @@ int main(int argc, char **argv){
while(1){ // guard for dead processes while(1){ // guard for dead processes
pid_t childpid = fork(); pid_t childpid = fork();
if(childpid){ 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); DBG("Created child with PID %d\n", childpid);
wait(NULL); wait(NULL);
WARNX("Child %d died\n", childpid); WARNX("Child %d died\n", childpid);
LOGWARN("Child %d died\n", childpid);
sleep(1); sleep(1);
}else{ }else{
prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies
@ -69,7 +78,8 @@ int main(int argc, char **argv){
} }
#endif #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 * INSERT CODE HERE
* connection check & device validation * connection check & device validation

View File

@ -20,19 +20,24 @@
* MA 02110-1301, USA. * MA 02110-1301, USA.
* *
*/ */
#include "usefull_macros.h"
#include "socket.h"
#include "term.h"
#include <netdb.h> // addrinfo #include <netdb.h> // addrinfo
#include <arpa/inet.h> // inet_ntop #include <arpa/inet.h> // inet_ntop
#include <limits.h> // INT_xxx #include <limits.h> // INT_xxx
#include <signal.h> // pthread_kill #include <poll.h> // poll
#include <unistd.h> // daemon #include <pthread.h>
#include <signal.h> // pthread_kill
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h> // syscall #include <sys/syscall.h> // syscall
#include <unistd.h> // daemon
#include <usefull_macros.h>
#include "cmdlnopts.h" // glob_pars #include "cmdlnopts.h" // glob_pars
#include "socket.h"
#include "term.h"
#define BUFLEN (10240) // temporary buffers
#define BUFLEN (1024)
// Max amount of connections // Max amount of connections
#define BACKLOG (30) #define BACKLOG (30)
@ -42,37 +47,7 @@ extern glob_pars *GP;
* Define global data buffers here * 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 ****************/ /**************** SERVER FUNCTIONS ****************/
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
/** /**
* Send data over socket * Send data over socket
* @param sock - socket fd * @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); "Content-type: text/plain\r\nContent-Length: %zd\r\n\r\n", Len);
if(L < 0){ if(L < 0){
WARN("sprintf()"); WARN("sprintf()");
LOGWARN("sprintf()");
return 0; return 0;
} }
if(L != write(sock, tbuf, L)){ if(L != write(sock, tbuf, L)){
LOGWARN("Can't write header");
WARN("write"); WARN("write");
return 0; return 0;
} }
@ -105,121 +82,162 @@ static int send_data(int sock, int webquery, char *textbuf){
//DBG("send %zd bytes\nBUF: %s", Len, buf); //DBG("send %zd bytes\nBUF: %s", Len, buf);
if(Len != write(sock, textbuf, Len)){ if(Len != write(sock, textbuf, Len)){
WARN("write()"); WARN("write()");
LOGERR("send_data(): write() failed");
return 0; return 0;
} }
LOGDBG("fd %d, write %s", textbuf);
return 1; return 1;
} }
// search a first word after needle without spaces // search a first word after needle without spaces
static char* stringscan(char *str, char *needle){ static char* stringscan(char *str, char *needle){
char *a, *e; char *a;//, *e;
char *end = str + strlen(str); char *end = str + strlen(str);
a = strstr(str, needle); a = strstr(str, needle);
if(!a) return NULL; if(!a) return NULL;
a += strlen(needle); a += strlen(needle);
while (a < end && (*a == ' ' || *a == '\r' || *a == '\t' || *a == '\r')) a++; while (a < end && (*a == ' ' || *a == '\r' || *a == '\t' || *a == '\r')) a++;
if(a >= end) return NULL; if(a >= end) return NULL;
e = strchr(a, ' ');
if(e) *e = 0;
return a; 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(); 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]; char buff[BUFLEN];
ssize_t rd; ssize_t rd;
double t0 = dtime(); if(!(rd = read(sock, buff, BUFLEN-1))){
/* LOGMSG("Client %d closed", sock);
* INSERT CODE HERE return 1;
* change to while(1) if socket shouldn't be closed after data transmission }
*/ //LOG("client send %zd bytes", rd);
while(dtime() - t0 < SOCKET_TIMEOUT){ DBG("Got %zd bytes", rd);
if(!waittoread(sock)){ // no data incoming if(rd < 0){ // error
continue; LOGWARN("Client %d close socket on error", sock);
} DBG("Nothing to read from fd %d (ret: %zd)", sock, rd);
if(!(rd = read(sock, buff, BUFLEN-1))){ return 1;
//LOG("socket closed. Exit"); }
break; // add trailing zero to be on the safe side
} buff[rd] = 0;
//LOG("client send %zd bytes", rd); // now we should check what do user want
DBG("Got %zd bytes", rd); char *found = buff;
if(rd < 0){ // error DBG("user send: %s", buff);
//LOG("some error occured"); if(!notchkhdr){
DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); if(0 == strncmp(buff, "GET", 3)){
break; DBG("GET");
} // GET web query have format GET /some.resource
// 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; webquery = 1;
char *slash = strchr(got, '/'); char *slash = strchr(buff, '/');
if(slash) found = slash + 1; if(slash){
// web query have format GET /some.resource found = slash + 1;
} char *eol = strstr(found, "HTTP");
// here we can process user data if(eol) *eol = 0;
DBG("user send: %s\nfound=%s", buff, found); }
if(GP->echo){ }else if(0 == strncmp(buff, "POST", 4)){
if(!send_data(sock, webquery, found)){ DBG("POST");
LOG("can't send data, some error occured"); 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); // here we can process user data
//DBG("closed"); DBG("found=%s", found);
//LOG("socket closed, exit"); LOGDBG("sockfd=%d, got %s", sock, buff);
pthread_exit(NULL); if(GP->echo){
return NULL; 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 // main socket server
static void *server(void *asock){ 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); int sock = *((int*)asock);
if(listen(sock, BACKLOG) == -1){ if(listen(sock, BACKLOG) == -1){
LOG("listen() failed"); LOGERR("listen() failed");
WARN("listen"); WARN("listen");
return NULL; 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){ while(1){
socklen_t size = sizeof(struct sockaddr_in); poll(poll_set, nfd, 1); // poll for 1ms
struct sockaddr_in their_addr; for(int fdidx = 0; fdidx < nfd; ++fdidx){ // poll opened FDs
int newsock; if((poll_set[fdidx].revents & POLLIN) == 0) continue;
if(!waittoread(sock)) continue; poll_set[fdidx].revents = 0;
newsock = accept(sock, (struct sockaddr*)&their_addr, &size); if(fdidx){ // client
if(newsock <= 0){ int fd = poll_set[fdidx].fd;
LOG("accept() failed"); if(handle_socket(fd, notchkhdr[fdidx])){ // socket closed - remove it from list
WARN("accept()"); close(fd);
continue; DBG("Client with fd %d closed", fd);
} LOGMSG("Client %d disconnected", fd);
struct sockaddr_in* pV4Addr = (struct sockaddr_in*)&their_addr; // move last to free space
struct in_addr ipAddr = pV4Addr->sin_addr; poll_set[fdidx] = poll_set[nfd - 1];
char str[INET_ADDRSTRLEN]; notchkhdr[fdidx] = notchkhdr[nfd - 1];
inet_ntop(AF_INET, &ipAddr, str, INET_ADDRSTRLEN); --nfd;
//LOG("get connection from %s", str); }else notchkhdr[fdidx] = 1;
DBG("Got connection from %s\n", str); }else{ // server
pthread_t handler_thread; socklen_t size = sizeof(struct sockaddr_in);
if(pthread_create(&handler_thread, NULL, handle_socket, (void*) &newsock)){ struct sockaddr_in their_addr;
LOG("server(): pthread_create() failed"); int newsock = accept(sock, (struct sockaddr*)&their_addr, &size);
WARN("pthread_create()"); if(newsock <= 0){
}else{ LOGERR("server(): accept() failed");
DBG("Thread created, detouch"); WARN("accept()");
pthread_detach(handler_thread); // don't care about thread state 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 // data gathering & socket management
@ -227,36 +245,26 @@ static void daemon_(int sock){
if(sock < 0) return; if(sock < 0) return;
pthread_t sock_thread; pthread_t sock_thread;
if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){
LOG("daemon_(): pthread_create() failed"); LOGERR("daemon_(): pthread_create() failed");
ERR("pthread_create()"); ERR("pthread_create()");
} }
double tgot = 0.;
do{ do{
if(pthread_kill(sock_thread, 0) == ESRCH){ // died if(pthread_kill(sock_thread, 0) == ESRCH){ // died
WARNX("Sockets thread died"); WARNX("Sockets thread died");
LOG("Sockets thread died"); LOGWARN("Sockets thread died");
pthread_join(sock_thread, NULL); pthread_join(sock_thread, NULL);
if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){
LOG("daemon_(): new pthread_create() failed"); LOGERR("daemon_(): new pthread_create() failed");
ERR("pthread_create()"); ERR("pthread_create()");
} }
} }
usleep(1000); // sleep a little or thread's won't be able to lock mutex usleep(1000); // sleep a little
if(dtime() - tgot < T_INTERVAL) continue;
tgot = dtime();
/* /*
* INSERT CODE HERE * INSERT CODE HERE
* Gather data (poll_device) * 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); }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_family = AF_INET;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; 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){ if(getaddrinfo(NULL, port, &hints, &res) != 0){
LOGERR("getaddrinfo");
ERR("getaddrinfo"); ERR("getaddrinfo");
} }
struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr; struct sockaddr_in *ia = (struct sockaddr_in*)res->ai_addr;
@ -284,24 +294,26 @@ void daemonize(char *port){
} }
int reuseaddr = 1; int reuseaddr = 1;
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){
LOGERR("setsockopt() error");
ERR("setsockopt"); ERR("setsockopt");
} }
if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){
close(sock); close(sock);
WARN("bind"); WARN("bind");
LOGWARN("bind() error");
continue; continue;
} }
break; // if we get here, we have a successfull connection break; // if we get here, we have a successfull connection
} }
if(p == NULL){ 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 // looped off the end of the list with no successful bind
ERRX("failed to bind socket"); ERRX("failed to bind socket");
} }
freeaddrinfo(res); freeaddrinfo(res);
daemon_(sock); daemon_(sock);
close(sock); close(sock);
LOG("socket closed, exit"); LOGERR("daemonize(): UNREACHABLE CODE REACHED!");
signals(0); signals(0);
} }

View File

@ -24,10 +24,10 @@
#ifndef __SOCKET_H__ #ifndef __SOCKET_H__
#define __SOCKET_H__ #define __SOCKET_H__
// timeout for socket closing
#define SOCKET_TIMEOUT (5.0)
// time interval for data polling (seconds) // time interval for data polling (seconds)
#define T_INTERVAL (10.) #define T_INTERVAL (10.)
// max amount of opened fd (+1 for server socket)
#define MAX_FDS (11)
void daemonize(char *port); void daemonize(char *port);

View File

@ -18,14 +18,18 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA. * MA 02110-1301, USA.
*/ */
#include "usefull_macros.h"
#include "term.h" #include <stdio.h>
#include <string.h>
#include <strings.h> // strncasecmp #include <strings.h> // strncasecmp
#include <time.h> // time(NULL) #include <time.h> // time(NULL)
#include <limits.h> // INT_MAX, INT_MIN #include <limits.h> // INT_MAX, INT_MIN
#include "term.h"
#define BUFLEN 1024 #define BUFLEN 1024
TTY_descr *ttydescr = NULL;
static char buf[BUFLEN]; static char buf[BUFLEN];
/** /**
@ -33,6 +37,7 @@ static char buf[BUFLEN];
* @return NULL if nothing was read or pointer to static buffer * @return NULL if nothing was read or pointer to static buffer
*/ */
static char *read_string(){ static char *read_string(){
if(!ttydescr) ERRX("Serial device not initialized");
size_t r = 0, l; size_t r = 0, l;
int LL = BUFLEN - 1; int LL = BUFLEN - 1;
char *ptr = NULL; char *ptr = NULL;
@ -47,7 +52,7 @@ static char *read_string(){
ptr = buf; ptr = buf;
double d0 = dtime(); double d0 = dtime();
do{ do{
if((l = read_tty(ptr, LL))){ if((l = read_tty(ttydescr))){
r += l; LL -= l; ptr += l; r += l; LL -= l; ptr += l;
if(ptr[-1] == '\n') break; if(ptr[-1] == '\n') break;
d0 = dtime(); d0 = dtime();
@ -64,35 +69,36 @@ static char *read_string(){
} }
/** /**
* Try to connect to `device` at BAUD_RATE speed * Try to connect to `device` at baudrate speed
* @return connection speed if success or 0 * @return 1 if OK
*/ */
void try_connect(char *device){ int try_connect(char *device, int baudrate){
if(!device) return; if(!device) return 0;
char tmpbuf[4096];
fflush(stdout); fflush(stdout);
tty_init(device); ttydescr = new_tty(device, baudrate, 1024);
while(read_tty(tmpbuf, 4096)); // clear rbuf if(ttydescr) ttydescr = tty_open(ttydescr, 1); // exclusive open
LOG("Connected to %s", device); 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 * run terminal emulation: send user's commands and show answers
*/ */
void run_terminal(){ void run_terminal(){
if(!ttydescr) ERRX("Terminal not connected");
green(_("Work in terminal mode without echo\n")); green(_("Work in terminal mode without echo\n"));
int rb; int rb;
char buf[BUFLEN];
size_t l; size_t l;
setup_con(); setup_con();
while(1){ while(1){
if((l = read_tty(buf, BUFLEN - 1))){ if((l = read_tty(ttydescr))){
buf[l] = 0; printf("%s", ttydescr->buf);
printf("%s", buf);
} }
if((rb = read_console())){ if((rb = read_console())){
buf[0] = (char) rb; char c = (char) rb;
write_tty(buf, 1); write_tty(ttydescr->comfd, &c, 1);
} }
} }
} }

View File

@ -22,6 +22,8 @@
#ifndef __TERM_H__ #ifndef __TERM_H__
#define __TERM_H__ #define __TERM_H__
#include <usefull_macros.h>
#define FRAME_MAX_LENGTH (300) #define FRAME_MAX_LENGTH (300)
#define MAX_MEMORY_DUMP_SIZE (0x800 * 4) #define MAX_MEMORY_DUMP_SIZE (0x800 * 4)
// Terminal timeout (seconds) // Terminal timeout (seconds)
@ -29,8 +31,9 @@
// Terminal polling timeout - 1 second // Terminal polling timeout - 1 second
#define T_POLLING_TMOUT (1.0) #define T_POLLING_TMOUT (1.0)
extern TTY_descr *ttydescr;
void run_terminal(); void run_terminal();
void try_connect(char *device); int try_connect(char *device, int baudrate);
char *poll_device(); char *poll_device();
#endif // __TERM_H__ #endif // __TERM_H__

0
Daemons/netsocket/HWpoweroff Executable file → Normal file
View File

0
Daemons/netsocket/HWpoweron Executable file → Normal file
View File

0
Daemons/netsocket/MOUNTpoweronoff Executable file → Normal file
View File

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,90 @@
/*
* This file is part of the weatherdaemon project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include <usefull_macros.h>
#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;
}

View File

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

View File

@ -0,0 +1,82 @@
/*
* This file is part of the weatherdaemon project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <stdio.h>
#include <sys/wait.h> // wait
#include <sys/prctl.h> //prctl
#include <usefull_macros.h>
#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;
}

View File

@ -0,0 +1,347 @@
/*
* This file is part of the weatherdaemon project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <netdb.h> // addrinfo
#include <arpa/inet.h> // inet_ntop
#include <limits.h> // INT_xxx
#include <poll.h> // poll
#include <pthread.h>
#include <signal.h> // pthread_kill
#include <stdio.h>
#include <string.h>
#include <sys/syscall.h> // syscall
#include <unistd.h> // daemon
#include <usefull_macros.h>
#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);
}

View File

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

View File

@ -0,0 +1,168 @@
/*
* This file is part of the weatherdaemon project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ctype.h> // isspace
#include <stdio.h>
#include <string.h>
#include <strings.h> // strncasecmp
#include <time.h> // time(NULL)
#include <limits.h> // 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 = "<?U> 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, "<?U>", 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;
}

View File

@ -0,0 +1,37 @@
/*
* This file is part of the weatherdaemon project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#ifndef __TERM_H__
#define __TERM_H__
#include <usefull_macros.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)
extern TTY_descr *ttydescr;
void run_terminal();
int try_connect(char *device, int baudrate);
char *poll_device();
#endif // __TERM_H__

0
Docs/Alignment/makelist Executable file → Normal file
View File

0
Docs/focus Executable file → Normal file
View File