diff --git a/CMakeLists.txt b/CMakeLists.txt index c81ba20..8108664 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,16 +72,11 @@ set(CMAKE_THREAD_PREFER_PTHREAD TRUE) set(THREADS_PREFER_PTHREAD_FLAG TRUE) find_package(Threads REQUIRED) -set(LIBMODBUS_MIN_VERSION "3.1.6") +set(LIBMODBUS_MIN_VERSION "3.1.10") find_package(PkgConfig REQUIRED) -pkg_check_modules (LIBMODBUS libmodbusepsi>=${LIBMODBUS_MIN_VERSION}) -if (LIBMODBUS_FOUND) - set(LIBMODBUS_NAME modbusepsi) -else() - pkg_check_modules (LIBMODBUS REQUIRED libmodbus>=${LIBMODBUS_MIN_VERSION}) - set(LIBMODBUS_NAME modbus) -endif() +pkg_check_modules (LIBMODBUS REQUIRED libmodbusepsi>=${LIBMODBUS_MIN_VERSION}) +set(LIBMODBUS_NAME modbusepsi) set(LIBMODBUS_PACKAGE lib${LIBMODBUS_NAME}5) set(LIBMODBUSDEV_PACKAGE lib${LIBMODBUS_NAME}-dev) @@ -98,7 +93,7 @@ else() add_subdirectory(3rdparty/json) endif() -set (MODBUSPP_CFLAGS_OTHER ${CMAKE_THREAD_LIBS_INIT} ${LIBMODBUS_CFLAGS}) +set (MODBUSPP_CFLAGS_OTHER ${CMAKE_THREAD_LIBS_INIT}) set (MODBUSPP_LDFLAGS_OTHER ${LIBMODBUS_LDFLAGS} -lpthread) include (GetDate) @@ -108,8 +103,6 @@ include (GitVersion) GetGitVersion(MODBUSPP_VERSION) set(MODBUSPP_VERSION ${MODBUSPP_VERSION_MAJOR}.${MODBUSPP_VERSION_MINOR}.${MODBUSPP_VERSION_PATCH}) -configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in - ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) @@ -125,7 +118,10 @@ check_symbol_exists(TIOCSRS485 sys/ioctl.h MODBUSPP_HAVE_TIOCRS485) check_symbol_exists(TIOCM_RTS sys/ioctl.h MODBUSPP_HAVE_TIOCM_RTS) list(APPEND CMAKE_REQUIRED_LIBRARIES ${LIBMODBUS_NAME}) -check_symbol_exists(modbus_rtu_set_recv_filter ${LIBMODBUS_NAME}/modbus-rtu.h MODBUSPP_HAVE_RTU_MULTI_SLAVES) +check_symbol_exists(modbus_serial_set_recv_filter ${LIBMODBUS_NAME}/modbus-serial.h MODBUSPP_HAVE_SERIAL_MULTI_SLAVES) + +configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h @ONLY) # Make relative paths absolute (needed later on) foreach(p LIB BIN INCLUDE CMAKE DATA DOC CODELITE) @@ -307,7 +303,7 @@ if (CPACK_GENERATOR STREQUAL "DEB") set(CPACK_DEBIAN_DEV_PACKAGE_NAME "libmodbuspp-dev") set(CPACK_COMPONENT_DEV_DESCRIPTION "${CPACK_DEBIAN_LIB_PACKAGE_NAME} - ${PROJECT_DESCRIPTION} (development files)\n${PROJECT_DESCRIPTION_TEXT}\n This package provides the development files.") set(CPACK_DEBIAN_DEV_FILE_NAME "lib${PROJECT_NAME}-dev_${CPACK_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb") - set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "${CPACK_DEBIAN_LIB_PACKAGE_NAME} (= ${CPACK_PACKAGE_VERSION}),pkg-config,git-core,${LIBMODBUSDEV_PACKAGE} (>= ${LIBMODBUS_VERSION})") + set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "${CPACK_DEBIAN_LIB_PACKAGE_NAME} (= ${CPACK_PACKAGE_VERSION}),pkg-fconfig,git-core,${LIBMODBUSDEV_PACKAGE} (>= ${LIBMODBUS_VERSION})") set(CPACK_DEBIAN_DEV_PACKAGE_SECTION "libdevel") #set(CPACK_PACKAGE_DESCRIPTION_FILE "${MODBUSPP_SRC_DIR}/doc/README-deb.md") diff --git a/README.md b/README.md index b4fe13a..129ec72 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ _"Filter on the Modbus unit identifier (slave) in RTU mode to avoid useless CRC To benefit from the routing capacity of the Router and Server classes in RTU, you must therefore use the fork of libmodbus named **libmodbusepsi**. In this fork released from the [piduino](http://apt.piduino.org) -repository, filtering can be disabled (with _modbus_rtu_set_recv_filter()_). +repository, filtering can be disabled (with _modbus_serial_set_recv_filter()_). Thus, it is the Server class which performs this filtering (after checking the CRC therefore). Effectively, this has the effect of loading the microprocessor, but, at present, the computing power of our machines is such that it does not diff --git a/config.h.in b/config.h.in index 4ce219c..187b07a 100644 --- a/config.h.in +++ b/config.h.in @@ -19,6 +19,6 @@ #include "version.h" #cmakedefine01 MODBUSPP_HAVE_TIOCRS485 #cmakedefine01 MODBUSPP_HAVE_TIOCM_RTS -#cmakedefine01 MODBUSPP_HAVE_RTU_MULTI_SLAVES +#cmakedefine01 MODBUSPP_HAVE_SERIAL_MULTI_SLAVES /* ========================================================================== */ diff --git a/dev/CMakeLists.txt b/dev/CMakeLists.txt index 47ef5c9..9ddb21e 100644 --- a/dev/CMakeLists.txt +++ b/dev/CMakeLists.txt @@ -17,6 +17,9 @@ cmake_minimum_required(VERSION 2.8.11) +option(INSTALL_TEMPLATES "Install codelite project templates to /usr/share/codelite" OFF) + +if(INSTALL_TEMPLATES) # set packaging dir if(NOT CPACK_PACKAGE_DIRECTORY) set(CPACK_PACKAGE_DIRECTORY ${CMAKE_BINARY_DIR}/packages) @@ -37,3 +40,4 @@ install (DIRECTORY codelite/unit-test-modbuspp DIRECTORY_PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ COMPONENT dev ) +endif(INSTALL_TEMPLATES) diff --git a/dev/cmake/GitVersion.cmake b/dev/cmake/GitVersion.cmake index 9490997..a85d2ae 100644 --- a/dev/cmake/GitVersion.cmake +++ b/dev/cmake/GitVersion.cmake @@ -36,7 +36,9 @@ function(GetGitVersion _prefix) if(GIT_FOUND) execute_process(COMMAND ${GIT_EXECUTABLE} describe - RESULT_VARIABLE ret OUTPUT_VARIABLE str OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE ret + OUTPUT_VARIABLE str OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET) endif() diff --git a/include/modbuspp.h b/include/modbuspp.h index 85f2155..b17dbe4 100644 --- a/include/modbuspp.h +++ b/include/modbuspp.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include #include diff --git a/include/modbuspp/asciilayer.h b/include/modbuspp/asciilayer.h new file mode 100644 index 0000000..c5094d8 --- /dev/null +++ b/include/modbuspp/asciilayer.h @@ -0,0 +1,211 @@ +/* Copyright © 2018-2019 Pascal JEAN, All rights reserved. + * This file is part of the libmodbuspp Library. + * + * The libmodbuspp Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * The libmodbuspp Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the libmodbuspp Library; if not, see . + */ +#pragma once + +#include + +namespace Modbus { + + /** + * @class AsciiLayer + * @brief RTU serial link layer + * + * This class can not and should not be instantiated by the user. + * It provides access to properties and methods specific to the RTU layer. + * + * An instance of this class is created by the constructor @b Device::Device() + * of the @b Device class (or its derived classes) if the RTU layer is selected. + * + * Access to this instance is done using the Device::rtu() method. + * + * @sa Device::Device() + * @sa Device::rtu() + * + * @author Pascal JEAN, aka epsilonrt + * @copyright GNU Lesser General Public License + */ + class AsciiLayer : public NetLayer { + public: + + /** + * @brief Constructor + */ + AsciiLayer (const std::string & port, const std::string & settings); + + /** + * @brief Destructor + */ + virtual ~AsciiLayer() = default; + + /** + * @brief Name of the serial port + * + * This property specifies the name of the serial port handled by the + * OS, eg. "/dev/ttyS0" or "/dev/ttyUSB0". + */ + const std::string & port() const; + + /** + * @brief Return the baudrate + */ + int baud() const; + + /** + * @brief Return the parity + */ + char parity() const; + + /** + * @brief Return the bits of stop + */ + int stop() const; + + /** + * @brief Get the current serial mode + * + * This function shall return the serial mode currently used. + * + * - @b Rs232: the serial line is set for RS232 communication. RS-232 + * (Recommended Standard 232) is the traditional name for a series of + * standards for serial binary single-ended data and control signals + * connecting between a DTE (Data Terminal Equipment) and a DCE + * (Data Circuit-terminating Equipment). + * It is commonly used in computer serial ports + * - @b Rs485: the serial line is set for RS485 communication. EIA-485, + * also known as TIA/EIA-485 or RS-485, is a standard defining the electrical + * characteristics of drivers and receivers for use in balanced digital + * multipoint systems. This standard is widely used for communications in + * industrial automation because it can be used effectively over long + * distances and in electrically noisy environments. + * . + * This function is only available on Linux kernels 2.6.28 onwards. + * + * @return return the current mode if successful. + * Otherwise it shall return @b UnknownMode (-1) and set errno. + * @sa setSerialMode() + */ + SerialMode serialMode(); + + /** + * @brief Set the serial mode + * + * This function shall set the selected serial mode @b mode. + * + * @return true if successful. + * Otherwise it shall return false and set errno. + * @sa serialMode() + */ + bool setSerialMode (SerialMode mode); + + /** + * @brief Get the current RTS mode + * + * This function shall get the current Request To Send mode + * + * @return the current RTS mode if successful. + * Otherwise it shall return @b UnknownRts (-1) and set errno. + * @sa setRts() + */ + SerialRts rts(); + + /** + * @brief Set the RTS mode + * + * This function shall set the Request To Send mode to communicate on a + * RS485 serial bus. + * + * @return true if successful. + * Otherwise it shall return false and set errno. + * @sa rts() + */ + bool setRts (SerialRts rts); + + /** + * @brief Get the current RTS delay + * + * This function shall get the current Request To Send delay period. + * @return the current RTS delay in microseconds if successful. + * Otherwise it shall return -1 and set errno. + * @sa setRtsDelay() + */ + int rtsDelay(); + + /** + * @brief Set the RTS delay + * + * This function shall set the Request To Send delay period in microseconds. + * + * @return true if successful. + * Otherwise it shall return false and set errno. + * @sa rtsDelay() + */ + bool setRtsDelay (int us); + + /** + * @overload + * + * @warning This function is not supported by Windows ! + */ + virtual int sendRawMessage (const Message * msg); + + /** + * @overload + */ + virtual bool prepareToSend (Message & msg); + + /** + * @overload + */ + static bool checkMessage (const Message & msg); + + /** + * @brief Extracts the baudrate from a settings string. + * @return the baudrate found. if no value is found, returns the default + * value, ie 19200. + */ + static int baud (const std::string & settings); + + /** + * @brief Extracts the parity from a settings string. + * @return the parity found. if no value is found, returns the default + * value, ie E for Even parity. + */ + static char parity (const std::string & settings); + + /** + * @brief Return the stop bits from a settings string. + * + * @return the number returned is determined based on the parity found. + * If the parity is None, this function returns 2, otherwise returns 1. + */ + static int stop (const std::string & settings); + + /** + * @brief Performing Modbus CRC16 generation of the buffer @b buf + */ + static uint8_t lrc8 (const uint8_t * buffer, uint16_t buffer_length); + + protected: + class Private; + AsciiLayer (std::unique_ptr &&dd); + + private: + PIMP_DECLARE_PRIVATE (AsciiLayer) + }; +} + +/* ========================================================================== */ diff --git a/include/modbuspp/data.h b/include/modbuspp/data.h index 49aca7c..365a3d9 100644 --- a/include/modbuspp/data.h +++ b/include/modbuspp/data.h @@ -16,6 +16,7 @@ */ #pragma once +#include #include // printf #include // memcpy ... #include @@ -219,6 +220,19 @@ namespace Modbus { print ( (const uint8_t *) m_registers.data(), size()); } + // update data value from MODBUS registers + // to call after reading the modbus registers + void updateValue() { + T v; + + for (auto & r : m_registers) { + r = hton (r); + } + std::memcpy (&v, m_registers.data(), sizeof (T)); + swap (v); + m_value = ntoh (v); + } + friend class Slave; friend class BufferedSlave; friend class Message; @@ -241,18 +255,6 @@ namespace Modbus { } } - // update data value from MODBUS registers - // to call after reading the modbus registers - void updateValue() { - T v; - - for (auto & r : m_registers) { - r = hton (r); - } - std::memcpy (&v, m_registers.data(), sizeof (T)); - swap (v); - m_value = ntoh (v); - } #endif /* __DOXYGEN__ not defined */ private: diff --git a/include/modbuspp/device.h b/include/modbuspp/device.h index 185e0da..6379d60 100644 --- a/include/modbuspp/device.h +++ b/include/modbuspp/device.h @@ -24,6 +24,7 @@ namespace Modbus { class NetLayer; class RtuLayer; + class AsciiLayer; class TcpLayer; class Message; @@ -477,6 +478,14 @@ namespace Modbus { */ RtuLayer & rtu(); + /** + * @brief underlying RTU layer (backend) + * + * This function shall return the RTU layer if it is the layer used by + * the device. If it does not, a @b std::domain_error exception is thrown. + */ + AsciiLayer & ascii(); + /** * @brief underlying TCP layer (backend) * @@ -546,8 +555,8 @@ namespace Modbus { protected: class Private; - Device (Private &dd); - std::unique_ptr d_ptr; + Device (std::unique_ptr &&dd); + std::unique_ptr d_ptr; private: PIMP_DECLARE_PRIVATE (Device) diff --git a/include/modbuspp/global.h b/include/modbuspp/global.h index 3a538d4..db69cf6 100644 --- a/include/modbuspp/global.h +++ b/include/modbuspp/global.h @@ -48,6 +48,14 @@ namespace Modbus { * protocol communication. */ Rtu, + /** + * @brief ASCII backend + * + * The ASCII backend is used in serial communication. + * The hex value of the binary data is transmitted + * in ASCII character representation. + */ + Ascii, /** * @brief TCP backend * @@ -105,9 +113,9 @@ namespace Modbus { * The @b RtsDown mode applies the same procedure but with an inverted RTS flag. */ enum SerialRts { - RtsNone = MODBUS_RTU_RTS_NONE, ///< no use of the RTS. - RtsUp = MODBUS_RTU_RTS_UP, ///< RTS flag ON during communication, OFF outside. - RtsDown = MODBUS_RTU_RTS_DOWN, ///< RTS flag OFF during communication, ON outside. + RtsNone = MODBUS_SERIAL_RTS_NONE, ///< no use of the RTS. + RtsUp = MODBUS_SERIAL_RTS_UP, ///< RTS flag ON during communication, OFF outside. + RtsDown = MODBUS_SERIAL_RTS_DOWN, ///< RTS flag OFF during communication, ON outside. UnknownRts = Unknown ///< Unknown RTS mode. }; diff --git a/include/modbuspp/master.h b/include/modbuspp/master.h index 0699ee0..f8053e3 100644 --- a/include/modbuspp/master.h +++ b/include/modbuspp/master.h @@ -259,7 +259,7 @@ namespace Modbus { protected: class Private; - Master (Private &dd); + Master (std::unique_ptr &&dd); private: PIMP_DECLARE_PRIVATE (Master) diff --git a/include/modbuspp/message.h b/include/modbuspp/message.h index cb72260..6936910 100644 --- a/include/modbuspp/message.h +++ b/include/modbuspp/message.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -46,7 +47,7 @@ namespace Modbus { * @return 1 if the message has been completely processed, 0 if the * message has not been processed, -1 if error. */ - typedef int (*Callback) (Message * msg, Device * sender); + using Callback = std::function; /** * @brief Constructors @@ -219,6 +220,14 @@ namespace Modbus { */ uint16_t crc () const; + /** + * @brief Return the LRC read in the message + * + * throw an std::domain_error exception if net()!=Rtu, + * an std::invalid_argument exception if aduSize()<8. + */ + uint8_t lrc () const; + /** * @brief Returns TCP/IP transaction identifier * diff --git a/include/modbuspp/netlayer.h b/include/modbuspp/netlayer.h index aaf622f..f568b3f 100644 --- a/include/modbuspp/netlayer.h +++ b/include/modbuspp/netlayer.h @@ -118,8 +118,8 @@ namespace Modbus { protected: class Private; - NetLayer (Private &dd); - std::unique_ptr d_ptr; + NetLayer (std::unique_ptr &&dd); + std::unique_ptr d_ptr; private: PIMP_DECLARE_PRIVATE (NetLayer) diff --git a/include/modbuspp/router.h b/include/modbuspp/router.h index f76ce7a..38ff60d 100644 --- a/include/modbuspp/router.h +++ b/include/modbuspp/router.h @@ -276,7 +276,7 @@ namespace Modbus { protected: class Private; - Router (Private &dd); + Router (std::unique_ptr &&dd); private: PIMP_DECLARE_PRIVATE (Router) diff --git a/include/modbuspp/rtulayer.h b/include/modbuspp/rtulayer.h index eac2b5f..2f91945 100644 --- a/include/modbuspp/rtulayer.h +++ b/include/modbuspp/rtulayer.h @@ -169,23 +169,22 @@ namespace Modbus { /** * @brief Extracts the baudrate from a settings string. - * @return the baudrate found. if no value is found, returns the default - * value, ie 19200. + * @return the baudrate found. If no valid value is found, an exception is thrown. */ static int baud (const std::string & settings); /** * @brief Extracts the parity from a settings string. - * @return the parity found. if no value is found, returns the default - * value, ie E for Even parity. + * @return the parity found. If no valid value is found, an exception is thrown. */ static char parity (const std::string & settings); /** * @brief Return the stop bits from a settings string. * - * @return the number returned is determined based on the parity found. - * If the parity is None, this function returns 2, otherwise returns 1. + * @return the number of stop bits. + * It is parsed from the last character of the settings string. + * If the last character is neither '1' or '2' an exception is thrown. */ static int stop (const std::string & settings); @@ -196,7 +195,7 @@ namespace Modbus { protected: class Private; - RtuLayer (Private &dd); + RtuLayer (std::unique_ptr &&dd); private: PIMP_DECLARE_PRIVATE (RtuLayer) diff --git a/include/modbuspp/server.h b/include/modbuspp/server.h index d935c85..2abdefc 100644 --- a/include/modbuspp/server.h +++ b/include/modbuspp/server.h @@ -18,6 +18,7 @@ #include #include +#include namespace Modbus { @@ -167,10 +168,13 @@ namespace Modbus { */ virtual ~Server(); + + virtual std::vector fds(); + /** * @overload */ - virtual void close(); + virtual void close() override; /** * @brief Performs all server operations @@ -307,7 +311,7 @@ namespace Modbus { protected: class Private; - Server (Private &dd); + Server (std::unique_ptr &&dd); private: PIMP_DECLARE_PRIVATE (Server) diff --git a/include/modbuspp/slave.h b/include/modbuspp/slave.h index 4774774..938cd1e 100644 --- a/include/modbuspp/slave.h +++ b/include/modbuspp/slave.h @@ -54,7 +54,7 @@ namespace Modbus { * The destructor closes the connection if it is open and releases all * affected resources. */ - virtual ~Slave(); + virtual ~Slave() = default; /** * @brief Get slave number diff --git a/include/modbuspp/tcplayer.h b/include/modbuspp/tcplayer.h index 9f1ba03..4e4c586 100644 --- a/include/modbuspp/tcplayer.h +++ b/include/modbuspp/tcplayer.h @@ -78,7 +78,7 @@ namespace Modbus { protected: class Private; - TcpLayer (Private &dd); + TcpLayer (std::unique_ptr &&dd); private: PIMP_DECLARE_PRIVATE (TcpLayer) diff --git a/libmodbuspp.project b/libmodbuspp.project index ce321fc..e2f02ee 100644 --- a/libmodbuspp.project +++ b/libmodbuspp.project @@ -107,6 +107,8 @@ + + @@ -157,6 +159,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + None + + + + + + + + + + + + + + @@ -197,6 +239,4 @@ - - diff --git a/modbuspp.pc.in b/modbuspp.pc.in index 8e86d4a..3e4014e 100644 --- a/modbuspp.pc.in +++ b/modbuspp.pc.in @@ -12,5 +12,5 @@ URL: https://github.com/epsilonrt/libmodbuspp Version: @MODBUSPP_VERSION@ Requires: Libs: -L${libdir} -lmodbuspp ${ldflags_other} -Cflags: -I${includedir} ${cflags_other} +Cflags: -I${includedir} -I${includedir}/@LIBMODBUS_NAME@ ${cflags_other} diff --git a/modbuspp.workspace b/modbuspp.workspace index 92bc5b1..706a099 100644 --- a/modbuspp.workspace +++ b/modbuspp.workspace @@ -41,19 +41,43 @@ - + - + + + + + + + + + + + - + - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/src/asciilayer.cpp b/src/asciilayer.cpp new file mode 100644 index 0000000..b35abec --- /dev/null +++ b/src/asciilayer.cpp @@ -0,0 +1,282 @@ +/* Copyright © 2018-2019 Pascal JEAN, All rights reserved. + * This file is part of the libmodbuspp Library. + * + * The libmodbuspp Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * The libmodbuspp Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the libmodbuspp Library; if not, see . + */ +#include +#include +#include "asciilayer_p.h" +#include "config.h" +#include "modbuspp/global.h" + +#ifndef _WIN32 +#include +#endif + +namespace Modbus { + // --------------------------------------------------------------------------- + // + // AsciiLayer Class + // + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + AsciiLayer::AsciiLayer (std::unique_ptr &&dd) : NetLayer (std::move(dd)) {} + + // --------------------------------------------------------------------------- + AsciiLayer::AsciiLayer (const std::string & port, const std::string & settings) : + NetLayer (std::unique_ptr(new AsciiLayer::Private (port, settings))) {} + + // --------------------------------------------------------------------------- + SerialMode AsciiLayer::serialMode() { + PIMP_D (AsciiLayer); + + int m = modbus_serial_get_serial_mode (d->ctx.get()); + if (m != -1) { + return static_cast (m); + } + return UnknownMode; + } + + // --------------------------------------------------------------------------- + bool AsciiLayer::setSerialMode (SerialMode mode) { + PIMP_D (AsciiLayer); + + return (modbus_serial_set_serial_mode (d->ctx.get(), static_cast (mode)) != -1); + } + + // --------------------------------------------------------------------------- + SerialRts AsciiLayer::rts() { + PIMP_D (AsciiLayer); + + int r = modbus_serial_get_rts (d->ctx.get()); + if (r != -1) { + + return static_cast (r); + } + return UnknownRts; + } + + // --------------------------------------------------------------------------- + bool AsciiLayer::setRts (SerialRts r) { + PIMP_D (AsciiLayer); + + return (modbus_serial_set_rts (d->ctx.get(), static_cast (r)) != -1); + } + + // --------------------------------------------------------------------------- + int AsciiLayer::rtsDelay() { + PIMP_D (AsciiLayer); + + return modbus_serial_get_rts_delay (d->ctx.get()); + } + + // --------------------------------------------------------------------------- + bool AsciiLayer::setRtsDelay (int delay) { + PIMP_D (AsciiLayer); + + return (modbus_serial_set_rts_delay (d->ctx.get(), delay) != -1); + } + + // --------------------------------------------------------------------------- + const std::string & AsciiLayer::port() const { + + return connection(); + } + + // --------------------------------------------------------------------------- + int AsciiLayer::baud() const { + PIMP_D (const AsciiLayer); + + return baud (d->settings); + } + + // --------------------------------------------------------------------------- + char AsciiLayer::parity() const { + PIMP_D (const AsciiLayer); + + return parity (d->settings); + } + + // --------------------------------------------------------------------------- + int AsciiLayer::stop() const { + PIMP_D (const AsciiLayer); + + return stop (d->settings); + } + + // --------------------------------------------------------------------------- + int AsciiLayer::sendRawMessage (const Message * msg) { + PIMP_D (const AsciiLayer); +#if defined(_WIN32) + errno = ENOTSUP; + return -1; +#else +#if MODBUSPP_HAVE_TIOCM_RTS + if (rts() != RtsNone) { + ssize_t size; + SerialRts r = (rts() == RtsDown) ? RtsUp : RtsDown; // complement the Rts state + + setRts (r); + usleep (rtsDelay()); + + size = write (modbus_get_socket (d->ctx.get()), msg->adu(), msg->aduSize()); + + usleep (d->oneByteTime * msg->aduSize() + rtsDelay()); + setRts ( (r == RtsDown) ? RtsUp : RtsDown); // restore initial state + + return size; + } + else { +#endif + return write (modbus_get_socket (d->ctx.get()), msg->adu(), msg->aduSize()); +#if MODBUSPP_HAVE_TIOCM_RTS + } +#endif +#endif + } + +static char nibble_to_hex_ascii(uint8_t nibble) +{ + char c; + + if (nibble < 10) { + c = nibble + '0'; + } else { + c = nibble - 10 + 'A'; + } + return c; +} + + // --------------------------------------------------------------------------- + bool AsciiLayer::prepareToSend (Message & msg) { + + if (msg.net() == Ascii && msg.size() >= 1) { + size_t aduSize = msg.aduSize(); + uint8_t * adu = msg.adu(); + + /* Skip colon */ + uint8_t lrc = lrc8(adu + 1, aduSize- 1); + adu[aduSize++] = lrc; + + uint8_t ascii_adu[MODBUS_ASCII_MAX_ADU_LENGTH]; + memset(ascii_adu, 0, MODBUS_ASCII_MAX_ADU_LENGTH); + ssize_t i, j = 0; + + for (i = 0; i < aduSize; i++) { + if ((i == 0 && adu[i] == ':') || + (i == aduSize - 2 && adu[i] == '\r') || + (i == aduSize - 1 && adu[i] == '\n')) { + ascii_adu[j++] = adu[i]; + } else { + ascii_adu[j++] = nibble_to_hex_ascii(adu[i] >> 4); + ascii_adu[j++] = nibble_to_hex_ascii(adu[i] & 0x0f); + } + } + ascii_adu[j++] = '\r'; + ascii_adu[j++] = '\n'; + ascii_adu[j] = '\0'; + + msg.setAduSize (j); + + std::memcpy(adu, ascii_adu, j); + + return true; + } + return false; + } + + // --------------------------------------------------------------------------- + bool AsciiLayer::checkMessage (const Message & msg) { + + return lrc8 (msg.adu(), msg.aduSize() - 2) == msg.lrc (); + } + + // --------------------------------------------------------------------------- + // static + int AsciiLayer::baud (const std::string & settings) { + int b; + try { + b = std::stoi (settings); + } + catch (...) { + b = 19200; + } + return b; + } + + // --------------------------------------------------------------------------- + // static + char AsciiLayer::parity (const std::string & settings) { + char p = 'N'; + size_t s = settings.length(); + + if (s >= 2) { + char c = settings[s - 2]; + if ( (c == 'E') || (c == 'O')) { + return c; + } + } + return p; + } + + // --------------------------------------------------------------------------- + // static + int AsciiLayer::stop (const std::string & settings) { + + if (parity (settings) == 'N') { + + return 2; + } + return 1; + } + + // --------------------------------------------------------------------------- + // static + uint8_t AsciiLayer::lrc8(const uint8_t *buffer, uint16_t buffer_length) + { + uint8_t lrc = 0; + while (buffer_length--) { + lrc += *buffer++; + } + /* Return two's complementing of the result */ + return -lrc; + } + + // --------------------------------------------------------------------------- + // + // AsciiLayer::Private Class + // + // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- + AsciiLayer::Private::Private (const std::string & port, const std::string & settings) : + NetLayer::Private (Ascii, port, settings, MODBUS_ASCII_MAX_ADU_LENGTH) { + + // RTU MUST BE 8-bits + ctx.reset( modbus_new_ascii (port.c_str(), AsciiLayer::baud (settings), + AsciiLayer::parity (settings), 8, + AsciiLayer::stop (settings)) ); + + if (! ctx) { + + throw std::invalid_argument ( + "Unable to create ASCII Modbus Backend(" + + port + "," + settings + ")\n" + lastError()); + } + oneByteTime = modbus_serial_get_rts_delay (ctx.get()); + } +} + +/* ========================================================================== */ diff --git a/src/asciilayer_p.h b/src/asciilayer_p.h new file mode 100644 index 0000000..aa73813 --- /dev/null +++ b/src/asciilayer_p.h @@ -0,0 +1,34 @@ +/* Copyright © 2018-2019 Pascal JEAN, All rights reserved. + * This file is part of the libmodbuspp Library. + * + * The libmodbuspp Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * The libmodbuspp Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the libmodbuspp Library; if not, see . + */ +#pragma once + +#include +#include "netlayer_p.h" + +namespace Modbus { + + class AsciiLayer::Private : public NetLayer::Private { + + public: + Private (const std::string & port, const std::string & settings); + virtual ~Private() = default; + + int oneByteTime; + }; +} + +/* ========================================================================== */ diff --git a/src/device.cpp b/src/device.cpp index 1d0df1d..911bf68 100644 --- a/src/device.cpp +++ b/src/device.cpp @@ -15,10 +15,12 @@ * along with the libmodbuspp Library; if not, see . */ #include +#include #include #include #include "device_p.h" #include "config.h" +#include "modbuspp/global.h" #include #include // for debug purposes @@ -37,7 +39,7 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - Device::Device (Device::Private &dd) : d_ptr (&dd) {} + Device::Device (std::unique_ptr &&dd) : d_ptr (std::move(dd)) {} // --------------------------------------------------------------------------- Device::Device () : d_ptr (new Private (this)) {} @@ -196,18 +198,29 @@ namespace Modbus { if (net() == Rtu) { PIMP_D (Device); - return * reinterpret_cast (d->backend); + return * reinterpret_cast (d->backend.get()); } throw std::domain_error ("Unable to return RTU layer !"); } + // --------------------------------------------------------------------------- + AsciiLayer & Device::ascii() { + + if (net() == Ascii) { + PIMP_D (Device); + + return * reinterpret_cast (d->backend.get()); + } + throw std::domain_error ("Unable to return ASCII layer !"); + } + // --------------------------------------------------------------------------- TcpLayer & Device::tcp() { if (net() == Tcp) { PIMP_D (Device); - return * reinterpret_cast (d->backend); + return * reinterpret_cast (d->backend.get()); } throw std::domain_error ("Unable to return TCP layer !"); } @@ -406,7 +419,7 @@ namespace Modbus { } while (d->recoveryLink && rc == -1 && !msg->isResponse()); - if (rc > 0 && rc != msg->size()) { + if (rc > 0 && static_cast(rc) != msg->aduSize()) { errno = EMBBADDATA; return -1; @@ -438,15 +451,9 @@ namespace Modbus { // --------------------------------------------------------------------------- Device::Private::Private (Device * q) : - q_ptr (q), isOpen (false), backend (0), recoveryLink (false), + q_ptr (q), isOpen (false), backend (nullptr), recoveryLink (false), debug (false) {} - // --------------------------------------------------------------------------- - Device::Private::~Private() { - - delete backend; - } - // --------------------------------------------------------------------------- void Device::Private::setConfigFromFile (const std::string & jsonfile, const std::string & key) { @@ -500,13 +507,17 @@ namespace Modbus { switch (net) { case Tcp: - backend = new TcpLayer (connection, settings); + backend = std::unique_ptr{new TcpLayer (connection, settings)}; break; case Rtu: - backend = new RtuLayer (connection, settings); + backend = std::unique_ptr{new RtuLayer (connection, settings)}; break; + case Ascii: + backend = std::unique_ptr{new AsciiLayer (connection, settings)}; + break; + default: throw std::invalid_argument ( "Unable to create Modbus Device for this net !"); @@ -538,9 +549,9 @@ namespace Modbus { // --------------------------------------------------------------------------- int Device::Private::defaultSlave (int addr) const { - if (addr < 0 && backend != 0) { - - return backend->net() == Rtu ? Broadcast : TcpSlave; + if (addr < 0 && backend != nullptr) { + Net n = backend->net(); + return (n == Rtu || n == Ascii) ? Broadcast : TcpSlave; } return addr; } @@ -619,6 +630,22 @@ namespace Modbus { dev->rtu().setRtsDelay (r); } } + if (config.contains ("ascii") && net == Ascii) { + auto ascii = config["ascii"]; + + if (ascii.contains ("mode")) { + auto m = ascii["mode"].get(); + dev->ascii().setSerialMode (m); + } + if (ascii.contains ("rts")) { + auto r = ascii["rts"].get(); + dev->ascii().setRts (r); + } + if (ascii.contains ("rts-delay")) { + auto r = ascii["rts-delay"].get(); + dev->ascii().setRtsDelay (r); + } + } } } } diff --git a/src/device_p.h b/src/device_p.h index bf090fb..bf58f86 100644 --- a/src/device_p.h +++ b/src/device_p.h @@ -28,7 +28,7 @@ namespace Modbus { public: Private (Device * q); - virtual ~Private(); + virtual ~Private() = default; virtual void setBackend (Net net, const std::string & connection, const std::string & settings); virtual void setConfig (const nlohmann::json & config); @@ -39,18 +39,26 @@ namespace Modbus { virtual bool open(); virtual void close(); inline modbus_t * ctx() { - return backend->context(); + if ( backend != nullptr ) { + return backend->context(); + } else { + return nullptr; + } } inline modbus_t * ctx() const { - return backend->context(); + if ( backend != nullptr ) { + return backend->context(); + } else { + return nullptr; + } } int defaultSlave (int addr) const; bool isConnected () const; void printError (const char * what = nullptr) const; - Device * const q_ptr; + Device * const q_ptr = nullptr; bool isOpen; - NetLayer * backend; + std::unique_ptr backend = nullptr; bool recoveryLink; bool debug; diff --git a/src/json_p.h b/src/json_p.h index c77e0b7..e1c2fd1 100644 --- a/src/json_p.h +++ b/src/json_p.h @@ -36,6 +36,7 @@ namespace Modbus { NLOHMANN_JSON_SERIALIZE_ENUM (Net, { {Rtu, "rtu"}, + {Ascii, "ascii"}, {Tcp, "tcp"}, {NoNet, nullptr}, }) diff --git a/src/master.cpp b/src/master.cpp index f7dc3c0..b9cd862 100644 --- a/src/master.cpp +++ b/src/master.cpp @@ -28,10 +28,10 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - Master::Master (Master::Private &dd) : Device (dd) {} + Master::Master (std::unique_ptr &&dd) : Device (std::move(dd)) {} // --------------------------------------------------------------------------- - Master::Master () : Device (*new Private (this)) {} + Master::Master () : Device (std::unique_ptr(new Private (this))) {} // --------------------------------------------------------------------------- Master::Master (Net net, const std::string & connection, @@ -185,9 +185,6 @@ namespace Modbus { // --------------------------------------------------------------------------- Master::Private::Private (Master * q) : Device::Private (q) {} - // --------------------------------------------------------------------------- - Master::Private::~Private() = default; - // --------------------------------------------------------------------------- // virtual void Master::Private::setBackend (Net net, const std::string & connection, @@ -201,6 +198,7 @@ namespace Modbus { break; case Rtu: + case Ascii: (void) addSlave (Broadcast); break; } diff --git a/src/master_p.h b/src/master_p.h index 1452222..c6aa5d5 100644 --- a/src/master_p.h +++ b/src/master_p.h @@ -26,12 +26,13 @@ namespace Modbus { public: Private (Master * q); - virtual ~Private(); - virtual void setBackend (Net net, const std::string & connection, - const std::string & settings); - virtual void setConfig (const nlohmann::json & config); + virtual ~Private() = default; - virtual Slave * addSlave (int slaveAddr); + void setBackend (Net net, const std::string & connection, + const std::string & settings) override; + void setConfig (const nlohmann::json & config) override; + + Slave * addSlave (int slaveAddr); std::map > slave; PIMP_DECLARE_PUBLIC (Master) diff --git a/src/message.cpp b/src/message.cpp index a59ed61..2a2bd32 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "message_p.h" #include "config.h" @@ -260,6 +261,13 @@ namespace Modbus { } break; + case Ascii: { + uint8_t lrc = AsciiLayer::lrc8 (adu(), aduSize()); + + setByte(size(), lrc); + } + break; + default: throw std::invalid_argument ( "Unable to set PDU for this net !"); @@ -338,6 +346,32 @@ namespace Modbus { return word (size() - 2); } + // --------------------------------------------------------------------------- + uint8_t Message::lrc () const { + std::cout << "message::lrc\n"; + if (net() != Ascii) { + + throw std::domain_error ("Unable to return LRC if backend is not ASCII !"); + } + if (aduSize() >= 4) { + + throw std::invalid_argument ("Unable to return LRC if ADU size less than 8 !"); + } + uint16_t lrc = word (size() - 2); + uint8_t lrc_hi = lrc >> 8 & 0xFF; + uint8_t lrc_lo = lrc & 0xFF; + lrc_hi -= '0'; + lrc_lo -= '0'; + + std::cout << "lrc_hi: " << lrc_hi << "\n"; + std::cout << "lrc_lo: " << lrc_lo << "\n"; + + uint8_t lrc_8_bit = lrc_hi << 4; + lrc_8_bit |= lrc_lo; + + return lrc_8_bit; + } + // --------------------------------------------------------------------------- void Message::setAduSize (size_t size) { PIMP_D (Message); @@ -478,18 +512,22 @@ namespace Modbus { // --------------------------------------------------------------------------- Message::Private::Private (Message * q, Net n) : - q_ptr (q), net (n), aduSize (0), isResponse (false), backend (0), + q_ptr (q), net (n), aduSize (0), isResponse (false), backend (nullptr), transactionId (1) { - NetLayer * b; + std::unique_ptr b = nullptr; switch (net) { case Tcp: - b = new TcpLayer ("*", "1502"); + b = std::unique_ptr (new TcpLayer{"*", "1502"} ); break; case Rtu: - b = new RtuLayer ("COM1", "9600E1"); + b = std::unique_ptr (new RtuLayer{"*", "1502"} ); + break; + + case Ascii: + b = std::unique_ptr (new AsciiLayer{"*", "1502"} ); break; default: @@ -500,7 +538,6 @@ namespace Modbus { pduBegin = modbus_get_header_length (b->context()); maxAduLength = b->maxAduLength(); - delete b; adu.resize (maxAduLength, 0); if (net == Tcp) { adu[6] = MODBUS_TCP_SLAVE; @@ -521,9 +558,6 @@ namespace Modbus { adu[pduBegin] = static_cast (func); } - - // --------------------------------------------------------------------------- - Message::Private::~Private() = default; } /* ========================================================================== */ diff --git a/src/message_p.h b/src/message_p.h index ca41bf3..0fe14a7 100644 --- a/src/message_p.h +++ b/src/message_p.h @@ -28,15 +28,15 @@ namespace Modbus { Private (Message * q, Net n); Private (Message * q, Net n, const std::vector & m); Private (Message * q, Net n, Function f); - virtual ~Private(); + virtual ~Private() = default; - Message * const q_ptr; + Message * const q_ptr = nullptr; Net net; int pduBegin; size_t aduSize; uint16_t maxAduLength; bool isResponse; - NetLayer * backend; + NetLayer * backend = nullptr; uint16_t transactionId; std::vector adu; }; diff --git a/src/netlayer.cpp b/src/netlayer.cpp index aa2e87e..f21a7e5 100644 --- a/src/netlayer.cpp +++ b/src/netlayer.cpp @@ -26,7 +26,7 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - NetLayer::NetLayer (NetLayer::Private &dd) : d_ptr (&dd) {} + NetLayer::NetLayer (std::unique_ptr &&dd) : d_ptr (std::move(dd)) {} // --------------------------------------------------------------------------- NetLayer::NetLayer () : @@ -53,14 +53,14 @@ namespace Modbus { modbus_t * NetLayer::context() { PIMP_D (NetLayer); - return d->ctx; + return d->ctx.get(); } // --------------------------------------------------------------------------- const modbus_t * NetLayer::context() const { PIMP_D (const NetLayer); - return d->ctx; + return d->ctx.get(); } // --------------------------------------------------------------------------- diff --git a/src/netlayer_p.h b/src/netlayer_p.h index dc510e3..d39cf9a 100644 --- a/src/netlayer_p.h +++ b/src/netlayer_p.h @@ -23,16 +23,15 @@ namespace Modbus { class NetLayer::Private { public: Private (Net n, const std::string & c, const std::string & s, uint16_t m) : - ctx (0), net (n), connection (c), settings (s), maxAduLength (m) {} - virtual ~Private() { - modbus_free (ctx); - } + net (n), connection (c), settings (s), maxAduLength (m) {} + virtual ~Private() = default; - modbus_t * ctx; Net net; std::string connection; std::string settings; uint16_t maxAduLength; + std::unique_ptr ctx = + {nullptr, [](modbus_t* p){ modbus_free(p); }}; }; } diff --git a/src/request.cpp b/src/request.cpp index 2898856..2dc240e 100644 --- a/src/request.cpp +++ b/src/request.cpp @@ -220,9 +220,6 @@ namespace Modbus { Request::Private::Private (Request * q, Net n, Function f) : Message::Private (q, n, f) {} - // --------------------------------------------------------------------------- - Request::Private::~Private() = default; - } /* ========================================================================== */ diff --git a/src/request_p.h b/src/request_p.h index b160083..06374a1 100644 --- a/src/request_p.h +++ b/src/request_p.h @@ -29,7 +29,7 @@ namespace Modbus { Private (Request * q, Net n); Private (Request * q, Net n, const std::vector & m); Private (Request * q, Net n, Function f); - virtual ~Private(); + virtual ~Private() = default; PIMP_DECLARE_PUBLIC (Request) }; } diff --git a/src/response.cpp b/src/response.cpp index 8cd20be..bcf1aa4 100644 --- a/src/response.cpp +++ b/src/response.cpp @@ -209,8 +209,6 @@ namespace Modbus { Response::Private::Private (Response * q, NetLayer * b, Function f) : Message::Private (q, b, f) {} - // --------------------------------------------------------------------------- - Response::Private::~Private() = default; } diff --git a/src/response_p.h b/src/response_p.h index f53c51d..bb49afd 100644 --- a/src/response_p.h +++ b/src/response_p.h @@ -26,7 +26,8 @@ namespace Modbus { Private (Response * q, NetLayer * b); Private (Response * q, NetLayer * b, const std::vector & m); Private (Response * q, NetLayer * b, Function f); - virtual ~Private(); + virtual ~Private() = default; + PIMP_DECLARE_PUBLIC (Response) }; } diff --git a/src/router.cpp b/src/router.cpp index 0f197a1..db5ad59 100644 --- a/src/router.cpp +++ b/src/router.cpp @@ -28,10 +28,10 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - Router::Router (Router::Private &dd) : Server (dd) {} + Router::Router (std::unique_ptr &&dd) : Server (std::move(dd)) {} // --------------------------------------------------------------------------- - Router::Router () : Server (*new Private (this)) {} + Router::Router () : Server (std::unique_ptr(new Private (this))) {} // --------------------------------------------------------------------------- Router::Router (Net net, const std::string & connection, @@ -152,9 +152,6 @@ namespace Modbus { // --------------------------------------------------------------------------- Router::Private::Private (Router * q) : Server::Private (q) {} - // --------------------------------------------------------------------------- - Router::Private::~Private() = default; - // --------------------------------------------------------------------------- // virtual void Router::Private::setConfig (const nlohmann::json & config) { diff --git a/src/router_p.h b/src/router_p.h index 00947eb..3f8eccc 100644 --- a/src/router_p.h +++ b/src/router_p.h @@ -25,7 +25,7 @@ namespace Modbus { public: Private (Router * q); - virtual ~Private(); + virtual ~Private() = default; virtual void setConfig (const nlohmann::json & config); virtual bool open(); diff --git a/src/rtulayer.cpp b/src/rtulayer.cpp index 6c265c5..a51266b 100644 --- a/src/rtulayer.cpp +++ b/src/rtulayer.cpp @@ -94,17 +94,17 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - RtuLayer::RtuLayer (RtuLayer::Private &dd) : NetLayer (dd) {} + RtuLayer::RtuLayer (std::unique_ptr &&dd) : NetLayer (std::move(dd)) {} // --------------------------------------------------------------------------- RtuLayer::RtuLayer (const std::string & port, const std::string & settings) : - NetLayer (*new Private (port, settings)) {} + NetLayer (std::unique_ptr(new Private(port, settings))) {} // --------------------------------------------------------------------------- SerialMode RtuLayer::serialMode() { PIMP_D (RtuLayer); - int m = modbus_rtu_get_serial_mode (d->ctx); + int m = modbus_serial_get_serial_mode (d->ctx.get()); if (m != -1) { return static_cast (m); } @@ -115,14 +115,14 @@ namespace Modbus { bool RtuLayer::setSerialMode (SerialMode mode) { PIMP_D (RtuLayer); - return (modbus_rtu_set_serial_mode (d->ctx, static_cast (mode)) != -1); + return (modbus_serial_set_serial_mode (d->ctx.get(), static_cast (mode)) != -1); } // --------------------------------------------------------------------------- SerialRts RtuLayer::rts() { PIMP_D (RtuLayer); - int r = modbus_rtu_get_rts (d->ctx); + int r = modbus_serial_get_rts (d->ctx.get()); if (r != -1) { return static_cast (r); @@ -134,21 +134,21 @@ namespace Modbus { bool RtuLayer::setRts (SerialRts r) { PIMP_D (RtuLayer); - return (modbus_rtu_set_rts (d->ctx, static_cast (r)) != -1); + return (modbus_serial_set_rts (d->ctx.get(), static_cast (r)) != -1); } // --------------------------------------------------------------------------- int RtuLayer::rtsDelay() { PIMP_D (RtuLayer); - return modbus_rtu_get_rts_delay (d->ctx); + return modbus_serial_get_rts_delay (d->ctx.get()); } // --------------------------------------------------------------------------- bool RtuLayer::setRtsDelay (int delay) { PIMP_D (RtuLayer); - return (modbus_rtu_set_rts_delay (d->ctx, delay) != -1); + return (modbus_serial_set_rts_delay (d->ctx.get(), delay) != -1); } // --------------------------------------------------------------------------- @@ -193,7 +193,7 @@ namespace Modbus { setRts (r); usleep (rtsDelay()); - size = write (modbus_get_socket (d->ctx), msg->adu(), msg->aduSize()); + size = write (modbus_get_socket (d->ctx.get()), msg->adu(), msg->aduSize()); usleep (d->oneByteTime * msg->aduSize() + rtsDelay()); setRts ( (r == RtsDown) ? RtsUp : RtsDown); // restore initial state @@ -202,7 +202,7 @@ namespace Modbus { } else { #endif - return write (modbus_get_socket (d->ctx), msg->adu(), msg->aduSize()); + return write (modbus_get_socket (d->ctx.get()), msg->adu(), msg->aduSize()); #if MODBUSPP_HAVE_TIOCM_RTS } #endif @@ -234,40 +234,48 @@ namespace Modbus { // --------------------------------------------------------------------------- // static int RtuLayer::baud (const std::string & settings) { - int b; try { - b = std::stoi (settings); + return std::stoi (settings); } catch (...) { - b = 19200; + throw std::invalid_argument ("RtuLayer settings\"" + settings + "\" has an invalid baud rate setting."); } - return b; } // --------------------------------------------------------------------------- // static char RtuLayer::parity (const std::string & settings) { - char p = 'N'; size_t s = settings.length(); if (s >= 2) { char c = settings[s - 2]; - if ( (c == 'E') || (c == 'O')) { - return c; + switch (c) { + case 'N': + case 'E': + case 'O': + return c; } } - return p; + + throw std::invalid_argument ("RtuLayer settings\"" + settings + "\" has an invalid parity setting."); } // --------------------------------------------------------------------------- // static int RtuLayer::stop (const std::string & settings) { + size_t s = settings.length(); - if (parity (settings) == 'N') { - - return 2; + if (s >= 3) { + char c = settings[s - 1]; + switch (c) { + case '1': + return 1; + case '2': + return 2; + } } - return 1; + + throw std::invalid_argument ("RtuLayer settings\"" + settings + "\" has an invalid stop bit setting."); } // --------------------------------------------------------------------------- @@ -299,16 +307,15 @@ namespace Modbus { NetLayer::Private (Rtu, port, settings, MODBUS_RTU_MAX_ADU_LENGTH) { // RTU MUST BE 8-bits - ctx = modbus_new_rtu (port.c_str(), RtuLayer::baud (settings), + ctx.reset( modbus_new_rtu (port.c_str(), RtuLayer::baud (settings), RtuLayer::parity (settings), 8, - RtuLayer::stop (settings)); + RtuLayer::stop (settings)) ); if (! ctx) { - throw std::invalid_argument ( "Unable to create RTU Modbus Backend(" + port + "," + settings + ")\n" + lastError()); } - oneByteTime = modbus_rtu_get_rts_delay (ctx); + oneByteTime = modbus_serial_get_rts_delay (ctx.get()); } } diff --git a/src/rtulayer_p.h b/src/rtulayer_p.h index 087258a..ea3d754 100644 --- a/src/rtulayer_p.h +++ b/src/rtulayer_p.h @@ -25,6 +25,8 @@ namespace Modbus { public: Private (const std::string & port, const std::string & settings); + virtual ~Private() = default; + int oneByteTime; }; } diff --git a/src/server.cpp b/src/server.cpp index f975f54..840fa42 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -27,6 +27,7 @@ # include # include #endif +#include #include "server_p.h" #include "config.h" @@ -41,11 +42,11 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - Server::Server (Server::Private &dd) : Device (dd) {} + Server::Server (std::unique_ptr &&dd) : Device (std::move(dd)) {} // --------------------------------------------------------------------------- Server::Server () : - Device (*new Private (this)) {} + Device (std::unique_ptr(new Private (this))) {} // --------------------------------------------------------------------------- Server::Server (Net net, const std::string & connection, @@ -82,24 +83,15 @@ namespace Modbus { // --------------------------------------------------------------------------- int Server::poll (long timeout) { + if(isRunning() || not isOpen()){ + // cann not proceed if the `loop()` is running + // OR connection is not established + return -1; + } - if (!isRunning() && isOpen()) { - PIMP_D (Server); - - if (!d->receiveTask.valid()) { - // starts the receiving thread - d->receiveTask = std::async (std::launch::async, Server::Private::receive, d); - } - - if (d->receiveTask.wait_for (std::chrono::milliseconds (timeout)) == std::future_status::ready) { + PIMP_D (Server); + return d->poll(timeout); - // message received ? - int rc = d->receiveTask.get(); - return d->task (rc); - } - return 0; - } - return -1; } // --------------------------------------------------------------------------- @@ -183,9 +175,9 @@ namespace Modbus { void Server::terminate () { PIMP_D (Server); - if (d->sock != -1) { - - ::shutdown (d->sock, SHUT_RDWR); + if (d->listen_sock != -1) { + ::shutdown (d->listen_sock, SHUT_RDWR); + ::close(d->listen_sock); } if (isRunning()) { @@ -195,6 +187,7 @@ namespace Modbus { // Wait for thread to join d->daemon.join(); } + } // --------------------------------------------------------------------------- @@ -225,6 +218,12 @@ namespace Modbus { d->messageCB = cb; } + std::vector Server::fds() { + PIMP_D(Server); + + return d->all_pollfds; + } + // --------------------------------------------------------------------------- // // Server::Private Class @@ -233,10 +232,10 @@ namespace Modbus { // --------------------------------------------------------------------------- Server::Private::Private (Server * q) : - Device::Private (q), sock (-1), req (0) {} + Device::Private (q), listen_sock (-1), req (0) { + all_pollfds.reserve(MAX_CONNECTIONS +1); + } - // --------------------------------------------------------------------------- - Server::Private::~Private() = default; // --------------------------------------------------------------------------- // virtual @@ -252,9 +251,9 @@ namespace Modbus { const std::string & settings) { Device::Private::setBackend (net, connection, settings); -#if MODBUSPP_HAVE_RTU_MULTI_SLAVES - if (net == Rtu) { - modbus_rtu_set_recv_filter (ctx(), FALSE); +#if MODBUSPP_HAVE_SERIAL_MULTI_SLAVES + if (net == Rtu || net == Ascii) { + modbus_serial_set_recv_filter (ctx(), FALSE); } #endif } @@ -291,13 +290,23 @@ namespace Modbus { switch (backend->net()) { case Tcp: - sock = modbus_tcp_pi_listen (ctx(), 1); - isOk = (sock != -1); + listen_sock = modbus_tcp_pi_listen (ctx(), MAX_CONNECTIONS); + if ( listen_sock != -1 ) { + isOk = true; + all_pollfds.push_back(pollfd{.fd=listen_sock, .events=POLL_IN, .revents=0}); + } break; case Rtu: - isOk = Device::Private::open(); - + case Ascii: { + if ( Device::Private::open() ) { + isOk = true; + int listen_sock = modbus_get_socket(ctx()); + all_pollfds.push_back(pollfd{.fd=listen_sock, .events=POLL_IN, .revents=0}); + } else { + std::cout << "fd after open: " << listen_sock << "\n"; + } + } default: break; } @@ -313,17 +322,16 @@ namespace Modbus { // --------------------------------------------------------------------------- void Server::Private::close() { - + std::lock_guard lg (d_guard); if (backend->net() == Tcp) { - - if (sock != -1) { - + if (listen_sock != -1) { #ifdef _WIN32 - ::closesocket (sock); + ::closesocket (listen_sock); #else - ::close (sock); + ::close (listen_sock); #endif - sock = -1; + all_pollfds.clear(); + listen_sock = -1; } } Device::Private::close(); @@ -390,9 +398,7 @@ namespace Modbus { rc = 0; } else { - if (messageCB) { - rc = messageCB (req.get(), q); } } @@ -400,30 +406,97 @@ namespace Modbus { return rc; } - // --------------------------------------------------------------------------- - // static - int Server::Private::receive (Private * d) { - int rc; - if ( (d->backend->net() == Tcp) && !d->isConnected()) { + int Server::Private::poll(int timeout) + { + int eventCount = ::poll(all_pollfds.data(), all_pollfds.size(), timeout); - // accept blocking call ! - if (modbus_tcp_pi_accept (d->ctx(), &d->sock) < 0) { + if ( eventCount == 0) { + // there is nothing to process + return 0; + } + if ( eventCount < 0 ) { + // handle error return -1; + } + + // handle events + std::vector new_pfds{}; + + for ( pollfd& pfd : all_pollfds ) { + + if ( pfd.revents & POLLIN ) { + + if ( backend->net() == Net::Tcp && pfd.fd == listen_sock ) { + // if there is an event on the 'listening' socket + // handle incomming connection request aka `connect()` + // but only Server::Private::MAX_CONNECTIONS + if ( all_pollfds.size() < MAX_CONNECTIONS ) { + int new_socket = modbus_tcp_accept(ctx(), &listen_sock); + if ( new_socket != -1 ) { + new_pfds.push_back(pollfd{ + .fd=new_socket, + .events=POLL_IN, + .revents=0}); + } + } + } else { + // handle incomming request + modbus_set_socket(ctx(), pfd.fd); + int rc = Server::Private::receive(this); + if ( rc == -1 ) { + // if receive fails after a successfull poll + // probably the connection is broken + ::close(pfd.fd); + pfd.fd = -1; + continue; + } + + task(rc); + } + } else if ( pfd.revents & POLLHUP ) { + ::close(pfd.fd); + pfd.fd = -1; } } + + // remove bad file descriptors from watch list + auto badFds = std::remove_if(all_pollfds.begin(), all_pollfds.end(), [](const pollfd& pfd) { + return (pfd.revents & (POLLERR|POLLHUP|POLLNVAL)) || (pfd.fd == -1); + }); + all_pollfds.erase(badFds, all_pollfds.end()); + + if ( all_pollfds.empty() ) { + return -1; + } + + // add new accepted file descriptors to watch list + if ( not new_pfds.empty() ) { + all_pollfds.insert(all_pollfds.end(), new_pfds.begin(), new_pfds.end()); + } + + return 0; + } + + // --------------------------------------------------------------------------- + // static + int Server::Private::receive (Private * d) { + std::lock_guard lg (d->d_guard); + + int rc = 0; + d->req->clear(); rc = modbus_receive (d->ctx(), d->req->adu()); - if (rc > 0) { - - d->req->setAduSize (rc); + if ( rc > 0 ) { + d->req->setAduSize(rc); } + return rc; } // --------------------------------------------------------------------------- // static - void * Server::Private::loop (std::future run, Private * d) { + void Server::Private::loop (std::future run, Private * d) { int rc; while (run.wait_for (std::chrono::milliseconds (100)) == std::future_status::timeout) { diff --git a/src/server_p.h b/src/server_p.h index 616a7ea..0fcc824 100644 --- a/src/server_p.h +++ b/src/server_p.h @@ -16,6 +16,7 @@ */ #pragma once +#include #include #include #include @@ -26,9 +27,10 @@ namespace Modbus { class Server::Private : public Device::Private { + static const int MAX_CONNECTIONS = 16; public: Private (Server * q); - virtual ~Private(); + virtual ~Private() = default; virtual void setBackend (Net net, const std::string & connection, const std::string & settings); virtual void setConfig (const nlohmann::json & config); @@ -36,20 +38,23 @@ namespace Modbus { virtual bool open(); virtual void close(); int task (int rc); + int poll (int timeout); BufferedSlave * addSlave (int slaveAddr, Device * master); - static void * loop (std::future run, Private * d); + static void loop (std::future run, Private * d); static int receive (Private * d); - int sock; + int listen_sock = -1; + std::vector all_pollfds {}; std::shared_ptr req; std::map > slave; - std::future receiveTask; std::thread daemon; std::promise stopDaemon; Message::Callback messageCB; + std::mutex d_guard; + PIMP_DECLARE_PUBLIC (Server) }; } diff --git a/src/slave.cpp b/src/slave.cpp index 1cdf2ed..0fbd5b6 100644 --- a/src/slave.cpp +++ b/src/slave.cpp @@ -35,8 +35,6 @@ namespace Modbus { Slave::Slave (int slaveAddr, Device * dev) : d_ptr (new Private (this, slaveAddr, dev)) {} - // --------------------------------------------------------------------------- - Slave::~Slave() = default; // --------------------------------------------------------------------------- int Slave::number() const { @@ -293,9 +291,6 @@ namespace Modbus { dev = d; } - // --------------------------------------------------------------------------- - Slave::Private::~Private() = default; - // --------------------------------------------------------------------------- // // Modbus::Json Namespace diff --git a/src/slave_p.h b/src/slave_p.h index d6f5071..b3c8700 100644 --- a/src/slave_p.h +++ b/src/slave_p.h @@ -28,7 +28,7 @@ namespace Modbus { public: Private (Slave * q); Private (Slave * q, int s, Device * d); - virtual ~Private(); + virtual ~Private() = default; inline modbus_t * ctx() { return dev->backend().context(); diff --git a/src/tcplayer.cpp b/src/tcplayer.cpp index 14a4865..e113ce9 100644 --- a/src/tcplayer.cpp +++ b/src/tcplayer.cpp @@ -37,11 +37,11 @@ namespace Modbus { // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- - TcpLayer::TcpLayer (TcpLayer::Private &dd) : NetLayer (dd) {} + TcpLayer::TcpLayer (std::unique_ptr &&dd) : NetLayer (std::move(dd)) {} // --------------------------------------------------------------------------- TcpLayer::TcpLayer (const std::string & host, const std::string & service) : - NetLayer (*new Private (host, service)) {} + NetLayer (std::unique_ptr(new Private (host, service))) {} // --------------------------------------------------------------------------- const std::string & TcpLayer::node() const { @@ -59,7 +59,7 @@ namespace Modbus { int TcpLayer::sendRawMessage (const Message * msg) { PIMP_D (TcpLayer); - return send (modbus_get_socket (d->ctx), msg->adu(), msg->aduSize(), + return send (modbus_get_socket (d->ctx.get()), msg->adu(), msg->aduSize(), MSG_NOSIGNAL); } @@ -107,7 +107,7 @@ namespace Modbus { node = host.c_str(); } - ctx = modbus_new_tcp_pi (node, service.c_str()); + ctx.reset( modbus_new_tcp_pi (node, service.c_str()) ); if (! ctx) { throw std::invalid_argument ( diff --git a/src/tcplayer_p.h b/src/tcplayer_p.h index a42b47d..a3fb590 100644 --- a/src/tcplayer_p.h +++ b/src/tcplayer_p.h @@ -25,6 +25,8 @@ namespace Modbus { public: Private (const std::string & host, const std::string & service); + virtual ~Private() = default; + uint16_t transactionId; }; }