diff --git a/CMakeLists.txt b/CMakeLists.txt index 00445ca..71970cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ aux_source_directory(3rdparty/libmodbus DIRSRCS) include_directories(3rdparty/libmodbus include) set(SOURCE_FILES - tests/main.cpp + src/main.cpp src/ConnectionConfig.h src/IModbusAdapter.h src/ModbusAdapter.h diff --git a/src/ConnectionConfig.h b/src/ConnectionConfig.h index 09bd306..c9249e2 100644 --- a/src/ConnectionConfig.h +++ b/src/ConnectionConfig.h @@ -1,43 +1,93 @@ #pragma once #include +#include -enum class ConnectionPort +enum class ConnectionPort : unsigned int { - CP_EXTERNAL, + CP_EXTERNAL = 0, CP_IOBUS, CP_TCP }; -enum class Parity +enum class Parity : unsigned int { - P_ODD, - P_EVEN, - P_NONE + PAR_ODD, + PAR_EVEN, + PAR_NONE +}; + +enum class ConnectionType : unsigned int +{ + CT_SERIAL, + CT_TCP, + CT_UNKNOWN }; class ConnectionConfig { public: - ConnectionConfig( ConnectionPort port, int baud = 115200, Parity parity = Parity::P_NONE, int dataBits = 8, int stopBits = 1 ) - { - - } + ConnectionConfig( ConnectionPort port, int baud = 115200, Parity parity = Parity::PAR_NONE, int dataBits = 8, int stopBits = 1, int timeOut = -1 ) + : m_port( port ) + , m_baudRate( baud ) + , m_parity( parity ) + , m_dataBits( dataBits ) + , m_stopBits( stopBits ) + , m_ipaddress() + , m_portnumber( -1 ) + , m_timeOut( timeOut ) + {} ConnectionConfig( ConnectionPort port, const std::string &ip, int portnum, int timeOut = -1 ) - { + : m_port( port ) + , m_baudRate( -1 ) + , m_parity( Parity::PAR_NONE ) + , m_dataBits( -1 ) + , m_stopBits( -1 ) + , m_ipaddress( ip ) + , m_portnumber( portnum ) + , m_timeOut( timeOut ) + {} - } + // Getters and Setters. Implemented to avoid outside meddling on the member variables. + ConnectionType getType() const { return m_conType; } + std::string getPort() const { return m_portMap.at(m_port); } + int getBaudRate() const { return m_baudRate; } + char getParity() const { return m_parityMap.at(m_parity); } + int getDataBits() const { return m_dataBits; } + int getStopBits() const { return m_stopBits; } + + std::string getIpAddress() const { return m_ipaddress; } + int getTcpPort() const { return m_portnumber; } + int getTimeOut() const { return m_timeOut; } private: + ConnectionType m_conType; + /// Serial connections - ConnectionPort m_serPort; + ConnectionPort m_port; int m_baudRate; Parity m_parity; int m_dataBits; int m_stopBits; /// TCP connections + std::string m_ipaddress; + int m_portnumber; + /// Generic + int m_timeOut; + std::unordered_map m_portMap = + { + { ConnectionPort::CP_EXTERNAL, "/dev/ttyUSB0" }, + { ConnectionPort::CP_IOBUS, "/dev/ttyUSB1" } + }; + + std::unordered_map m_parityMap = + { + { Parity::PAR_EVEN, 'E' }, + { Parity::PAR_ODD, 'O' }, + { Parity::PAR_NONE, 'N' } + }; }; diff --git a/src/IModbusAdapter.h b/src/IModbusAdapter.h index c83af9b..c565c22 100644 --- a/src/IModbusAdapter.h +++ b/src/IModbusAdapter.h @@ -4,7 +4,69 @@ #pragma once +// std +#include +#include +#include + +using modbusData = std::vector>; + +// Forward Declaration +class ConnectionConfig; + +/*! + * \brief The IModbusAdapter class + */ class IModbusAdapter { + +public: virtual ~IModbusAdapter() {} + + virtual void ModbusConnect( const ConnectionConfig &conncfg ) = 0; + + /*! + * \brief ModbusDisconnect + */ + virtual void ModbusDisconnect() = 0; + + /*! + * \brief ModbusReadData + * \param slaveId + * \param startAddress + * \param noOfItems + */ + virtual modbusData ModbusReadData( int slaveId, int startAddress, int noOfItems ) = 0; + + /*! + * \brief ModbusReadHoldReg + * \param slaveId + * \param startAddress + * \param noOfItems + * \return + */ + virtual modbusData ModbusReadHoldReg( int slaveId, int startAddress, int noOfItems ) = 0; + + /*! + * \brief ModBusWriteData + * \param slaveId + * \param funtionCode + * \param startAddress + * \param noOfItems + * \param values + */ + virtual void ModBusWriteData( int slaveId, int funtionCode, int startAddress, int noOfItems, std::vectorvalues ) = 0; + + /*! + * \brief isConnected + * \return + */ + virtual bool isConnected() const = 0; + + /*! + * \brief ErrorString + * \param errnum + * \return + */ + virtual std::string ErrorString( int errnum ) const = 0; }; diff --git a/src/ModbusAdapter.cpp b/src/ModbusAdapter.cpp index e69de29..f3e7723 100644 --- a/src/ModbusAdapter.cpp +++ b/src/ModbusAdapter.cpp @@ -0,0 +1,317 @@ + +#include "ConnectionConfig.h" +#include "ModbusAdapter.h" + +#include /// Added for memset functionality + +ModbusAdapter::ModbusAdapter() + : m_modbus( nullptr ) +{ + this->InitBuffers(); +} + +ModbusAdapter::~ModbusAdapter() +{ + +} + +void ModbusAdapter::ModbusConnect( const ConnectionConfig &conncfg ) +{ + if( m_connected ) + { + this->ModbusDisconnect(); + } + + this->m_connType = conncfg.getType(); + + switch( m_connType ) + { + case ConnectionType::CT_SERIAL: + this->ModbusConnectRTU( conncfg.getPort(), conncfg.getBaudRate(), conncfg.getParity(), conncfg.getDataBits(), conncfg.getStopBits(), conncfg.getTimeOut(), MODBUS_RTU_RTS_NONE ); + break; + + case ConnectionType::CT_TCP: + break; + + default: + // throw a sensible message or return an errorcode. + break; + } +} + +void ModbusAdapter::ModbusDisconnect() +{ + if( m_modbus != nullptr ) + { + if( m_connected ) + { + modbus_close( m_modbus ); + modbus_free( m_modbus ); + } + // Clean up after ourselves. + m_modbus = nullptr; + } + m_connected = false; +} + +modbusData ModbusAdapter::ModbusReadData( int slaveId, int functionCode, int startAddress, int noOfItems ) +{ + if( m_modbus == nullptr ) + { + // No context + return modbusData(); + } + + + + int resultValue = -1; + bool is16Bit = false; + + modbus_set_slave( m_modbus, slaveId ); + + // Request data from modbus. + switch( functionCode ) + { + case MODBUS_FC_READ_COILS: + resultValue = modbus_read_bits( m_modbus, startAddress, noOfItems, m_dest ); + break; + case MODBUS_FC_READ_DISCRETE_INPUTS: + resultValue = modbus_read_input_bits( m_modbus, startAddress, noOfItems, m_dest ); + break; + case MODBUS_FC_READ_HOLDING_REGISTERS: + resultValue = modbus_read_registers( m_modbus, startAddress, noOfItems, m_dest16 ); + break; + case MODBUS_FC_READ_INPUT_REGISTERS: + resultValue = modbus_read_input_registers( m_modbus, startAddress, noOfItems, m_dest16 ); + break; + + default: + break; + + } + + // Read the databuffers + if( resultValue != noOfItems ) + { + return modbusData(); + } + + modbusData resultData; + for( int index = 0; index < noOfItems; ++index ) + { + resultData.push_back( (is16Bit ? static_cast(m_dest16[index]) : static_cast(m_dest[index])) ); + } + + modbus_flush( m_modbus ); // flush data + this->ClearBuffers(); + + return resultData; +} + +modbusData ModbusAdapter::ModbusReadHoldReg( int slaveId, int startAddress, int noOfItems ) +{ + if( m_modbus == nullptr ) + { + return modbusData(); + } + + int resultValue = -1; /// Return value from read functions + + // -- Start Critical Section --- ?? // + + modbus_set_slave( m_modbus, slaveId ); + /// Request data from modbus + resultValue = modbus_read_registers( m_modbus, startAddress, noOfItems, m_dest16 ); + + /// Read the databuffers + if( resultValue != noOfItems ) + { + return modbusData(); + } + + modbusData resultData; + for( int index = 0; index < noOfItems; ++index ) + { + resultData.push_back( static_cast(m_dest16[index]) ); + } + + modbus_flush( m_modbus ); + this->ClearBuffers(); + + // -- End Critical Section --- ?? // + + return resultData; +} + +void ModbusAdapter::ModBusWriteData( int slaveId, int functionCode, int startAddress, int noOfItems, std::vectorvalues ) +{ + if( m_modbus == nullptr ) + { + return; + } + + // ------------------------------------------------ + // Create 8 bits databuffer + auto * data8 = new uint8_t[noOfItems]; + for( int index = 0; index < noOfItems; ++index ) + { + data8[index] = values[index]; + } + + // Create same buffer for 16 bits data + auto * data16 = new uint8_t[noOfItems]; + for( int index = 0; index < noOfItems; ++index ) + { + data16[index] = values[index]; + } + // ------------------------------------------------ + + int resultValue = -1; + + modbus_set_slave( m_modbus, slaveId ); + + // Request data from modbus + switch( functionCode ) + { + case MODBUS_FC_WRITE_SINGLE_COIL: + resultValue = modbus_write_bit( m_modbus, startAddress, values[0] ); + noOfItems = 1; + break; + case MODBUS_FC_WRITE_SINGLE_REGISTER: + resultValue = modbus_write_register( m_modbus, startAddress, values[0] ); + noOfItems = 1; + break; + case MODBUS_FC_WRITE_MULTIPLE_COILS: + + resultValue = modbus_write_bits( m_modbus, startAddress, noOfItems, data8 ); + break; + case MODBUS_FC_WRITE_MULTIPLE_REGISTERS: + resultValue = modbus_write_bits( m_modbus, startAddress, noOfItems, data16 ); + break; + } + + delete[] data8; + delete[] data16; + modbus_flush(m_modbus); // Flush data. + + if( resultValue != noOfItems ) + { + // Create a log line that writing the register failed. Maybe increment ErrorCounter(s) + } +} + +bool ModbusAdapter::isConnected() const +{ + return m_connected; +} + +std::string ModbusAdapter::ErrorString( int errnum ) +{ + switch(errnum) + { + case EINVAL: + return "Protocol context is NULL"; + break; + case ETIMEDOUT: + return "Timeout"; + break; + case ECONNRESET: + return "Connection reset"; + break; + case ECONNREFUSED: + return "Connection refused"; + break; + case EPIPE: + return "Socket error"; + break; + default: + return modbus_strerror(errno); + } +} + +/* ============= PRIVATE METHODS ============= */ +void ModbusAdapter::ModbusConnectRTU( const std::string &serialport, int baud, char parity, int dataBits, int stopBits, int RTS, int timeOut ) +{ + m_modbus = modbus_new_rtu( serialport.c_str(), baud, parity, dataBits, stopBits, RTS ); + +#ifdef LIB_MODBUS_DEBUG_OUTPUT + // Do sensible logging through PRIVA_LOG + m_modbus_set_debug( m_modbus, 1 ); +#endif + if( m_modbus == nullptr ) + { + // We can stop here. Nothing to be done as we don't have a valid context + // Log to PRIVA_LOG + return; + } + else if( m_modbus && modbus_connect( m_modbus ) == -1 ) + { + // We could not connect to the selected serial port. + // We can stop here. Nothing to be done as we don't have a valid connection + modbus_free( m_modbus ); + m_connected = false; + return; + } + + // Set recovery mode + modbus_set_error_recovery( m_modbus, MODBUS_ERROR_RECOVERY_PROTOCOL ); + + // Set the response timeout + modbus_set_response_timeout( m_modbus, timeOut, 0 ); + + m_connected = true; +} + +void ModbusAdapter::ModbusConnectTCP( const std::string &ip, int port, int timeOut ) +{ + if( ip.empty() ) + { + // Nothing to be done. Set an Logline to PRIVA_LOG and exit. + return; + } + + m_modbus = modbus_new_tcp( ip.c_str(), port ); + +#ifdef LIB_MODBUS_DEBUG_OUTPUT + // Do sensible logging through PRIVA_LOG + m_modbus_set_debug( m_modbus, 1 ); +#endif + + if( m_modbus == nullptr ) + { + // We can stop here. Nothing to be done as we don't have a valid context + // Log to PRIVA_LOG + return; + } + else if( m_modbus && modbus_connect( m_modbus ) == -1 ) + { + // We could not connect to the selected serial port. + // We can stop here. Nothing to be done as we don't have a valid connection + modbus_free( m_modbus ); + m_connected = false; + return; + } + + // Set recovery mode + modbus_set_error_recovery( m_modbus, MODBUS_ERROR_RECOVERY_PROTOCOL ); + + // Set the response timeout + modbus_set_response_timeout( m_modbus, timeOut, 0 ); + + m_connected = true; +} + +void ModbusAdapter::InitBuffers() +{ + // Setup memory for Data. + m_dest = (uint8_t *)malloc(2000 + sizeof(uint8_t)); + m_dest16 = (uint16_t *)malloc(125 * sizeof(uint16_t)); + + this->ClearBuffers(); +} + +void ModbusAdapter::ClearBuffers() +{ + memset(m_dest, 0, 2000 * sizeof(uint8_t)); + memset(m_dest16, 0, 125 * sizeof(uint16_t)); +} diff --git a/src/ModbusAdapter.h b/src/ModbusAdapter.h index 9a6a8ce..708c2b6 100644 --- a/src/ModbusAdapter.h +++ b/src/ModbusAdapter.h @@ -6,17 +6,94 @@ #include "ConnectionConfig.h" #include "IModbusAdapter.h" +#include "modbus.h" -/// \brief This class represents a single modbus context. Each context will -/// result in an instance of this class. It is not intended to be -/// created directly but through a factory. The factory will create -/// the object and return the pointer to its interface. +// std +#include +#include +#include +/// @brief The ModbusAdapter class represents a single modbus context. Each context will +/// result in an instance of this class. It is not intended to be +/// created directly but through a factory. The factory will create +/// the object and return the pointer to its interface. class ModbusAdapter : public IModbusAdapter { public: + /*! + * \brief ModbusAdapter + */ ModbusAdapter(); + + /*! + * \brief ~ModbusAdapter + */ virtual ~ModbusAdapter(); - void ModbusConnectRTU(const ConnectionConfig &conncfg); + /*! + * \brief ModbusConnect + * \param conncfg + */ + void ModbusConnect( const ConnectionConfig &conncfg ); + + /*! + * \brief ModbusDisconnect + */ + void ModbusDisconnect(); + + /*! + * \brief ModbusReadData + * \param slaveId + * \param startAddress + * \param noOfItems + */ + modbusData ModbusReadData( int slaveId, int functionCode, int startAddress, int noOfItems ); + + /*! + * \brief ModbusReadHoldReg + * \param slaveId + * \param startAddress + * \param noOfItems + * \return + */ + modbusData ModbusReadHoldReg( int slaveId, int startAddress, int noOfItems ); + + /*! + * \brief ModBusWriteData + * \param slaveId + * \param funtionCode + * \param startAddress + * \param noOfItems + * \param values + */ + void ModBusWriteData( int slaveId, int functionCode, int startAddress, int noOfItems, std::vectorvalues ); + + /*! + * \brief isConnected + * \return + */ + bool isConnected() const; + + /*! + * \brief ErrorString + * \param errnum + * \return + */ + std::string ErrorString( int errnum ); + +private: // Methods + void ModbusConnectRTU( const std::string &serialport, int baud, char parity, int dataBits, int stopBits, int RTS, int timeOut ); + void ModbusConnectTCP( const std::string &ip, int port, int timeOut = -1 ); + + void InitBuffers(); + void ClearBuffers(); + +private: // Members + ConnectionType m_connType { ConnectionType::CT_UNKNOWN }; ///> The type of connection this instance provides. Needed for administration + bool m_connected { false }; ///> Shows if the connection is still active. + modbus_t *m_modbus; ///> The actual low-level modbus instance as a raw-pointer. ( unique_pointer gives an error on this struct ) + + // Return value Buffers ( Room for improvement ) + uint8_t *m_dest; + uint16_t *m_dest16; }; diff --git a/tests/main.cpp b/src/main.cpp index da175c2..da175c2 100644 --- a/tests/main.cpp +++ b/src/main.cpp