diff --git a/modbus_relay/web_management/index.html b/modbus_relay/web_management/index.html new file mode 100644 index 0000000..13eabab --- /dev/null +++ b/modbus_relay/web_management/index.html @@ -0,0 +1,96 @@ + + + + Relay Control + + + +
+ + + + +
Left
+
Right
+
+ + + + \ No newline at end of file diff --git a/modbus_relay/web_management/relay_daemon/Makefile b/modbus_relay/web_management/relay_daemon/Makefile new file mode 100644 index 0000000..f1704a3 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/Makefile @@ -0,0 +1,59 @@ +# run `make DEF=...` to add extra defines +PROGRAM := modbus_relay +LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all +LDFLAGS += -lusefull_macros -lmodbus +SRCS := $(wildcard *.c) +DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111 +OBJDIR := mk +CFLAGS += -O2 -Wall -Wextra -Wno-trampolines +OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o)) +DEPS := $(OBJS:.o=.d) +TARGFILE := $(OBJDIR)/TARGET +CC = gcc +#TARGET := RELEASE + +ifeq ($(shell test -e $(TARGFILE) && echo -n yes),yes) + TARGET := $(file < $(TARGFILE)) +else + TARGET := RELEASE +endif + +ifeq ($(TARGET), DEBUG) + .DEFAULT_GOAL := debug +endif + +release: CFLAGS += -flto +release: LDFLAGS += -flto +release: $(PROGRAM) + +debug: CFLAGS += -DEBUG -Werror +debug: TARGET := DEBUG +debug: $(PROGRAM) + +$(TARGFILE): $(OBJDIR) + @echo -e "\t\tTARGET: $(TARGET)" + @echo "$(TARGET)" > $(TARGFILE) + +$(PROGRAM) : $(TARGFILE) $(OBJS) + @echo -e "\t\tLD $(PROGRAM)" + $(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM) + +$(OBJDIR): + @mkdir $(OBJDIR) + +ifneq ($(MAKECMDGOALS),clean) +-include $(DEPS) +endif + +$(OBJDIR)/%.o: %.c + @echo -e "\t\tCC $<" + $(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $< + +clean: + @echo -e "\t\tCLEAN" + @rm -rf $(OBJDIR) 2>/dev/null || true + +xclean: clean + @rm -f $(PROGRAM) + +.PHONY: clean xclean diff --git a/modbus_relay/web_management/relay_daemon/cmdlnopts.c b/modbus_relay/web_management/relay_daemon/cmdlnopts.c new file mode 100644 index 0000000..10d6531 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/cmdlnopts.c @@ -0,0 +1,83 @@ +/* geany_encoding=koi8-r + * cmdlnopts.c - the only function that parse cmdln args and returns glob parameters + * + * Copyright 2013 Edward V. Emelianoff + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include +#include +#include +#include +#include +#include "cmdlnopts.h" +#include "usefull_macros.h" + +/* + * here are global parameters initialisation + */ +static int help; +sl_loglevel_e verblvl = LOGLEVEL_WARN; + +glob_pars GP = { + .baudrate = 9600, + .device = "/dev/ttyUSB0", + .nodenum = 1, + .port = "9000", +}; + +/* + * Define command line options by filling structure: + * name has_arg flag val type argptr help +*/ +static sl_option_t cmdlnopts[] = { + {"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"}, + {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&GP.verbose), "verbose level for (each `-v` increase it)"}, + {"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&GP.logfile), "logging file name"}, + {"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&GP.baudrate), "interface baudrate (default: 9600)"}, + {"device", NEED_ARG, NULL, 'd', arg_string, APTR(&GP.device), "serial device path (default: /dev/ttyUSB0)"}, + {"node", NEED_ARG, NULL, 'n', arg_int, APTR(&GP.nodenum), "node number (default: 1)"}, + {"port", NEED_ARG, NULL, 'p', arg_string, APTR(&GP.port), "socket port (default: 9000)"}, + 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 + */ +void parse_args(int argc, char **argv){ + size_t hlen = 1024; + char helpstring[1024], *hptr = helpstring; + snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); + // format of help: "Usage: progname [args]\n" + sl_helpstring(helpstring); + // parse arguments + sl_parseargs(&argc, &argv, cmdlnopts); + if(help) sl_showhelp(-1, cmdlnopts); + if(argc > 0) WARNX("Omit %d unexpected arguments", argc); +} + +void verbose(sl_loglevel_e lvl, const char *fmt, ...){ + if(lvl > verblvl) return; + va_list ar; + va_start(ar, fmt); + vprintf(fmt, ar); + va_end(ar); + fflush(stdout); +} diff --git a/modbus_relay/web_management/relay_daemon/cmdlnopts.h b/modbus_relay/web_management/relay_daemon/cmdlnopts.h new file mode 100644 index 0000000..bbd2173 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/cmdlnopts.h @@ -0,0 +1,42 @@ +/* + * This file is part of the modbus_relay project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +/* + * here are some typedef's for global data + */ +typedef struct{ + int verbose; // verbose level + int baudrate; // interface baudrate (default: 9600) + int nodenum; // node number (default: 1) + char *port; // socket port (default: 9000) + char *logfile; // logfile name + char *device; // serial device path (default: /dev/ttyUSB0) +} glob_pars; + +extern glob_pars GP; +extern sl_loglevel_e verblvl; +void parse_args(int argc, char **argv); +void verbose(sl_loglevel_e lvl, const char *fmt, ...); + +#define VMSG(...) verbose(LOGLEVEL_MSG, __VA_ARGS__) +#define VDBG(...) verbose(LOGLEVEL_DBG, __VA_ARGS__) +#define VANY(...) verbose(LOGLEVEL_ANY, __VA_ARGS__) diff --git a/modbus_relay/web_management/relay_daemon/main.c b/modbus_relay/web_management/relay_daemon/main.c new file mode 100644 index 0000000..5b006f8 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/main.c @@ -0,0 +1,134 @@ +/* + * This file is part of the modbus_relay project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "cmdlnopts.h" +#include "server.h" + +void signals(int sig){ + if(sig){ + signal(sig, SIG_IGN); + DBG("Get signal %d, quit.\n", sig); + LOGERR("Exit with status %d", sig); + }else LOGERR("Exit"); + exit(sig); +} + +int main(int argc, char **argv){ + sl_init(); + parse_args(argc, argv); + verblvl = GP.verbose + LOGLEVEL_WARN; + if(verblvl >= LOGLEVEL_AMOUNT) verblvl = LOGLEVEL_AMOUNT - 1; + if(GP.logfile) OPENLOG(GP.logfile, verblvl, 1); + LOGMSG("Started"); + signal(SIGTERM, signals); + signal(SIGINT, signals); + signal(SIGQUIT, signals); + signal(SIGTSTP, SIG_IGN); + signal(SIGHUP, signals); + VMSG("Try to open %s @%d ... ", GP.device, GP.baudrate); + modbus_t *ctx = modbus_new_rtu(GP.device, GP.baudrate, 'N', 8, 1); + modbus_set_response_timeout(ctx, 0, 100000); + if(modbus_set_slave(ctx, GP.nodenum)) ERRX("Can't set modbus slave"); + if(modbus_connect(ctx) < 0) ERR("Can't open device %s", GP.device); + VMSG("OK!\n"); +#ifndef EBUG + while(1){ + pid_t childpid = fork(); + if(childpid){ // master + LOGMSG("Created child with pid %d", childpid); + wait(NULL); + LOGWARN("Child %d died", childpid); + sleep(5); // wait a little before respawn + }else{ // slave + prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies + break; + } + } +#endif + runserver(GP.port, ctx); +#if 0 + uint8_t dest8[8] = {0}; + if(GP.setall){ + memset(dest8, 1, 8); + if(modbus_write_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't set all relays"); + }else if(GP.resetall){ + if(modbus_write_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't clear all relays"); + }else{ + if(GP.resetrelay){ + int **p = GP.resetrelay; + while(*p){ + int n = **p; + if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]"); + else{ + if(modbus_write_bit(ctx, n, 0) < 0) WARNX("Can't reset relay #%d", n); + else VMSG("RELAY%d=0\n", n); + } + ++p; + } + } + if(GP.setrelay){ + int **p = GP.setrelay; + while(*p){ + int n = **p; + if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]"); + else{ + if(modbus_write_bit(ctx, n, 1) < 0) WARNX("Can't set relay #%d", n); + else VMSG("RELAY%d=1\n", n); + } + ++p; + } + } + } + if(GP.getinput){ + if(modbus_read_input_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't read inputs"); + else{ + int **p = GP.getinput; + while(*p){ + int n = **p; + if(n > 7 || n < 0) WARNX("Input number should be in [0, 7]"); + else printf("INPUT%d=%u\n", n, dest8[n]); + ++p; + } + } + } + if(GP.getrelay){ + if(modbus_read_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't read relays"); + else{ + int **p = GP.getrelay; + while(*p){ + int n = **p; + if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]"); + else printf("RELAY%d=%u\n", n, dest8[n]); + ++p; + } + } + } +#endif + + LOGMSG("End"); + VMSG("End\n"); + modbus_close(ctx); + modbus_free(ctx); + return 0; +} diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay b/modbus_relay/web_management/relay_daemon/modbus_relay new file mode 100755 index 0000000..501aa43 Binary files /dev/null and b/modbus_relay/web_management/relay_daemon/modbus_relay differ diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.cflags b/modbus_relay/web_management/relay_daemon/modbus_relay.cflags new file mode 100644 index 0000000..68d5165 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/modbus_relay.cflags @@ -0,0 +1 @@ +-std=c17 \ No newline at end of file diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.config b/modbus_relay/web_management/relay_daemon/modbus_relay.config new file mode 100644 index 0000000..44e2342 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/modbus_relay.config @@ -0,0 +1,3 @@ +// Add predefined macros for your project here. For example: +// #define THE_ANSWER 42 +#define _DEFAULT_SOURCE diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.creator b/modbus_relay/web_management/relay_daemon/modbus_relay.creator new file mode 100644 index 0000000..e94cbbd --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/modbus_relay.creator @@ -0,0 +1 @@ +[General] diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.creator.user b/modbus_relay/web_management/relay_daemon/modbus_relay.creator.user new file mode 100644 index 0000000..106191f --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/modbus_relay.creator.user @@ -0,0 +1,184 @@ + + + + + + EnvironmentId + {cf63021e-ef53-49b0-b03b-2f2570cdf3b6} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + true + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + KOI8-R + false + 4 + false + 0 + 80 + true + true + 1 + 0 + false + false + false + 1 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + true + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + false + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 4 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + Desktop + {91347f2c-5221-46a7-80b1-0a054ca02f79} + 0 + 0 + 0 + + /home/eddy/Docs/SAO/ELECTRONICS/Modbus/modbus_relay + + + + all + + true + GenericProjectManager.GenericMakeStep + + 1 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + GenericProjectManager.GenericMakeStep + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + По умолчанию + GenericProjectManager.GenericBuildConfiguration + + 1 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + + 2 + + false + -e cpu-cycles --call-graph dwarf,4096 -F 250 + + ProjectExplorer.CustomExecutableRunConfiguration + + false + true + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.cxxflags b/modbus_relay/web_management/relay_daemon/modbus_relay.cxxflags new file mode 100644 index 0000000..6435dfc --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/modbus_relay.cxxflags @@ -0,0 +1 @@ +-std=c++17 \ No newline at end of file diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.files b/modbus_relay/web_management/relay_daemon/modbus_relay.files new file mode 100644 index 0000000..c544e3f --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/modbus_relay.files @@ -0,0 +1,5 @@ +cmdlnopts.c +cmdlnopts.h +main.c +server.c +server.h diff --git a/modbus_relay/web_management/relay_daemon/modbus_relay.includes b/modbus_relay/web_management/relay_daemon/modbus_relay.includes new file mode 100644 index 0000000..e69de29 diff --git a/modbus_relay/web_management/relay_daemon/server.c b/modbus_relay/web_management/relay_daemon/server.c new file mode 100644 index 0000000..2a9838a --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/server.c @@ -0,0 +1,354 @@ +/* + * This file is part of the modbus_relay project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include // addrinfo +#include // inet_ntop +#include +#include // INT_xxx +#include // pthread_kill +#include // daemon +#include +#include // syscall +#include +#include +#include +#include +#include + +#include "server.h" + +#define BUFLEN (10240) +// Max amount of connections +#define BACKLOG (30) + +// state of ins/outs +static int relay[2] = {0}, in[2] = {0}; +static int relaycmd[2] = {-1, -1}; // user command: open/close + +/**************** 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 ****************/ +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 != send(sock, tbuf, L, MSG_NOSIGNAL)){ + 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){ + //putlog("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 = sl_dtime(); + while(sl_dtime() - t0 < SOCKET_TIMEOUT){ + if(!waittoread(sock)){ // no data incoming + continue; + } + if(!(rd = read(sock, buff, BUFLEN-1))){ + break; + } + DBG("Got %zd bytes", rd); + if(rd < 0){ // error + DBG("Nothing to read from fd %d (ret: %zd)", sock, rd); + break; + } + t0 = sl_dtime(); + // 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); + pthread_mutex_lock(&mutex); + if(found){ + int needstatus = 0; + if(0 == strcmp(found, "status")){ + needstatus = 1; + }else if(0 == strcmp(found, "stop")){ + needstatus = 1; + relaycmd[0] = 0; relaycmd[1] = 0; + }else if(0 == strcmp(found, "open")){ + needstatus = 1; + relaycmd[0] = 1; relaycmd[1] = 0; + }else if(0 == strcmp(found, "close")){ + needstatus = 1; + relaycmd[0] = 0; relaycmd[1] = 1; + } + if(needstatus){ + DBG("User asks for status"); + snprintf(buff, BUFLEN-1, "relay0=%d\nrelay1=%d\nin0=%d\nin1=%d\n", + relay[0], relay[1], in[0], in[1]); + send_data(sock, webquery, buff); + } + } + pthread_mutex_unlock(&mutex); + break; + } + close(sock); + pthread_exit(NULL); + return NULL; +} + +// main socket server +static void *server(void *asock){ + LOGMSG("server(): getpid: %d, pthread_self: %lu, tid: %lu",getpid(), pthread_self(), syscall(SYS_gettid)); + int sock = *((int*)asock); + if(listen(sock, BACKLOG) == -1){ + LOGWARN("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){ + LOGWARN("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); + //putlog("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)){ + LOGWARN("server(): pthread_create() failed"); + WARN("pthread_create()"); + }else{ + DBG("Thread created, detouch"); + pthread_detach(handler_thread); // don't care about thread state + } + } + LOGERR("server(): UNREACHABLE CODE REACHED!"); +} + +// data gathering & socket management +static void daemon_(int sock, modbus_t *ctx){ + if(sock < 0 || !ctx) return; + pthread_t sock_thread; + if(pthread_create(&sock_thread, NULL, server, (void*) &sock)){ + LOGERR("daemon_(): pthread_create() failed"); + ERR("pthread_create()"); + } + double tgot = sl_dtime(), lasttchanged = -1.; + 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()"); + } + } + usleep(1000); // sleep a little or thread's won't be able to lock mutex + if(sl_dtime() - tgot < T_INTERVAL) continue; + DBG("tgot-time=%g", tgot - sl_dtime()); + tgot = sl_dtime(); + uint8_t dest8[8] = {0}; + if(modbus_read_input_bits(ctx, 0, 8, dest8) < 0){ + WARNX("Can't read inputs"); + LOGWARN("Can't read inputs"); + }else{ + pthread_mutex_lock(&mutex); + in[0] = dest8[0]; in[1] = dest8[1]; + DBG("ins: %d/%d", in[0], in[1]); + pthread_mutex_unlock(&mutex); + } + if(modbus_read_bits(ctx, 0, 8, dest8) < 0){ + WARNX("Can't read relays"); + LOGWARN("Can't read relays"); + }else{ + pthread_mutex_lock(&mutex); + relay[0] = dest8[0]; relay[1] = dest8[1]; + DBG("outs: %d/%d", relay[0], relay[1]); + pthread_mutex_unlock(&mutex); + } + // check relay commands + pthread_mutex_lock(&mutex); + if(lasttchanged > -1. && (sl_dtime() - lasttchanged) > RELAYS_TIMEOUT && + (relaycmd[0] == -1 && relaycmd[1] == -1)){ + relaycmd[0] = 0; // turn off relays + relaycmd[1] = 0; + } + if(relaycmd[0] > -1 || relaycmd[1] > -1){ + DBG("relaycmd: %d/%d", relaycmd[0], relaycmd[1]); + if(relaycmd[0] == 1 && relaycmd[1] == 1){ // wrong! Turn both off + relaycmd[0] = 0; relaycmd[1] = 0; + } + if(relaycmd[0] == 0 && relaycmd[1] == 0) lasttchanged = -1.; + // turn off + for(int i = 0; i < 2; ++i) if(relaycmd[i] == 0){ + if(modbus_write_bit(ctx, i, 0) < 0){ + WARNX("Can't reset relay #%d", i); + LOGWARN("Can't reset relay #%d", i); + }else{ + relaycmd[i] = -1; + LOGMSG("RELAY%d=0\n", i); + } + } + // turn on + for(int i = 0; i < 2; ++i) if(relaycmd[i] == 1){ + if(modbus_write_bit(ctx, i, 1) < 0){ + WARNX("Can't set relay #%d", i); + LOGWARN("Can't set relay #%d", i); + }else{ + relaycmd[i] = -1; + LOGMSG("RELAY%d=1\n", i); + lasttchanged = sl_dtime(); + } + } + } + pthread_mutex_unlock(&mutex); + }while(1); + LOGERR("daemon_(): UNREACHABLE CODE REACHED!"); +} + +/** + * Run daemon service + */ +void runserver(const char *port, modbus_t *ctx){ + FNAME(); + if(!port || !ctx) return; + 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){ + LOGERR("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, ctx); + close(sock); + LOGERR("socket closed, exit"); + signals(0); +} diff --git a/modbus_relay/web_management/relay_daemon/server.h b/modbus_relay/web_management/relay_daemon/server.h new file mode 100644 index 0000000..6cf6e01 --- /dev/null +++ b/modbus_relay/web_management/relay_daemon/server.h @@ -0,0 +1,29 @@ +/* + * This file is part of the modbus_relay project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +// timeout for socket closing if nothing received +#define SOCKET_TIMEOUT (60.0) +// timeout to turn relays off +#define RELAYS_TIMEOUT (10.0) +// time interval for MODBUS data polling (seconds) +#define T_INTERVAL (1.) + +void runserver(const char *port, modbus_t *ctx); +int closeserver();