Skip to content

Commit 26a61f7

Browse files
Merge pull request #13 from NikolasK-source/main
Release 1.2.0
2 parents ef7aa06 + 757bf0d commit 26a61f7

File tree

8 files changed

+264
-66
lines changed

8 files changed

+264
-66
lines changed

.github/workflows/cmake.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ name: CMake
22

33
on:
44
push:
5-
branches: [ "main", "development" ]
65
pull_request:
76
branches: [ "main" ]
87

.github/workflows/codeql-analysis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ name: "CodeQL"
1313

1414
on:
1515
push:
16-
branches: [ "main", "development" ]
16+
branches: [ "main", "release" ]
1717
pull_request:
1818
# The branches below must be a subset of the branches above
1919
branches: [ "main" ]

.github/workflows/flatpak_test.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
on:
2-
push:
3-
branches: [ "main", "development" ]
4-
pull_request:
2+
[ push, pull_request ]
53
name: Flatpak_test
64
jobs:
75
flatpak:
@@ -27,4 +25,4 @@ jobs:
2725
with:
2826
bundle: test_modbus-tcp-client-shm.flatpak
2927
manifest-path: network.koesling.test-modbus-tcp-client-shm.yml
30-
cache-key: flatpak-builder-${{ github.sha }}
28+
cache-key: flatpak-builder-${{ github.sha }}

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13.4 FATAL_ERROR)
44
# ======================================================================================================================
55

66
# project
7-
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.1.1)
7+
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.2.0)
88

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

docs/index.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,30 @@ cmake --build build
118118
The binary is located in the build directory.
119119

120120

121+
## Common Problems and Fixes
122+
123+
### Failed to create Shared Memory
124+
It can happen that the client reports the following error on startup:
125+
```
126+
Failed to create shared memory ...: File exists
127+
```
128+
This can be caused by:
129+
- Another modbus client is running that uses the shared memory with the given name.
130+
If you want to run multiple instances simultaneously use the option ```--name-prefix``` to change the name of the shared memory.
131+
- Any other application uses a shared memory with the given name (unlikely but possible)
132+
- A previous instance of a modbus client crashed or was forcefully terminated and was not able to unlink the shared memory.
133+
In this case, the option ```--force``` can be used to force the use of shared memory.
134+
In the other cases this option should not be used.
135+
136+
### Connection frequently times out
137+
138+
If the connection frequently times out, it may be reasonable to increase the tcp timeout with the option ```--tcp-timeout```.
139+
It is per default set to 5 seconds.
140+
141+
The two options ```--byte-timeout``` and ```--response-timeout``` change the timeout behavior of the modbus connection.
142+
These should only be changed by experienced users.
143+
See the [libmodbus documentation](https://libmodbus.org/docs/v3.1.7/) ([byte timeout](https://libmodbus.org/docs/v3.1.7/modbus_set_byte_timeout.html) and [response timeout](https://libmodbus.org/docs/v3.1.7/modbus_set_response_timeout.html)) for more details.
144+
121145
## Links to related projects
122146

123147
### General Shared Memory Tools

src/Modbus_TCP_Slave.cpp

Lines changed: 93 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <system_error>
1717
#include <unistd.h>
1818

19+
#include <iostream>
20+
1921
namespace Modbus {
2022
namespace TCP {
2123

@@ -29,21 +31,74 @@ Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mappi
2931
throw std::runtime_error("failed to create modbus instance: " + error_msg);
3032
}
3133

34+
modbus_mapping_t *mb_mapping;
35+
3236
if (mapping == nullptr) {
3337
// create new mapping with the maximum number of registers
34-
this->mapping = modbus_mapping_new(MAX_REGS, MAX_REGS, MAX_REGS, MAX_REGS);
35-
if (this->mapping == nullptr) {
38+
mb_mapping = modbus_mapping_new(MAX_REGS, MAX_REGS, MAX_REGS, MAX_REGS);
39+
if (mb_mapping == nullptr) {
3640
const std::string error_msg = modbus_strerror(errno);
3741
modbus_free(modbus);
3842
throw std::runtime_error("failed to allocate memory: " + error_msg);
3943
}
40-
delete_mapping = true;
44+
delete_mapping = mapping;
4145
} else {
4246
// use the provided mapping object
43-
this->mapping = mapping;
44-
delete_mapping = false;
47+
mb_mapping = mapping;
48+
delete_mapping = nullptr;
49+
}
50+
51+
// use mapping for all client ids
52+
for (std::size_t i = 0; i < MAX_CLIENT_IDS; ++i) {
53+
this->mappings[i] = mapping;
54+
}
55+
56+
listen();
57+
58+
#ifdef OS_LINUX
59+
if (tcp_timeout) set_tcp_timeout(tcp_timeout);
60+
#else
61+
static_cast<void>(tcp_timeout);
62+
#endif
63+
}
64+
65+
Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t **mappings, std::size_t tcp_timeout) {
66+
// create modbus object
67+
modbus = modbus_new_tcp(ip.c_str(), static_cast<int>(port));
68+
if (modbus == nullptr) {
69+
const std::string error_msg = modbus_strerror(errno);
70+
throw std::runtime_error("failed to create modbus instance: " + error_msg);
4571
}
4672

73+
delete_mapping = nullptr;
74+
75+
for (std::size_t i = 0; i < MAX_CLIENT_IDS; ++i) {
76+
if (mappings[i] == nullptr) {
77+
if (delete_mapping == nullptr) {
78+
delete_mapping = modbus_mapping_new(MAX_REGS, MAX_REGS, MAX_REGS, MAX_REGS);
79+
80+
if (delete_mapping == nullptr) {
81+
const std::string error_msg = modbus_strerror(errno);
82+
modbus_free(modbus);
83+
throw std::runtime_error("failed to allocate memory: " + error_msg);
84+
}
85+
}
86+
this->mappings[i] = delete_mapping;
87+
} else {
88+
this->mappings[i] = mappings[i];
89+
}
90+
}
91+
92+
listen();
93+
94+
#ifdef OS_LINUX
95+
if (tcp_timeout) set_tcp_timeout(tcp_timeout);
96+
#else
97+
static_cast<void>(tcp_timeout);
98+
#endif
99+
}
100+
101+
void Slave::listen() {
47102
// create tcp socket
48103
socket = modbus_tcp_listen(modbus, 1);
49104
if (socket == -1) {
@@ -58,48 +113,47 @@ Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mappi
58113
if (tmp != 0) {
59114
throw std::system_error(errno, std::generic_category(), "Failed to set socket option SO_KEEPALIVE");
60115
}
116+
}
61117

62118
#ifdef OS_LINUX
63-
if (tcp_timeout) {
64-
// set user timeout (~= timeout for tcp connection)
65-
unsigned user_timeout = static_cast<unsigned>(tcp_timeout) * 1000;
66-
tmp = setsockopt(socket, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, sizeof(keepalive));
67-
if (tmp != 0) {
68-
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_USER_TIMEOUT");
69-
}
119+
void Slave::set_tcp_timeout(std::size_t tcp_timeout) {
120+
// set user timeout (~= timeout for tcp connection)
121+
unsigned user_timeout = static_cast<unsigned>(tcp_timeout) * 1000;
122+
int tmp = setsockopt(socket, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, sizeof(tcp_timeout));
123+
if (tmp != 0) {
124+
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_USER_TIMEOUT");
125+
}
70126

71-
// start sending keepalive request after one second without request
72-
unsigned keepidle = 1;
73-
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
74-
if (tmp != 0) {
75-
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPIDLE");
76-
}
127+
// start sending keepalive request after one second without request
128+
unsigned keepidle = 1;
129+
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
130+
if (tmp != 0) {
131+
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPIDLE");
132+
}
77133

78-
// send up to 5 keepalive requests during the timeout time, but not more than one per second
79-
unsigned keepintvl = std::max(static_cast<unsigned>(tcp_timeout / 5), 1u);
80-
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
81-
if (tmp != 0) {
82-
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPINTVL");
83-
}
134+
// send up to 5 keepalive requests during the timeout time, but not more than one per second
135+
unsigned keepintvl = std::max(static_cast<unsigned>(tcp_timeout / 5), 1u);
136+
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
137+
if (tmp != 0) {
138+
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPINTVL");
139+
}
84140

85-
// 5 keepalive requests if the timeout time is >= 5s; else send one request each second
86-
unsigned keepcnt = std::min(static_cast<unsigned>(tcp_timeout), 5u);
87-
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
88-
if (tmp != 0) {
89-
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPCNT");
90-
}
141+
// 5 keepalive requests if the timeout time is >= 5s; else send one request each second
142+
unsigned keepcnt = std::min(static_cast<unsigned>(tcp_timeout), 5u);
143+
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
144+
if (tmp != 0) {
145+
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPCNT");
91146
}
92-
#else
93-
static_cast<void>(tcp_timeout);
94-
#endif
95147
}
148+
#endif
149+
96150

97151
Slave::~Slave() {
98152
if (modbus != nullptr) {
99153
modbus_close(modbus);
100154
modbus_free(modbus);
101155
}
102-
if (mapping != nullptr && delete_mapping) modbus_mapping_free(mapping);
156+
if (delete_mapping) modbus_mapping_free(delete_mapping);
103157
if (socket != -1) { close(socket); }
104158
}
105159

@@ -141,6 +195,11 @@ bool Slave::handle_request() {
141195
int rc = modbus_receive(modbus, query);
142196

143197
if (rc > 0) {
198+
const auto CLIENT_ID = query[6];
199+
200+
// get mapping
201+
auto mapping = mappings[CLIENT_ID];
202+
144203
// handle request
145204
int ret = modbus_reply(modbus, query, rc, mapping);
146205
if (ret == -1) {

src/Modbus_TCP_Slave.hpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,49 @@
77

88
#include <modbus/modbus.h>
99
#include <string>
10+
#include <unordered_map>
1011

1112
namespace Modbus {
1213
namespace TCP {
1314

15+
constexpr std::size_t MAX_CLIENT_IDS = 256;
16+
1417
//! Modbus TCP slave
1518
class Slave {
1619
private:
17-
modbus_t *modbus; //!< modbus object (see libmodbus library)
18-
modbus_mapping_t *mapping; //!< modbus data object (see libmodbus library)
19-
bool delete_mapping; //!< indicates whether the mapping object was created by this instance
20-
int socket = -1; //!< socket of the modbus connection
20+
modbus_t *modbus; //!< modbus object (see libmodbus library)
21+
modbus_mapping_t
22+
*mappings[MAX_CLIENT_IDS]; //!< modbus data objects (one per possible client id) (see libmodbus library)
23+
modbus_mapping_t *delete_mapping; //!< contains a pointer to a mapping that is to be deleted
24+
int socket = -1; //!< socket of the modbus connection
2125

2226
public:
2327
/*! \brief create modbus slave (TCP server)
2428
*
2529
* @param ip ip to listen for incoming connections (default 0.0.0.0 (any))
2630
* @param port port to listen for incoming connections (default 502)
27-
* @param mapping modbus mapping object (nullptr: an mapping object with maximum size is generated)
31+
* @param mapping modbus mapping object for all client ids
32+
* nullptr: an mapping object with maximum size is generated
33+
* @param tcp_timeout tcp timeout (currently only available on linux systems)
2834
*/
2935
explicit Slave(const std::string &ip = "0.0.0.0",
3036
short unsigned int port = 502,
3137
modbus_mapping_t *mapping = nullptr,
3238
std::size_t tcp_timeout = 5);
3339

40+
/**
41+
* @brief create modbus slave (TCP server) with dedicated mappings per client id
42+
*
43+
* @param ip ip to listen for incoming connections
44+
* @param port port to listen for incoming connections
45+
* @param mappings modbus mappings (one for each possible id)
46+
* @param tcp_timeout tcp timeout (currently only available on linux systems)
47+
*/
48+
Slave(const std::string &ip,
49+
short unsigned int port,
50+
modbus_mapping_t *mappings[MAX_CLIENT_IDS],
51+
std::size_t tcp_timeout = 5);
52+
3453
/*! \brief destroy the modbus slave
3554
*
3655
*/
@@ -89,6 +108,13 @@ class Slave {
89108
* @return socket of the modbus connection
90109
*/
91110
[[nodiscard]] int get_socket() const noexcept { return socket; }
111+
112+
private:
113+
#ifdef OS_LINUX
114+
void set_tcp_timeout(std::size_t tcp_timeout);
115+
#endif
116+
117+
void listen();
92118
};
93119

94120
} // namespace TCP

0 commit comments

Comments
 (0)