From a2a51859a09a3c0d56f8636e869ffab6e691c829 Mon Sep 17 00:00:00 2001 From: Nikolas Koesling Date: Thu, 23 May 2024 10:24:30 +0200 Subject: [PATCH 1/5] print library versions with option --longversion --- src/main.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index d73058d..4101f77 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,8 +12,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -254,6 +257,23 @@ int main(int argc, char **argv) { #endif << '\n'; std::cout << " from git commit " << RCS_HASH << '\n'; + + std::cout << "Libraries:\n"; + + std::cout << " " << cxxshm_version_info::NAME << ' ' << cxxshm_version_info::VERSION_STR << '\n'; + std::cout << " compiled with " << cxxshm_version_info::COMPILER << '\n'; + std::cout << " on system " << cxxshm_version_info::SYSTEM << '\n'; + std::cout << " from git commit " << cxxshm_version_info::GIT_HASH << '\n'; + + std::cout << " " << cxxsemaphore_version_info::NAME << ' ' << cxxsemaphore_version_info::VERSION_STR << '\n'; + std::cout << " compiled with " << cxxsemaphore_version_info::COMPILER << '\n'; + std::cout << " on system " << cxxsemaphore_version_info::SYSTEM << '\n'; + std::cout << " from git commit " << cxxsemaphore_version_info::GIT_HASH << '\n'; + + std::cout << " libmodbus " << LIBMODBUS_VERSION_STRING << '\n'; + + std::cout << " cxxopts " << static_cast(cxxopts::version.major) << '.' << static_cast(cxxopts::version.minor) << '.' << static_cast(cxxopts::version.patch) << '\n'; + return EX_OK; } From b11fc0a44b5b709d822d17a3f515acb1b6b2c75a Mon Sep 17 00:00:00 2001 From: Nikolas Koesling Date: Thu, 23 May 2024 10:26:27 +0200 Subject: [PATCH 2/5] fix format --- src/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 4101f77..4abe8bd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -272,7 +272,9 @@ int main(int argc, char **argv) { std::cout << " libmodbus " << LIBMODBUS_VERSION_STRING << '\n'; - std::cout << " cxxopts " << static_cast(cxxopts::version.major) << '.' << static_cast(cxxopts::version.minor) << '.' << static_cast(cxxopts::version.patch) << '\n'; + std::cout << " cxxopts " << static_cast(cxxopts::version.major) << '.' + << static_cast(cxxopts::version.minor) << '.' << static_cast(cxxopts::version.patch) + << '\n'; return EX_OK; } From fa0f3083db0ec198893cdcb3d8ddcfd7a5890263 Mon Sep 17 00:00:00 2001 From: Nikolas Koesling Date: Thu, 30 May 2024 19:56:36 +0200 Subject: [PATCH 3/5] add option to send signal to other processes on values changed --- src/CMakeLists.txt | 11 +-------- src/Mb_Proc_Signal.cpp | 43 ++++++++++++++++++++++++++++++++++ src/Mb_Proc_Signal.hpp | 32 +++++++++++++++++++++++++ src/Modbus_TCP_Client_poll.cpp | 12 +++++++++- src/Modbus_TCP_Client_poll.hpp | 7 +++++- src/main.cpp | 13 +++++++++- 6 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 src/Mb_Proc_Signal.cpp create mode 100644 src/Mb_Proc_Signal.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index def96c3..d149f3e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,16 +7,7 @@ target_sources(${Target} PRIVATE Modbus_TCP_Client_poll.cpp) target_sources(${Target} PRIVATE license.cpp) target_sources(${Target} PRIVATE sa_to_str.cpp) target_sources(${Target} PRIVATE Print_Time.cpp) - - -# ---------------------------------------- header files (*.jpp, *.h, ...) ---------------------------------------------- -# ====================================================================================================================== -target_sources(${Target} PRIVATE modbus_shm.hpp) -target_sources(${Target} PRIVATE Modbus_TCP_Client_poll.hpp) -target_sources(${Target} PRIVATE license.hpp) -target_sources(${Target} PRIVATE sa_to_str.hpp) -target_sources(${Target} PRIVATE Print_Time.hpp) - +target_sources(${Target} PRIVATE Mb_Proc_Signal.cpp) # ---------------------------------------- subdirectories -------------------------------------------------------------- # ====================================================================================================================== diff --git a/src/Mb_Proc_Signal.cpp b/src/Mb_Proc_Signal.cpp new file mode 100644 index 0000000..e3fd77e --- /dev/null +++ b/src/Mb_Proc_Signal.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 Nikolas Koesling . + * This program is free software. You can redistribute it and/or modify it under the terms of the GPLv3 License. + */ + +#include "Mb_Proc_Signal.hpp" + +#include +#include +#include + +Mb_Proc_Signal Mb_Proc_Signal::instance; // NOLINT + +Mb_Proc_Signal &Mb_Proc_Signal::get_instance() { + return instance; +} + +void Mb_Proc_Signal::add_process(pid_t process) { + processes.insert(process); +} + +void Mb_Proc_Signal::send_signal() { + for (auto proc : processes) { + auto ret = kill(proc, SIGUSR1); + if (ret == -1) { + throw std::system_error( + errno, std::generic_category(), "Failed to send signal to process " + std::to_string(proc)); + } + } +} + +void mb_callback(uint8_t mb_funtion_code) { + switch (mb_funtion_code) { + case MODBUS_FC_WRITE_SINGLE_COIL: + case MODBUS_FC_WRITE_SINGLE_REGISTER: + case MODBUS_FC_WRITE_MULTIPLE_COILS: + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + case MODBUS_FC_WRITE_AND_READ_REGISTERS: Mb_Proc_Signal::get_instance().send_signal(); break; + default: + // do nothing + break; + } +} diff --git a/src/Mb_Proc_Signal.hpp b/src/Mb_Proc_Signal.hpp new file mode 100644 index 0000000..a82c087 --- /dev/null +++ b/src/Mb_Proc_Signal.hpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 Nikolas Koesling . + * This program is free software. You can redistribute it and/or modify it under the terms of the GPLv3 License. + */ + +#include +#include +#include + +class Mb_Proc_Signal final { +private: + std::unordered_set processes; + + Mb_Proc_Signal() = default; + + static Mb_Proc_Signal instance; + +public: + Mb_Proc_Signal(const Mb_Proc_Signal &) = delete; + Mb_Proc_Signal(Mb_Proc_Signal &&) = delete; + Mb_Proc_Signal &operator=(const Mb_Proc_Signal &) = delete; + Mb_Proc_Signal &operator=(Mb_Proc_Signal &&) = delete; + ~Mb_Proc_Signal() = default; + + static Mb_Proc_Signal &get_instance(); + + void add_process(pid_t process); + + void send_signal(); +}; + +void mb_callback(uint8_t mb_funtion_code); diff --git a/src/Modbus_TCP_Client_poll.cpp b/src/Modbus_TCP_Client_poll.cpp index eb1e39f..cc4aaaf 100644 --- a/src/Modbus_TCP_Client_poll.cpp +++ b/src/Modbus_TCP_Client_poll.cpp @@ -263,7 +263,10 @@ void Client_Poll::set_response_timeout(double timeout) { return static_cast(timeout.sec) + (static_cast(timeout.usec) / (1000.0 * 1000.0)); // NOLINT } -Client_Poll::run_t Client_Poll::run(int signal_fd, bool reconnect, int timeout) { +Client_Poll::run_t Client_Poll::run(int signal_fd, + bool reconnect, + int timeout, + void (*mb_function_callback)(uint8_t mb_function_code)) { std::size_t i = 0; // poll signal fd @@ -418,6 +421,13 @@ Client_Poll::run_t Client_Poll::run(int signal_fd, bool reconnect, int timeout) << std::endl; // NOLINT close_con(client_addrs); } + + // function code callback + if (mb_function_callback) { + const auto FUNCTION_CODE = query[7]; + mb_function_callback(FUNCTION_CODE); + } + } else if (rc == -1) { if (errno != ECONNRESET) { std::cerr << Print_Time::iso << " ERROR: modbus_receive failed: " << modbus_strerror(errno) diff --git a/src/Modbus_TCP_Client_poll.hpp b/src/Modbus_TCP_Client_poll.hpp index c0a2d46..ef8d577 100644 --- a/src/Modbus_TCP_Client_poll.hpp +++ b/src/Modbus_TCP_Client_poll.hpp @@ -144,10 +144,15 @@ class Client_Poll { * true: continue listening for new connections if there is no client (Mosbus Server) left * @param timeout timeout valoue for call of poll (see: man 2 poll) * @param signal_fd signal file descriptor for termination signals + * @param mb_function_callback callback function that is called with the modbus function code on each modbus + * telegram * @return true continue * @return false terminate */ - run_t run(int signal_fd, bool reconnect = true, int timeout = -1); + run_t run(int signal_fd, + bool reconnect = true, + int timeout = -1, + void (*mb_function_callback)(uint8_t mb_function_code) = nullptr); private: #ifdef OS_LINUX diff --git a/src/main.cpp b/src/main.cpp index 4abe8bd..53ab90f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,6 +3,7 @@ * This program is free software. You can redistribute it and/or modify it under the terms of the GPLv3 License. */ +#include "Mb_Proc_Signal.hpp" #include "Modbus_TCP_Client_poll.hpp" #include "Print_Time.hpp" #include "generated/version_info.hpp" @@ -188,6 +189,8 @@ int main(int argc, char **argv) { options.add_options("version information")("git-hash", "print git hash"); options.add_options("other")("license", "show licences (short)"); options.add_options("other")("license-full", "show licences (full license text)"); + options.add_options("signal")( + "k,signal", "send SIGUSR to process on writing modbus commands", cxxopts::value>()); // parse arguments cxxopts::ParseResult args; @@ -440,6 +443,14 @@ int main(int argc, char **argv) { } } + bool SIGNAL_PROCESS = args.count("signal") > 0; + if (SIGNAL_PROCESS) { + auto &processes = args["signal"].as>(); + for (auto proc : processes) { + Mb_Proc_Signal::get_instance().add_process(proc); + } + } + // create modbus client std::unique_ptr client; @@ -487,7 +498,7 @@ int main(int argc, char **argv) { try { [&]() { while (true) { - auto ret = client->run(signal_fd, RECONNECT, -1); + auto ret = client->run(signal_fd, RECONNECT, -1, SIGNAL_PROCESS ? &mb_callback : nullptr); switch (ret) { case Modbus::TCP::Client_Poll::run_t::ok: continue; From 5bf942fffe3a42db60ac4bc50e97c95b5b07026b Mon Sep 17 00:00:00 2001 From: Nikolas Koesling Date: Wed, 27 Aug 2025 20:09:41 +0200 Subject: [PATCH 4/5] register processes for SIGUSR1 by sending SIGUSR1 to modbus-tcp-client-shm --- .clang-tidy | 7 +++-- .clang-tidy-noerrors | 7 +++-- README.md | 50 ++++++++++++++++++++++++++-------- cmake_files/warnings.cmake | 2 ++ src/Mb_Proc_Signal.cpp | 33 ++++++++++++++++++---- src/Mb_Proc_Signal.hpp | 2 +- src/Modbus_TCP_Client_poll.cpp | 41 +++++++++++++++++++++++----- src/Modbus_TCP_Client_poll.hpp | 17 ++++++++---- src/main.cpp | 37 ++++++++++++------------- 9 files changed, 141 insertions(+), 55 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index d69012a..4c7269d 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -50,9 +50,10 @@ Checks: 'bugprone-*, readability-static-definition-in-anonymous-namespace, readability-string-compare, readability-uniqueptr-delete-release, - readability-use-anyofallof - -modernize-use-trailing-return-type - -bugprone-exception-escape' + readability-use-anyofallof, + -modernize-use-trailing-return-type, + -bugprone-exception-escape, + -clang-diagnostic-switch-default' WarningsAsErrors: '*, -modernize-*, -readability-* diff --git a/.clang-tidy-noerrors b/.clang-tidy-noerrors index 7df421a..08792ad 100644 --- a/.clang-tidy-noerrors +++ b/.clang-tidy-noerrors @@ -50,8 +50,9 @@ Checks: 'bugprone-*, readability-static-definition-in-anonymous-namespace, readability-string-compare, readability-uniqueptr-delete-release, - readability-use-anyofallof - -modernize-use-trailing-return-type - -bugprone-exception-escape' + readability-use-anyofallof, + -modernize-use-trailing-return-type, + -bugprone-exception-escape, + -clang-diagnostic-switch-default' WarningsAsErrors: '' HeaderFilterRegex: '' diff --git a/README.md b/README.md index 29f4481..9e812ba 100644 --- a/README.md +++ b/README.md @@ -3,24 +3,28 @@ Modbus tcp client that stores its data (registers) in shared memory objects. ## Dependencies + - cxxopts by jarro2783 (https://github.com/jarro2783/cxxopts) (only required for building the application) - libmodbus by Stéphane Raimbault (https://github.com/stephane/libmodbus) - cxxshm (https://github.com/NikolasK-source/cxxshm) - cxxsemaphore (https://github.com/NikolasK-source/cxxsemaphore) On Arch linux they are available via the official repositories and the AUR: + - https://archlinux.org/packages/extra/any/cxxopts/ - https://aur.archlinux.org/packages/libmodbus - https://aur.archlinux.org/packages/cxxshm - https://aur.archlinux.org/packages/cxxsemaphore ## Build + ``` cmake -B build -DCMAKE_CXX_COMPILER=$(which clang++) -DCMAKE_BUILD_TYPE=Release -DCLANG_FORMAT=OFF -DCLANG_TIDY=OFF -DCOMPILER_WARNINGS=OFF -DBUILD_DOC=OFF cmake --build . ``` ## Use + ``` modbus-tcp-client-shm [OPTION...] @@ -33,14 +37,21 @@ modbus-tcp-client-shm [OPTION...] shared memory options: -n, --name-prefix arg shared memory name prefix (default: modbus_) - --force Force the use of the shared memory even if it already exists. Do not use this option per default! It should only be used if the shared memory of an improperly terminated instance continues to exist as an orphan - and is no longer used. - -s, --separate arg Use a separate shared memory for requests with the specified client id. The client id (as hex value) is appended to the shared memory prefix (e.g. modbus_fc_DO). You can specify multiple client ids by - separating them with ','. Use --separate-all to generate separate shared memories for all possible client ids. - --separate-all like --separate, but for all client ids (creates 1028 shared memory files! check/set 'ulimit -n' before using this option.) + --force Force the use of the shared memory even if it already exists. + Do not use this option per default! + It should only be used if the shared memory of an improperly terminated instance continues + to exist as an orphan and is no longer used. + -s, --separate arg Use a separate shared memory for requests with the specified client id. + The client id (as hex value) is appended to the shared memory prefix (e.g. modbus_fc_DO). + You can specify multiple client ids by separating them with ','. + Use --separate-all to generate separate shared memories for all possible client ids. + --separate-all like --separate, but for all client ids (creates 1028 shared memory files! + check/set 'ulimit -n' before using this option.) --semaphore arg protect the shared memory with a named semaphore against simultaneous access - --semaphore-force Force the use of the semaphore even if it already exists. Do not use this option per default! It should only be used if the semaphore of an improperly terminated instance continues to exist as an orphan and is - no longer used. + --semaphore-force Force the use of the semaphore even if it already exists. + Do not use this option per default! + It should only be used if the semaphore of an improperly terminated instance continues + to exist as an orphan and is no longer used. -b, --permissions arg permission bits that are applied when creating a shared memory. (default: 0640) modbus options: @@ -49,9 +60,15 @@ modbus-tcp-client-shm [OPTION...] --ao-registers arg number of analog output registers (default: 65536) --ai-registers arg number of analog input registers (default: 65536) -m, --monitor output all incoming and outgoing packets to stdout - --byte-timeout arg timeout interval in seconds between two consecutive bytes of the same message. In most cases it is sufficient to set the response timeout. Fractional values are possible. - --response-timeout arg set the timeout interval in seconds used to wait for a response. When a byte timeout is set, if the elapsed time for the first byte of response is longer than the given timeout, a timeout is detected. When - byte timeout is disabled, the full confirmation response must be received before expiration of the response timeout. Fractional values are possible. + --byte-timeout arg timeout interval in seconds between two consecutive bytes of the same message. + In most cases it is sufficient to set the response timeout. + Fractional values are possible. + --response-timeout arg set the timeout interval in seconds used to wait for a response. + When a byte timeout is set, if the elapsed time for the first byte of response is + longer than the given timeout, a timeout is detected. + When byte timeout is disabled, the full confirmation response must be received + before expiration of the response timeout. + Fractional values are possible. other options: -h, --help print usage @@ -63,6 +80,11 @@ modbus-tcp-client-shm [OPTION...] --longversion print version (including compiler and system info) and exit --shortversion print version (only version string) and exit --git-hash print git hash + + signal options: + -k, --signal arg send SIGUSR1 to process on writing modbus commands + --signal-register allow processes to register themselves for receiving SIGUSR1 on writing modbus commands + by sending SIGUSR1. The modbus registers are mapped to shared memory objects: @@ -75,10 +97,14 @@ The modbus registers are mapped to shared memory objects: ``` ### Use privileged ports -The standard modbus port (502) can be used only by the root user under linux by default. -To circumvent this, you can create an entry in the iptables that redirects packets on the standard modbus port to a higher port. + +The standard modbus port (502) can be used only by the root user under linux by default. +To circumvent this, you can create an entry in the iptables that redirects packets on the standard modbus port to a +higher port. The following example redirects packets from port 502 (standard modbus port) to port 5020 + ``` iptables -A PREROUTING -t nat -p tcp --dport 502 -j REDIRECT --to-port 5020 ``` + The modbus client must be called with the option ```-p 5020``` diff --git a/cmake_files/warnings.cmake b/cmake_files/warnings.cmake index 1a2c42a..1926faa 100644 --- a/cmake_files/warnings.cmake +++ b/cmake_files/warnings.cmake @@ -62,6 +62,8 @@ function(clangwarn target) target_compile_options(${target} PUBLIC -Wno-nested-anon-types) target_compile_options(${target} PUBLIC -Wno-gnu-anonymous-struct) target_compile_options(${target} PUBLIC -Wno-source-uses-openmp) + target_compile_options(${target} PUBLIC -Wno-switch-default) + target_compile_options(${target} PUBLIC -Wno-disabled-macro-expansion) endfunction() diff --git a/src/Mb_Proc_Signal.cpp b/src/Mb_Proc_Signal.cpp index e3fd77e..1cec6c9 100644 --- a/src/Mb_Proc_Signal.cpp +++ b/src/Mb_Proc_Signal.cpp @@ -4,10 +4,14 @@ */ #include "Mb_Proc_Signal.hpp" +#include "Print_Time.hpp" #include +#include +#include #include #include +#include Mb_Proc_Signal Mb_Proc_Signal::instance; // NOLINT @@ -16,17 +20,34 @@ Mb_Proc_Signal &Mb_Proc_Signal::get_instance() { } void Mb_Proc_Signal::add_process(pid_t process) { + auto ret = kill(process, 0); + if (ret == -1) { + if (errno == ESRCH) { throw std::runtime_error(std::format("no such process: {}", process)); } + throw std::system_error( + errno, std::generic_category(), std::format("Failed to send signal to process {}", process)); + } processes.insert(process); } -void Mb_Proc_Signal::send_signal() { +void Mb_Proc_Signal::send_signal(const union sigval &value) { + std::vector erased; for (auto proc : processes) { - auto ret = kill(proc, SIGUSR1); + auto ret = sigqueue(proc, SIGUSR1, value); if (ret == -1) { - throw std::system_error( - errno, std::generic_category(), "Failed to send signal to process " + std::to_string(proc)); + if (errno == ESRCH) { + erased.emplace_back(proc); + } else { + throw std::system_error( + errno, std::generic_category(), std::format("Failed to send signal to process {}", proc)); + } } } + + for (auto proc : erased) { + std::cerr << Print_Time::iso << " WARNING: process " << proc + << " does no longer exist. Removing from SIGUSR1 receivers.\n"; + processes.erase(proc); + } } void mb_callback(uint8_t mb_funtion_code) { @@ -35,7 +56,9 @@ void mb_callback(uint8_t mb_funtion_code) { case MODBUS_FC_WRITE_SINGLE_REGISTER: case MODBUS_FC_WRITE_MULTIPLE_COILS: case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: - case MODBUS_FC_WRITE_AND_READ_REGISTERS: Mb_Proc_Signal::get_instance().send_signal(); break; + case MODBUS_FC_WRITE_AND_READ_REGISTERS: + Mb_Proc_Signal::get_instance().send_signal({.sival_int = mb_funtion_code}); + break; default: // do nothing break; diff --git a/src/Mb_Proc_Signal.hpp b/src/Mb_Proc_Signal.hpp index a82c087..5706e67 100644 --- a/src/Mb_Proc_Signal.hpp +++ b/src/Mb_Proc_Signal.hpp @@ -26,7 +26,7 @@ class Mb_Proc_Signal final { void add_process(pid_t process); - void send_signal(); + void send_signal(const union sigval &value); }; void mb_callback(uint8_t mb_funtion_code); diff --git a/src/Modbus_TCP_Client_poll.cpp b/src/Modbus_TCP_Client_poll.cpp index cc4aaaf..1c8b6f6 100644 --- a/src/Modbus_TCP_Client_poll.cpp +++ b/src/Modbus_TCP_Client_poll.cpp @@ -5,6 +5,7 @@ #include "Modbus_TCP_Client_poll.hpp" +#include "Mb_Proc_Signal.hpp" #include "Print_Time.hpp" #include "sa_to_str.hpp" @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -31,14 +33,15 @@ static constexpr long SEMAPHORE_ERROR_DEC = 1; static constexpr long SEMAPHORE_ERROR_MAX = 1000; //* maximum time to wait for semaphore (100ms) -static constexpr struct timespec SEMAPHORE_MAX_TIME = {0, 100'000}; +static constexpr struct timespec SEMAPHORE_MAX_TIME = {.tv_sec = 0, .tv_nsec = 100'000}; Client_Poll::Client_Poll(const std::string &host, const std::string &service, + bool allow_sigusr1, modbus_mapping_t *mapping, std::size_t tcp_timeout, // NOLINT std::size_t max_clients) // NOLINT - : max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}) { + : max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}), allow_sigusr1(allow_sigusr1) { const char *host_str = "::"; if (!(host.empty() || host == "any")) host_str = host.c_str(); @@ -82,10 +85,11 @@ Client_Poll::Client_Poll(const std::string &host, Client_Poll::Client_Poll(const std::string &host, const std::string &service, + bool allow_sigusr1, std::array &mappings, std::size_t tcp_timeout, // NOLINT std::size_t max_clients) // NOLINT - : max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}) { + : max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}), allow_sigusr1(allow_sigusr1) { const char *host_str = "::"; if (!(host.empty() || host == "any")) host_str = host.c_str(); @@ -311,10 +315,33 @@ Client_Poll::run_t Client_Poll::run(int signal_fd, if (fd.revents & POLLNVAL) throw std::logic_error("poll (server socket) returned POLLNVAL"); if (fd.revents & POLLERR) throw std::logic_error("poll (signal fd) returned POLLERR"); if (fd.revents & POLLHUP) throw std::logic_error("poll (signal fd) returned POLLHUP"); - if (fd.revents & POLLIN) return run_t::term_signal; - std::ostringstream sstr; - sstr << "poll (signal fd) returned unknown revent: " << fd.revents; - throw std::logic_error(sstr.str()); + if (fd.revents & POLLIN) { + signalfd_siginfo siginfo {}; + const auto read_size = read(signal_fd, &siginfo, sizeof(siginfo)); + if (read_size == -1) { + throw std::system_error(errno, std::generic_category(), "Failed to read signalfd"); + } + + if (siginfo.ssi_signo == SIGUSR1 && allow_sigusr1) { + const auto pid = siginfo.ssi_pid; + try { + Mb_Proc_Signal::get_instance().add_process(static_cast(pid)); + std::cerr << Print_Time::iso << " INFO: process " << pid + << " registered for SIGUSR1 on writing modbus commands\n"; + } catch (const std::runtime_error &err) { + std::cerr << Print_Time::iso << " WARNING: process " << pid + << " registered for SIGUSR1: " << err.what() << "\n"; + } + return run_t::ok; + + } else { + return run_t::term_signal; + } + } else { + std::ostringstream sstr; + sstr << "poll (signal fd) returned unknown revent: " << fd.revents; + throw std::logic_error(sstr.str()); + } } } diff --git a/src/Modbus_TCP_Client_poll.hpp b/src/Modbus_TCP_Client_poll.hpp index ef8d577..3e43498 100644 --- a/src/Modbus_TCP_Client_poll.hpp +++ b/src/Modbus_TCP_Client_poll.hpp @@ -42,32 +42,39 @@ class Client_Poll { std::unique_ptr semaphore; long semaphore_error_counter = 0; + bool allow_sigusr1; public: /*! \brief create modbus client (TCP server) * * @param host host to listen for incoming connections (default 0.0.0.0 (any)) * @param service service/port to listen for incoming connections (default 502) + * @param allow_sigusr1 allow other processes to register for SIGUSR1 on writing modbus commands by sending SIGUSR1 * @param mapping modbus mapping object for all client ids * nullptr: an mapping object with maximum size is generated * @param tcp_timeout tcp timeout (currently only available on linux systems) + * @param max_clients max number of allowed clients */ - explicit Client_Poll(const std::string &host = "any", - const std::string &service = "502", - modbus_mapping_t *mapping = nullptr, - std::size_t tcp_timeout = 5, - std::size_t max_clients = 1); + explicit Client_Poll(const std::string &host = "any", + const std::string &service = "502", + bool allow_sigusr1 = false, + modbus_mapping_t *mapping = nullptr, + std::size_t tcp_timeout = 5, + std::size_t max_clients = 1); /** * @brief create modbus client (TCP server) with dedicated mappings per client id * * @param host host to listen for incoming connections * @param service service/port to listen for incoming connections + * @param allow_sigusr1 allow other processes to register for SIGUSR1 on writing modbus commands by sending SIGUSR1 * @param mappings modbus mappings (one for each possible id) * @param tcp_timeout tcp timeout (currently only available on linux systems) + * @param max_clients max number of allowed clients */ Client_Poll(const std::string &host, const std::string &service, + bool allow_sigusr1, std::array &mappings, std::size_t tcp_timeout = 5, std::size_t max_clients = 1); diff --git a/src/main.cpp b/src/main.cpp index 53ab90f..cfc022b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -56,16 +56,17 @@ static volatile bool terminate = false; // NOLINT /*! \brief signal handler (SIGINT and SIGTERM) * */ -constexpr std::array TERM_SIGNALS = {SIGINT, - SIGTERM, - SIGHUP, - SIGIO, // should not happen - SIGPIPE, - SIGPOLL, // should not happen - SIGPROF, // should not happen - SIGUSR1, - SIGUSR2, - SIGVTALRM}; +const std::array HANDLED_SIGNALS = {SIGINT, + SIGTERM, + SIGHUP, + SIGIO, // should not happen + SIGPIPE, + SIGPOLL, // should not happen + SIGPROF, // should not happen + SIGUSR1, + SIGUSR2, + SIGVTALRM}; + /*! \brief main function * @@ -87,14 +88,10 @@ int main(int argc, char **argv) { std::cerr << Print_Time::iso << " WARNING: !!!! You should not execute this program with root privileges !!!!'n"; -#ifdef COMPILER_CLANG -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" -#endif // create signal fd sigset_t sigmask; sigemptyset(&sigmask); - for (const auto SIGNO : TERM_SIGNALS) { + for (const auto SIGNO : HANDLED_SIGNALS) { sigaddset(&sigmask, SIGNO); } @@ -108,9 +105,6 @@ int main(int argc, char **argv) { perror("signal_fd"); return EX_OSERR; } -#ifdef COMPILER_CLANG -# pragma clang diagnostic pop -#endif // all command line arguments options.add_options("network")( @@ -190,7 +184,10 @@ int main(int argc, char **argv) { options.add_options("other")("license", "show licences (short)"); options.add_options("other")("license-full", "show licences (full license text)"); options.add_options("signal")( - "k,signal", "send SIGUSR to process on writing modbus commands", cxxopts::value>()); + "k,signal", "send SIGUSR1 to process on writing modbus commands", cxxopts::value>()); + options.add_options("signal")("signal-register", + "allow processes to register themselves for receiving SIGUSR1 on writing modbus " + "commands by sending SIGUSR1."); // parse arguments cxxopts::ParseResult args; @@ -450,6 +447,7 @@ int main(int argc, char **argv) { Mb_Proc_Signal::get_instance().add_process(proc); } } + if (args.count("signal-register") > 0) SIGNAL_PROCESS = true; // create modbus client @@ -457,6 +455,7 @@ int main(int argc, char **argv) { try { client = std::make_unique(args["host"].as(), args["service"].as(), + args["signal-register"].count() > 0, mb_mappings, #ifdef OS_LINUX args["tcp-timeout"].as(), From 146aeb6fc7c6d5cd8b8a5e26150536246181c87d Mon Sep 17 00:00:00 2001 From: Nikolas Koesling Date: Wed, 27 Aug 2025 20:13:58 +0200 Subject: [PATCH 5/5] increment version to 1.7.0 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f93248..5406c8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) # ====================================================================================================================== # project -project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.6.3) +project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.7.0) # settings set(Target "modbus-tcp-client-shm") # Executable name (without file extension!)