From 3ccdb3e20df6d9667d2b74e78a6ca77fcf6a70b9 Mon Sep 17 00:00:00 2001 From: Mateusz Mazur Date: Sun, 27 Oct 2024 13:56:15 +0100 Subject: [PATCH] Support for Windows compilation Thanks for contributions from: - RalfOGit - MightyPiggie PR: https://github.com/Mazurel/Modbus/pull/5 Co-authored-by: RalfOGit Co-authored-by: MightyPiggie Signed-off-by: Mateusz Mazur --- .github/workflows/cmake-multi-platform.yml | 8 +++-- .gitignore | 5 +++- CMakeLists.txt | 17 ++++++++--- include/MB/crc.hpp | 18 +++++++---- include/MB/modbusCell.hpp | 2 +- include/MB/modbusException.hpp | 13 ++++---- include/MB/modbusRequest.hpp | 12 ++++---- include/MB/modbusResponse.hpp | 8 ++--- include/MB/modbusUtils.hpp | 6 ++-- src/CMakeLists.txt | 14 +++++---- src/Serial/connection.cpp | 4 +-- src/TCP/connection.cpp | 34 ++++++++++++--------- src/modbusException.cpp | 21 +++++++++---- src/modbusRequest.cpp | 35 +++++++++++----------- src/modbusResponse.cpp | 34 ++++++++++----------- tests/CMakeLists.txt | 7 ++++- 16 files changed, 142 insertions(+), 96 deletions(-) diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index fd8dcf6..e8245b3 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -30,9 +30,10 @@ jobs: - os: ubuntu-latest c_compiler: clang cpp_compiler: clang++ - exclude: - os: windows-latest c_compiler: cl + cpp_compiler: cl + exclude: - os: windows-latest c_compiler: gcc - os: windows-latest @@ -67,11 +68,12 @@ jobs: -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DMODBUS_EXAMPLE=ON -DMODBUS_TESTS=ON - -DMODBUS_COMMUNICATION=${{ matrix.os == 'windows-latest' && 'OFF' || matrix.os == 'ubuntu-latest' && 'ON' }} + -DMODBUS_TCP_COMMUNICATION=${{ matrix.os == 'windows-latest' && 'OFF' || matrix.os == 'ubuntu-latest' && 'ON' }} + -DMODBUS_SERIAL_COMMUNICATION=${{ matrix.os == 'windows-latest' && 'OFF' || matrix.os == 'ubuntu-latest' && 'ON' }} -S ${{ github.workspace }} - name: Build run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} - name: Test - run: ${{ steps.strings.outputs.build-output-dir }}/tests/Google_Tests_run + run: ${{ steps.strings.outputs.build-output-dir }}/${{ matrix.os == 'windows-latest' && 'tests\Release\Google_Tests_run.exe' || matrix.os == 'ubuntu-latest' && '/tests/Google_Tests_run' }} diff --git a/.gitignore b/.gitignore index f928faf..bfe535d 100644 --- a/.gitignore +++ b/.gitignore @@ -30,9 +30,12 @@ *.exe *.out *.app - build +# Windows specific +out +.vs + # Gtags GTAGS GRTAGS diff --git a/CMakeLists.txt b/CMakeLists.txt index dda75b7..8e63559 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,26 @@ cmake_minimum_required(VERSION 3.13) -project(protocolConverter) +project(modbus) set(CMAKE_CXX_STANDARD 17) if (MSVC) - add_compile_options(/W3) + add_compile_options(/W3 /MT) + # Otherwise, MSVC complains about strncopy + add_definitions(-D_CRT_SECURE_NO_WARNINGS) else() add_compile_options(-Wall -Wextra -Werror -pedantic) endif() option(MODBUS_EXAMPLE "Build example program" OFF) option(MODBUS_TESTS "Build tests" OFF) -option(MODBUS_COMMUNICATION "Use Modbus communication library" ON) +option(MODBUS_TCP_COMMUNICATION "Use Modbus TCP communication library" ON) + +if(NOT win32) + # Serial not supported on Windows + option(MODBUS_SERIAL_COMMUNICATION "Use Modbus serial communication library" OFF) # not supported by windows platform +else() + message(STATUS "Modbus Serial not supported on Windows.") +endif() add_subdirectory(src) @@ -21,5 +30,5 @@ endif() if(MODBUS_EXAMPLE) add_executable(ex example/main.cpp) - target_link_libraries(ex Modbus) + target_link_libraries(ex PUBLIC Modbus_Core) endif() diff --git a/include/MB/crc.hpp b/include/MB/crc.hpp index 8016f61..0738cf0 100644 --- a/include/MB/crc.hpp +++ b/include/MB/crc.hpp @@ -1,13 +1,19 @@ #include +#include #include //! This namespace contains functions used for CRC calculation namespace MB::CRC { - //! Calculates CRC based on the input buffer - C style - uint16_t calculateCRC(const uint8_t *buff, std::size_t len); +//! Calculates CRC based on the input buffer - C style +uint16_t calculateCRC(const uint8_t *buff, std::size_t len); - //! Calculate CRC based on the input vector of bytes - inline uint16_t calculateCRC(const std::vector &buffer) { - return calculateCRC(buffer.begin().base(), buffer.size()); +//! Calculate CRC based on the input vector of bytes +inline uint16_t calculateCRC(const std::vector &buffer, std::optional len = std::nullopt) { + std::size_t bufferLength = buffer.size(); + if (len.has_value() && bufferLength >= *len) { + bufferLength = *len; } -}; + + return calculateCRC(static_cast(&(*buffer.begin())), bufferLength); +} +}; // namespace MB::CRC diff --git a/include/MB/modbusCell.hpp b/include/MB/modbusCell.hpp index 8c1e875..b57faba 100644 --- a/include/MB/modbusCell.hpp +++ b/include/MB/modbusCell.hpp @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include /** diff --git a/include/MB/modbusException.hpp b/include/MB/modbusException.hpp index 6660199..92ec695 100644 --- a/include/MB/modbusException.hpp +++ b/include/MB/modbusException.hpp @@ -4,9 +4,10 @@ #pragma once +#include +#include #include #include -#include #include #include "modbusUtils.hpp" @@ -81,12 +82,14 @@ class ModbusException : public std::exception { /** * This function is less optimal, it is just to be compatible with - * std::excepetion You should preferably use toString() + * `std::exception`, you should preferably use toString() */ [[nodiscard]] const char *what() const noexcept override { - auto og = toString(); - char *str = new char[og.size()]; - stpcpy(str, og.c_str()); + const std::size_t MAX_STRING_SIZE = 1024; + static char str[MAX_STRING_SIZE]; + auto originalStr = toString(); + std::strncpy(str, originalStr.c_str(), + std::min(originalStr.size(), MAX_STRING_SIZE)); return str; } diff --git a/include/MB/modbusRequest.hpp b/include/MB/modbusRequest.hpp index 0e9c0b8..e5df10a 100644 --- a/include/MB/modbusRequest.hpp +++ b/include/MB/modbusRequest.hpp @@ -81,14 +81,14 @@ class ModbusRequest { std::vector values = {}) noexcept; /** - * Copy constructor for the response. - */ - ModbusRequest(const ModbusRequest&); + * Copy constructor for the response. + */ + ModbusRequest(const ModbusRequest &); /** - * Equal operator for the response. - */ - ModbusRequest& operator=(const ModbusRequest &); + * Equal operator for the response. + */ + ModbusRequest &operator=(const ModbusRequest &); //! Returns string representation of object [[nodiscard]] std::string toString() const noexcept; diff --git a/include/MB/modbusResponse.hpp b/include/MB/modbusResponse.hpp index b1e9078..f9d65eb 100644 --- a/include/MB/modbusResponse.hpp +++ b/include/MB/modbusResponse.hpp @@ -83,12 +83,12 @@ class ModbusResponse { /** * Copy constructor for the response. */ - ModbusResponse(const ModbusResponse&); + ModbusResponse(const ModbusResponse &); /** - * Equal operator for the response. - */ - ModbusResponse& operator=(const ModbusResponse &); + * Equal operator for the response. + */ + ModbusResponse &operator=(const ModbusResponse &); //! Converts object to it's string representation [[nodiscard]] std::string toString() const; diff --git a/include/MB/modbusUtils.hpp b/include/MB/modbusUtils.hpp index a6c6da6..5d093c9 100644 --- a/include/MB/modbusUtils.hpp +++ b/include/MB/modbusUtils.hpp @@ -204,8 +204,7 @@ inline std::string mbFunctionToStr(MBFunctionCode code) noexcept { //! Create uint16_t from buffer of two bytes, ex. { 0x01, 0x02 } => 0x0102 inline uint16_t bigEndianConv(const uint8_t *buf) { - return static_cast(buf[1]) + - (static_cast(buf[0]) << 8u); + return static_cast(buf[1]) + (static_cast(buf[0]) << 8u); } //! @deprecated Calculates CRC - please use functions from `MB::CRC` @@ -234,7 +233,6 @@ inline void pushUint16(std::vector &buffer, const uint16_t val) { } //! Ignore some value explicitly -template -inline void ignore_result(T&& v) { (void)v; } +template inline void ignore_result(T &&v) { (void)v; } } // namespace MB::utils diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 16abe5b..1281743 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,16 +17,20 @@ set(CORE_SOURCE_FILES ) add_library(Modbus_Core) -target_sources(Modbus_Core PRIVATE ${CORE_SOURCE_FILES} PUBLIC ${CORE_HEADER_FILES}) +target_sources(Modbus_Core PRIVATE ${CORE_SOURCE_FILES} INTERFACE ${CORE_HEADER_FILES}) target_include_directories(Modbus_Core PUBLIC ${PROJECT_SOURCE_DIR}/include PRIVATE ${MODBUS_HEADER_FILES_DIR}) add_library(Modbus) target_link_libraries(Modbus Modbus_Core) +if(MODBUS_SERIAL_COMMUNICATION) + message(STATUS "Enabling Modbus Serial") + add_subdirectory(Serial) + target_link_libraries(Modbus Modbus_Serial) +endif() -if(MODBUS_COMMUNICATION) - message("Modbus communication is experimental") +if(MODBUS_TCP_COMMUNICATION) + message(STATUS "Enabling Modbus Serial") add_subdirectory(TCP) - add_subdirectory(Serial) - target_link_libraries(Modbus Modbus_TCP Modbus_Serial) + target_link_libraries(Modbus Modbus_TCP) endif() diff --git a/src/Serial/connection.cpp b/src/Serial/connection.cpp index f922860..fa6a251 100644 --- a/src/Serial/connection.cpp +++ b/src/Serial/connection.cpp @@ -55,8 +55,8 @@ std::vector Connection::awaitRawMessage() { std::vector data(1024); pollfd waitingFD; - waitingFD.fd = this->_fd; - waitingFD.events = POLLIN; + waitingFD.fd = this->_fd; + waitingFD.events = POLLIN; waitingFD.revents = POLLIN; if (::poll(&waitingFD, 1, _timeout) <= 0) { diff --git a/src/TCP/connection.cpp b/src/TCP/connection.cpp index 68b924d..34bcb2a 100644 --- a/src/TCP/connection.cpp +++ b/src/TCP/connection.cpp @@ -26,7 +26,8 @@ std::vector Connection::sendRequest(const MB::ModbusRequest &req) { std::vector rawReq; rawReq.reserve(6); - rawReq.push_back(static_cast(reinterpret_cast(&_messageID)[1])); + rawReq.push_back( + static_cast(reinterpret_cast(&_messageID)[1])); rawReq.push_back(static_cast(_messageID)); rawReq.push_back(0x00); rawReq.push_back(0x00); @@ -34,7 +35,8 @@ std::vector Connection::sendRequest(const MB::ModbusRequest &req) { std::vector dat = req.toRaw(); uint32_t size = dat.size(); - rawReq.push_back(static_cast(reinterpret_cast(&size)[1])); + rawReq.push_back( + static_cast(reinterpret_cast(&size)[1])); rawReq.push_back(static_cast(size)); rawReq.insert(rawReq.end(), dat.begin(), dat.end()); @@ -48,7 +50,8 @@ std::vector Connection::sendResponse(const MB::ModbusResponse &res) { std::vector rawReq; rawReq.reserve(6); - rawReq.push_back(static_cast(reinterpret_cast(&_messageID)[1])); + rawReq.push_back( + static_cast(reinterpret_cast(&_messageID)[1])); rawReq.push_back(static_cast(_messageID)); rawReq.push_back(0x00); rawReq.push_back(0x00); @@ -56,7 +59,8 @@ std::vector Connection::sendResponse(const MB::ModbusResponse &res) { std::vector dat = res.toRaw(); uint32_t size = dat.size(); - rawReq.push_back(static_cast(reinterpret_cast(&size)[1])); + rawReq.push_back( + static_cast(reinterpret_cast(&size)[1])); rawReq.push_back(static_cast(size)); rawReq.insert(rawReq.end(), dat.begin(), dat.end()); @@ -70,7 +74,8 @@ std::vector Connection::sendException(const MB::ModbusException &ex) { std::vector rawReq; rawReq.reserve(6); - rawReq.push_back(static_cast(reinterpret_cast(&_messageID)[1])); + rawReq.push_back( + static_cast(reinterpret_cast(&_messageID)[1])); rawReq.push_back(static_cast(_messageID)); rawReq.push_back(0x00); rawReq.push_back(0x00); @@ -78,7 +83,8 @@ std::vector Connection::sendException(const MB::ModbusException &ex) { std::vector dat = ex.toRaw(); uint32_t size = dat.size(); - rawReq.push_back(static_cast(reinterpret_cast(&size)[1])); + rawReq.push_back( + static_cast(reinterpret_cast(&size)[1])); rawReq.push_back(static_cast(size)); rawReq.insert(rawReq.end(), dat.begin(), dat.end()); @@ -90,8 +96,8 @@ std::vector Connection::sendException(const MB::ModbusException &ex) { std::vector Connection::awaitRawMessage() { pollfd pfd; - pfd.fd = this->_sockfd; - pfd.events = POLLIN; + pfd.fd = this->_sockfd; + pfd.events = POLLIN; pfd.revents = POLLIN; if (::poll(&pfd, 1, 60 * 1000 /* 1 minute means the connection has died */) <= 0) { throw MB::ModbusException(MB::utils::ConnectionClosed); @@ -115,8 +121,8 @@ std::vector Connection::awaitRawMessage() { MB::ModbusRequest Connection::awaitRequest() { pollfd pfd; - pfd.fd = this->_sockfd; - pfd.events = POLLIN; + pfd.fd = this->_sockfd; + pfd.events = POLLIN; pfd.revents = POLLIN; if (::poll(&pfd, 1, 60 * 1000 /* 1 minute means the connection has died */) <= 0) { throw MB::ModbusException(MB::utils::Timeout); @@ -146,8 +152,8 @@ MB::ModbusRequest Connection::awaitRequest() { MB::ModbusResponse Connection::awaitResponse() { pollfd pfd; - pfd.fd = this->_sockfd; - pfd.events = POLLIN; + pfd.fd = this->_sockfd; + pfd.events = POLLIN; pfd.revents = POLLIN; if (::poll(&pfd, 1, this->_timeout) <= 0) { @@ -195,8 +201,8 @@ Connection Connection::with(std::string addr, int port) { sockaddr_in server; server.sin_family = AF_INET; - server.sin_port = ::htons(port); - server.sin_addr = { inet_addr(addr.c_str()) }; + server.sin_port = ::htons(port); + server.sin_addr = {inet_addr(addr.c_str())}; if (::connect(sock, reinterpret_cast(&server), sizeof(server)) < 0) throw std::runtime_error("Cannot connect, errno = " + std::to_string(errno)); diff --git a/src/modbusException.cpp b/src/modbusException.cpp index 52a85d6..70948aa 100644 --- a/src/modbusException.cpp +++ b/src/modbusException.cpp @@ -3,13 +3,22 @@ // Licensed under: MIT License #include "modbusException.hpp" +#include "modbusUtils.hpp" + +#include +#include +#include using namespace MB; // Construct Modbus exception from raw data ModbusException::ModbusException(const std::vector &inputData, - bool CRC) noexcept { - if (inputData.size() != ((CRC) ? 5 : 3)) { + bool checkCRC) noexcept { + const std::size_t PACKET_SIZE_WITHOUT_CRC = 3; + const std::size_t PACKET_SIZE_WITH_CRC = 5; + + if (inputData.size() != + ((checkCRC) ? PACKET_SIZE_WITH_CRC : PACKET_SIZE_WITHOUT_CRC)) { _slaveId = 0xFF; _functionCode = utils::Undefined; _validSlave = false; @@ -22,11 +31,11 @@ ModbusException::ModbusException(const std::vector &inputData, _validSlave = true; _errorCode = static_cast(inputData[2]); - if (CRC) { - auto CRC = *reinterpret_cast(&inputData[3]); - auto calculatedCRC = utils::calculateCRC(inputData.begin().base(), 3); + if (checkCRC) { + const auto actualCrc = *reinterpret_cast(&inputData[3]); + const uint16_t calculatedCRC = MB::CRC::calculateCRC(inputData, PACKET_SIZE_WITHOUT_CRC); - if (CRC != calculatedCRC) { + if (actualCrc != calculatedCRC) { _errorCode = utils::ErrorCodeCRCError; } } diff --git a/src/modbusRequest.cpp b/src/modbusRequest.cpp index 2944341..d9f6a62 100644 --- a/src/modbusRequest.cpp +++ b/src/modbusRequest.cpp @@ -6,6 +6,7 @@ #include "modbusUtils.hpp" #include +#include #include using namespace MB; @@ -30,20 +31,18 @@ ModbusRequest::ModbusRequest(uint8_t slaveId, utils::MBFunctionCode functionCode } } -ModbusRequest::ModbusRequest(const ModbusRequest& reference) : - _slaveID(reference.slaveID()), - _functionCode(reference.functionCode()), - _address(reference.registerAddress()), - _registersNumber(reference.numberOfRegisters()), - _values(reference.registerValues()){ } +ModbusRequest::ModbusRequest(const ModbusRequest &reference) + : _slaveID(reference.slaveID()), _functionCode(reference.functionCode()), + _address(reference.registerAddress()), + _registersNumber(reference.numberOfRegisters()), + _values(reference.registerValues()) {} - -ModbusRequest& ModbusRequest::operator=(const ModbusRequest &reference) { - this->_slaveID = reference.slaveID(); - this->_functionCode = reference.functionCode(); - this->_address = reference.registerAddress(); +ModbusRequest &ModbusRequest::operator=(const ModbusRequest &reference) { + this->_slaveID = reference.slaveID(); + this->_functionCode = reference.functionCode(); + this->_address = reference.registerAddress(); this->_registersNumber = reference.numberOfRegisters(); - this->_values = reference.registerValues(); + this->_values = reference.registerValues(); return *this; } @@ -106,16 +105,18 @@ ModbusRequest::ModbusRequest(const std::vector &inputData, bool CRC) { if (crcIndex == -1 || static_cast(crcIndex) + 2 > inputData.size()) throw ModbusException(utils::InvalidByteOrder); - auto recvCRC = *reinterpret_cast(&inputData[crcIndex]); - auto myCRC = utils::calculateCRC(inputData.begin().base(), crcIndex); + auto receivedCRC = *reinterpret_cast(&inputData[crcIndex]); + const auto inputDataLen = static_cast(crcIndex); + auto calculatedCRC = MB::CRC::calculateCRC(inputData, inputDataLen); - if (recvCRC != myCRC) { + if (receivedCRC != calculatedCRC) { throw ModbusException(utils::InvalidCRC, _slaveID); } } } catch (const ModbusException &ex) { throw ex; - } catch (const std::exception &ex) { + } catch (const std::exception&) { + // TODO: Save the exception somewhere throw ModbusException(utils::InvalidByteOrder); } } @@ -172,7 +173,7 @@ std::vector ModbusRequest::toRaw() const noexcept { utils::pushUint16(result, value.reg()); } } else { - int end = result.size() - 1; + std::size_t end = result.size() - 1; for (std::size_t i = 0; i < _values.size(); i++) { if (i % 8 == 0) { result.push_back(0x00); diff --git a/src/modbusResponse.cpp b/src/modbusResponse.cpp index 905bb3c..6bec6ce 100644 --- a/src/modbusResponse.cpp +++ b/src/modbusResponse.cpp @@ -30,20 +30,18 @@ ModbusResponse::ModbusResponse(uint8_t slaveId, utils::MBFunctionCode functionCo } } -ModbusResponse::ModbusResponse(const ModbusResponse& reference) : - _slaveID(reference.slaveID()), - _functionCode(reference.functionCode()), - _address(reference.registerAddress()), - _registersNumber(reference.numberOfRegisters()), - _values(reference.registerValues()){ } - - -ModbusResponse& ModbusResponse::operator=(const ModbusResponse &reference) { - this->_slaveID = reference.slaveID(); - this->_functionCode = reference.functionCode(); - this->_address = reference.registerAddress(); +ModbusResponse::ModbusResponse(const ModbusResponse &reference) + : _slaveID(reference.slaveID()), _functionCode(reference.functionCode()), + _address(reference.registerAddress()), + _registersNumber(reference.numberOfRegisters()), + _values(reference.registerValues()) {} + +ModbusResponse &ModbusResponse::operator=(const ModbusResponse &reference) { + this->_slaveID = reference.slaveID(); + this->_functionCode = reference.functionCode(); + this->_address = reference.registerAddress(); this->_registersNumber = reference.numberOfRegisters(); - this->_values = reference.registerValues(); + this->_values = reference.registerValues(); return *this; } @@ -109,16 +107,18 @@ ModbusResponse::ModbusResponse(std::vector inputData, bool CRC) { if (crcIndex == -1 || static_cast(crcIndex) + 2 > inputData.size()) throw ModbusException(utils::InvalidByteOrder); - auto recievedCRC = *reinterpret_cast(&inputData[crcIndex]); - auto myCRC = utils::calculateCRC(inputData.begin().base(), crcIndex); + const auto receivedCRC = *reinterpret_cast(&inputData[crcIndex]); + const auto inputDataLen = static_cast(crcIndex); + const auto calculatedCRC = MB::CRC::calculateCRC(inputData, inputDataLen); - if (recievedCRC != myCRC) { + if (receivedCRC != calculatedCRC) { throw ModbusException(utils::InvalidCRC, _slaveID); } } } catch (const ModbusException &ex) { throw ex; - } catch (const std::exception &ex) { + } catch (const std::exception&) { + // TODO: Save the exception somewhere throw ModbusException(utils::InvalidByteOrder); } } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5bb94ac..f5a8e9c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,6 +2,11 @@ project(Google_tests) add_subdirectory(googletest) include_directories(${gtest_SOURCE_DIR}/include ${gtest_SOURCE_DIR}) +if (MSVC) + set(gtest_force_shared_crt on) + add_compile_options(/MT) +endif() + set(TestFiles MB/ModbusRequestTests.cpp MB/ModbusResponseTests.cpp MB/ModbusExceptionTests.cpp @@ -10,5 +15,5 @@ set(TestFiles MB/ModbusRequestTests.cpp add_executable(Google_Tests_run ${TestFiles}) -target_link_libraries(Google_Tests_run Modbus) +target_link_libraries(Google_Tests_run Modbus_Core) target_link_libraries(Google_Tests_run gtest gtest_main)