Skip to content

Commit

Permalink
Merge pull request #13 from NikolasK-source/main
Browse files Browse the repository at this point in the history
Release 1.2.0
  • Loading branch information
NikolasK-source authored Aug 19, 2022
2 parents ef7aa06 + 757bf0d commit 26a61f7
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 66 deletions.
1 change: 0 additions & 1 deletion .github/workflows/cmake.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ name: CMake

on:
push:
branches: [ "main", "development" ]
pull_request:
branches: [ "main" ]

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ name: "CodeQL"

on:
push:
branches: [ "main", "development" ]
branches: [ "main", "release" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "main" ]
Expand Down
6 changes: 2 additions & 4 deletions .github/workflows/flatpak_test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
on:
push:
branches: [ "main", "development" ]
pull_request:
[ push, pull_request ]
name: Flatpak_test
jobs:
flatpak:
Expand All @@ -27,4 +25,4 @@ jobs:
with:
bundle: test_modbus-tcp-client-shm.flatpak
manifest-path: network.koesling.test-modbus-tcp-client-shm.yml
cache-key: flatpak-builder-${{ github.sha }}
cache-key: flatpak-builder-${{ github.sha }}
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13.4 FATAL_ERROR)
# ======================================================================================================================

# project
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.1.1)
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.2.0)

# settings
set(Target "modbus-tcp-client-shm") # Executable name (without file extension!)
Expand Down
24 changes: 24 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,30 @@ cmake --build build
The binary is located in the build directory.


## Common Problems and Fixes

### Failed to create Shared Memory
It can happen that the client reports the following error on startup:
```
Failed to create shared memory ...: File exists
```
This can be caused by:
- Another modbus client is running that uses the shared memory with the given name.
If you want to run multiple instances simultaneously use the option ```--name-prefix``` to change the name of the shared memory.
- Any other application uses a shared memory with the given name (unlikely but possible)
- A previous instance of a modbus client crashed or was forcefully terminated and was not able to unlink the shared memory.
In this case, the option ```--force``` can be used to force the use of shared memory.
In the other cases this option should not be used.

### Connection frequently times out

If the connection frequently times out, it may be reasonable to increase the tcp timeout with the option ```--tcp-timeout```.
It is per default set to 5 seconds.

The two options ```--byte-timeout``` and ```--response-timeout``` change the timeout behavior of the modbus connection.
These should only be changed by experienced users.
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.

## Links to related projects

### General Shared Memory Tools
Expand Down
127 changes: 93 additions & 34 deletions src/Modbus_TCP_Slave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include <system_error>
#include <unistd.h>

#include <iostream>

namespace Modbus {
namespace TCP {

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

modbus_mapping_t *mb_mapping;

if (mapping == nullptr) {
// create new mapping with the maximum number of registers
this->mapping = modbus_mapping_new(MAX_REGS, MAX_REGS, MAX_REGS, MAX_REGS);
if (this->mapping == nullptr) {
mb_mapping = modbus_mapping_new(MAX_REGS, MAX_REGS, MAX_REGS, MAX_REGS);
if (mb_mapping == nullptr) {
const std::string error_msg = modbus_strerror(errno);
modbus_free(modbus);
throw std::runtime_error("failed to allocate memory: " + error_msg);
}
delete_mapping = true;
delete_mapping = mapping;
} else {
// use the provided mapping object
this->mapping = mapping;
delete_mapping = false;
mb_mapping = mapping;
delete_mapping = nullptr;
}

// use mapping for all client ids
for (std::size_t i = 0; i < MAX_CLIENT_IDS; ++i) {
this->mappings[i] = mapping;
}

listen();

#ifdef OS_LINUX
if (tcp_timeout) set_tcp_timeout(tcp_timeout);
#else
static_cast<void>(tcp_timeout);
#endif
}

Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t **mappings, std::size_t tcp_timeout) {
// create modbus object
modbus = modbus_new_tcp(ip.c_str(), static_cast<int>(port));
if (modbus == nullptr) {
const std::string error_msg = modbus_strerror(errno);
throw std::runtime_error("failed to create modbus instance: " + error_msg);
}

delete_mapping = nullptr;

for (std::size_t i = 0; i < MAX_CLIENT_IDS; ++i) {
if (mappings[i] == nullptr) {
if (delete_mapping == nullptr) {
delete_mapping = modbus_mapping_new(MAX_REGS, MAX_REGS, MAX_REGS, MAX_REGS);

if (delete_mapping == nullptr) {
const std::string error_msg = modbus_strerror(errno);
modbus_free(modbus);
throw std::runtime_error("failed to allocate memory: " + error_msg);
}
}
this->mappings[i] = delete_mapping;
} else {
this->mappings[i] = mappings[i];
}
}

listen();

#ifdef OS_LINUX
if (tcp_timeout) set_tcp_timeout(tcp_timeout);
#else
static_cast<void>(tcp_timeout);
#endif
}

void Slave::listen() {
// create tcp socket
socket = modbus_tcp_listen(modbus, 1);
if (socket == -1) {
Expand All @@ -58,48 +113,47 @@ Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mappi
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option SO_KEEPALIVE");
}
}

#ifdef OS_LINUX
if (tcp_timeout) {
// set user timeout (~= timeout for tcp connection)
unsigned user_timeout = static_cast<unsigned>(tcp_timeout) * 1000;
tmp = setsockopt(socket, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, sizeof(keepalive));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_USER_TIMEOUT");
}
void Slave::set_tcp_timeout(std::size_t tcp_timeout) {
// set user timeout (~= timeout for tcp connection)
unsigned user_timeout = static_cast<unsigned>(tcp_timeout) * 1000;
int tmp = setsockopt(socket, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, sizeof(tcp_timeout));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_USER_TIMEOUT");
}

// start sending keepalive request after one second without request
unsigned keepidle = 1;
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPIDLE");
}
// start sending keepalive request after one second without request
unsigned keepidle = 1;
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &keepidle, sizeof(keepidle));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPIDLE");
}

// send up to 5 keepalive requests during the timeout time, but not more than one per second
unsigned keepintvl = std::max(static_cast<unsigned>(tcp_timeout / 5), 1u);
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPINTVL");
}
// send up to 5 keepalive requests during the timeout time, but not more than one per second
unsigned keepintvl = std::max(static_cast<unsigned>(tcp_timeout / 5), 1u);
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &keepintvl, sizeof(keepintvl));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPINTVL");
}

// 5 keepalive requests if the timeout time is >= 5s; else send one request each second
unsigned keepcnt = std::min(static_cast<unsigned>(tcp_timeout), 5u);
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPCNT");
}
// 5 keepalive requests if the timeout time is >= 5s; else send one request each second
unsigned keepcnt = std::min(static_cast<unsigned>(tcp_timeout), 5u);
tmp = setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &keepcnt, sizeof(keepcnt));
if (tmp != 0) {
throw std::system_error(errno, std::generic_category(), "Failed to set socket option TCP_KEEPCNT");
}
#else
static_cast<void>(tcp_timeout);
#endif
}
#endif


Slave::~Slave() {
if (modbus != nullptr) {
modbus_close(modbus);
modbus_free(modbus);
}
if (mapping != nullptr && delete_mapping) modbus_mapping_free(mapping);
if (delete_mapping) modbus_mapping_free(delete_mapping);
if (socket != -1) { close(socket); }
}

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

if (rc > 0) {
const auto CLIENT_ID = query[6];

// get mapping
auto mapping = mappings[CLIENT_ID];

// handle request
int ret = modbus_reply(modbus, query, rc, mapping);
if (ret == -1) {
Expand Down
36 changes: 31 additions & 5 deletions src/Modbus_TCP_Slave.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,49 @@

#include <modbus/modbus.h>
#include <string>
#include <unordered_map>

namespace Modbus {
namespace TCP {

constexpr std::size_t MAX_CLIENT_IDS = 256;

//! Modbus TCP slave
class Slave {
private:
modbus_t *modbus; //!< modbus object (see libmodbus library)
modbus_mapping_t *mapping; //!< modbus data object (see libmodbus library)
bool delete_mapping; //!< indicates whether the mapping object was created by this instance
int socket = -1; //!< socket of the modbus connection
modbus_t *modbus; //!< modbus object (see libmodbus library)
modbus_mapping_t
*mappings[MAX_CLIENT_IDS]; //!< modbus data objects (one per possible client id) (see libmodbus library)
modbus_mapping_t *delete_mapping; //!< contains a pointer to a mapping that is to be deleted
int socket = -1; //!< socket of the modbus connection

public:
/*! \brief create modbus slave (TCP server)
*
* @param ip ip to listen for incoming connections (default 0.0.0.0 (any))
* @param port port to listen for incoming connections (default 502)
* @param mapping modbus mapping object (nullptr: an mapping object with maximum size is generated)
* @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)
*/
explicit Slave(const std::string &ip = "0.0.0.0",
short unsigned int port = 502,
modbus_mapping_t *mapping = nullptr,
std::size_t tcp_timeout = 5);

/**
* @brief create modbus slave (TCP server) with dedicated mappings per client id
*
* @param ip ip to listen for incoming connections
* @param port port to listen for incoming connections
* @param mappings modbus mappings (one for each possible id)
* @param tcp_timeout tcp timeout (currently only available on linux systems)
*/
Slave(const std::string &ip,
short unsigned int port,
modbus_mapping_t *mappings[MAX_CLIENT_IDS],
std::size_t tcp_timeout = 5);

/*! \brief destroy the modbus slave
*
*/
Expand Down Expand Up @@ -89,6 +108,13 @@ class Slave {
* @return socket of the modbus connection
*/
[[nodiscard]] int get_socket() const noexcept { return socket; }

private:
#ifdef OS_LINUX
void set_tcp_timeout(std::size_t tcp_timeout);
#endif

void listen();
};

} // namespace TCP
Expand Down
Loading

0 comments on commit 26a61f7

Please sign in to comment.