Skip to content

Commit 0300299

Browse files
Merge pull request #43 from SHMModbus/main
release 1.7.0
2 parents 2a894e2 + 146aeb6 commit 0300299

File tree

11 files changed

+262
-62
lines changed

11 files changed

+262
-62
lines changed

.clang-tidy

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ Checks: 'bugprone-*,
5050
readability-static-definition-in-anonymous-namespace,
5151
readability-string-compare,
5252
readability-uniqueptr-delete-release,
53-
readability-use-anyofallof
54-
-modernize-use-trailing-return-type
55-
-bugprone-exception-escape'
53+
readability-use-anyofallof,
54+
-modernize-use-trailing-return-type,
55+
-bugprone-exception-escape,
56+
-clang-diagnostic-switch-default'
5657
WarningsAsErrors: '*,
5758
-modernize-*,
5859
-readability-*

.clang-tidy-noerrors

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ Checks: 'bugprone-*,
5050
readability-static-definition-in-anonymous-namespace,
5151
readability-string-compare,
5252
readability-uniqueptr-delete-release,
53-
readability-use-anyofallof
54-
-modernize-use-trailing-return-type
55-
-bugprone-exception-escape'
53+
readability-use-anyofallof,
54+
-modernize-use-trailing-return-type,
55+
-bugprone-exception-escape,
56+
-clang-diagnostic-switch-default'
5657
WarningsAsErrors: ''
5758
HeaderFilterRegex: ''

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR)
99
# ======================================================================================================================
1010

1111
# project
12-
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.6.3)
12+
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.7.0)
1313

1414
# settings
1515
set(Target "modbus-tcp-client-shm") # Executable name (without file extension!)

README.md

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,28 @@
33
Modbus tcp client that stores its data (registers) in shared memory objects.
44

55
## Dependencies
6+
67
- cxxopts by jarro2783 (https://github.com/jarro2783/cxxopts) (only required for building the application)
78
- libmodbus by Stéphane Raimbault (https://github.com/stephane/libmodbus)
89
- cxxshm (https://github.com/NikolasK-source/cxxshm)
910
- cxxsemaphore (https://github.com/NikolasK-source/cxxsemaphore)
1011

1112
On Arch linux they are available via the official repositories and the AUR:
13+
1214
- https://archlinux.org/packages/extra/any/cxxopts/
1315
- https://aur.archlinux.org/packages/libmodbus
1416
- https://aur.archlinux.org/packages/cxxshm
1517
- https://aur.archlinux.org/packages/cxxsemaphore
1618

1719
## Build
20+
1821
```
1922
cmake -B build -DCMAKE_CXX_COMPILER=$(which clang++) -DCMAKE_BUILD_TYPE=Release -DCLANG_FORMAT=OFF -DCLANG_TIDY=OFF -DCOMPILER_WARNINGS=OFF -DBUILD_DOC=OFF
2023
cmake --build .
2124
```
2225

2326
## Use
27+
2428
```
2529
modbus-tcp-client-shm [OPTION...]
2630
@@ -33,14 +37,21 @@ modbus-tcp-client-shm [OPTION...]
3337
3438
shared memory options:
3539
-n, --name-prefix arg shared memory name prefix (default: modbus_)
36-
--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
37-
and is no longer used.
38-
-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
39-
separating them with ','. Use --separate-all to generate separate shared memories for all possible client ids.
40-
--separate-all like --separate, but for all client ids (creates 1028 shared memory files! check/set 'ulimit -n' before using this option.)
40+
--force Force the use of the shared memory even if it already exists.
41+
Do not use this option per default!
42+
It should only be used if the shared memory of an improperly terminated instance continues
43+
to exist as an orphan and is no longer used.
44+
-s, --separate arg Use a separate shared memory for requests with the specified client id.
45+
The client id (as hex value) is appended to the shared memory prefix (e.g. modbus_fc_DO).
46+
You can specify multiple client ids by separating them with ','.
47+
Use --separate-all to generate separate shared memories for all possible client ids.
48+
--separate-all like --separate, but for all client ids (creates 1028 shared memory files!
49+
check/set 'ulimit -n' before using this option.)
4150
--semaphore arg protect the shared memory with a named semaphore against simultaneous access
42-
--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
43-
no longer used.
51+
--semaphore-force Force the use of the semaphore even if it already exists.
52+
Do not use this option per default!
53+
It should only be used if the semaphore of an improperly terminated instance continues
54+
to exist as an orphan and is no longer used.
4455
-b, --permissions arg permission bits that are applied when creating a shared memory. (default: 0640)
4556
4657
modbus options:
@@ -49,9 +60,15 @@ modbus-tcp-client-shm [OPTION...]
4960
--ao-registers arg number of analog output registers (default: 65536)
5061
--ai-registers arg number of analog input registers (default: 65536)
5162
-m, --monitor output all incoming and outgoing packets to stdout
52-
--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.
53-
--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
54-
byte timeout is disabled, the full confirmation response must be received before expiration of the response timeout. Fractional values are possible.
63+
--byte-timeout arg timeout interval in seconds between two consecutive bytes of the same message.
64+
In most cases it is sufficient to set the response timeout.
65+
Fractional values are possible.
66+
--response-timeout arg set the timeout interval in seconds used to wait for a response.
67+
When a byte timeout is set, if the elapsed time for the first byte of response is
68+
longer than the given timeout, a timeout is detected.
69+
When byte timeout is disabled, the full confirmation response must be received
70+
before expiration of the response timeout.
71+
Fractional values are possible.
5572
5673
other options:
5774
-h, --help print usage
@@ -63,6 +80,11 @@ modbus-tcp-client-shm [OPTION...]
6380
--longversion print version (including compiler and system info) and exit
6481
--shortversion print version (only version string) and exit
6582
--git-hash print git hash
83+
84+
signal options:
85+
-k, --signal arg send SIGUSR1 to process on writing modbus commands
86+
--signal-register allow processes to register themselves for receiving SIGUSR1 on writing modbus commands
87+
by sending SIGUSR1.
6688
6789
6890
The modbus registers are mapped to shared memory objects:
@@ -75,10 +97,14 @@ The modbus registers are mapped to shared memory objects:
7597
```
7698

7799
### Use privileged ports
78-
The standard modbus port (502) can be used only by the root user under linux by default.
79-
To circumvent this, you can create an entry in the iptables that redirects packets on the standard modbus port to a higher port.
100+
101+
The standard modbus port (502) can be used only by the root user under linux by default.
102+
To circumvent this, you can create an entry in the iptables that redirects packets on the standard modbus port to a
103+
higher port.
80104
The following example redirects packets from port 502 (standard modbus port) to port 5020
105+
81106
```
82107
iptables -A PREROUTING -t nat -p tcp --dport 502 -j REDIRECT --to-port 5020
83108
```
109+
84110
The modbus client must be called with the option ```-p 5020```

cmake_files/warnings.cmake

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ function(clangwarn target)
6262
target_compile_options(${target} PUBLIC -Wno-nested-anon-types)
6363
target_compile_options(${target} PUBLIC -Wno-gnu-anonymous-struct)
6464
target_compile_options(${target} PUBLIC -Wno-source-uses-openmp)
65+
target_compile_options(${target} PUBLIC -Wno-switch-default)
66+
target_compile_options(${target} PUBLIC -Wno-disabled-macro-expansion)
6567

6668
endfunction()
6769

src/CMakeLists.txt

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,7 @@ target_sources(${Target} PRIVATE Modbus_TCP_Client_poll.cpp)
77
target_sources(${Target} PRIVATE license.cpp)
88
target_sources(${Target} PRIVATE sa_to_str.cpp)
99
target_sources(${Target} PRIVATE Print_Time.cpp)
10-
11-
12-
# ---------------------------------------- header files (*.jpp, *.h, ...) ----------------------------------------------
13-
# ======================================================================================================================
14-
target_sources(${Target} PRIVATE modbus_shm.hpp)
15-
target_sources(${Target} PRIVATE Modbus_TCP_Client_poll.hpp)
16-
target_sources(${Target} PRIVATE license.hpp)
17-
target_sources(${Target} PRIVATE sa_to_str.hpp)
18-
target_sources(${Target} PRIVATE Print_Time.hpp)
19-
10+
target_sources(${Target} PRIVATE Mb_Proc_Signal.cpp)
2011

2112
# ---------------------------------------- subdirectories --------------------------------------------------------------
2213
# ======================================================================================================================

src/Mb_Proc_Signal.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (C) 2024 Nikolas Koesling <nikolas@koesling.info>.
3+
* This program is free software. You can redistribute it and/or modify it under the terms of the GPLv3 License.
4+
*/
5+
6+
#include "Mb_Proc_Signal.hpp"
7+
#include "Print_Time.hpp"
8+
9+
#include <cerrno>
10+
#include <format>
11+
#include <iostream>
12+
#include <modbus/modbus.h>
13+
#include <system_error>
14+
#include <vector>
15+
16+
Mb_Proc_Signal Mb_Proc_Signal::instance; // NOLINT
17+
18+
Mb_Proc_Signal &Mb_Proc_Signal::get_instance() {
19+
return instance;
20+
}
21+
22+
void Mb_Proc_Signal::add_process(pid_t process) {
23+
auto ret = kill(process, 0);
24+
if (ret == -1) {
25+
if (errno == ESRCH) { throw std::runtime_error(std::format("no such process: {}", process)); }
26+
throw std::system_error(
27+
errno, std::generic_category(), std::format("Failed to send signal to process {}", process));
28+
}
29+
processes.insert(process);
30+
}
31+
32+
void Mb_Proc_Signal::send_signal(const union sigval &value) {
33+
std::vector<pid_t> erased;
34+
for (auto proc : processes) {
35+
auto ret = sigqueue(proc, SIGUSR1, value);
36+
if (ret == -1) {
37+
if (errno == ESRCH) {
38+
erased.emplace_back(proc);
39+
} else {
40+
throw std::system_error(
41+
errno, std::generic_category(), std::format("Failed to send signal to process {}", proc));
42+
}
43+
}
44+
}
45+
46+
for (auto proc : erased) {
47+
std::cerr << Print_Time::iso << " WARNING: process " << proc
48+
<< " does no longer exist. Removing from SIGUSR1 receivers.\n";
49+
processes.erase(proc);
50+
}
51+
}
52+
53+
void mb_callback(uint8_t mb_funtion_code) {
54+
switch (mb_funtion_code) {
55+
case MODBUS_FC_WRITE_SINGLE_COIL:
56+
case MODBUS_FC_WRITE_SINGLE_REGISTER:
57+
case MODBUS_FC_WRITE_MULTIPLE_COILS:
58+
case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
59+
case MODBUS_FC_WRITE_AND_READ_REGISTERS:
60+
Mb_Proc_Signal::get_instance().send_signal({.sival_int = mb_funtion_code});
61+
break;
62+
default:
63+
// do nothing
64+
break;
65+
}
66+
}

src/Mb_Proc_Signal.hpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright (C) 2024 Nikolas Koesling <nikolas@koesling.info>.
3+
* This program is free software. You can redistribute it and/or modify it under the terms of the GPLv3 License.
4+
*/
5+
6+
#include <cstdint>
7+
#include <unistd.h>
8+
#include <unordered_set>
9+
10+
class Mb_Proc_Signal final {
11+
private:
12+
std::unordered_set<pid_t> processes;
13+
14+
Mb_Proc_Signal() = default;
15+
16+
static Mb_Proc_Signal instance;
17+
18+
public:
19+
Mb_Proc_Signal(const Mb_Proc_Signal &) = delete;
20+
Mb_Proc_Signal(Mb_Proc_Signal &&) = delete;
21+
Mb_Proc_Signal &operator=(const Mb_Proc_Signal &) = delete;
22+
Mb_Proc_Signal &operator=(Mb_Proc_Signal &&) = delete;
23+
~Mb_Proc_Signal() = default;
24+
25+
static Mb_Proc_Signal &get_instance();
26+
27+
void add_process(pid_t process);
28+
29+
void send_signal(const union sigval &value);
30+
};
31+
32+
void mb_callback(uint8_t mb_funtion_code);

src/Modbus_TCP_Client_poll.cpp

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "Modbus_TCP_Client_poll.hpp"
77

8+
#include "Mb_Proc_Signal.hpp"
89
#include "Print_Time.hpp"
910
#include "sa_to_str.hpp"
1011

@@ -13,6 +14,7 @@
1314
#include <netinet/tcp.h>
1415
#include <sstream>
1516
#include <stdexcept>
17+
#include <sys/signalfd.h>
1618
#include <sys/socket.h>
1719
#include <system_error>
1820

@@ -31,14 +33,15 @@ static constexpr long SEMAPHORE_ERROR_DEC = 1;
3133
static constexpr long SEMAPHORE_ERROR_MAX = 1000;
3234

3335
//* maximum time to wait for semaphore (100ms)
34-
static constexpr struct timespec SEMAPHORE_MAX_TIME = {0, 100'000};
36+
static constexpr struct timespec SEMAPHORE_MAX_TIME = {.tv_sec = 0, .tv_nsec = 100'000};
3537

3638
Client_Poll::Client_Poll(const std::string &host,
3739
const std::string &service,
40+
bool allow_sigusr1,
3841
modbus_mapping_t *mapping,
3942
std::size_t tcp_timeout, // NOLINT
4043
std::size_t max_clients) // NOLINT
41-
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}) {
44+
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}), allow_sigusr1(allow_sigusr1) {
4245
const char *host_str = "::";
4346
if (!(host.empty() || host == "any")) host_str = host.c_str();
4447

@@ -82,10 +85,11 @@ Client_Poll::Client_Poll(const std::string &host,
8285

8386
Client_Poll::Client_Poll(const std::string &host,
8487
const std::string &service,
88+
bool allow_sigusr1,
8589
std::array<modbus_mapping_t *, MAX_CLIENT_IDS> &mappings,
8690
std::size_t tcp_timeout, // NOLINT
8791
std::size_t max_clients) // NOLINT
88-
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}) {
92+
: max_clients(max_clients), poll_fds(max_clients + 2, {0, 0, 0}), allow_sigusr1(allow_sigusr1) {
8993
const char *host_str = "::";
9094
if (!(host.empty() || host == "any")) host_str = host.c_str();
9195

@@ -263,7 +267,10 @@ void Client_Poll::set_response_timeout(double timeout) {
263267
return static_cast<double>(timeout.sec) + (static_cast<double>(timeout.usec) / (1000.0 * 1000.0)); // NOLINT
264268
}
265269

266-
Client_Poll::run_t Client_Poll::run(int signal_fd, bool reconnect, int timeout) {
270+
Client_Poll::run_t Client_Poll::run(int signal_fd,
271+
bool reconnect,
272+
int timeout,
273+
void (*mb_function_callback)(uint8_t mb_function_code)) {
267274
std::size_t i = 0;
268275

269276
// poll signal fd
@@ -308,10 +315,33 @@ Client_Poll::run_t Client_Poll::run(int signal_fd, bool reconnect, int timeout)
308315
if (fd.revents & POLLNVAL) throw std::logic_error("poll (server socket) returned POLLNVAL");
309316
if (fd.revents & POLLERR) throw std::logic_error("poll (signal fd) returned POLLERR");
310317
if (fd.revents & POLLHUP) throw std::logic_error("poll (signal fd) returned POLLHUP");
311-
if (fd.revents & POLLIN) return run_t::term_signal;
312-
std::ostringstream sstr;
313-
sstr << "poll (signal fd) returned unknown revent: " << fd.revents;
314-
throw std::logic_error(sstr.str());
318+
if (fd.revents & POLLIN) {
319+
signalfd_siginfo siginfo {};
320+
const auto read_size = read(signal_fd, &siginfo, sizeof(siginfo));
321+
if (read_size == -1) {
322+
throw std::system_error(errno, std::generic_category(), "Failed to read signalfd");
323+
}
324+
325+
if (siginfo.ssi_signo == SIGUSR1 && allow_sigusr1) {
326+
const auto pid = siginfo.ssi_pid;
327+
try {
328+
Mb_Proc_Signal::get_instance().add_process(static_cast<pid_t>(pid));
329+
std::cerr << Print_Time::iso << " INFO: process " << pid
330+
<< " registered for SIGUSR1 on writing modbus commands\n";
331+
} catch (const std::runtime_error &err) {
332+
std::cerr << Print_Time::iso << " WARNING: process " << pid
333+
<< " registered for SIGUSR1: " << err.what() << "\n";
334+
}
335+
return run_t::ok;
336+
337+
} else {
338+
return run_t::term_signal;
339+
}
340+
} else {
341+
std::ostringstream sstr;
342+
sstr << "poll (signal fd) returned unknown revent: " << fd.revents;
343+
throw std::logic_error(sstr.str());
344+
}
315345
}
316346
}
317347

@@ -418,6 +448,13 @@ Client_Poll::run_t Client_Poll::run(int signal_fd, bool reconnect, int timeout)
418448
<< std::endl; // NOLINT
419449
close_con(client_addrs);
420450
}
451+
452+
// function code callback
453+
if (mb_function_callback) {
454+
const auto FUNCTION_CODE = query[7];
455+
mb_function_callback(FUNCTION_CODE);
456+
}
457+
421458
} else if (rc == -1) {
422459
if (errno != ECONNRESET) {
423460
std::cerr << Print_Time::iso << " ERROR: modbus_receive failed: " << modbus_strerror(errno)

0 commit comments

Comments
 (0)