Skip to content

Commit fb77362

Browse files
Merge pull request #2 from NikolasK-source/development
Development
2 parents c0e70f0 + f9066bc commit fb77362

File tree

9 files changed

+288
-48
lines changed

9 files changed

+288
-48
lines changed

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[submodule "libs/cxxopts"]
22
path = libs/cxxopts
3-
url = https://github.com/NikolasK-source/cxxopts.git
3+
url = https://github.com/NikolasK-source/cxxopts.git
44
[submodule "libs/libmodbus"]
55
path = libs/libmodbus
66
url = https://github.com/stephane/libmodbus

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.0.3)
7+
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.1.0)
88

99
# settings
1010
set(Target "Modbus_TCP_client_shm") # Executable name (without file extension!)

src/Modbus_TCP_Slave.cpp

Lines changed: 136 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@
55

66
#include "Modbus_TCP_Slave.hpp"
77

8+
#include <algorithm>
9+
#include <arpa/inet.h>
10+
#include <cstring>
11+
#include <netinet/in.h>
12+
#include <netinet/tcp.h>
13+
#include <sstream>
814
#include <stdexcept>
15+
#include <sys/socket.h>
16+
#include <system_error>
917
#include <unistd.h>
1018

1119
namespace Modbus {
1220
namespace TCP {
1321

1422
static constexpr int MAX_REGS = 0x10000;
1523

16-
Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mapping) {
24+
Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mapping, std::size_t tcp_timeout) {
1725
// create modbus object
1826
modbus = modbus_new_tcp(ip.c_str(), static_cast<int>(port));
1927
if (modbus == nullptr) {
@@ -42,6 +50,48 @@ Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mappi
4250
const std::string error_msg = modbus_strerror(errno);
4351
throw std::runtime_error("failed to create tcp socket: " + error_msg);
4452
}
53+
54+
// set socket options
55+
// enable socket keepalive (--> fail if connection partner is not reachable)
56+
int keepalive = 1;
57+
int tmp = setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive));
58+
if (tmp != 0) {
59+
throw std::system_error(errno, std::generic_category(), "Failed to set socket option SO_KEEPALIVE");
60+
}
61+
62+
#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+
}
70+
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+
}
77+
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+
}
84+
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+
}
91+
}
92+
#else
93+
static_cast<void>(tcp_timeout);
94+
#endif
4595
}
4696

4797
Slave::~Slave() {
@@ -60,12 +110,29 @@ void Slave::set_debug(bool debug) {
60110
}
61111
}
62112

63-
void Slave::connect_client() {
113+
std::string Slave::connect_client() {
64114
int tmp = modbus_tcp_accept(modbus, &socket);
65115
if (tmp < 0) {
66116
const std::string error_msg = modbus_strerror(errno);
67117
throw std::runtime_error("modbus_tcp_accept failed: " + error_msg);
68118
}
119+
120+
struct sockaddr_in peer_addr;
121+
socklen_t len = sizeof(peer_addr);
122+
tmp = getpeername(modbus_get_socket(modbus), reinterpret_cast<struct sockaddr *>(&peer_addr), &len);
123+
124+
if (tmp < 0) {
125+
const std::string error_msg = modbus_strerror(errno);
126+
throw std::runtime_error("getpeername failed: " + error_msg);
127+
}
128+
129+
char buffer[INET_ADDRSTRLEN];
130+
inet_ntop(peer_addr.sin_family, &peer_addr.sin_addr, buffer, sizeof(buffer));
131+
132+
std::ostringstream sstr;
133+
sstr << buffer << ':' << htons(peer_addr.sin_port);
134+
135+
return sstr.str();
69136
}
70137

71138
bool Slave::handle_request() {
@@ -75,7 +142,11 @@ bool Slave::handle_request() {
75142

76143
if (rc > 0) {
77144
// handle request
78-
modbus_reply(modbus, query, rc, mapping);
145+
int ret = modbus_reply(modbus, query, rc, mapping);
146+
if (ret == -1) {
147+
const std::string error_msg = modbus_strerror(errno);
148+
throw std::runtime_error("modbus_reply failed: " + error_msg + ' ' + std::to_string(errno));
149+
}
79150
} else if (rc == -1) {
80151
if (errno == ECONNRESET) return true;
81152

@@ -86,5 +157,67 @@ bool Slave::handle_request() {
86157
return false;
87158
}
88159

160+
struct timeout_t {
161+
uint32_t sec;
162+
uint32_t usec;
163+
};
164+
165+
static inline timeout_t double_to_timeout_t(double timeout) {
166+
timeout_t ret {};
167+
168+
ret.sec = static_cast<uint32_t>(timeout);
169+
170+
double fractional = timeout - static_cast<double>(ret.sec);
171+
ret.usec = static_cast<uint32_t>(fractional * 1000.0 * 1000.0);
172+
173+
return ret;
174+
}
175+
176+
void Slave::set_byte_timeout(double timeout) {
177+
const auto T = double_to_timeout_t(timeout);
178+
auto ret = modbus_set_byte_timeout(modbus, T.sec, T.usec);
179+
180+
if (ret != 0) {
181+
const std::string error_msg = modbus_strerror(errno);
182+
throw std::runtime_error("modbus_receive failed: " + error_msg + ' ' + std::to_string(errno));
183+
}
184+
}
185+
186+
void Slave::set_response_timeout(double timeout) {
187+
const auto T = double_to_timeout_t(timeout);
188+
auto ret = modbus_set_response_timeout(modbus, T.sec, T.usec);
189+
190+
if (ret != 0) {
191+
const std::string error_msg = modbus_strerror(errno);
192+
throw std::runtime_error("modbus_receive failed: " + error_msg + ' ' + std::to_string(errno));
193+
}
194+
}
195+
196+
double Slave::get_byte_timeout() {
197+
timeout_t timeout {};
198+
199+
auto ret = modbus_get_byte_timeout(modbus, &timeout.sec, &timeout.usec);
200+
201+
if (ret != 0) {
202+
const std::string error_msg = modbus_strerror(errno);
203+
throw std::runtime_error("modbus_receive failed: " + error_msg + ' ' + std::to_string(errno));
204+
}
205+
206+
return static_cast<double>(timeout.sec) + (static_cast<double>(timeout.usec) / (1000.0 * 1000.0));
207+
}
208+
209+
double Slave::get_response_timeout() {
210+
timeout_t timeout {};
211+
212+
auto ret = modbus_get_response_timeout(modbus, &timeout.sec, &timeout.usec);
213+
214+
if (ret != 0) {
215+
const std::string error_msg = modbus_strerror(errno);
216+
throw std::runtime_error("modbus_receive failed: " + error_msg + ' ' + std::to_string(errno));
217+
}
218+
219+
return static_cast<double>(timeout.sec) + (static_cast<double>(timeout.usec) / (1000.0 * 1000.0));
220+
}
221+
89222
} // namespace TCP
90223
} // namespace Modbus

src/Modbus_TCP_Slave.hpp

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@ class Slave {
2626
* @param port port to listen for incoming connections (default 502)
2727
* @param mapping modbus mapping object (nullptr: an mapping object with maximum size is generated)
2828
*/
29-
explicit Slave(const std::string &ip = "0.0.0.0",
30-
short unsigned int port = 502,
31-
modbus_mapping_t *mapping = nullptr);
29+
explicit Slave(const std::string &ip = "0.0.0.0",
30+
short unsigned int port = 502,
31+
modbus_mapping_t *mapping = nullptr,
32+
std::size_t tcp_timeout = 5);
3233

3334
/*! \brief destroy the modbus slave
3435
*
@@ -43,15 +44,46 @@ class Slave {
4344

4445
/*! \brief wait for client to connect
4546
*
47+
* @return ip of the connected client
4648
*/
47-
void connect_client();
49+
std::string connect_client();
4850

4951
/*! \brief wait for request from Master and generate reply
5052
*
5153
* @return true: connection closed
5254
*/
5355
bool handle_request();
5456

57+
/*!
58+
* \brief set byte timeout
59+
*
60+
* @details see https://libmodbus.org/docs/v3.1.7/modbus_set_byte_timeout.html
61+
*
62+
* @param timeout byte timeout in seconds
63+
*/
64+
void set_byte_timeout(double timeout);
65+
66+
/*!
67+
* \brief set byte timeout
68+
*
69+
* @details see https://libmodbus.org/docs/v3.1.7/modbus_set_response_timeout.html
70+
*
71+
* @param timeout byte response in seconds
72+
*/
73+
void set_response_timeout(double timeout);
74+
75+
/**
76+
* \brief get byte timeout in seconds
77+
* @return byte timeout
78+
*/
79+
double get_byte_timeout();
80+
81+
/**
82+
* \brief get response timeout in seconds
83+
* @return response timeout
84+
*/
85+
double get_response_timeout();
86+
5587
/*! \brief get the modbus socket
5688
*
5789
* @return socket of the modbus connection

0 commit comments

Comments
 (0)