diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e5678b..ba4a1f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13.4 FATAL_ERROR) # ====================================================================================================================== # project -project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.2.2) +project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.2.3) # settings set(Target "modbus-tcp-client-shm") # Executable name (without file extension!) diff --git a/README.md b/README.md index 3eca6fc..af28dd0 100644 --- a/README.md +++ b/README.md @@ -16,27 +16,46 @@ As an alternative to the ```git submodule``` commands, the ```--recursive``` opt ## Use ``` -Modbus_TCP_client_shm [OPTION...] +modbus-tcp-client-shm [OPTION...] - -i, --ip arg ip to listen for incoming connections (default: 0.0.0.0) - -p, --port arg port to listen for incoming connections (default: 502) - -n, --name-prefix arg shared memory name prefix (default: modbus_) - --do-registers arg number of digital output registers (default: 65536) - --di-registers arg number of digital input registers (default: 65536) - --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 - -r, --reconnect do not terminate if Master disconnects. - -h, --help print usage + -i, --ip arg ip to listen for incoming connections (default: 0.0.0.0) + -p, --port arg port to listen for incoming connections (default: 502) + -n, --name-prefix arg shared memory name prefix (default: modbus_) + --do-registers arg number of digital output registers (default: 65536) + --di-registers arg number of digital input registers (default: 65536) + --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 + -r, --reconnect do not terminate if the Modbus Server disconnects. + --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. + -t, --tcp-timeout arg tcp timeout in seconds. Set to 0 to use the system defaults (not recommended). (default: + 5) + --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 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.) + -h, --help print usage + --version print version information + --license show licences The modbus registers are mapped to shared memory objects: - type | name | master-access | shm name - -----|---------------------------|-----------------|---------------- - DO | Discrete Output Coils | read-write | DO - DI | Discrete Input Coils | read-only | DI - AO | Discrete Output Registers | read-write | AO - AI | Discrete Input Registers | read-only | AI + type | name | mb-server-access | shm name + -----|---------------------------|------------------|---------------- + DO | Discrete Output Coils | read-write | DO + DI | Discrete Input Coils | read-only | DI + AO | Discrete Output Registers | read-write | AO + AI | Discrete Input Registers | read-only | AI ``` ### Use privileged ports diff --git a/docs/index.md b/docs/index.md index 5fe324b..0f94c1b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ One for each register type: - Discrete Input Registers (AI) All registers are initialized with 0 at the beginning. -The Modbus master reads and writes directly the values from these shared memories. +The Modbus server reads and writes directly the values from these shared memories. The actual functionality of the client is realized by applications that read data from or write data to the shared memory. @@ -21,14 +21,14 @@ The actual functionality of the client is realized by applications that read dat ## Use the Application The application can be started completely without command line arguments. In this case the client listens for connections on all IPs on port 502 (the default modbus port). -The application terminates if the master disconnects. +The application terminates if the Modbus server disconnects. The arguments ```--port``` and ```--ip``` can be used to specify port and ip to listen to. By using the command line argument ```--monitor``` all incoming and outgoing packets are printed on stdout. -This option should be used carefully, as it generates large amounts of output depending on the masters polling cycle and the number of used registers. +This option should be used carefully, as it generates large amounts of output depending on the Modbus servers polling cycle and the number of used registers. -The ```--reconnect``` option can be used to specify that the application is not terminated when the master disconnects, but waits for a new connection. +The ```--reconnect``` option can be used to specify that the application is not terminated when the Modbus Server disconnects, but waits for a new connection. The client creates four shared memories and names them ```modbus_DO```, ```modbus_DI```, ```modbus_AO``` and ```modbus_AI``` by default. The prefix modbus_ can be changed via the argument ```--name-prefix```. @@ -76,8 +76,14 @@ This option cannot be used with flatpaks. setcap 'cap_net_bind_service=+ep' /path/to/binary ``` +## Install -## Using the Flatpak package +### Using the Modbus Collection Flapak Package: Shared Memory Modbus (recommended) +[SHM-Modbus](https://nikolask-source.github.io/SHM_Modbus/) is a collection of the shared memory modbus tools. +It is available as flatpak and published on flathub as ```network.koesling.shm-modbs```. + + +### Using the Standalone Flatpak Package The flatpak package can be installed via the .flatpak file. This can be downloaded from the GitHub [projects release page](https://github.com/NikolasK-source/modbus_tcp_client_shm/releases): @@ -94,7 +100,7 @@ To enable calling with ```modbus-tcp-client-shm``` [this script](https://gist.gi In order to be executable everywhere, the path in which the script is placed must be in the ```PATH``` environment variable. -## Build from Source +### Build from Source The following packages are required for building the application: - cmake diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e897917..a3a9cf1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,14 +3,14 @@ target_sources(${Target} PRIVATE main.cpp) target_sources(${Target} PRIVATE modbus_shm.cpp) -target_sources(${Target} PRIVATE Modbus_TCP_Slave.cpp) +target_sources(${Target} PRIVATE Modbus_TCP_Client.cpp) target_sources(${Target} PRIVATE license.cpp) # ---------------------------------------- header files (*.jpp, *.h, ...) ---------------------------------------------- # ====================================================================================================================== target_sources(${Target} PRIVATE modbus_shm.hpp) -target_sources(${Target} PRIVATE Modbus_TCP_Slave.hpp) +target_sources(${Target} PRIVATE Modbus_TCP_Client.hpp) target_sources(${Target} PRIVATE license.hpp) diff --git a/src/Modbus_TCP_Slave.cpp b/src/Modbus_TCP_Client.cpp similarity index 93% rename from src/Modbus_TCP_Slave.cpp rename to src/Modbus_TCP_Client.cpp index 51ac6f8..76f1312 100644 --- a/src/Modbus_TCP_Slave.cpp +++ b/src/Modbus_TCP_Client.cpp @@ -3,7 +3,7 @@ * This program is free software. You can redistribute it and/or modify it under the terms of the MIT License. */ -#include "Modbus_TCP_Slave.hpp" +#include "Modbus_TCP_Client.hpp" #include #include @@ -23,7 +23,7 @@ namespace TCP { static constexpr int MAX_REGS = 0x10000; -Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mapping, std::size_t tcp_timeout) { +Client::Client(const std::string &ip, unsigned short port, modbus_mapping_t *mapping, std::size_t tcp_timeout) { // create modbus object modbus = modbus_new_tcp(ip.c_str(), static_cast(port)); if (modbus == nullptr) { @@ -62,7 +62,7 @@ Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t *mappi #endif } -Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t **mappings, std::size_t tcp_timeout) { +Client::Client(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(port)); if (modbus == nullptr) { @@ -98,7 +98,7 @@ Slave::Slave(const std::string &ip, unsigned short port, modbus_mapping_t **mapp #endif } -void Slave::listen() { +void Client::listen() { // create tcp socket socket = modbus_tcp_listen(modbus, 1); if (socket == -1) { @@ -116,7 +116,7 @@ void Slave::listen() { } #ifdef OS_LINUX -void Slave::set_tcp_timeout(std::size_t tcp_timeout) { +void Client::set_tcp_timeout(std::size_t tcp_timeout) { // set user timeout (~= timeout for tcp connection) unsigned user_timeout = static_cast(tcp_timeout) * 1000; int tmp = setsockopt(socket, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout, sizeof(tcp_timeout)); @@ -148,7 +148,7 @@ void Slave::set_tcp_timeout(std::size_t tcp_timeout) { #endif -Slave::~Slave() { +Client::~Client() { if (modbus != nullptr) { modbus_close(modbus); modbus_free(modbus); @@ -157,14 +157,14 @@ Slave::~Slave() { if (socket != -1) { close(socket); } } -void Slave::set_debug(bool debug) { +void Client::set_debug(bool debug) { if (modbus_set_debug(modbus, debug)) { const std::string error_msg = modbus_strerror(errno); throw std::runtime_error("failed to enable modbus debugging mode: " + error_msg); } } -std::string Slave::connect_client() { +std::string Client::connect_client() { int tmp = modbus_tcp_accept(modbus, &socket); if (tmp < 0) { const std::string error_msg = modbus_strerror(errno); @@ -189,7 +189,7 @@ std::string Slave::connect_client() { return sstr.str(); } -bool Slave::handle_request() { +bool Client::handle_request() { // receive modbus request uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; int rc = modbus_receive(modbus, query); @@ -232,7 +232,7 @@ static inline timeout_t double_to_timeout_t(double timeout) { return ret; } -void Slave::set_byte_timeout(double timeout) { +void Client::set_byte_timeout(double timeout) { const auto T = double_to_timeout_t(timeout); auto ret = modbus_set_byte_timeout(modbus, T.sec, T.usec); @@ -242,7 +242,7 @@ void Slave::set_byte_timeout(double timeout) { } } -void Slave::set_response_timeout(double timeout) { +void Client::set_response_timeout(double timeout) { const auto T = double_to_timeout_t(timeout); auto ret = modbus_set_response_timeout(modbus, T.sec, T.usec); @@ -252,7 +252,7 @@ void Slave::set_response_timeout(double timeout) { } } -double Slave::get_byte_timeout() { +double Client::get_byte_timeout() { timeout_t timeout {}; auto ret = modbus_get_byte_timeout(modbus, &timeout.sec, &timeout.usec); @@ -265,7 +265,7 @@ double Slave::get_byte_timeout() { return static_cast(timeout.sec) + (static_cast(timeout.usec) / (1000.0 * 1000.0)); } -double Slave::get_response_timeout() { +double Client::get_response_timeout() { timeout_t timeout {}; auto ret = modbus_get_response_timeout(modbus, &timeout.sec, &timeout.usec); diff --git a/src/Modbus_TCP_Slave.hpp b/src/Modbus_TCP_Client.hpp similarity index 79% rename from src/Modbus_TCP_Slave.hpp rename to src/Modbus_TCP_Client.hpp index 0d489c4..2dc51e3 100644 --- a/src/Modbus_TCP_Slave.hpp +++ b/src/Modbus_TCP_Client.hpp @@ -14,8 +14,8 @@ namespace TCP { constexpr std::size_t MAX_CLIENT_IDS = 256; -//! Modbus TCP slave -class Slave { +//! Modbus TCP client +class Client { private: modbus_t *modbus; //!< modbus object (see libmodbus library) modbus_mapping_t @@ -24,7 +24,7 @@ class Slave { int socket = -1; //!< socket of the modbus connection public: - /*! \brief create modbus slave (TCP server) + /*! \brief create modbus client (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) @@ -32,28 +32,28 @@ class Slave { * 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); + explicit Client(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 + * @brief create modbus client (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); + Client(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 + /*! \brief destroy the modbus client * */ - ~Slave(); + ~Client(); /*! \brief enable/disable debugging output * @@ -67,7 +67,7 @@ class Slave { */ std::string connect_client(); - /*! \brief wait for request from Master and generate reply + /*! \brief wait for request from Modbus Server and generate reply * * @return true: connection closed */ diff --git a/src/main.cpp b/src/main.cpp index b321ba6..9d9ecd4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,7 @@ # pragma GCC diagnostic pop #endif -#include "Modbus_TCP_Slave.hpp" +#include "Modbus_TCP_Client.hpp" #include "license.hpp" #include "modbus_shm.hpp" @@ -123,7 +123,7 @@ int main(int argc, char **argv) { ("m,monitor", "output all incoming and outgoing packets to stdout") ("r,reconnect", - "do not terminate if the Modbus master disconnects.") + "do not terminate if the Modbus Server disconnects.") ("byte-timeout", "timeout interval in seconds between two consecutive bytes of the same message. " "In most cases it is sufficient to set the response timeout. " @@ -179,12 +179,12 @@ int main(int argc, char **argv) { std::cout << options.help() << std::endl; std::cout << std::endl; std::cout << "The modbus registers are mapped to shared memory objects:" << std::endl; - std::cout << " type | name | master-access | shm name" << std::endl; - std::cout << " -----|---------------------------|-----------------|----------------" << std::endl; - std::cout << " DO | Discrete Output Coils | read-write | DO" << std::endl; - std::cout << " DI | Discrete Input Coils | read-only | DI" << std::endl; - std::cout << " AO | Discrete Output Registers | read-write | AO" << std::endl; - std::cout << " AI | Discrete Input Registers | read-only | AI" << std::endl; + std::cout << " type | name | mb-server-access | shm name" << std::endl; + std::cout << " -----|---------------------------|------------------|----------------" << std::endl; + std::cout << " DO | Discrete Output Coils | read-write | DO" << std::endl; + std::cout << " DI | Discrete Input Coils | read-only | DI" << std::endl; + std::cout << " AO | Discrete Output Registers | read-write | AO" << std::endl; + std::cout << " AI | Discrete Input Registers | read-only | AI" << std::endl; std::cout << std::endl; std::cout << "This application uses the following libraries:" << std::endl; std::cout << " - cxxopts by jarro2783 (https://github.com/jarro2783/cxxopts)" << std::endl; @@ -308,29 +308,29 @@ int main(int argc, char **argv) { } - // create slave - std::unique_ptr slave; + // create modbus client + std::unique_ptr client; try { - slave = std::make_unique(args["ip"].as(), - args["port"].as(), - mb_mappings.data(), + client = std::make_unique(args["ip"].as(), + args["port"].as(), + mb_mappings.data(), #ifdef OS_LINUX - args["tcp-timeout"].as()); + args["tcp-timeout"].as()); #else - 0); + 0); #endif - slave->set_debug(args.count("monitor")); + client->set_debug(args.count("monitor")); } catch (const std::runtime_error &e) { std::cerr << e.what() << std::endl; return EX_SOFTWARE; } - socket = slave->get_socket(); + socket = client->get_socket(); // set timeouts if required try { - if (args.count("response-timeout")) { slave->set_response_timeout(args["response-timeout"].as()); } + if (args.count("response-timeout")) { client->set_response_timeout(args["response-timeout"].as()); } - if (args.count("byte-timeout")) { slave->set_byte_timeout(args["byte-timeout"].as()); } + if (args.count("byte-timeout")) { client->set_byte_timeout(args["byte-timeout"].as()); } } catch (const std::runtime_error &e) { std::cerr << e.what() << std::endl; return EX_SOFTWARE; @@ -339,10 +339,10 @@ int main(int argc, char **argv) { // connection loop do { // connect client - std::cerr << "Waiting for Master to establish a connection..." << std::endl; - std::string client; + std::cerr << "Waiting for Modbus Server to establish a connection..." << std::endl; + std::string client_name; try { - client = slave->connect_client(); + client_name = client->connect_client(); } catch (const std::runtime_error &e) { if (!terminate) { std::cerr << e.what() << std::endl; @@ -351,13 +351,13 @@ int main(int argc, char **argv) { break; } - std::cerr << "Master (" << client << ") established connection." << std::endl; + std::cerr << "Modbus Server (" << client_name << ") established connection." << std::endl; // ========== MAIN LOOP ========== (handle requests) bool connection_closed = false; while (!terminate && !connection_closed) { try { - connection_closed = slave->handle_request(); + connection_closed = client->handle_request(); } catch (const std::runtime_error &e) { // clang-tidy (LLVM 12.0.1) warning "Condition is always true" is not correct if (!terminate) std::cerr << e.what() << std::endl; @@ -365,7 +365,7 @@ int main(int argc, char **argv) { } } - if (connection_closed) std::cerr << "Master closed connection." << std::endl; + if (connection_closed) std::cerr << "Modbus Server closed connection." << std::endl; } while (args.count("reconnect")); std::cerr << "Terminating..." << std::endl;