├── .projectile ├── .clang-format ├── thrd ├── fmt-6.1.2.tar.gz ├── CMakeLists.txt ├── gtest.cmake └── fmt.cmake ├── coverage.bat ├── .gitmodules ├── src ├── base │ ├── modbus_lrc.cpp │ ├── modbus_logger.h │ ├── modbus_single_bit_access_process.cpp │ ├── modbus_sixteen_bit_access_process.cpp │ ├── modbus_logger.cpp │ ├── modbus_crc32.cpp │ ├── buffer.cpp │ └── modbus_frame.h ├── tools │ ├── modbusserver_client_session.h │ ├── modbus_url_parser.h │ ├── request │ ├── modbus_client_types.h │ ├── modbusserver_client_session.cpp │ ├── modbus_qt_serialport.cpp │ ├── modbus_serial_server.cpp │ ├── modbus_tcp_server.cpp │ ├── modbus_qt_socket.cpp │ ├── modbus_client_p.h │ ├── modbus_server.cpp │ └── modbus_reconnectable_iodevice.cpp └── CMakeLists.txt ├── test ├── modbus_test_frame.cpp ├── modbus_test_bytearray_dump.cpp ├── modbus_test_subarray.cpp ├── modbus_test_sixteen_value_test.cpp ├── modbus_test_single_bit_access_process.cpp ├── modbus_test_sixteen_bit_access_process.cpp ├── modbus_test_data_checker.cpp ├── CMakeLists.txt ├── modbus_test_adu.cpp ├── modbus_test_mocker.h ├── modbus_test_sixteen_bit_access.cpp ├── modbus_test_single_bit_access.cpp ├── modbus_test_rtu_frame_decoder_client_decode.cpp ├── modbus_test_mbap_frame_decoder_client_decode.cpp └── modbus_test_server.cpp ├── .gitignore ├── examples ├── CMakeLists.txt ├── simple_modbus_server.cpp ├── simple_modbus_client.cpp ├── serialport_client_sixteen_bit_access.cpp └── modbus_example_serialport_client.cpp ├── LICENSE ├── CMakeLists.txt ├── .github └── workflows │ └── build.yaml ├── include ├── modbus │ ├── base │ │ ├── smart_assert.h │ │ ├── modbus_data.h │ │ ├── modbus_tool.h │ │ ├── sixteen_bit_access.h │ │ ├── modbus.h │ │ ├── single_bit_access.h │ │ └── modbus_types.h │ └── tools │ │ ├── modbus_server.h │ │ └── modbus_client.h └── bytes │ └── buffer.h ├── .clang-tidy └── README.md /.projectile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | -------------------------------------------------------------------------------- /thrd/fmt-6.1.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paopaol/modbus/HEAD/thrd/fmt-6.1.2.tar.gz -------------------------------------------------------------------------------- /thrd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(./fmt.cmake) 2 | if(MODBUS_BUILD_TEST) 3 | include(./gtest.cmake) 4 | endif() 5 | -------------------------------------------------------------------------------- /coverage.bat: -------------------------------------------------------------------------------- 1 | set SOURCE_DIR=%cd% 2 | OpenCppCoverage.exe --source=%SOURCE_DIR% --excluded_sources=%SOURCE_DIR%\thrd --excluded_sources=%SOURCE_DIR%\build %SOURCE_DIR%\build\test\Debug\modbus_test.exe -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thrd/googletest"] 2 | path = thrd/googletest 3 | url = https://github.com/google/googletest 4 | tag = v1.8.1 5 | [submodule "thrd/fmt"] 6 | path = thrd/fmt 7 | url = https://github.com/fmtlib/fmt 8 | -------------------------------------------------------------------------------- /src/base/modbus_lrc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace modbus { 4 | uint8_t tool::lrc_modbus(const uint8_t *data, size_t len) { 5 | unsigned char lrc = 0; 6 | 7 | while (len--) { 8 | lrc += *data++; 9 | } 10 | 11 | return (~lrc) + 1; 12 | } 13 | 14 | } // namespace modbus 15 | -------------------------------------------------------------------------------- /test/modbus_test_frame.cpp: -------------------------------------------------------------------------------- 1 | #include "bytes/buffer.h" 2 | #include "gmock/gmock-matchers.h" 3 | #include "gtest/internal/gtest-internal.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace modbus; 15 | -------------------------------------------------------------------------------- /test/modbus_test_bytearray_dump.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | TEST(TestData, dump_dumpByteArray_outputIsHexString) { 8 | uint8_t binary[5] = {0x01, 0x33, 0x4b, 0xab, 0x3b}; 9 | modbus::ByteArray byteArray(binary, binary + 5); 10 | 11 | auto hexString = modbus::tool::dumpHex(byteArray); 12 | EXPECT_EQ(hexString, " 01 33 4b ab 3b"); 13 | } 14 | -------------------------------------------------------------------------------- /thrd/gtest.cmake: -------------------------------------------------------------------------------- 1 | include(ExternalProject) 2 | 3 | ExternalProject_Add( 4 | googletest 5 | GIT_REPOSITORY https://github.com/google/googletest 6 | GIT_TAG main 7 | PREFIX googletest 8 | CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 9 | -Dgtest_force_shared_crt=ON 10 | -DCMAKE_INSTALL_PREFIX= 11 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 12 | -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}) 13 | set_target_properties(googletest PROPERTIES FOLDER "googletest") 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | #cmake 35 | build 36 | 37 | #qt creator 38 | CMakeLists.txt.user 39 | -------------------------------------------------------------------------------- /test/modbus_test_subarray.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TEST(TestSubArray, subarrayToLast) { 6 | modbus::ByteArray array = {0x1, 0x2, 0x3}; 7 | 8 | auto sub = modbus::tool::subArray(array, 2); 9 | EXPECT_THAT(sub, testing::ElementsAre(0x03)); 10 | } 11 | 12 | TEST(TestSubArray, subarraySomeWhere) { 13 | modbus::ByteArray array = {0x1, 0x2, 0x3, 0x4, 0x5}; 14 | 15 | auto sub = modbus::tool::subArray(array, 2, 2); 16 | EXPECT_THAT(sub, testing::ElementsAre(0x3, 0x4)); 17 | } 18 | -------------------------------------------------------------------------------- /src/base/modbus_logger.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_LOGGER_H 2 | #define MODBUS_LOGGER_H 3 | 4 | #include 5 | #include 6 | 7 | namespace modbus { 8 | void logString(const std::string &prefix, LogLevel level, 9 | const std::string &msg); 10 | 11 | template > 12 | void log(const std::string &prefix, LogLevel level, const S &format_str, 13 | Args &&... args) { 14 | logString(prefix, level, fmt::format(format_str, args...)); 15 | } 16 | } // namespace modbus 17 | 18 | #endif /* MODBUS_LOGGER_H */ 19 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(modbus_example_serialport_client 2 | modbus_example_serialport_client.cpp) 3 | target_link_libraries(modbus_example_serialport_client modbus) 4 | 5 | add_executable(serialport_client_sixteen_bit_access 6 | "./serialport_client_sixteen_bit_access.cpp") 7 | target_link_libraries(serialport_client_sixteen_bit_access modbus) 8 | 9 | add_executable(simple_modbus_client "./simple_modbus_client.cpp") 10 | target_link_libraries(simple_modbus_client modbus) 11 | 12 | add_executable(simple_modbus_server "./simple_modbus_server.cpp") 13 | target_link_libraries(simple_modbus_server modbus) 14 | -------------------------------------------------------------------------------- /thrd/fmt.cmake: -------------------------------------------------------------------------------- 1 | # include(ExternalProject) 2 | 3 | # ExternalProject_Add( fmtlib URL ${CMAKE_CURRENT_SOURCE_DIR}/fmt-6.1.2.tar.gz 4 | # PREFIX fmtlib CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 5 | # -DCMAKE_INSTALL_PREFIX= -DFMT_TEST=OFF 6 | # -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 7 | # -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}) set_target_properties(fmtlib 8 | # PROPERTIES FOLDER "fmtlib") 9 | 10 | include(FetchContent) 11 | 12 | # set(FMT_TEST 0) set(BUILD_SHARED_LIBS 1) 13 | 14 | FetchContent_Declare( 15 | fmt 16 | URL ${CMAKE_CURRENT_SOURCE_DIR}/fmt-6.1.2.tar.gz 17 | URL_HASH MD5=2914e3ac33595103d6b27c87364b034f) 18 | 19 | FetchContent_MakeAvailable(fmt) 20 | -------------------------------------------------------------------------------- /test/modbus_test_sixteen_value_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TEST(SixteenBitValue, construct) { 6 | modbus::SixteenBitValue v1(0x11, 0x22); 7 | EXPECT_THAT(v1.twoBytes(), testing::ElementsAre(0x11, 0x22)); 8 | EXPECT_EQ(v1.toUint16(), 0x1122); 9 | EXPECT_EQ(v1.toUint16(modbus::SixteenBitValue::ByteOrder::kHostByteOrder), 10 | 0x1122); 11 | EXPECT_EQ(v1.toUint16(modbus::SixteenBitValue::ByteOrder::kNetworkByteOrder), 12 | 0x2211); 13 | 14 | modbus::SixteenBitValue v2(0x1122); 15 | EXPECT_THAT(v1.twoBytes(), testing::ElementsAre(0x11, 0x22)); 16 | 17 | modbus::SixteenBitValue v3; 18 | } 19 | 20 | TEST(SixteenBitValue, equal_test) { 21 | modbus::SixteenBitValue v1(0x11, 0x22); 22 | auto v2 = v1; 23 | EXPECT_EQ(v1, v2); 24 | } 25 | -------------------------------------------------------------------------------- /test/modbus_test_single_bit_access_process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TEST(SingleBitAccessProcess, readSingleBitProcess) { 6 | modbus::SingleBitAccess access; 7 | 8 | access.setStartAddress(0x03); 9 | access.setQuantity(0x03); 10 | 11 | auto request = modbus::createRequest(0x01, modbus::FunctionCode::kReadCoils, 12 | access, access.marshalReadRequest()); 13 | modbus::Response response; 14 | 15 | response.setServerAddress(0x01); 16 | response.setFunctionCode(modbus::FunctionCode::kReadCoils); 17 | response.setData(modbus::ByteArray({0x01, 0x05})); 18 | 19 | bool ok = modbus::processReadSingleBit(request, response, &access); 20 | EXPECT_EQ(ok, true); 21 | EXPECT_EQ(access.value(access.startAddress()), true); 22 | EXPECT_EQ(access.value(access.startAddress() + 1), false); 23 | EXPECT_EQ(access.value(access.startAddress() + 2), true); 24 | } 25 | -------------------------------------------------------------------------------- /test/modbus_test_sixteen_bit_access_process.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TEST(SixteenBitAccessProcess, ProcessReadMultipleRegisters) { 6 | modbus::SixteenBitAccess access; 7 | access.setStartAddress(0); 8 | access.setQuantity(3); 9 | 10 | modbus::Request request = 11 | modbus::createRequest(0x01, modbus::FunctionCode::kReadHoldingRegisters, 12 | access, access.marshalMultipleReadRequest()); 13 | modbus::Response response; 14 | 15 | response.setError(modbus::Error::kNoError); 16 | response.setFunctionCode(modbus::FunctionCode::kReadHoldingRegisters); 17 | response.setServerAddress(0x01); 18 | response.setData( 19 | modbus::ByteArray({0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03})); 20 | 21 | bool ok = modbus::processReadRegisters(request, response, &access); 22 | EXPECT_EQ(ok, true); 23 | EXPECT_EQ(access.value(0x00).toUint16(), 1); 24 | EXPECT_EQ(access.value(0x01).toUint16(), 2); 25 | EXPECT_EQ(access.value(0x02).toUint16(), 3); 26 | } 27 | -------------------------------------------------------------------------------- /src/tools/modbusserver_client_session.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUSSERVER_CLIENT_SESSION_H 2 | #define MODBUSSERVER_CLIENT_SESSION_H 3 | 4 | #include "base/modbus_frame.h" 5 | #include "modbus/base/modbus.h" 6 | #include "modbus/base/modbus_types.h" 7 | #include 8 | 9 | namespace modbus { 10 | 11 | class AbstractConnection; 12 | class QModbusServerPrivate; 13 | class ClientSession { 14 | public: 15 | ClientSession(QModbusServerPrivate *d, AbstractConnection *connction, 16 | const CheckSizeFuncTable &table); 17 | ~ClientSession(); 18 | 19 | std::string fullName() const; 20 | 21 | void handleModbusRequest(pp::bytes::Buffer &buffer); 22 | 23 | private: 24 | void processModbusRequest(pp::bytes::Buffer &buffer); 25 | void ReplyResponse(); 26 | 27 | QModbusServerPrivate *d_ = nullptr; 28 | AbstractConnection *client = nullptr; 29 | std::unique_ptr decoder; 30 | std::unique_ptr encoder; 31 | Adu request_; 32 | Adu response_; 33 | pp::bytes::Buffer writeBuffer; 34 | }; 35 | 36 | using ClientSessionPtr = std::shared_ptr; 37 | 38 | } // namespace modbus 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 paopaol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/simple_modbus_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) { 5 | QCoreApplication app(argc, argv); 6 | 7 | auto modbusServer = modbus::createServer("modbus.tcp://:33333"); 8 | // auto modbusServer = modbus::createServer("modbus.file:///COM1?9600-8-n-1"); 9 | 10 | modbusServer->setServerAddress(0x01); 11 | modbusServer->setTransferMode(modbus::TransferMode::kMbap); 12 | modbusServer->enableDump(false); 13 | 14 | modbusServer->handleCoils(0x00, 100); 15 | modbusServer->handleDiscreteInputs(0x00, 0x10); 16 | modbusServer->handleHoldingRegisters(0x00, 0x20); 17 | modbusServer->handleInputRegisters(0x00, 0x20); 18 | 19 | std::vector data; 20 | modbusServer->writeHodingRegisters(0, {{0, 5}}); 21 | 22 | QObject::connect( 23 | modbusServer, &modbus::QModbusServer::holdingRegisterValueChanged, 24 | [&](modbus::Address _t1, const QVector &_t2) {}); 25 | 26 | bool success = modbusServer->listenAndServe(); 27 | if (!success) { 28 | return 1; 29 | } 30 | 31 | return app.exec(); 32 | } 33 | -------------------------------------------------------------------------------- /test/modbus_test_data_checker.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace modbus; 5 | 6 | struct Result { 7 | CheckSizeResult retCode; 8 | size_t size; 9 | }; 10 | 11 | struct Tester { 12 | CheckSizeFunc func; 13 | ByteArray data; 14 | Result expect; 15 | }; 16 | 17 | TEST(DC, bytesRequired) { 18 | ByteArray array({0x01, 0x02, 0x03, 0x04}); 19 | ByteArray enougn({0x03, 0x02, 0x03, 0x04}); 20 | ByteArray short_({0x03, 0x02}); 21 | 22 | std::vector testers = { 23 | {bytesRequired<4>, array, {CheckSizeResult::kSizeOk, 4}}, 24 | {bytesRequired<2>, array, {CheckSizeResult::kSizeOk, 4}}, 25 | {bytesRequired<8>, array, {CheckSizeResult::kNeedMoreData, 4}}, 26 | {bytesRequiredStoreInArrayIndex<0>, 27 | enougn, 28 | {CheckSizeResult::kSizeOk, 4}}, 29 | {bytesRequiredStoreInArrayIndex<0>, 30 | short_, 31 | {CheckSizeResult::kNeedMoreData, 4}}, 32 | }; 33 | 34 | for (auto &tester : testers) { 35 | struct Result actual; 36 | actual.retCode = 37 | tester.func(actual.size, tester.data.data(), tester.data.size()); 38 | EXPECT_EQ(actual.retCode, tester.expect.retCode); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/base/modbus_single_bit_access_process.cpp: -------------------------------------------------------------------------------- 1 | #include "modbus_logger.h" 2 | #include 3 | 4 | namespace modbus { 5 | static bool validateSingleBitAccessResponse(const modbus::Response &resp, 6 | const std::string &log_prefix); 7 | 8 | bool processReadSingleBit(const Request &request, const Response &response, 9 | SingleBitAccess *access, 10 | const std::string &log_prefix) { 11 | if (!access) { 12 | log(log_prefix, LogLevel::kError, "SingleBitAccess access is nullptr"); 13 | return false; 14 | } 15 | 16 | bool success = validateSingleBitAccessResponse(response, log_prefix); 17 | if (!success) { 18 | return false; 19 | } 20 | *access = modbus::any::any_cast(request.userData()); 21 | success = access->unmarshalReadResponse(response.data()); 22 | if (!success) { 23 | log(log_prefix, LogLevel::kWarning, 24 | "unmarshal single bit access: data is invalid"); 25 | return false; 26 | } 27 | return true; 28 | } 29 | 30 | static bool validateSingleBitAccessResponse(const modbus::Response &resp, 31 | const std::string &log_prefix) { 32 | if (resp.isException()) { 33 | log(log_prefix, LogLevel::kError, resp.errorString()); 34 | return false; 35 | } 36 | return true; 37 | } 38 | } // namespace modbus 39 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(src-files 2 | "./base/modbus_crc32.cpp" 3 | "./base/modbus_lrc.cpp" 4 | "./base/modbus_logger.cpp" 5 | "./base/modbus_logger.h" 6 | "./base/modbus_sixteen_bit_access_process.cpp" 7 | "./base/modbus_single_bit_access_process.cpp" 8 | "./base/buffer.cpp" 9 | "./tools/modbus_client.cpp" 10 | "./tools/modbus_reconnectable_iodevice.cpp" 11 | "./tools/modbus_client_p.h" 12 | "${modbus_root_dir}/include/modbus/tools/modbus_client.h" 13 | "./tools/modbus_qt_socket.cpp" 14 | "./tools/modbus_qt_serialport.cpp" 15 | "./tools/modbus_server.cpp" 16 | "${modbus_root_dir}/include/modbus/tools/modbus_server.h" 17 | "./tools/modbus_tcp_server.cpp" 18 | "./tools/modbus_serial_server.cpp" 19 | "./tools/modbusserver_client_session.cpp" 20 | "./tools/modbusserver_client_session.h" 21 | "./tools/modbus_server_p.h") 22 | 23 | add_library(modbus ${src-files}) 24 | target_include_directories(modbus PRIVATE ".") 25 | target_include_directories(modbus PRIVATE "./base") 26 | target_link_libraries(modbus PUBLIC Qt5::Core) 27 | target_link_libraries(modbus PUBLIC Qt5::SerialPort) 28 | target_link_libraries(modbus PUBLIC Qt5::Network) 29 | 30 | target_include_directories( 31 | modbus PUBLIC 32 | "${PROJECT_SOURCE_DIR}/include") 33 | if(WIN32) 34 | target_link_libraries(modbus PUBLIC fmt::fmt) 35 | else() 36 | target_link_libraries(modbus PUBLIC fmt::fmt) 37 | target_link_libraries(modbus PUBLIC pthread) 38 | endif() 39 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(modbus) 4 | 5 | option(MODBUS_BUILD_TEST "build unit test" OFF) 6 | option(MODBUS_BUILD_EXAMPLE "build examples" ON) 7 | 8 | if(NOT CMAKE_BUILD_TYPE) 9 | set(CMAKE_BUILD_TYPE Release) 10 | endif() 11 | set(CMAKE_CONFIGURATION_TYPES ${CMAKE_BUILD_TYPE}) 12 | 13 | set(CMAKE_EXPORT_COMPILE_COMMANDS 1) 14 | set(modbus_root_dir ${CMAKE_CURRENT_SOURCE_DIR}) 15 | if(MSVC) 16 | add_compile_options("/WX-") 17 | add_compile_options("/EHsc") 18 | add_compile_options("/wd4800") 19 | add_compile_options("/wd4267") 20 | add_compile_options("/wd4018") 21 | add_compile_definitions("_CRT_SECURE_NO_WARNINGS") 22 | add_compile_definitions("_SCL_SECURE_NO_WARNINGS") 23 | set(CMAKE_INSTALL_LIBDIR "lib") 24 | else() 25 | add_compile_options("-std=c++11" "-Wall" "-Werror") 26 | include(GNUInstallDirs) 27 | endif() 28 | 29 | set(CMAKE_AUTOMOC ON) 30 | set(CMAKE_AUTOUIC ON) 31 | set(CMAKE_AUTORCC ON) 32 | find_package(Qt5 REQUIRED Core SerialPort Network) 33 | 34 | add_subdirectory("./thrd") 35 | 36 | include_directories(".") 37 | include_directories(include) 38 | 39 | add_subdirectory(src) 40 | 41 | if(MODBUS_BUILD_TEST) 42 | enable_testing() 43 | add_subdirectory(test) 44 | endif() 45 | 46 | if(MODBUS_BUILD_EXAMPLE) 47 | add_subdirectory(examples) 48 | endif() 49 | 50 | install( 51 | TARGETS modbus 52 | LIBRARY DESTINATION lib 53 | ARCHIVE DESTINATION lib 54 | RUNTIME DESTINATION bin) 55 | 56 | install(DIRECTORY include DESTINATION .) 57 | -------------------------------------------------------------------------------- /src/base/modbus_sixteen_bit_access_process.cpp: -------------------------------------------------------------------------------- 1 | #include "modbus_logger.h" 2 | #include 3 | #include 4 | #include 5 | 6 | namespace modbus { 7 | static bool validateSixteenBitAccessResponse(const Response &resp, 8 | const std::string &log_prefix); 9 | 10 | bool processReadRegisters(const Request &request, const Response &response, 11 | SixteenBitAccess *access, 12 | const std::string &log_prefix) { 13 | if (!access) { 14 | log(log_prefix, LogLevel::kError, "SixteenBitAccess access is nullptr"); 15 | return false; 16 | } 17 | 18 | bool success = validateSixteenBitAccessResponse(response, log_prefix); 19 | if (!success) { 20 | return false; 21 | } 22 | *access = modbus::any::any_cast(request.userData()); 23 | success = access->unmarshalReadResponse(response.data()); 24 | if (!success) { 25 | log(log_prefix, LogLevel::kWarning, 26 | "unmarshalReadRegister: data is invalid"); 27 | return false; 28 | } 29 | return true; 30 | } 31 | 32 | static bool validateSixteenBitAccessResponse(const Response &resp, 33 | const std::string &log_prefix) { 34 | if (resp.isException()) { 35 | log(log_prefix, LogLevel::kError, resp.errorString()); 36 | return false; 37 | } 38 | return true; 39 | } 40 | 41 | } // namespace modbus 42 | -------------------------------------------------------------------------------- /src/tools/modbus_url_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_URL_PARSER_H 2 | #define MODBUS_URL_PARSER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace internal { 10 | struct Config { 11 | QString scheme; 12 | 13 | QString serialName; 14 | QSerialPort::BaudRate baudRate; 15 | QSerialPort::DataBits dataBits; 16 | QSerialPort::Parity parity; 17 | QSerialPort::StopBits stopBits; 18 | 19 | uint16_t port; 20 | QString host; 21 | }; 22 | 23 | inline Config parseConfig(const QString &url) { 24 | Config config; 25 | QUrl qurl(url); 26 | 27 | config.scheme = qurl.scheme(); 28 | config.port = qurl.port(502); 29 | config.host = qurl.host(); 30 | config.serialName = qurl.path().mid(1); 31 | 32 | QString query = qurl.query(); 33 | QStringList serialConfig = query.split("-"); 34 | if (serialConfig.size() != 4) { 35 | serialConfig = QString("9600-8-n-1").split("-"); 36 | } 37 | static QMap parties = { 38 | {"n", QSerialPort::NoParity}, {"e", QSerialPort::EvenParity}, 39 | {"o", QSerialPort::OddParity}, {"N", QSerialPort::NoParity}, 40 | {"E", QSerialPort::EvenParity}, {"O", QSerialPort::OddParity}}; 41 | config.baudRate = static_cast(serialConfig[0].toInt()); 42 | config.dataBits = static_cast(serialConfig[1].toInt()); 43 | config.parity = parties[serialConfig[2]]; 44 | config.stopBits = static_cast(serialConfig[3].toInt()); 45 | return config; 46 | } 47 | } // namespace internal 48 | #endif /* MODBUS_URL_PARSER_H */ 49 | -------------------------------------------------------------------------------- /src/tools/request: -------------------------------------------------------------------------------- 1 | modbus_client.cpp:67:3: element->requestFrame = createModbusFrame(d->transferMode_); 2 | modbus_client.cpp:68:3: element->requestFrame->setAdu(*element->request); 3 | modbus_client.cpp:68:34: element->requestFrame->setAdu(*element->request); 4 | modbus_client.cpp:70:3: element->responseFrame = createModbusFrame(d->transferMode_); 5 | modbus_client.cpp:71:3: element->responseFrame->setAdu(element->response); 6 | modbus_client.cpp:71:34: element->responseFrame->setAdu(element->response); 7 | modbus_client.cpp:73:3: element->retryTimes = d->retryTimes_; 8 | modbus_client.cpp:382:3: element->bytesWritten = 0; 9 | modbus_client.cpp:383:3: element->dataRecived.clear(); 10 | modbus_client.cpp:394:26: const auto &request = *element->request; 11 | modbus_client.cpp:395:20: auto &response = element->response; 12 | modbus_client.cpp:397:7: if (element->retryTimes-- > 0) { 13 | modbus_client.cpp:400:29: d->device_->name(), element->retryTimes); 14 | modbus_client.cpp:444:23: auto &dataRecived = element->dataRecived; 15 | modbus_client.cpp:445:19: auto &request = element->request; 16 | modbus_client.cpp:452:17: auto result = element->responseFrame->unmarshal(dataRecived, &error); 17 | modbus_client.cpp:459:21: Response response(element->responseFrame->adu()); 18 | modbus_client.cpp:504:19: auto &request = element->request; 19 | modbus_client.cpp:505:3: element->bytesWritten += bytes; 20 | modbus_client.cpp:506:7: if (element->bytesWritten != element->requestFrame->marshalSize()) { 21 | modbus_client.cpp:506:32: if (element->bytesWritten != element->requestFrame->marshalSize()) { 22 | -------------------------------------------------------------------------------- /src/base/modbus_logger.cpp: -------------------------------------------------------------------------------- 1 | #include "fmt/format.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | using namespace std::chrono; 10 | 11 | namespace modbus { 12 | static std::string timeOfNow(); 13 | static LogWriter g_logger = [](LogLevel level, const std::string &msg) { 14 | std::string levelString; 15 | std::string date = timeOfNow(); 16 | 17 | switch (level) { 18 | case LogLevel::kDebug: 19 | levelString = "[Debug ] "; 20 | break; 21 | case LogLevel::kInfo: 22 | levelString = "[Info ] "; 23 | break; 24 | case LogLevel::kWarning: 25 | levelString = "[Warning] "; 26 | break; 27 | case LogLevel::kError: 28 | levelString = "[Error ] "; 29 | break; 30 | } 31 | std::cout << levelString << date << " - " << msg << std::endl; 32 | }; 33 | 34 | void registerLogMessage(const LogWriter &logger) { 35 | static std::once_flag once_; 36 | std::call_once(once_, [&]() { g_logger = logger; }); 37 | } 38 | 39 | void logString(const std::string &prefix, LogLevel level, 40 | const std::string &msg) { 41 | g_logger(level, fmt::format("{} {}", prefix, msg)); 42 | } 43 | 44 | static std::string timeOfNow() { 45 | char tmp[128] = {0}; 46 | time_t timep; 47 | 48 | time(&timep); 49 | struct tm now_time; 50 | #ifdef WIN32 51 | localtime_s(&now_time, &timep); 52 | #else 53 | localtime_r(&timep, &now_time); 54 | #endif 55 | strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S", &now_time); 56 | return tmp; 57 | } 58 | 59 | } // namespace modbus 60 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Qt5 REQUIRED Test) 2 | 3 | include_directories("../src/tools") 4 | include_directories("../src") 5 | include_directories("../src/base") 6 | 7 | include(GoogleTest) 8 | 9 | set(src-list 10 | "./modbus_test_bytearray_dump.cpp" 11 | "./modbus_test_data_checker.cpp" 12 | "./modbus_test_subarray.cpp" 13 | "./modbus_test_adu.cpp" 14 | "./modbus_test_rtu_frame_decoder_client_decode.cpp" 15 | "./modbus_test_mbap_frame_decoder_client_decode.cpp" 16 | "./modbus_test_mocker.h" 17 | "./modbus_test_single_bit_access.cpp" 18 | "./modbus_test_sixteen_value_test.cpp" 19 | "./modbus_test_sixteen_bit_access.cpp" 20 | "./modbus_test_sixteen_bit_access_process.cpp" 21 | "./modbus_test_single_bit_access_process.cpp" 22 | "./modbus_test_serial_client.cpp" 23 | "./modbus_test_server.cpp") 24 | 25 | add_executable(modbus_test ${src-list}) 26 | add_dependencies(modbus_test googletest) 27 | 28 | target_include_directories( 29 | modbus_test PRIVATE "${PROJECT_BINARY_DIR}/thrd/googletest/include") 30 | target_link_directories( 31 | modbus_test PRIVATE 32 | ${PROJECT_BINARY_DIR}/thrd/googletest/${CMAKE_INSTALL_LIBDIR}) 33 | target_link_libraries(modbus_test modbus) 34 | target_link_libraries(modbus_test debug gtest_maind optimized gtest_main) 35 | target_link_libraries(modbus_test debug gtestd optimized gtest) 36 | target_link_libraries(modbus_test debug gmockd optimized gmock) 37 | target_link_libraries(modbus_test Qt5::Core) 38 | target_link_libraries(modbus_test Qt5::SerialPort) 39 | target_link_libraries(modbus_test Qt5::Test) 40 | gtest_add_tests(modbus_test "./modbus_test_bytearray_dump.cpp" AUTO) 41 | -------------------------------------------------------------------------------- /src/tools/modbus_client_types.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_CLIENT_TYPES_H 2 | #define MODBUS_CLIENT_TYPES_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace modbus { 10 | 11 | template class StateManager { 12 | public: 13 | StateManager() = default; 14 | explicit StateManager(StateType state) : state_(state) {} 15 | void setState(StateType state) { state_ = state; } 16 | StateType state() { return state_; } 17 | 18 | private: 19 | StateType state_; 20 | }; 21 | 22 | enum class ConnectionState { kOpening, kOpened, kClosing, kClosed }; 23 | inline std::ostream &operator<<(std::ostream &output, 24 | const ConnectionState &state) { 25 | switch (state) { 26 | case ConnectionState::kOpening: 27 | output << "opening"; 28 | break; 29 | case ConnectionState::kOpened: 30 | output << "opened"; 31 | break; 32 | case ConnectionState::kClosing: 33 | output << "closing"; 34 | break; 35 | case ConnectionState::kClosed: 36 | output << "closed"; 37 | break; 38 | default: 39 | output.setstate(std::ios_base::failbit); 40 | } 41 | 42 | return output; 43 | } 44 | 45 | struct Element { 46 | Response response; 47 | QByteArray dumpReadArray; 48 | size_t bytesWritten = 0; 49 | size_t totalBytes = 0; 50 | int retryTimes = 0; 51 | std::unique_ptr request = nullptr; 52 | }; 53 | 54 | using ElementQueue = std::deque; 55 | inline void createElement(std::unique_ptr &request, Element *element) { 56 | element->request.swap(request); 57 | } 58 | 59 | } // namespace modbus 60 | 61 | #endif /* MODBUS_CLIENT_TYPES_H */ 62 | -------------------------------------------------------------------------------- /examples/simple_modbus_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static void processFunctionCode3(modbus::ServerAddress serverAddress, 5 | modbus::FunctionCode functionCode, 6 | modbus::Address address, 7 | modbus::Quantity quantity, 8 | const std::vector &data, 9 | modbus::Error error) { 10 | if (error != modbus::Error::kNoError) { 11 | return; 12 | } 13 | } 14 | 15 | static void processFunctionCode6(modbus::ServerAddress serverAddress, 16 | modbus::Address address, modbus::Error error) { 17 | qDebug() << "write signle register:" << (error == modbus::Error::kNoError); 18 | } 19 | 20 | int main(int argc, char *argv[]) { 21 | QCoreApplication app(argc, argv); 22 | 23 | modbus::QModbusClient *client = modbus::newQtSerialClient("COM1"); 24 | 25 | QObject::connect(client, &modbus::QModbusClient::readRegistersFinished, &app, 26 | processFunctionCode3); 27 | QObject::connect(client, &modbus::QModbusClient::writeSingleRegisterFinished, 28 | &app, processFunctionCode6); 29 | client->open(); 30 | 31 | /** 32 | * function code 0x03 33 | */ 34 | client->readRegisters(modbus::ServerAddress(0x01), modbus::FunctionCode(0x03), 35 | modbus::Address(0x00), modbus::Quantity(0x02)); 36 | 37 | /** 38 | * function code 0x06 39 | */ 40 | client->writeSingleRegister(modbus::ServerAddress(0x01), 41 | modbus::Address(0x01), 42 | modbus::SixteenBitValue(0x17)); 43 | 44 | return app.exec(); 45 | } 46 | -------------------------------------------------------------------------------- /src/base/modbus_crc32.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace modbus { 4 | 5 | static void invert_uint8(uint8_t *dest, uint8_t *src); 6 | static void invert_uint16(uint16_t *dest, uint16_t *src); 7 | 8 | uint16_t tool::crc16_modbus(const uint8_t *data, size_t size) { 9 | uint16_t in = 0xFFFF; 10 | uint16_t poly = 0x8005; 11 | uint8_t ch = 0; 12 | 13 | while (size--) { 14 | ch = *(data++); 15 | invert_uint8(&ch, &ch); 16 | in ^= (ch << 8); 17 | for (int i = 0; i < 8; i++) { 18 | if (in & 0x8000) 19 | in = (in << 1) ^ poly; 20 | else 21 | in = in << 1; 22 | } 23 | } 24 | invert_uint16(&in, &in); 25 | return (in); 26 | } 27 | 28 | void CrcCtx::clear() { 29 | in = 0xFFFF; 30 | poly = 0x8005; 31 | } 32 | 33 | void CrcCtx::crc16(const uint8_t *data, size_t size) { 34 | uint8_t ch = 0; 35 | 36 | while (size--) { 37 | ch = *(data++); 38 | invert_uint8(&ch, &ch); 39 | in ^= (ch << 8); 40 | for (int i = 0; i < 8; i++) { 41 | if (in & 0x8000) 42 | in = (in << 1) ^ poly; 43 | else 44 | in = in << 1; 45 | } 46 | } 47 | } 48 | 49 | uint16_t CrcCtx::end() { 50 | invert_uint16(&in, &in); 51 | return (in); 52 | } 53 | 54 | void invert_uint8(uint8_t *dest, uint8_t *src) { 55 | int i; 56 | uint8_t tmp[4]; 57 | tmp[0] = 0; 58 | for (i = 0; i < 8; i++) { 59 | if (src[0] & (1 << i)) 60 | tmp[0] |= 1 << (7 - i); 61 | } 62 | dest[0] = tmp[0]; 63 | } 64 | 65 | void invert_uint16(uint16_t *dest, uint16_t *src) { 66 | int i; 67 | uint16_t tmp[4]; 68 | tmp[0] = 0; 69 | for (i = 0; i < 16; i++) { 70 | if (src[0] & (1 << i)) 71 | tmp[0] |= 1 << (15 - i); 72 | } 73 | dest[0] = tmp[0]; 74 | } 75 | } // namespace modbus 76 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build-and-test 2 | 3 | on: push 4 | 5 | jobs: 6 | build-windows: 7 | runs-on: windows-latest 8 | # strategy: 9 | # metrix: 10 | # qt_ver: [5.6.1] 11 | # qt_arch: [win64_msvc2015_64] 12 | # - name: Install Qt 13 | # uses: jurplel/install-qt-action@v2 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v2 18 | 19 | - name: Install Qt 20 | uses: jurplel/install-qt-action@v2 21 | with: 22 | version: "5.9.1" 23 | host: "windows" 24 | target: "desktop" 25 | arch: "win64_msvc2017_64" 26 | dir: "${{ github.workspace }}/qtinstall" 27 | install-deps: "true" 28 | modules: "qtcharts qtwebengine" 29 | mirror: "http://mirrors.ocf.berkeley.edu/qt/" 30 | setup-python: "true" 31 | tools: "tools_ifw,4.0,qt.tools.ifw.40 tools_qtcreator,4.13.2-0,qt.tools.qtcreator" 32 | tools-only: "false" 33 | aqtversion: "==0.10.1" 34 | py7zrversion: "==0.11.1" 35 | extra: "--external 7z" 36 | 37 | - name: Setup Vs 38 | uses: seanmiddleditch/gha-setup-vsdevenv@master 39 | 40 | - name: Create Release Build Binary Directory 41 | run: | 42 | dir "${{ github.workspace }}/qtinstall/" 43 | 44 | - name: Create Release Build Binary Directory 45 | run: | 46 | mkdir release 47 | cd release 48 | cmake .. -DMODBUS_BUILD_TEST=ON -DMODBUS_BUILD_EXAMPLE=ON -DCMAKE_INSTALL_PREFIX=libmodbus -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=${{ github.workspace }}/buildtools/Qt/5.12.9/win64_msvc2017_64 49 | 50 | - name: Build Release 51 | run: | 52 | cmake --build release --config release 53 | 54 | - name: Run Unit Test 55 | run: | 56 | ./release/test/Release/modbus_test 57 | 58 | - name: Install 59 | run: | 60 | cmake --build release --target install --config release 61 | -------------------------------------------------------------------------------- /include/modbus/base/smart_assert.h: -------------------------------------------------------------------------------- 1 | #ifndef __SMART_ASSERT_H_ 2 | #define __SMART_ASSERT_H_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct functor { 9 | template functor &operator()(const type &val) { 10 | std::cout << val << std::endl; 11 | return (*this); 12 | } 13 | }; 14 | 15 | struct smart_asserter { 16 | smart_asserter() 17 | : smart_asserter_a(*this), smart_asserter_b(*this), m_first_value(true) {} 18 | 19 | ~smart_asserter() { exit(1); } 20 | 21 | smart_asserter &smart_asserter_a; 22 | smart_asserter &smart_asserter_b; 23 | 24 | smart_asserter &print_error(const char *file, int line, const char *exp) { 25 | std::cout << "smart assert failed: " << exp << ", " 26 | << "file " << file << ", " 27 | << "line " << line << std::endl; 28 | return (*this); 29 | } 30 | 31 | template 32 | smart_asserter &print_context(const char *name, type val) { 33 | if (m_first_value) { 34 | m_first_value = false; 35 | std::cout << "the context: " << std::endl; 36 | } 37 | std::cout << '\t' << name << ':' << val << std::endl; 38 | return (*this); 39 | } 40 | 41 | bool m_first_value; 42 | }; 43 | 44 | #define smart_asserter_a(exp) smart_asserter_op(b, exp) 45 | #define smart_asserter_b(exp) smart_asserter_op(a, exp) 46 | #define smart_asserter_op(n, exp) \ 47 | smart_asserter_a.print_context(#exp, exp).smart_asserter_##n 48 | #define smart_assert(exp) \ 49 | if (exp) \ 50 | ; \ 51 | else \ 52 | smart_asserter().print_error(__FILE__, __LINE__, #exp).smart_asserter_a 53 | 54 | #endif // __SMART_ASSERT_H_ 55 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # Disable abseil-no-namespace: https://bugs.llvm.org/show_bug.cgi?id=47947 3 | Checks: '-*, 4 | -cppcoreguidelines-avoid-goto, 5 | -cppcoreguidelines-avoid-magic-numbers, 6 | -cppcoreguidelines-macro-usage, 7 | abseil-*, 8 | -abseil-no-namespace, 9 | bugprone-*, 10 | -bugprone-narrowing-conversions, 11 | -bugprone-too-small-loop-variable, 12 | performance-*, 13 | performance-unnecessary-copy-initialization, 14 | performance-unnecessary-value-param, 15 | performance-move-const-arg, 16 | google-*, 17 | -google-runtime-int, 18 | -google-runtime-references, 19 | -google-readability-avoid-underscore-in-googletest-name, 20 | misc-definitions-in-headers, 21 | misc-static-assert, 22 | misc-unconventional-assign-operator, 23 | misc-uniqueptr-reset-release, 24 | misc-unused-alias-decls, 25 | misc-unused-using-decls, 26 | misc-unused-parameters, 27 | modernize-make-unique, 28 | modernize-use-noexcept, 29 | -modernize-redundant-void-arg, 30 | modernize-replace-auto-ptr, 31 | modernize-shrink-to-fit, 32 | modernize-use-bool-literals, 33 | modernize-use-nullptr, 34 | modernize-use-override, 35 | modernize-use-equals-default, 36 | readability-container-size-empty, 37 | readability-deleted-default, 38 | readability-function-size, 39 | readability-inconsistent-declaration-parameter-name, 40 | readability-redundant-control-flow, 41 | readability-redundant-smartptr-get, 42 | readability-braces-around-statements, 43 | readability-string-compare' 44 | WarningsAsErrors: '*' 45 | CheckOptions: 46 | - key: readability-function-size.StatementThreshold 47 | value: '60' 48 | - key: modernize-make-unique.MakeSmartPtrFunction 49 | value: 'absl::make_unique' 50 | - key: modernize-make-unique.MakeSmartPtrFunctionHeader 51 | value: 'absl/memory/memory.h' 52 | - key: google-readability-braces-around-statements.ShortStatementLines 53 | value: 1 54 | - key: readability-identifier-naming.ClassCase 55 | value: CamelCase 56 | - key: readability-function-size.ParameterThreshold 57 | value: 7 58 | 59 | -------------------------------------------------------------------------------- /test/modbus_test_adu.cpp: -------------------------------------------------------------------------------- 1 | #include "base/modbus_frame.h" 2 | #include "modbus/base/modbus.h" 3 | #include "modbus/base/modbus_types.h" 4 | #include "modbus_test_mocker.h" 5 | #include 6 | 7 | using namespace modbus; 8 | 9 | // TEST(ModbusRtuFrameDecoder, server_decode_readCoils_response_success) { 10 | // const ByteArray expect({0x00, 0x01, 0x00, 0x01, 0x00, 0x11, 0xAC, 0x17}); 11 | // pp::bytes::Buffer buffer; 12 | // buffer.Write(expect); 13 | 14 | // Adu adu; 15 | // ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 16 | 17 | // decoder.Decode(buffer, &adu); 18 | // EXPECT_EQ(true, decoder.IsDone()); 19 | // EXPECT_EQ(Error::kNoError, decoder.LasError()); 20 | // EXPECT_EQ(adu.serverAddress(), 0x00); 21 | // EXPECT_EQ(adu.functionCode(), 0x01); 22 | // EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x01, 0x00, 0x11)); 23 | //} 24 | 25 | //============================================================ 26 | 27 | TEST(TestModbusAdu, Constructor) { 28 | Adu adu(ServerAddress(1), FunctionCode::kReadCoils); 29 | EXPECT_EQ(FunctionCode::kReadCoils, adu.functionCode()); 30 | EXPECT_EQ(adu.error(), Error::kNoError); 31 | EXPECT_EQ(adu.isValid(), false); 32 | } 33 | 34 | TEST(TestModbusAdu, ConstructorError) { 35 | Adu adu(ServerAddress(1), FunctionCode::kReadCoils); 36 | EXPECT_EQ(FunctionCode::kReadCoils, adu.functionCode()); 37 | adu.setError(Error::kTimeout); 38 | EXPECT_EQ(adu.error(), Error::kTimeout); 39 | EXPECT_EQ(adu.isValid(), true); 40 | } 41 | 42 | 43 | 44 | TEST(TestAdu, modbusAduMarshalData) { 45 | struct Result { 46 | int size; 47 | modbus::ByteArray data; 48 | } expect{5, ByteArray({0x01, 0x01, 0x01, 0x02, 0x03})}; 49 | 50 | Adu adu; 51 | struct Result actual; 52 | 53 | adu.setServerAddress(0x1); 54 | adu.setFunctionCode(FunctionCode::kReadCoils); 55 | adu.setData({1, 2, 3}); 56 | actual.size = adu.marshalSize(); 57 | actual.data = adu.marshalAduWithoutCrc(); 58 | 59 | EXPECT_EQ(actual.data, expect.data); 60 | EXPECT_EQ(actual.size, expect.size); 61 | } 62 | -------------------------------------------------------------------------------- /include/bytes/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef HHT_BYTES_BUFFER_H 2 | #define HHT_BYTES_BUFFER_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace pp { 12 | namespace bytes { 13 | class Buffer; 14 | typedef std::shared_ptr BufferRef; 15 | 16 | class Buffer { 17 | public: 18 | Buffer(); 19 | ~Buffer() {} 20 | 21 | // Read All 22 | size_t Read(std::vector &p); 23 | 24 | // Read One Byte 25 | uint8_t ReadByte(); 26 | 27 | // Read N Bytes from buffer 28 | size_t ReadBytes(std::vector &p, size_t n); 29 | 30 | size_t Read(uint8_t *buffer, size_t n); 31 | size_t Read(char *buffer, size_t n); 32 | size_t ZeroCopyRead(uint8_t **ptr, size_t n); 33 | size_t ZeroCopyRead(char **ptr, size_t n); 34 | 35 | // write data into buffer 36 | size_t Write(const uint8_t d); 37 | size_t Write(const uint8_t *d, size_t len); 38 | size_t Write(const char *d, size_t len); 39 | 40 | size_t Write(const std::string &s); 41 | 42 | size_t Write(const std::vector &p); 43 | size_t Write(const std::vector &p); 44 | 45 | void UnReadByte(/*error*/); 46 | 47 | void UnReadBytes(size_t n /*,error &e*/); 48 | 49 | // return unreaded data size 50 | size_t Len() const; 51 | 52 | size_t Cap() const; 53 | 54 | void Reset(); 55 | 56 | bool PeekAt(std::vector &p, size_t index, size_t size) const; 57 | bool ZeroCopyPeekAt(uint8_t **p, size_t index, size_t size) const; 58 | bool ZeroCopyPeekAt(char **p, size_t index, size_t size) const; 59 | 60 | void Optimization(); 61 | 62 | void Resize(size_t len); 63 | 64 | // ReadFrom 65 | // WriteTo 66 | 67 | private: 68 | void growSpace(size_t len); 69 | 70 | size_t leftSpace(); 71 | 72 | void hasWritten(size_t len); 73 | 74 | void hasReaded(size_t len); 75 | 76 | uint8_t *beginWrite(); 77 | 78 | const uint8_t *beginWrite() const; 79 | 80 | uint8_t *lastRead(); 81 | 82 | const uint8_t *beginRead() const; 83 | 84 | uint8_t *begin(); 85 | 86 | const uint8_t *begin() const; 87 | 88 | std::vector b; 89 | size_t ridx; 90 | size_t widx; 91 | size_t size_; 92 | }; 93 | 94 | } // namespace bytes 95 | } // namespace pp 96 | 97 | #endif 98 | 99 | // void enable_wakeup(errors::error_code &error); 100 | -------------------------------------------------------------------------------- /examples/serialport_client_sixteen_bit_access.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void usage(); 9 | 10 | int main(int argc, char *argv[]) { 11 | QCoreApplication app(argc, argv); 12 | 13 | if (argc != 2) { 14 | qDebug() << "No serial port specified!"; 15 | usage(); 16 | return 1; 17 | } 18 | QString serialportName = argv[1]; 19 | QScopedPointer client( 20 | modbus::newQtSerialClient(serialportName)); 21 | 22 | client->setPrefix("one"); 23 | client->setOpenRetryTimes(5, 5000); 24 | client->setRetryTimes(3); 25 | 26 | auto sendAfter = [&](int delay) { 27 | QTimer::singleShot(delay, [&]() { 28 | client->readRegisters(modbus::ServerAddress(0x01), 29 | modbus::FunctionCode::kReadHoldingRegisters, 0, 10); 30 | client->readRegisters(modbus::ServerAddress(0x02), 31 | modbus::FunctionCode::kReadHoldingRegisters, 0, 10); 32 | }); 33 | }; 34 | 35 | QObject::connect(client.data(), &modbus::QModbusClient::clientClosed, [&]() { 36 | qDebug() << "client is closed" << client->errorString(); 37 | app.quit(); 38 | }); 39 | QObject::connect(client.data(), &modbus::QModbusClient::clientOpened, [&]() { 40 | qDebug() << "client is opened"; 41 | sendAfter(0); 42 | }); 43 | 44 | QObject::connect(client.data(), &modbus::QModbusClient::readRegistersFinished, 45 | [&](modbus::ServerAddress serverAddress, 46 | modbus::FunctionCode functionCode, 47 | modbus::Address startAddress, modbus::Quantity quantity, 48 | const std::vector &data, modbus::Error error) { 49 | std::shared_ptr _( 50 | nullptr, std::bind([&]() { 51 | printf("pending Request size:%zu\n", 52 | client->pendingRequestSize()); 53 | if (client->pendingRequestSize() == 0) { 54 | sendAfter(3000); 55 | } 56 | })); 57 | // handle you code 58 | }); 59 | 60 | client->open(); 61 | 62 | return app.exec(); 63 | } 64 | 65 | static void usage() { 66 | printf("usage: serialport_client_sixteen_bit_access serialport\n"); 67 | printf("example: serialport_client_sixteen_bit_access COM4\n"); 68 | } 69 | -------------------------------------------------------------------------------- /include/modbus/base/modbus_data.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_DATA_GENERATOR_H_ 2 | #define __MODBUS_DATA_GENERATOR_H_ 3 | 4 | #include "modbus_types.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace modbus { 14 | /// only store subclass of AbstractData 15 | class any { 16 | public: 17 | any() : content(0) {} 18 | 19 | template 20 | any(const ValueType &value) : content(new holder(value)) {} 21 | 22 | any(const any &other) : content(other.content ? other.content->clone() : 0) {} 23 | 24 | ~any() { delete content; } 25 | 26 | any &swap(any &rhs) { 27 | std::swap(content, rhs.content); 28 | return *this; 29 | } 30 | 31 | any &operator=(const any &rhs) { 32 | any(rhs).swap(*this); 33 | return *this; 34 | } 35 | 36 | bool empty() const { return !content; } 37 | 38 | const std::type_info &type() const { 39 | return content ? content->type() : typeid(void); 40 | } 41 | 42 | template 43 | static inline ValueType *any_cast(any *operand) { 44 | return &static_cast *>(operand->content)->held; 45 | } 46 | 47 | template 48 | static inline const ValueType *any_cast(const any *operand) { 49 | return any_cast(const_cast(operand)); 50 | } 51 | 52 | template static inline ValueType any_cast(any &operand) { 53 | using nonref = typename std::remove_reference::type; 54 | nonref *result = any_cast(&operand); 55 | if (!result) 56 | throw std::bad_cast(); 57 | return *result; 58 | } 59 | 60 | template 61 | static inline ValueType any_cast(const any &operand) { 62 | using nonref = typename std::remove_reference::type; 63 | return any_cast(const_cast(operand)); 64 | } 65 | 66 | private: 67 | class placeholder { 68 | public: 69 | virtual ~placeholder() {} 70 | 71 | public: 72 | virtual const std::type_info &type() const = 0; 73 | 74 | virtual placeholder *clone() const = 0; 75 | }; 76 | 77 | template class holder : public placeholder { 78 | public: 79 | holder(const ValueType &value) : held(value) {} 80 | virtual const std::type_info &type() const { return typeid(ValueType); } 81 | virtual placeholder *clone() const { return new holder(held); } 82 | 83 | ValueType held; 84 | 85 | private: 86 | holder &operator=(const holder &); 87 | }; 88 | 89 | placeholder *content; 90 | }; 91 | 92 | } // namespace modbus 93 | 94 | #endif // __MODBUS_DATA_GENERATOR_H_ 95 | -------------------------------------------------------------------------------- /examples/modbus_example_serialport_client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | static void logMessage(modbus::LogLevel level, const std::string &msg) { 7 | switch (level) { 8 | case modbus::LogLevel::kDebug: 9 | qDebug() << msg.c_str(); 10 | break; 11 | case modbus::LogLevel::kInfo: 12 | qInfo() << msg.c_str(); 13 | break; 14 | case modbus::LogLevel::kWarning: 15 | qWarning() << msg.c_str(); 16 | break; 17 | case modbus::LogLevel::kError: 18 | qWarning() << msg.c_str(); 19 | break; 20 | } 21 | } 22 | 23 | int main(int argc, char *argv[]) { 24 | QCoreApplication app(argc, argv); 25 | 26 | modbus::registerLogMessage(logMessage); 27 | 28 | QScopedPointer client( 29 | modbus::newQtSerialClient("/dev/ttyS0")); 30 | 31 | client->setOpenRetryTimes(5, 5000); 32 | 33 | QObject::connect(client.data(), &modbus::QModbusClient::clientClosed, [&]() { 34 | qDebug() << "client is closed" << client->errorString(); 35 | }); 36 | QObject::connect(client.data(), &modbus::QModbusClient::clientOpened, [&]() { 37 | qDebug() << "client is opened"; 38 | 39 | auto request = std::unique_ptr(new modbus::Request); 40 | 41 | modbus::SingleBitAccess access; 42 | access.setStartAddress(0); 43 | access.setQuantity(5); 44 | 45 | request->setServerAddress(0); 46 | request->setFunctionCode(modbus::FunctionCode::kReadCoils); 47 | request->setUserData(access); 48 | request->setData(access.marshalReadRequest()); 49 | 50 | client->sendRequest(request); 51 | }); 52 | 53 | QObject::connect( 54 | client.data(), &modbus::QModbusClient::requestFinished, 55 | [&](const modbus::Request &req, const modbus::Response &resp) { 56 | if (resp.error() != modbus::Error::kNoError) { 57 | qDebug() << resp.errorString().c_str(); 58 | return; 59 | } 60 | 61 | if (resp.isException()) { 62 | qDebug() << resp.errorString().c_str(); 63 | return; 64 | } 65 | modbus::SingleBitAccess access = 66 | modbus::any::any_cast(req.userData()); 67 | bool ok = access.unmarshalReadResponse(resp.data()); 68 | if (!ok) { 69 | qDebug() << "data is invalid"; 70 | return; 71 | } 72 | modbus::Address address = access.startAddress(); 73 | for (int offset = 0; offset < access.quantity(); offset++) { 74 | modbus::Address currentAddress = address + offset; 75 | std::cout << "address: " << currentAddress 76 | << " value: " << access.value(currentAddress); 77 | } 78 | }); 79 | 80 | client->open(); 81 | 82 | return app.exec(); 83 | } 84 | -------------------------------------------------------------------------------- /src/tools/modbusserver_client_session.cpp: -------------------------------------------------------------------------------- 1 | #include "modbusserver_client_session.h" 2 | #include "modbus/base/modbus.h" 3 | #include "modbus_frame.h" 4 | #include "modbus_server_p.h" 5 | 6 | namespace modbus { 7 | 8 | ClientSession::ClientSession(QModbusServerPrivate *d, 9 | AbstractConnection *connction, 10 | const CheckSizeFuncTable &table) 11 | : d_(d), client(connction) { 12 | decoder = createModbusFrameDecoder(d_->transferMode_, table); 13 | encoder = createModbusFrameEncoder(d_->transferMode_); 14 | } 15 | 16 | ClientSession::~ClientSession() { client->deleteLater(); } 17 | 18 | std::string ClientSession::fullName() const { return client->fullName(); } 19 | 20 | void ClientSession::handleModbusRequest(pp::bytes::Buffer &buffer) { 21 | processModbusRequest(buffer); 22 | ReplyResponse(); 23 | // fixme:use move 24 | response_ = Adu(); 25 | request_ = Adu(); 26 | } 27 | 28 | void ClientSession::processModbusRequest(pp::bytes::Buffer &buffer) { 29 | decoder->Decode(buffer, &request_); 30 | if (!decoder->IsDone()) { 31 | log(d_->log_prefix_, LogLevel::kDebug, "{} need more data", 32 | client->fullName()); 33 | return; 34 | } 35 | const auto lastError = decoder->LasError(); 36 | decoder->Clear(); 37 | buffer.Reset(); 38 | 39 | /** 40 | *if the requested server address is not self server address, and is 41 | *not brocast too, discard the recived buffer. 42 | */ 43 | if (request_.serverAddress() != d_->serverAddress_ && 44 | request_.serverAddress() != Adu::kBrocastAddress) { 45 | log(d_->log_prefix_, LogLevel::kError, 46 | "{} unexpected server address,my " 47 | "address[{}]", 48 | client->fullName(), d_->serverAddress_); 49 | return; 50 | } 51 | 52 | if (lastError != Error::kNoError) { 53 | log(d_->log_prefix_, LogLevel::kError, "{} invalid request", 54 | client->fullName(), lastError); 55 | d_->createErrorReponse(request_.functionCode(), lastError, &response_); 56 | return; 57 | } 58 | 59 | /** 60 | *if the function code is not supported, 61 | *discard the recive buffer, 62 | */ 63 | if (!d_->handleFuncRouter_.contains(request_.functionCode())) { 64 | log(d_->log_prefix_, LogLevel::kError, "{} unsupported function code", 65 | client->fullName(), request_.functionCode()); 66 | 67 | d_->createErrorReponse(request_.functionCode(), Error::kIllegalFunctionCode, 68 | &response_); 69 | return; 70 | } 71 | if (request_.serverAddress() == Adu::kBrocastAddress) { 72 | d_->processBrocastRequest(&request_); 73 | return; 74 | } 75 | d_->processRequest(&request_, &response_); 76 | } 77 | 78 | void ClientSession::ReplyResponse() { 79 | if (!response_.isValid()) { 80 | return; 81 | } 82 | 83 | response_.setTransactionId(request_.transactionId()); 84 | encoder->Encode(&response_, writeBuffer); 85 | 86 | uint8_t *p = nullptr; 87 | const int len = writeBuffer.Len(); 88 | writeBuffer.ZeroCopyRead(&p, len); 89 | 90 | client->write(reinterpret_cast(p), len); 91 | 92 | if (d_->enableDump_) { 93 | log(d_->log_prefix_, LogLevel::kDebug, "S[{}]:[{}]", client->fullName(), 94 | dump(d_->transferMode_, reinterpret_cast(p), len)); 95 | } 96 | } 97 | 98 | } // namespace modbus 99 | -------------------------------------------------------------------------------- /test/modbus_test_mocker.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_TEST_MOCKER_H 2 | #define MODBUS_TEST_MOCKER_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace testing; 15 | 16 | /** 17 | * a mock of serial port 18 | * we use this class for testing of modbus::QModbusClient 19 | */ 20 | class MockSerialPort : public modbus::AbstractIoDevice { 21 | Q_OBJECT 22 | public: 23 | explicit MockSerialPort(QObject *parent = nullptr) 24 | : modbus::AbstractIoDevice(parent) { 25 | setupCallName(); 26 | } 27 | ~MockSerialPort() override = default; 28 | MOCK_METHOD0(open, void()); 29 | MOCK_METHOD0(close, void()); 30 | MOCK_METHOD2(write, void(const char *data, size_t size)); 31 | MOCK_METHOD0(readAll, QByteArray()); 32 | MOCK_METHOD0(clear, void()); 33 | MOCK_METHOD0(name, std::string()); 34 | 35 | void setupCallName() { 36 | ON_CALL(*this, open).WillByDefault(Invoke([&]() { emit opened(); })); 37 | ON_CALL(*this, close).WillByDefault(Invoke([&]() { emit closed(); })); 38 | ON_CALL(*this, write(_, _)) 39 | .WillByDefault(Invoke([&](const char *data, size_t size) { 40 | emit bytesWritten(size); 41 | emit readyRead(); 42 | })); 43 | ON_CALL(*this, clear()).WillByDefault([&]() {}); 44 | 45 | EXPECT_CALL(*this, clear()).WillRepeatedly([&]() {}); 46 | EXPECT_CALL(*this, name).WillRepeatedly(Return("COM1")); 47 | EXPECT_CALL(*this, open).WillRepeatedly(Invoke([&]() { emit opened(); })); 48 | EXPECT_CALL(*this, close).WillRepeatedly(Invoke([&]() { emit closed(); })); 49 | } 50 | 51 | /* void setupOpenSuccessWriteFailedDelegate() { */ 52 | /* ON_CALL(*this, open).WillByDefault(Invoke([&]() { emit opened(); })); */ 53 | /* ON_CALL(*this, close).WillByDefault(Invoke([&]() { emit closed(); })); */ 54 | /* ON_CALL(*this, write) */ 55 | /* .WillByDefault(Invoke([&](const char *data, size_t size) { */ 56 | /* emit error("write serial failed"); */ 57 | /* })); */ 58 | /* } */ 59 | /* void setupOpenFailed() { */ 60 | /* ON_CALL(*this, open).WillByDefault(Invoke([&]() { */ 61 | /* emit error("open serial failed"); */ 62 | /* })); */ 63 | /* } */ 64 | 65 | /* void setupTestForWrite() { */ 66 | /* ON_CALL(*this, open).WillByDefault(Invoke([&]() { emit opened(); })); */ 67 | /* ON_CALL(*this, close).WillByDefault(Invoke([&]() { emit closed(); })); */ 68 | /* ON_CALL(*this, write) */ 69 | /* .WillByDefault(Invoke( */ 70 | /* [&](const char *data, size_t size) { emit bytesWritten(size); 71 | * })); */ 72 | /* } */ 73 | 74 | /* void setupTestForWriteRead() { */ 75 | /* ON_CALL(*this, open).WillByDefault(Invoke([&]() { emit opened(); })); */ 76 | /* ON_CALL(*this, close).WillByDefault(Invoke([&]() { emit closed(); })); */ 77 | /* ON_CALL(*this, write) */ 78 | /* .WillByDefault(Invoke([&](const char *data, size_t size) { */ 79 | /* emit bytesWritten(size); */ 80 | /* QTimer::singleShot(10, [&]() { emit readyRead(); }); */ 81 | /* })); */ 82 | /* } */ 83 | }; 84 | 85 | #endif /* MODBUS_TEST_MOCKER_H */ 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # modbus 2 | a modbus library for c++11,using qt 3 | 4 | ## required 5 | QT version >= 5.6 6 | 7 | gcc version >= 4.9 8 | 9 | vs version >= vs 2015 10 | 11 | ## features 12 | [x] modbus master serial client 13 | 14 | [x] modbus master tcp client 15 | 16 | [x] modbus master udp client 17 | 18 | [x] disconnection and reconnection 19 | 20 | [x] request failed and retry 21 | 22 | [x] single bit access 23 | 24 | [x] sixteen bit access 25 | 26 | [x] custom functions 27 | 28 | ## function support 29 | 30 | [x] 0x01 Read coils 31 | 32 | [x] 0x02 Read input discrete 33 | 34 | [x] 0x03 Read multiple registers 35 | 36 | [x] 0x04 Read input register 37 | 38 | [x] 0x05 Write single coil 39 | 40 | [x] 0x06 Write single register 41 | 42 | [x] 0x0f Write multiple coils 43 | 44 | [x] 0x10 Write multiple registers 45 | 46 | [x] 0x17 Read/Write multiple registers 47 | 48 | 49 | ## build from source 50 | 51 | * windows 52 | 53 | ```cmd 54 | git clone --recursive https://github.com/paopaol/modbus.git 55 | cd modbus 56 | cmake -Bbuild -H. -G"Visual Studio 14 2015" -DCMAKE_PREFIX_PATH=%QTDIR% 57 | cmake --build build --config rlease 58 | ``` 59 | 60 | * linux 61 | 62 | ```cmd 63 | git clone --recursive https://github.com/paopaol/modbus.git 64 | cd modbus 65 | cmake -Bbuild -H. -DCMAKE_PREFIX_PATH=$QTDIR 66 | cmake --build build --config rlease 67 | ``` 68 | 69 | 70 | ## examples 71 | 72 | * single bit access 73 | 74 | ```cpp 75 | #include 76 | #include 77 | 78 | static void processFunctionCode3( 79 | modbus::ServerAddress serverAddress, modbus::Address address, 80 | const QVector &valueList, modbus::Error error) { 81 | if (error != modbus::Error::kNoError) { 82 | return; 83 | } 84 | 85 | for (const auto &value : valueList) { 86 | qDebug() << "value is:" << value.toUint16(); 87 | } 88 | } 89 | 90 | static void processFunctionCode6(modbus::ServerAddress serverAddress, 91 | modbus::Address address, modbus::Error error) { 92 | qDebug() << "write signle register:" << (error == modbus::Error::kNoError); 93 | } 94 | 95 | int main(int argc, char *argv[]) { 96 | QCoreApplication app(argc, argv); 97 | 98 | modbus::QModbusClient *client = modbus::newQtSerialClient("COM1"); 99 | 100 | QObject::connect(client, &modbus::QModbusClient::readRegistersFinished, &app, 101 | processFunctionCode3); 102 | QObject::connect(client, &modbus::QModbusClient::writeSingleRegisterFinished, 103 | &app, processFunctionCode6); 104 | client->open(); 105 | 106 | /** 107 | * function code 0x03 108 | */ 109 | client->readRegisters(modbus::ServerAddress(0x01), modbus::FunctionCode(0x03), 110 | modbus::Address(0x00), modbus::Quantity(0x02)); 111 | 112 | /** 113 | * function code 0x06 114 | */ 115 | client->writeSingleRegister(modbus::ServerAddress(0x01), 116 | modbus::Address(0x01), 117 | modbus::SixteenBitValue(0x17)); 118 | 119 | return app.exec(); 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /src/tools/modbus_qt_serialport.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | namespace modbus { 6 | class QtSerialPort : public AbstractIoDevice { 7 | Q_OBJECT 8 | public: 9 | explicit QtSerialPort(QObject *parent = nullptr) : AbstractIoDevice(parent) { 10 | setupEnvironment(); 11 | } 12 | ~QtSerialPort() override = default; 13 | 14 | bool 15 | setBaudRate(qint32 baudRate, 16 | QSerialPort::Directions directions = QSerialPort::AllDirections) { 17 | return serialPort_.setBaudRate(baudRate, directions); 18 | } 19 | 20 | bool setDataBits(QSerialPort::DataBits dataBits) { 21 | return serialPort_.setDataBits(dataBits); 22 | } 23 | 24 | bool setParity(QSerialPort::Parity parity) { 25 | return serialPort_.setParity(parity); 26 | } 27 | 28 | bool setStopBits(QSerialPort::StopBits stopBits) { 29 | return serialPort_.setStopBits(stopBits); 30 | } 31 | 32 | void setPortName(const QString &name) { serialPort_.setPortName(name); } 33 | std::string name() override { return serialPort_.portName().toStdString(); } 34 | 35 | void open() override { 36 | bool success = serialPort_.open(QIODevice::ReadWrite); 37 | if (!success) { 38 | return; 39 | } 40 | emit opened(); 41 | } 42 | 43 | void close() override { 44 | if (serialPort_.isOpen()) { 45 | serialPort_.close(); 46 | return; 47 | } 48 | emit closed(); 49 | } 50 | 51 | void write(const char *data, size_t size) override { 52 | serialPort_.write(data, size); 53 | } 54 | 55 | QByteArray readAll() override { return serialPort_.readAll(); } 56 | 57 | void clear() override { serialPort_.clear(); } 58 | 59 | private: 60 | void setupEnvironment() { 61 | connect(&serialPort_, &QSerialPort::aboutToClose, this, 62 | [&]() { emit closed(); }); 63 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 1)) 64 | connect(&serialPort_, 65 | static_cast( 66 | &QSerialPort::error), 67 | this, [&](QSerialPort::SerialPortError err) { 68 | #else 69 | connect(&serialPort_, &QSerialPort::errorOccurred, this, 70 | [&](QSerialPort::SerialPortError err) { 71 | #endif 72 | if (err == QSerialPort::SerialPortError::NoError) { 73 | emit error(""); 74 | return; 75 | } 76 | emit error(serialPort_.errorString()); 77 | }); 78 | connect(&serialPort_, &QSerialPort::bytesWritten, this, 79 | &QtSerialPort::bytesWritten); 80 | connect(&serialPort_, &QSerialPort::readyRead, this, 81 | &QtSerialPort::readyRead); 82 | } 83 | 84 | QSerialPort serialPort_; 85 | }; 86 | 87 | QModbusClient * 88 | newQtSerialClient(const QString &serialName, QSerialPort::BaudRate baudRate, 89 | QSerialPort::DataBits dataBits, QSerialPort::Parity parity, 90 | QSerialPort::StopBits stopBits, QObject *parent) { 91 | QtSerialPort *port = new QtSerialPort(parent); 92 | port->setBaudRate(baudRate); 93 | port->setDataBits(dataBits); 94 | port->setParity(parity); 95 | port->setStopBits(stopBits); 96 | port->setPortName(serialName); 97 | 98 | QModbusClient *client = new QModbusClient(port, parent); 99 | return client; 100 | } 101 | #include "modbus_qt_serialport.moc" 102 | 103 | } // namespace modbus 104 | -------------------------------------------------------------------------------- /src/tools/modbus_serial_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace modbus { 8 | class SerialConnection : public AbstractConnection { 9 | Q_OBJECT 10 | public: 11 | explicit SerialConnection(QSerialPort *serialPort, QObject *parent = nullptr) 12 | : AbstractConnection(parent), serialPort_(serialPort), 13 | readBuffer_(new pp::bytes::Buffer()) { 14 | connect(serialPort_, &QSerialPort::aboutToClose, this, 15 | [&]() { emit disconnected(fd()); }); 16 | connect(serialPort_, &QSerialPort::readyRead, this, 17 | &SerialConnection::onClientReadyRead); 18 | } 19 | ~SerialConnection() override = default; 20 | 21 | bool open() { 22 | bool success = serialPort_->open(QIODevice::ReadWrite); 23 | if (!success) { 24 | log(prefix(), LogLevel::kError, "open {} {}", 25 | serialPort_->portName().toStdString(), 26 | serialPort_->errorString().toStdString()); 27 | return false; 28 | } 29 | return true; 30 | } 31 | quintptr fd() const override { return quintptr(serialPort_->handle()); } 32 | 33 | void write(const char *data, size_t size) override { 34 | serialPort_->write(data, size); 35 | } 36 | 37 | std::string name() const override { 38 | return serialPort_->portName().toStdString(); 39 | } 40 | 41 | std::string fullName() const override { 42 | return serialPort_->portName().toStdString(); 43 | } 44 | 45 | private: 46 | void onClientReadyRead() { 47 | while (serialPort_->bytesAvailable() > 0) { 48 | char buf[1024] = {0}; 49 | int size = serialPort_->read(buf, sizeof(buf)); 50 | readBuffer_->Write(buf, size); 51 | } 52 | emit messageArrived(fd(), readBuffer_); 53 | } 54 | 55 | QSerialPort *serialPort_; 56 | BytesBufferPtr readBuffer_; 57 | }; 58 | 59 | class SerialServer : public AbstractServer { 60 | Q_OBJECT 61 | public: 62 | explicit SerialServer(QSerialPort *serialPort, QObject *parent = nullptr) 63 | : AbstractServer(parent), 64 | serialConnection_(new SerialConnection(serialPort, this)) {} 65 | 66 | bool listenAndServe() override { 67 | bool success = serialConnection_->open(); 68 | if (!success) { 69 | return false; 70 | } 71 | handleNewConnFunc_(serialConnection_); 72 | return true; 73 | } 74 | 75 | private: 76 | SerialConnection *serialConnection_; 77 | }; 78 | 79 | QModbusServer *createQModbusSerialServer(const QString &serialName, 80 | QSerialPort::BaudRate baudRate, 81 | QSerialPort::DataBits dataBits, 82 | QSerialPort::Parity parity, 83 | QSerialPort::StopBits stopBits, 84 | QObject *parent) { 85 | QSerialPort *serialPort = new QSerialPort(parent); 86 | serialPort->setBaudRate(baudRate); 87 | serialPort->setDataBits(dataBits); 88 | serialPort->setParity(parity); 89 | serialPort->setStopBits(stopBits); 90 | serialPort->setPortName(serialName); 91 | 92 | auto serialServer = new SerialServer(serialPort, parent); 93 | auto modbusServer = new QModbusServer(serialServer, parent); 94 | modbusServer->setTransferMode(TransferMode::kRtu); 95 | return modbusServer; 96 | } 97 | 98 | } // namespace modbus 99 | 100 | #include "modbus_serial_server.moc" 101 | -------------------------------------------------------------------------------- /test/modbus_test_sixteen_bit_access.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | TEST(SixteenBitAccess, setgetStartAddress) { 5 | modbus::SixteenBitAccess access; 6 | 7 | access.setStartAddress(3); 8 | EXPECT_EQ(3, access.startAddress()); 9 | } 10 | 11 | TEST(SixteenBitAccess, setgetQuantity) { 12 | modbus::SixteenBitAccess access; 13 | 14 | access.setQuantity(3); 15 | EXPECT_EQ(3, access.quantity()); 16 | } 17 | 18 | TEST(SixteenBitAccess, setValue_outOfRange) { 19 | modbus::SixteenBitAccess access; 20 | 21 | access.setStartAddress(0x00); 22 | access.setQuantity(0x01); 23 | 24 | access.setValue(0x02, 4); 25 | bool ok; 26 | access.value(0x1000, &ok); 27 | EXPECT_FALSE(ok); 28 | } 29 | 30 | TEST(SixteenBitAccess, setgetValue) { 31 | modbus::SixteenBitAccess access; 32 | 33 | access.setStartAddress(0x00); 34 | access.setQuantity(0x10); 35 | access.setValue(0x05); 36 | EXPECT_EQ(0x05, access.value(0x00).toUint16()); 37 | 38 | access.setValue(0x01, 1); 39 | EXPECT_EQ(1, access.value(0x01).toUint16()); 40 | 41 | access.setValue(0x02, 4); 42 | EXPECT_EQ(4, access.value(0x02).toUint16()); 43 | 44 | // test not exists values 45 | bool ok; 46 | access.value(0x1000, &ok); 47 | EXPECT_FALSE(ok); 48 | } 49 | 50 | TEST(SixteenBitAccess, marshalMultipleReadRequest_success) { 51 | modbus::SixteenBitAccess access; 52 | 53 | access.setStartAddress(0x6b); 54 | access.setQuantity(0x03); 55 | 56 | modbus::ByteArray expectedArray({0x00, 0x6b, 0x00, 0x03}); 57 | modbus::ByteArray array = access.marshalMultipleReadRequest(); 58 | EXPECT_EQ(array, expectedArray); 59 | } 60 | 61 | TEST(SixteenBitAccess, marshalSingleWriteRequest_success) { 62 | modbus::SixteenBitAccess access; 63 | 64 | access.setStartAddress(0x01); 65 | access.setValue(0x03); 66 | 67 | modbus::ByteArray expectedArray({0x00, 0x01, 0x00, 0x03}); 68 | auto array = access.marshalSingleWriteRequest(); 69 | EXPECT_EQ(array, expectedArray); 70 | } 71 | 72 | TEST(SixteenBitAccess, marshalMultipleWriteRequest_success) { 73 | modbus::SixteenBitAccess access; 74 | 75 | access.setStartAddress(0x01); 76 | access.setQuantity(0x02); 77 | access.setValue(access.startAddress(), 0x0a); 78 | access.setValue(access.startAddress() + 1, 0x0102); 79 | 80 | modbus::ByteArray expectedArray( 81 | {0x00, 0x01, 0x00, 0x02, 0x04, 0x00, 0x0a, 0x01, 0x02}); 82 | auto array = access.marshalMultipleWriteRequest(); 83 | EXPECT_EQ(array, expectedArray); 84 | } 85 | 86 | TEST(SixteenBitAccess, unmarshalReadResponse_success) { 87 | modbus::SixteenBitAccess access; 88 | 89 | access.setStartAddress(0x6b); 90 | access.setQuantity(0x03); 91 | 92 | modbus::ByteArray response({0x06, 0x02, 0x2b, 0x00, 0x00, 0x00, 0x64}); 93 | 94 | bool success = access.unmarshalReadResponse(response); 95 | EXPECT_EQ(true, success); 96 | EXPECT_EQ(0x022b, access.value(access.startAddress()).toUint16()); 97 | EXPECT_EQ(0x00, access.value(access.startAddress() + 1).toUint16()); 98 | EXPECT_EQ(0x64, access.value(access.startAddress() + 2).toUint16()); 99 | } 100 | 101 | TEST(SixteenBitAccess, unmarshalReadResponse_failed) { 102 | modbus::SixteenBitAccess access; 103 | 104 | access.setStartAddress(0x6b); 105 | access.setQuantity(0x03); 106 | 107 | modbus::ByteArray badResponse({0x06, 0x02, 0x2b, 0x00, 0x00, 0x00}); 108 | bool success = access.unmarshalReadResponse(badResponse); 109 | EXPECT_FALSE(success); 110 | 111 | modbus::ByteArray badResponse2({0x05, 0x02, 0x2b, 0x00, 0x00, 0x00}); 112 | success = access.unmarshalReadResponse(badResponse2); 113 | EXPECT_FALSE(success); 114 | } 115 | -------------------------------------------------------------------------------- /include/modbus/base/modbus_tool.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_TOOL_H_ 2 | #define __MODBUS_TOOL_H_ 3 | 4 | #include "fmt/core.h" 5 | #include "modbus_types.h" 6 | #include 7 | 8 | namespace modbus { 9 | struct CrcCtx { 10 | uint16_t in = 0xFFFF; 11 | uint16_t poly = 0x8005; 12 | 13 | void clear(); 14 | void crc16(const uint8_t *data, size_t size); 15 | uint16_t end(); 16 | }; 17 | 18 | class tool { 19 | public: 20 | static inline std::string dumpHex(const ByteArray &byteArray, 21 | const std::string &delimiter = " ") { 22 | std::string hexString; 23 | const int size = byteArray.size(); 24 | hexString.reserve(size * 3); 25 | for (const auto &ch : byteArray) { 26 | hexString.append(fmt::format("{}{:02x}", delimiter, ch)); 27 | } 28 | return hexString; 29 | } 30 | 31 | static inline std::string dumpHex(const uint8_t *data, int size, 32 | const std::string &delimiter = " ") { 33 | std::string hexString; 34 | hexString.reserve(size * 3); 35 | for (int i = 0; i < size; i++) { 36 | hexString.append(fmt::format("{}{:02x}", delimiter, data[i])); 37 | } 38 | return hexString; 39 | } 40 | 41 | static inline std::string dumpRaw(const ByteArray &byteArray) { 42 | std::string output; 43 | output.reserve(byteArray.size()); 44 | for (const auto &ch : byteArray) { 45 | output += ch; 46 | } 47 | return output; 48 | } 49 | 50 | static inline std::string dumpRaw(const uint8_t *data, int size) { 51 | std::string output; 52 | output.reserve(size); 53 | for (int i = 0; i < size; i++) { 54 | output += data[i]; 55 | } 56 | return output; 57 | } 58 | 59 | static inline ByteArray fromHexString(const uint8_t *hexString, int size) { 60 | static std::map table = { 61 | {'0', 0}, {'1', 1}, {'2', 2}, {'3', 3}, {'4', 4}, {'5', 5}, 62 | {'6', 6}, {'7', 7}, {'8', 8}, {'9', 9}, {'a', 10}, {'b', 11}, 63 | {'c', 12}, {'d', 13}, {'e', 14}, {'f', 15}, {'A', 10}, {'B', 11}, 64 | {'C', 12}, {'D', 13}, {'E', 14}, {'F', 15}}; 65 | 66 | ByteArray array; 67 | for (int i = 0; i < size && size >= 2; i += 2) { 68 | auto first = table.find(hexString[i]); 69 | auto second = table.find(hexString[i + 1]); 70 | if (first == table.end() || second == table.end()) { 71 | break; 72 | } 73 | char ch = 0; 74 | ch = first->second << 4 | second->second; 75 | array.push_back(ch); 76 | } 77 | return array; 78 | } 79 | 80 | static uint16_t crc16_modbus(const uint8_t *data, size_t size); 81 | static uint8_t lrc_modbus(const uint8_t *data, size_t len); 82 | /** 83 | * Calculate the crc check of data and then return data+crc. 84 | The crc check is added in the order of the first low order and the high 85 | order. 86 | */ 87 | static inline ByteArray appendCrc(const ByteArray &data) { 88 | uint16_t crc = crc16_modbus((uint8_t *)(data.data()), data.size()); 89 | auto dataWithCrc = data; 90 | /// first push low bit 91 | dataWithCrc.push_back(crc % 256); 92 | /// second push high bit 93 | dataWithCrc.push_back(crc / 256); 94 | return dataWithCrc; 95 | } 96 | 97 | static inline ByteArray appendLrc(const ByteArray &data) { 98 | uint8_t lrc = lrc_modbus((uint8_t *)data.data(), data.size()); 99 | auto dataWithLrc = data; 100 | dataWithLrc.push_back(lrc); 101 | 102 | return dataWithLrc; 103 | } 104 | 105 | static inline ByteArray subArray(const ByteArray &array, size_t index, 106 | int n = -1) { 107 | if (n == -1) { 108 | return ByteArray(array.begin() + index, array.end()); 109 | } else { 110 | return ByteArray(array.begin() + index, array.begin() + index + n); 111 | } 112 | } 113 | }; // namespace modbus 114 | 115 | } // namespace modbus 116 | 117 | #endif // __MODBUS_TOOL_H_ 118 | -------------------------------------------------------------------------------- /src/tools/modbus_tcp_server.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace modbus { 11 | static QList localIpList(); 12 | 13 | class TcpConnection : public AbstractConnection { 14 | Q_OBJECT 15 | public: 16 | explicit TcpConnection(quintptr fd, QObject *parent = nullptr) 17 | : AbstractConnection(parent), fd_(fd), 18 | readBuffer_(new pp::bytes::Buffer()) { 19 | socket_.setSocketDescriptor(fd); 20 | connect(&socket_, &QTcpSocket::disconnected, this, 21 | [&]() { emit disconnected(fd_); }); 22 | connect(&socket_, &QTcpSocket::readyRead, this, 23 | &TcpConnection::onClientReadyRead); 24 | } 25 | ~TcpConnection() override = default; 26 | 27 | quintptr fd() const override { return socket_.socketDescriptor(); } 28 | 29 | void write(const char *data, size_t size) override { 30 | socket_.write(data, size); 31 | } 32 | std::string name() const override { 33 | return socket_.peerAddress().toString().toStdString(); 34 | } 35 | std::string fullName() const override { 36 | QString address; 37 | auto peerAddress = socket_.peerAddress(); 38 | bool ok = true; 39 | QHostAddress ipv4(peerAddress.toIPv4Address(&ok)); 40 | if (ok) { 41 | address = ipv4.toString(); 42 | } else { 43 | QHostAddress ipv6(peerAddress.toIPv6Address()); 44 | address = ipv6.toString(); 45 | } 46 | return QString("%1:%2").arg(address).arg(socket_.peerPort()).toStdString(); 47 | } 48 | 49 | private: 50 | void onClientReadyRead() { 51 | while (socket_.bytesAvailable() > 0) { 52 | char buf[1024] = {0}; 53 | int size = socket_.read(buf, sizeof(buf)); 54 | readBuffer_->Write(buf, size); 55 | } 56 | emit messageArrived(socket_.socketDescriptor(), readBuffer_); 57 | } 58 | 59 | QTcpSocket socket_; 60 | quintptr fd_ = -1; 61 | BytesBufferPtr readBuffer_; 62 | }; 63 | 64 | class PrivateTcpServer : public QTcpServer { 65 | Q_OBJECT 66 | public: 67 | explicit PrivateTcpServer(QObject *parent = nullptr) : QTcpServer(parent) {} 68 | ~PrivateTcpServer() override = default; 69 | signals: 70 | void newConnectionArrived(qintptr _t1); 71 | 72 | protected: 73 | void incomingConnection(qintptr socketDescriptor) override { 74 | emit newConnectionArrived(socketDescriptor); 75 | } 76 | }; 77 | 78 | class TcpServer : public AbstractServer { 79 | Q_OBJECT 80 | public: 81 | explicit TcpServer(QObject *parent = nullptr) : AbstractServer(parent) { 82 | connect(&tcpServer_, &PrivateTcpServer::newConnectionArrived, this, 83 | &TcpServer::incomingConnection); 84 | } 85 | void setListenPort(uint16_t port) { port_ = port; } 86 | bool listenAndServe() override { 87 | bool success = tcpServer_.listen(QHostAddress::Any, port_); 88 | if (!success) { 89 | log(prefix(), LogLevel::kError, "tcp server listen(:{}) failed. {}", 90 | port_, tcpServer_.errorString().toStdString()); 91 | return false; 92 | } 93 | 94 | auto ipList = localIpList(); 95 | log(prefix(), LogLevel::kInfo, "tcp server listened at [{}]:{}", 96 | ipList.join(",").toStdString(), port_); 97 | return true; 98 | } 99 | 100 | protected: 101 | void incomingConnection(qintptr socketDescriptor) { 102 | if (!handleNewConnFunc_) { 103 | QTcpSocket unused; 104 | unused.setSocketDescriptor(socketDescriptor); 105 | unused.close(); 106 | return; 107 | } 108 | 109 | // log 110 | auto conn = new TcpConnection(socketDescriptor, this); 111 | handleNewConnFunc_(conn); 112 | } 113 | 114 | private: 115 | PrivateTcpServer tcpServer_; 116 | uint16_t port_ = 502; 117 | }; 118 | 119 | QModbusServer *createQModbusTcpServer(uint16_t port, QObject *parent) { 120 | auto tcpServer = new TcpServer(parent); 121 | tcpServer->setListenPort(port); 122 | auto modbusServer = new QModbusServer(tcpServer, parent); 123 | modbusServer->setTransferMode(TransferMode::kMbap); 124 | return modbusServer; 125 | } 126 | 127 | static QList localIpList() { 128 | QList ipList; 129 | auto interfaceList = QNetworkInterface::allInterfaces(); 130 | for (auto &networkInterface : interfaceList) { 131 | auto addressEntrys = networkInterface.addressEntries(); 132 | for (auto &addessEntry : addressEntrys) { 133 | auto ip = addessEntry.ip(); 134 | if (ip.protocol() != 135 | QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { 136 | continue; 137 | } 138 | ipList.push_back(ip.toString()); 139 | } 140 | } 141 | return ipList; 142 | } 143 | 144 | } // namespace modbus 145 | 146 | #include "modbus_tcp_server.moc" 147 | -------------------------------------------------------------------------------- /src/base/buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace pp { 5 | namespace bytes { 6 | 7 | Buffer::Buffer() : b(8192), ridx(0), widx(0), size_(b.size()) {} 8 | 9 | // Read All 10 | size_t Buffer::Read(std::vector &p) { return ReadBytes(p, Len()); } 11 | 12 | // Read One Byte 13 | uint8_t Buffer::ReadByte() { 14 | uint8_t *ch = lastRead(); 15 | hasReaded(1); 16 | return *ch; 17 | } 18 | 19 | // Read N Bytes from buffer 20 | size_t Buffer::ReadBytes(std::vector &p, size_t n) { 21 | assert(n >= 0 && "buffer::readbytes(), bad input paramer"); 22 | 23 | p.clear(); 24 | n = n > Len() ? Len() : n; 25 | std::copy(lastRead(), lastRead() + n, std::back_inserter(p)); 26 | hasReaded(n); 27 | return n; 28 | } 29 | 30 | size_t Buffer::Read(uint8_t *buffer, size_t n) { 31 | assert(n >= 0 && "buffer::read(), bad input paramer"); 32 | n = n > Len() ? Len() : n; 33 | std::copy(lastRead(), lastRead() + n, buffer); 34 | hasReaded(n); 35 | return n; 36 | } 37 | 38 | size_t Buffer::Read(char *buffer, size_t n) { 39 | return Read(reinterpret_cast(buffer), n); 40 | } 41 | 42 | size_t Buffer::ZeroCopyRead(uint8_t **ptr, size_t n) { 43 | assert(n >= 0 && "buffer::read(), bad input paramer"); 44 | n = n > Len() ? Len() : n; 45 | *ptr = lastRead(); 46 | hasReaded(n); 47 | return n; 48 | } 49 | 50 | size_t Buffer::ZeroCopyRead(char **ptr, size_t n) { 51 | return ZeroCopyRead(reinterpret_cast(ptr), n); 52 | } 53 | 54 | size_t Buffer::Write(const uint8_t d) { return Write(&d, 1); } 55 | 56 | // write data into buffer 57 | size_t Buffer::Write(const uint8_t *d, size_t len) { 58 | if (leftSpace() < len) { 59 | Optimization(); 60 | } 61 | if (leftSpace() < len) { 62 | growSpace(static_cast(size_ + len)); 63 | } 64 | std::copy(d, d + len, beginWrite()); 65 | hasWritten(len); 66 | return len; 67 | } 68 | 69 | size_t Buffer ::Write(const char *d, size_t len) { 70 | return Write(reinterpret_cast(d), len); 71 | } 72 | size_t Buffer::Write(const std::string &s) { 73 | return Write(s.c_str(), static_cast(s.size())); 74 | } 75 | 76 | size_t Buffer::Write(const std::vector &p) { 77 | return Write(p.data(), static_cast(p.size())); 78 | } 79 | 80 | size_t Buffer::Write(const std::vector &p) { 81 | return Write(p.data(), static_cast(p.size())); 82 | } 83 | 84 | void Buffer::UnReadByte(/*error*/) { UnReadBytes(1); } 85 | 86 | void Buffer::UnReadBytes(size_t n /*,error &e*/) { 87 | assert(static_cast(lastRead() - begin()) >= n && 88 | "buffer::unreadbytes too much data size"); 89 | ridx -= n; 90 | } 91 | 92 | // return unreaded data size 93 | size_t Buffer::Len() const { return widx - ridx; } 94 | 95 | size_t Buffer::Cap() const { return size_; } 96 | 97 | void Buffer::Reset() { 98 | ridx = 0; 99 | widx = 0; 100 | } 101 | 102 | bool Buffer::PeekAt(std::vector &p, size_t index, size_t size) const { 103 | if (index < 0 || index >= Len()) { 104 | return false; 105 | } 106 | if (size <= 0) { 107 | return false; 108 | } 109 | index = ridx + index; 110 | size_t len = widx - index; 111 | if (size > len) { 112 | return false; 113 | } 114 | 115 | p.clear(); 116 | std::copy(b.data() + index, b.data() + index + size, std::back_inserter(p)); 117 | return true; 118 | } 119 | 120 | bool Buffer::ZeroCopyPeekAt(uint8_t **p, size_t index, size_t size) const { 121 | if (index < 0 || index >= Len()) { 122 | return false; 123 | } 124 | if (size <= 0) { 125 | return false; 126 | } 127 | index = ridx + index; 128 | size_t len = widx - index; 129 | if (size > len) { 130 | return false; 131 | } 132 | 133 | *p = (uint8_t *)b.data() + index; 134 | return true; 135 | } 136 | 137 | bool Buffer::ZeroCopyPeekAt(char **p, size_t index, size_t size) const { 138 | return ZeroCopyPeekAt(reinterpret_cast(p), index, size); 139 | } 140 | 141 | void Buffer::Optimization() { 142 | if (ridx == 0) { 143 | return; 144 | } 145 | 146 | size_t len = Len(); 147 | std::copy(begin() + ridx, begin() + widx, begin()); 148 | ridx = 0; 149 | widx = ridx + len; 150 | assert(widx < size_); 151 | } 152 | 153 | void Buffer::Resize(size_t len) { 154 | if (leftSpace() < len) { 155 | Optimization(); 156 | } 157 | if (leftSpace() < len) { 158 | growSpace(static_cast(size_ + len)); 159 | } 160 | hasWritten(len); 161 | } 162 | 163 | // ReadFrom 164 | // WriteTo 165 | 166 | void Buffer::growSpace(size_t len) { 167 | b.resize(widx + len); 168 | size_ = b.size(); 169 | } 170 | 171 | size_t Buffer::leftSpace() { return size_ - widx; } 172 | 173 | void Buffer::hasWritten(size_t len) { 174 | widx += len; 175 | assert(widx <= size_); 176 | } 177 | void Buffer::hasReaded(size_t len) { ridx += len; } 178 | 179 | uint8_t *Buffer::beginWrite() { return begin() + widx; } 180 | 181 | const uint8_t *Buffer::beginWrite() const { return begin() + widx; } 182 | 183 | uint8_t *Buffer::lastRead() { return begin() + ridx; } 184 | 185 | const uint8_t *Buffer::beginRead() const { return begin() + ridx; } 186 | 187 | uint8_t *Buffer::begin() { return &b[0]; } 188 | 189 | const uint8_t *Buffer::begin() const { return &b[0]; } 190 | 191 | // BufferRef NewBuffer(); 192 | // { 193 | // BufferRef b = std::make_shared(); 194 | // return b; 195 | // } 196 | } // namespace bytes 197 | } // namespace pp 198 | -------------------------------------------------------------------------------- /src/tools/modbus_qt_socket.cpp: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_QT_SOCKET_H 2 | #define MODBUS_QT_SOCKET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace modbus { 10 | class QtTcpSocket : public AbstractIoDevice { 11 | Q_OBJECT 12 | public: 13 | explicit QtTcpSocket(QObject *parent = nullptr) 14 | : AbstractIoDevice(parent), socket_(new QTcpSocket(this)) { 15 | setupEnvironment(); 16 | } 17 | ~QtTcpSocket() override { 18 | if (socket_->isOpen()) { 19 | socket_->close(); 20 | } 21 | socket_->deleteLater(); 22 | } 23 | 24 | void setHostName(const QString &hostName) { hostName_ = hostName; } 25 | 26 | void setPort(quint16 port) { port_ = port; } 27 | 28 | std::string name() override { 29 | return QString("%1:%2").arg(hostName_).arg(port_).toStdString(); 30 | } 31 | 32 | void open() override { socket_->connectToHost(hostName_, port_); } 33 | 34 | void close() override { 35 | if (socket_->isOpen()) { 36 | socket_->close(); 37 | return; 38 | } 39 | emit closed(); 40 | } 41 | 42 | void write(const char *data, size_t size) override { 43 | socket_->write(data, size); 44 | } 45 | 46 | QByteArray readAll() override { return socket_->readAll(); } 47 | 48 | void clear() override {} 49 | 50 | private: 51 | void setupEnvironment() { 52 | socket_->socketOption(QAbstractSocket::KeepAliveOption); 53 | socket_->setSocketOption(QAbstractSocket::LowDelayOption, 1); 54 | connect(socket_, &QAbstractSocket::disconnected, this, 55 | &QtTcpSocket::closed); 56 | connect(socket_, &QAbstractSocket::connected, this, &QtTcpSocket::opened); 57 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 1)) 58 | connect( 59 | socket_, 60 | static_cast( 61 | &QAbstractSocket::error), 62 | this, [&](QAbstractSocket::SocketError /*err*/) { 63 | #else 64 | connect(socket_, &QAbstractSocket::errorOccurred, this, 65 | [&](QAbstractSocket::SocketError err) { 66 | #endif 67 | emit error(socket_->errorString()); 68 | }); 69 | connect(socket_, &QAbstractSocket::bytesWritten, this, 70 | &QtTcpSocket::bytesWritten); 71 | connect(socket_, &QAbstractSocket::readyRead, this, 72 | &QtTcpSocket::readyRead); 73 | } 74 | 75 | QString hostName_; 76 | quint16 port_; 77 | QTcpSocket *socket_; 78 | }; 79 | 80 | class QtUdpSocket : public AbstractIoDevice { 81 | Q_OBJECT 82 | public: 83 | explicit QtUdpSocket(QObject *parent = nullptr) 84 | : AbstractIoDevice(parent), socket_(new QUdpSocket(this)) { 85 | setupEnvironment(); 86 | } 87 | ~QtUdpSocket() override { socket_->deleteLater(); } 88 | 89 | void setHostName(const QString &hostName) { hostName_ = hostName; } 90 | 91 | void setPort(quint16 port) { port_ = port; } 92 | 93 | std::string name() override { 94 | return QString("%1:%2").arg(hostName_).arg(port_).toStdString(); 95 | } 96 | 97 | void open() override { emit opened(); } 98 | 99 | void close() override { 100 | emit closed(); 101 | return; 102 | } 103 | 104 | void write(const char *data, size_t size) override { 105 | socket_->writeDatagram(data, size, QHostAddress(hostName_), port_); 106 | } 107 | 108 | QByteArray readAll() override { 109 | QByteArray datagram; 110 | 111 | do { 112 | datagram.resize(socket_->pendingDatagramSize()); 113 | socket_->readDatagram(datagram.data(), datagram.size()); 114 | } while (socket_->hasPendingDatagrams()); 115 | return datagram; 116 | } 117 | 118 | void clear() override {} 119 | 120 | private: 121 | void setupEnvironment() { 122 | socket_->bind(port_); 123 | 124 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 1)) 125 | connect( 126 | socket_, 127 | static_cast( 128 | &QAbstractSocket::error), 129 | this, [&](QAbstractSocket::SocketError err) { 130 | #else 131 | connect(socket_, &QAbstractSocket::errorOccurred, this, 132 | [&](QAbstractSocket::SocketError err) { 133 | #endif 134 | emit error(socket_->errorString()); 135 | }); 136 | connect(socket_, &QAbstractSocket::bytesWritten, this, 137 | &QtUdpSocket::bytesWritten); 138 | connect(socket_, &QAbstractSocket::readyRead, this, 139 | &QtUdpSocket::readyRead); 140 | } 141 | 142 | QString hostName_; 143 | quint16 port_; 144 | QUdpSocket *socket_; 145 | }; 146 | 147 | QModbusClient *newSocketClient(QAbstractSocket::SocketType type, 148 | const QString &hostName, quint16 port, 149 | QObject *parent) { 150 | 151 | AbstractIoDevice *ioDevice = nullptr; 152 | if (type == QAbstractSocket::TcpSocket) { 153 | QtTcpSocket *socket = new QtTcpSocket(parent); 154 | socket->setHostName(hostName); 155 | socket->setPort(port); 156 | ioDevice = socket; 157 | } else { 158 | QtUdpSocket *socket = new QtUdpSocket(parent); 159 | socket->setHostName(hostName); 160 | socket->setPort(port); 161 | ioDevice = socket; 162 | } 163 | 164 | QModbusClient *client = new QModbusClient(ioDevice, parent); 165 | client->setTransferMode(TransferMode::kMbap); 166 | return client; 167 | } 168 | #include "modbus_qt_socket.moc" 169 | } // namespace modbus 170 | 171 | #endif /* MODBUS_QT_SOCKET_H */ 172 | -------------------------------------------------------------------------------- /test/modbus_test_single_bit_access.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | TEST(SingleBitAccess, marshalReadRequest) { 5 | modbus::SingleBitAccess access; 6 | 7 | access.setStartAddress(1); 8 | access.setQuantity(8); 9 | modbus::ByteArray expectPayload({0x00, 0x01, 0x00, 0x08}); 10 | modbus::ByteArray payload = access.marshalReadRequest(); 11 | 12 | EXPECT_EQ(expectPayload, payload); 13 | } 14 | 15 | TEST(SingleBitAccess, set_get) { 16 | { 17 | modbus::SingleBitAccess access; 18 | access.setStartAddress(1); 19 | EXPECT_EQ(1, access.startAddress()); 20 | } 21 | 22 | { 23 | modbus::SingleBitAccess access; 24 | access.setQuantity(8); 25 | EXPECT_EQ(8, access.quantity()); 26 | } 27 | 28 | { 29 | modbus::SingleBitAccess access; 30 | access.setValue(true); 31 | EXPECT_EQ(true, access.value(access.startAddress())); 32 | } 33 | 34 | { 35 | modbus::SingleBitAccess access; 36 | access.setValue(0x1234 /*no exists*/, true); 37 | EXPECT_FALSE(access.value(access.startAddress())); 38 | } 39 | } 40 | 41 | TEST(SingleBitAccess, setValue_getValue) { 42 | modbus::SingleBitAccess access; 43 | 44 | access.setValue(true); 45 | auto valueEx = access.value(access.startAddress()); 46 | EXPECT_EQ(valueEx, true); 47 | 48 | access.setValue(modbus::Address(0x03), true); 49 | valueEx = access.value(0x03); 50 | EXPECT_EQ(valueEx, true); 51 | } 52 | 53 | TEST(SingleBitAccess, marshalSingleWriteRequest) { 54 | modbus::SingleBitAccess access; 55 | 56 | access.setStartAddress(0xac); 57 | /** 58 | * singleWrite must set quantity to 1 59 | */ 60 | access.setQuantity(1); 61 | access.setValue(true); 62 | 63 | modbus::ByteArray expectPayload({0x00, 0xac, 0xff, 0x00}); 64 | 65 | modbus::ByteArray payload = access.marshalSingleWriteRequest(); 66 | EXPECT_EQ(expectPayload, payload); 67 | } 68 | 69 | TEST(SingleBitAccess, marshalMultipleWriteRequest) { 70 | modbus::SingleBitAccess access; 71 | modbus::Address startAddress = 0x13; 72 | 73 | access.setStartAddress(startAddress); 74 | access.setQuantity(10); 75 | 76 | // cd 01 77 | // cd 78 | // 1100 1101 79 | access.setValue(startAddress, true); 80 | access.setValue(startAddress + 1, false); 81 | access.setValue(startAddress + 2, true); 82 | access.setValue(startAddress + 3, true); 83 | 84 | access.setValue(startAddress + 4, false); 85 | access.setValue(startAddress + 5, false); 86 | access.setValue(startAddress + 6, true); 87 | access.setValue(startAddress + 7, true); 88 | // 01 89 | // 0000 0001 90 | access.setValue(startAddress + 8, true); 91 | access.setValue(startAddress + 9, false); 92 | 93 | modbus::ByteArray expectData({0x00, 0x13, 0x00, 0x0a, 0x02, 0xcd, 0x01}); 94 | modbus::ByteArray data = access.marshalMultipleWriteRequest(); 95 | EXPECT_EQ(data, expectData); 96 | } 97 | 98 | TEST(SingleBitAccess, unmarshalReadResponse_dataIsValid_unmarshalSuccess) { 99 | modbus::SingleBitAccess access; 100 | 101 | access.setStartAddress(0x13); 102 | access.setQuantity(0x13); 103 | 104 | modbus::ByteArray goodData( 105 | {0x03, 0xcd /*1100 1101*/, 0x6b, 0x05 /*0000 0101*/}); 106 | bool ok = access.unmarshalReadResponse(goodData); 107 | EXPECT_EQ(ok, true); 108 | EXPECT_EQ(access.value(0x13), true); 109 | EXPECT_EQ(access.value(0x14), false); 110 | EXPECT_EQ(access.value(0x15), true); 111 | EXPECT_EQ(access.value(0x16), true); 112 | EXPECT_EQ(access.value(23), false); 113 | EXPECT_EQ(access.value(24), false); 114 | EXPECT_EQ(access.value(25), true); 115 | EXPECT_EQ(access.value(26), true); 116 | 117 | EXPECT_EQ(access.value(35), true); 118 | EXPECT_EQ(access.value(36), false); 119 | EXPECT_EQ(access.value(37), true); 120 | } 121 | 122 | TEST(SingleBitAccess, marshalReadResponse_success) { 123 | modbus::SingleBitAccess access; 124 | 125 | access.setStartAddress(0x01); 126 | access.setQuantity(0x09); 127 | 128 | access.setValue(access.startAddress() + 0, true); 129 | access.setValue(access.startAddress() + 1, false); 130 | access.setValue(access.startAddress() + 2, true); 131 | access.setValue(access.startAddress() + 3, true); 132 | access.setValue(access.startAddress() + 4, false); 133 | access.setValue(access.startAddress() + 5, true); 134 | access.setValue(access.startAddress() + 6, true); 135 | access.setValue(access.startAddress() + 7, true); 136 | access.setValue(access.startAddress() + 8, false); 137 | 138 | auto dataArray = access.marshalReadResponse(); 139 | EXPECT_EQ(dataArray, modbus::ByteArray({0x02, 0xed, 0x00})); 140 | } 141 | 142 | TEST(SingleBitAccess, unmarshalReadResponse_dataIsInValid_unmarshalFailed) { 143 | modbus::SingleBitAccess access; 144 | 145 | access.setStartAddress(0x13); 146 | access.setQuantity(0x13); 147 | 148 | modbus::ByteArray badData({0x03, 0xcd /*1100 1101*/, 0x6b}); 149 | bool ok = access.unmarshalReadResponse(badData); 150 | EXPECT_EQ(ok, false); 151 | } 152 | 153 | TEST(SingleBitAccess, unmarshalReadRequest_success) { 154 | modbus::SingleBitAccess access; 155 | 156 | bool ok = 157 | access.unmarshalReadRequest(modbus::ByteArray({0x00, 0x01, 0x00, 0x20})); 158 | EXPECT_EQ(ok, true); 159 | EXPECT_EQ(access.startAddress(), 0x01); 160 | EXPECT_EQ(access.quantity(), 0x20); 161 | } 162 | 163 | TEST(SingleBitAccess, unmarshalReadRequest_failed) { 164 | modbus::SingleBitAccess access; 165 | 166 | bool ok = access.unmarshalReadRequest(modbus::ByteArray({0x00, 0x01, 0x20})); 167 | EXPECT_EQ(ok, false); 168 | } 169 | -------------------------------------------------------------------------------- /include/modbus/base/sixteen_bit_access.h: -------------------------------------------------------------------------------- 1 | #ifndef SIXTEEN_BIT_ACCESS_H 2 | #define SIXTEEN_BIT_ACCESS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace modbus { 11 | class SixteenBitAccess { 12 | public: 13 | SixteenBitAccess() = default; 14 | virtual ~SixteenBitAccess() = default; 15 | 16 | void setStartAddress(Address address) { 17 | startAddress_ = address; 18 | if (quantity() == 0) { 19 | setQuantity(1); 20 | } 21 | } 22 | Address startAddress() const { return startAddress_; } 23 | 24 | void setQuantity(Quantity quantity) { 25 | quantity_ = quantity; 26 | value_array_.resize(quantity_ * 2); 27 | } 28 | 29 | Quantity quantity() const { return quantity_; } 30 | 31 | void setValue(uint16_t value) { setValue(startAddress_, value); } 32 | void setValue(Address address, uint16_t value) { 33 | if (address >= startAddress_ + quantity() || address < startAddress_) { 34 | /// out of range 35 | return; 36 | } 37 | SixteenBitValue v(value); 38 | size_t i = (address - startAddress_) * 2; 39 | value_array_[i] = v.firstByte(); 40 | value_array_[i + 1] = v.secondByte(); 41 | } 42 | 43 | ByteArray value() const { return value_array_; } 44 | 45 | SixteenBitValue value(Address address, bool *ok = nullptr) const { 46 | Address start_address = startAddress(); 47 | Quantity quan = quantity(); 48 | int size = value_array_.size(); 49 | for (int i = (address - startAddress_) * 2; 50 | i >= 0 && address < start_address + quan; i += 2) { 51 | if (ok) { 52 | *ok = true; 53 | } 54 | if (i >= size) { 55 | break; 56 | } 57 | return SixteenBitValue(value_array_[i], value_array_[i + 1]); 58 | } 59 | 60 | if (ok) { 61 | *ok = false; 62 | } 63 | return SixteenBitValue(); 64 | } 65 | 66 | ByteArray marshalMultipleReadRequest() const { 67 | return ByteArray({static_cast(startAddress_ / 256), 68 | static_cast(startAddress_ % 256), 69 | static_cast(quantity() / 256), 70 | static_cast(quantity() % 256)}); 71 | } 72 | 73 | bool unmarshalAddressQuantity(const ByteArray &data) { 74 | size_t size; 75 | auto result = bytesRequired<4>(size, data.data(), data.size()); 76 | if (result != CheckSizeResult::kSizeOk) { 77 | return false; 78 | } 79 | startAddress_ = data[0] * 256 + data[1]; 80 | setQuantity(data[2] * 256 + data[3]); 81 | return true; 82 | } 83 | 84 | bool unmarshalSingleWriteRequest(const ByteArray &data) { 85 | size_t size; 86 | auto result = bytesRequired<4>(size, data.data(), data.size()); 87 | if (result != CheckSizeResult::kSizeOk) { 88 | return false; 89 | } 90 | startAddress_ = data[0] * 256 + data[1]; 91 | setQuantity(1); 92 | SixteenBitValue value(data[2], data[3]); 93 | setValue(startAddress_, value.toUint16()); 94 | return true; 95 | } 96 | 97 | bool unmarshalMulitpleWriteRequest(const ByteArray &data) { 98 | size_t size; 99 | auto result = 100 | bytesRequiredStoreInArrayIndex<4>(size, data.data(), data.size()); 101 | if (result != CheckSizeResult::kSizeOk) { 102 | return false; 103 | } 104 | startAddress_ = data[0] * 256 + data[1]; 105 | setQuantity(data[2] * 256 + data[3]); 106 | auto lenght = data[4]; 107 | if (lenght % 2 != 0) { 108 | return false; 109 | } 110 | if (quantity() != lenght / 2) { 111 | return false; 112 | } 113 | value_array_ = tool::subArray(data, 5); 114 | return true; 115 | } 116 | 117 | ByteArray marshalSingleWriteRequest() const { 118 | ByteArray array; 119 | 120 | array.reserve(4); 121 | 122 | array.push_back(startAddress_ / 256); 123 | array.push_back(startAddress_ % 256); 124 | 125 | array.push_back(value_array_[0]); 126 | array.push_back(value_array_[1]); 127 | 128 | return array; 129 | } 130 | 131 | ByteArray marshalMultipleWriteRequest() const { 132 | ByteArray array; 133 | array.reserve(5 + value_array_.size()); 134 | 135 | array.push_back(startAddress_ / 256); 136 | array.push_back(startAddress_ % 256); 137 | 138 | array.push_back(quantity() / 256); 139 | array.push_back(quantity() % 256); 140 | 141 | array.push_back(quantity() * 2); 142 | 143 | array.insert(array.end(), value_array_.begin(), value_array_.end()); 144 | return array; 145 | } 146 | 147 | ByteArray marshalMultipleReadResponse() { 148 | ByteArray array; 149 | array.reserve(1 + value_array_.size()); 150 | 151 | array.push_back(quantity() * 2); 152 | array.insert(array.end(), value_array_.begin(), value_array_.end()); 153 | return array; 154 | } 155 | 156 | bool unmarshalReadResponse(const ByteArray &data) { 157 | size_t size = 0; 158 | auto result = 159 | bytesRequiredStoreInArrayIndex<0>(size, data.data(), data.size()); 160 | if (result != CheckSizeResult::kSizeOk) { 161 | return false; 162 | } 163 | 164 | /** 165 | * Sixteen bit value, one value use 2 bytes.so, index0 + numberOfValues * 2 166 | */ 167 | if (size % 2 != 1) { 168 | return false; 169 | } 170 | 171 | value_array_ = tool::subArray(data, 1); 172 | return true; 173 | } 174 | 175 | private: 176 | Address startAddress_ = 0; 177 | Quantity quantity_ = 0; 178 | ByteArray value_array_; 179 | }; 180 | 181 | bool processReadRegisters(const Request &request, const Response &response, 182 | SixteenBitAccess *access, 183 | const std::string &log_prefix = ""); 184 | } // namespace modbus 185 | 186 | #endif /* SIXTEEN_BIT_ACCESS_H */ 187 | -------------------------------------------------------------------------------- /include/modbus/base/modbus.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_H_ 2 | #define __MODBUS_H_ 3 | 4 | #include "bytes/buffer.h" 5 | #include "modbus/base/modbus_types.h" 6 | #include "modbus_data.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace modbus { 13 | 14 | /** 15 | *Different function codes carry different data in the data in the pdu. So we 16 | *use this class to check whether the size of the data in the received request 17 | *or response is valid 18 | */ 19 | enum class CheckSizeResult { kNeedMoreData, kSizeOk, kFailed }; 20 | 21 | using CheckSizeFunc = std::function; 23 | 24 | template 25 | static inline CheckSizeResult 26 | bytesRequired(size_t &size, const uint8_t * /*buffer*/, int len) { 27 | if (len < nbytes) { 28 | return CheckSizeResult::kNeedMoreData; 29 | } 30 | size = nbytes; 31 | return CheckSizeResult::kSizeOk; 32 | } 33 | 34 | template 35 | static inline CheckSizeResult 36 | bytesRequiredStoreInArrayIndex(size_t &size, const uint8_t *buffer, int len) { 37 | int preSize = index + 1; 38 | if (len < preSize) { 39 | return CheckSizeResult::kNeedMoreData; 40 | } 41 | size_t bytes = buffer[index]; 42 | if ((size_t)len < bytes + preSize) { 43 | return CheckSizeResult::kNeedMoreData; 44 | } 45 | size = bytes + preSize; 46 | return CheckSizeResult::kSizeOk; 47 | } 48 | 49 | /** 50 | * Application data unit 51 | * in modbus frame, it is address field + pdu + error checking. 52 | * but our adu not include error checking 53 | */ 54 | class Adu { 55 | public: 56 | static const ServerAddress kBrocastAddress = 0; 57 | static const uint8_t kExceptionByte = 0x80; 58 | 59 | Adu() {} 60 | Adu(ServerAddress serverAddress, FunctionCode functionCode) 61 | : serverAddress_(serverAddress), functionCode_(functionCode) {} 62 | 63 | ~Adu() {} 64 | 65 | void setServerAddress(ServerAddress serverAddress) { 66 | serverAddress_ = serverAddress; 67 | } 68 | ServerAddress serverAddress() const { return serverAddress_; } 69 | bool isBrocast() { return serverAddress_ == kBrocastAddress; } 70 | 71 | void setFunctionCode(FunctionCode functionCode) { 72 | functionCode_ = functionCode; 73 | } 74 | FunctionCode functionCode() const { 75 | return FunctionCode(uint8_t(functionCode_) & ~kExceptionByte); 76 | } 77 | void setError(Error errorCode) { 78 | functionCode_ = FunctionCode(functionCode_ | kExceptionByte); 79 | setData({static_cast(errorCode)}); 80 | } 81 | void setData(const ByteArray &byteArray) { data_ = byteArray; } 82 | void setData(const uint8_t *data, int n) { 83 | data_.resize(n); 84 | std::copy(data, data + n, data_.begin()); 85 | } 86 | 87 | const ByteArray &data() const { return data_; } 88 | 89 | bool isException() const { return functionCode_ & kExceptionByte; } 90 | 91 | Error error() const { 92 | if (!isException()) { 93 | return Error::kNoError; 94 | } 95 | return Error(data_[0]); 96 | } 97 | 98 | std::string errorString() const { 99 | std::stringstream s; 100 | 101 | s << error(); 102 | return s.str(); 103 | } 104 | 105 | bool isValid() const { return !data_.empty(); } 106 | 107 | size_t marshalSize() const { return 1 + 1 + data_.size(); } 108 | 109 | void setTransactionId(uint16_t transactionId) { 110 | transactionId_ = transactionId; 111 | } 112 | 113 | uint16_t transactionId() const { return transactionId_; } 114 | /** 115 | * @brief marshalAduWithoutCrc,that is: serveraddress + fuction code + payload 116 | */ 117 | ByteArray marshalAduWithoutCrc() { 118 | ByteArray array; 119 | array.reserve(2 + data_.size()); 120 | 121 | array.push_back(serverAddress()); 122 | if (isException()) { 123 | array.push_back(FunctionCode(functionCode() | kExceptionByte)); 124 | } else { 125 | array.push_back(functionCode()); 126 | } 127 | for (int i = 0, size = data_.size(); i < size; i++) { 128 | array.push_back(data_[i]); 129 | } 130 | return array; 131 | } 132 | 133 | private: 134 | ServerAddress serverAddress_ = 0; 135 | // Pdu pdu_; 136 | FunctionCode functionCode_ = FunctionCode::kInvalidCode; 137 | ByteArray data_; 138 | uint16_t transactionId_ = 0; 139 | }; 140 | 141 | // the index of array will be used as the functionCode 142 | using CheckSizeFuncTable = std::array; 143 | 144 | class ModbusFrameDecoder { 145 | public: 146 | explicit ModbusFrameDecoder(const CheckSizeFuncTable &table) 147 | : checkSizeFuncTable_(table) {} 148 | 149 | virtual ~ModbusFrameDecoder() = default; 150 | 151 | virtual CheckSizeResult Decode(pp::bytes::Buffer &buffer, Adu *adu) = 0; 152 | virtual bool IsDone() const = 0; 153 | virtual void Clear() = 0; 154 | virtual Error LasError() const = 0; 155 | 156 | protected: 157 | CheckSizeFuncTable checkSizeFuncTable_; 158 | }; 159 | 160 | class ModbusFrameEncoder { 161 | public: 162 | virtual ~ModbusFrameEncoder() = default; 163 | 164 | virtual void Encode(const Adu *adu, pp::bytes::Buffer &buffer) = 0; 165 | }; 166 | 167 | /** 168 | * a modbus request 169 | */ 170 | class Request : public Adu { 171 | public: 172 | Request() {} 173 | Request(ServerAddress serverAddress, FunctionCode functionCode, 174 | const any &userData, const ByteArray &data) 175 | : Adu(serverAddress, functionCode), userData_(userData) { 176 | setData(data); 177 | } 178 | Request(const Adu &adu) : Adu(adu) {} 179 | void setUserData(const any &userData) { userData_ = userData; } 180 | any userData() const { return userData_; } 181 | const any &userData() { return userData_; } 182 | 183 | private: 184 | any userData_; 185 | }; 186 | 187 | /** 188 | * a modbus response 189 | */ 190 | using Response = Adu; 191 | 192 | void registerLogMessage(const LogWriter &logger); 193 | 194 | } // namespace modbus 195 | 196 | #endif // __MODBUS_H_ 197 | -------------------------------------------------------------------------------- /src/tools/modbus_client_p.h: -------------------------------------------------------------------------------- 1 | #ifndef MODBUS_SERIAL_CLIENT_P_H 2 | #define MODBUS_SERIAL_CLIENT_P_H 3 | 4 | #include "modbus/base/modbus.h" 5 | #include "modbus_client_types.h" 6 | #include "modbus_frame.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace modbus { 15 | enum class SessionState { kIdle, kSendingRequest, kWaitingResponse }; 16 | 17 | inline std::ostream &operator<<(std::ostream &output, 18 | const SessionState &state) { 19 | switch (state) { 20 | case SessionState::kIdle: 21 | output << "idle"; 22 | break; 23 | case SessionState::kSendingRequest: 24 | output << "sending-request"; 25 | break; 26 | case SessionState::kWaitingResponse: 27 | output << "waiting-response"; 28 | break; 29 | default: 30 | output.setstate(std::ios_base::failbit); 31 | } 32 | 33 | return output; 34 | } 35 | 36 | class QModbusClientPrivate : public QObject { 37 | Q_OBJECT 38 | public: 39 | explicit QModbusClientPrivate(AbstractIoDevice *serialPort, 40 | QObject *parent = nullptr) 41 | : QObject(parent) { 42 | initMemberValues(); 43 | device_ = new ReconnectableIoDevice(serialPort, this); 44 | } 45 | ~QModbusClientPrivate() override = default; 46 | 47 | Element *enqueueAndPeekLastElement() { 48 | elementQueue_.push_back(new Element()); 49 | return elementQueue_.back(); 50 | } 51 | 52 | void scheduleNextRequest(int delay) { 53 | /** 54 | * only in idle state can send request 55 | */ 56 | if (sessionState_.state() != SessionState::kIdle) { 57 | return; 58 | } 59 | 60 | if (elementQueue_.empty()) { 61 | return; 62 | } 63 | 64 | /*after some delay, the request will be sent,so we change the state to 65 | * sending request*/ 66 | sessionState_.setState(SessionState::kSendingRequest); 67 | QTimer::singleShot(delay, this, [&]() { 68 | if (elementQueue_.empty()) { 69 | return; 70 | } 71 | smart_assert(sessionState_.state() == 72 | SessionState::kSendingRequest)(sessionState_.state()); 73 | /** 74 | * take out the first request,send it out, 75 | */ 76 | auto &ele = elementQueue_.front(); 77 | 78 | // set next transactionId 79 | if (transferMode_ == TransferMode::kMbap) { 80 | ele->request->setTransactionId(nextTransactionId_++); 81 | } 82 | 83 | encoder_->Encode(ele->request.get(), writerBuffer_); 84 | ele->totalBytes = writerBuffer_.Len(); 85 | if (enableDump_) { 86 | log(log_prefix_, LogLevel::kDebug, "{} will send: {}", device_->name(), 87 | dump(transferMode_, writerBuffer_)); 88 | } 89 | 90 | uint8_t *p = nullptr; 91 | int len = writerBuffer_.Len(); 92 | writerBuffer_.ZeroCopyRead(&p, len); 93 | device_->write(reinterpret_cast(p), len); 94 | }); 95 | } 96 | 97 | void initMemberValues() { 98 | sessionState_.setState(SessionState::kIdle); 99 | waitConversionDelay_ = 200; 100 | t3_5_ = 60; 101 | waitResponseTimeout_ = 1000; 102 | retryTimes_ = 0; /// default no retry 103 | transferMode_ = TransferMode::kRtu; 104 | 105 | waitResponseTimer_ = new QTimer(this); 106 | checkSizeFuncTable_ = creatDefaultCheckSizeFuncTableForClient(); 107 | decoder_ = createModbusFrameDecoder(transferMode_, checkSizeFuncTable_); 108 | encoder_ = createModbusFrameEncoder(transferMode_); 109 | } 110 | 111 | /** 112 | * In rtu mode, only one request can be sent at the same time and then 113 | * processed. If multiple requests are sent consecutively, subsequent requests 114 | * are not ignored and are placed in the queue. Each time a request is taken 115 | * from the queue is processed, and when a request is completely processed, 116 | * the next element in the queue is processed. For the current code 117 | * implementation, the first element in the queue is the request that is 118 | * currently being processed. So, after the request is processed, it will be 119 | * removed. 120 | */ 121 | ElementQueue elementQueue_; 122 | StateManager sessionState_; 123 | ReconnectableIoDevice *device_ = nullptr; 124 | int waitConversionDelay_; 125 | int t3_5_; 126 | int waitResponseTimeout_; 127 | int retryTimes_; 128 | QTimer *waitResponseTimer_ = nullptr; 129 | bool waitTimerAlive_ = true; 130 | QString errorString_; 131 | 132 | /// the default transfer mode must be rtu mode 133 | TransferMode transferMode_; 134 | 135 | /// defualt is disabled 136 | bool enableDiagnosis_ = false; 137 | RuntimeDiagnosis runtimeDiagnosis_; 138 | bool enableDump_ = true; 139 | 140 | CheckSizeFuncTable checkSizeFuncTable_; 141 | std::unique_ptr decoder_; 142 | std::unique_ptr encoder_; 143 | uint16_t nextTransactionId_ = 0x01; 144 | 145 | pp::bytes::Buffer readBuffer_; 146 | pp::bytes::Buffer writerBuffer_; 147 | std::string log_prefix_; 148 | }; 149 | 150 | class ReconnectableIoDevicePrivate : public QObject { 151 | Q_OBJECT 152 | public: 153 | explicit ReconnectableIoDevicePrivate(AbstractIoDevice *iodevice, 154 | QObject *parent = nullptr) 155 | : QObject(parent), ioDevice_(iodevice) { 156 | ioDevice_->setParent(this); 157 | } 158 | 159 | ~ReconnectableIoDevicePrivate() override = default; 160 | 161 | int openRetryTimes_ = 0; 162 | int openRetryTimesBack_ = 0; 163 | int reopenDelay_ = 1000; 164 | AbstractIoDevice *ioDevice_; 165 | std::string log_prefix_; 166 | /** 167 | * if user call ReconnectableIoDevice::close(), this is force close 168 | * if the connection broken,the device is closed, this is not force close 169 | */ 170 | bool forceClose_ = false; 171 | StateManager connectionState_; 172 | QString errorString_; 173 | }; 174 | 175 | } // namespace modbus 176 | 177 | #endif /* MODBUS_SERIAL_CLIENT_P_H */ 178 | -------------------------------------------------------------------------------- /src/tools/modbus_server.cpp: -------------------------------------------------------------------------------- 1 | #include "modbus_server_p.h" 2 | #include "modbus_url_parser.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace modbus { 9 | QModbusServer::QModbusServer(AbstractServer *server, QObject *parent) 10 | : QObject(parent), d_ptr(new QModbusServerPrivate(this)) { 11 | qRegisterMetaType("SixteenBitValue"); 12 | qRegisterMetaType
("Address"); 13 | qRegisterMetaType>("QVector"); 14 | qRegisterMetaType("ByteArray"); 15 | Q_D(QModbusServer); 16 | d->setServer(server); 17 | d->setEnv(); 18 | } 19 | 20 | QModbusServer::~QModbusServer() = default; 21 | 22 | void QModbusServer::setMaxClients(int maxClients) { 23 | Q_D(QModbusServer); 24 | d->setMaxClients(maxClients); 25 | } 26 | 27 | int QModbusServer::maxClients() const { 28 | const Q_D(QModbusServer); 29 | return d->maxClients(); 30 | } 31 | 32 | void QModbusServer::setTransferMode(TransferMode transferMode) { 33 | Q_D(QModbusServer); 34 | d->setTransferMode(transferMode); 35 | } 36 | 37 | TransferMode QModbusServer::transferMode() const { 38 | const Q_D(QModbusServer); 39 | return d->transferMode(); 40 | } 41 | 42 | void QModbusServer::setServerAddress(ServerAddress serverAddress) { 43 | Q_D(QModbusServer); 44 | d->setServerAddress(serverAddress); 45 | } 46 | 47 | void QModbusServer::enableDump(bool enable) { 48 | Q_D(QModbusServer); 49 | d->enableDump(enable); 50 | } 51 | 52 | void QModbusServer::setPrefix(const QString &prefix) { 53 | Q_D(QModbusServer); 54 | d->log_prefix_ = prefix.toStdString(); 55 | } 56 | 57 | ServerAddress QModbusServer::serverAddress() const { 58 | const Q_D(QModbusServer); 59 | return d->serverAddress(); 60 | } 61 | 62 | void QModbusServer::addBlacklist(const QString &clientIp) { 63 | Q_D(QModbusServer); 64 | d->addBlacklist(clientIp); 65 | } 66 | 67 | QList QModbusServer::blacklist() const { 68 | const Q_D(QModbusServer); 69 | return d->blacklist(); 70 | } 71 | 72 | void QModbusServer::setCanWriteSingleBitValueFunc( 73 | const canWriteSingleBitValueFunc &func) { 74 | Q_D(QModbusServer); 75 | d->setCanWriteSingleBitValueFunc(func); 76 | } 77 | 78 | void QModbusServer::setCanWriteSixteenBitValueFunc( 79 | const canWriteSixteenBitValueFunc &func) { 80 | Q_D(QModbusServer); 81 | d->setCanWriteSixteenBitValueFunc(func); 82 | } 83 | 84 | // read write 85 | void QModbusServer::handleHoldingRegisters(Address startAddress, 86 | Quantity quantity) { 87 | Q_D(QModbusServer); 88 | d->handleHoldingRegisters(startAddress, quantity); 89 | } 90 | // read only 91 | void QModbusServer::handleInputRegisters(Address startAddress, 92 | Quantity quantity) { 93 | Q_D(QModbusServer); 94 | d->handleInputRegisters(startAddress, quantity); 95 | } 96 | // read only 97 | void QModbusServer::handleDiscreteInputs(Address startAddress, 98 | Quantity quantity) { 99 | Q_D(QModbusServer); 100 | d->handleDiscreteInputs(startAddress, quantity); 101 | } 102 | // read write 103 | void QModbusServer::handleCoils(Address startAddress, Quantity quantity) { 104 | 105 | Q_D(QModbusServer); 106 | d->handleCoils(startAddress, quantity); 107 | } 108 | 109 | bool QModbusServer::holdingRegisterValue(Address address, 110 | SixteenBitValue *value) { 111 | Q_D(QModbusServer); 112 | return d->holdingRegisterValue(address, value); 113 | } 114 | 115 | bool QModbusServer::inputRegisterValue(Address address, 116 | SixteenBitValue *value) { 117 | Q_D(QModbusServer); 118 | return d->inputRegisterValue(address, value); 119 | } 120 | 121 | bool QModbusServer::coilsValue(Address address) { 122 | Q_D(QModbusServer); 123 | return d->coilsValue(address); 124 | } 125 | 126 | bool QModbusServer::inputDiscreteValue(Address address) { 127 | Q_D(QModbusServer); 128 | return d->inputDiscreteValue(address); 129 | } 130 | 131 | Error QModbusServer::writeCoils(Address address, bool setValue) { 132 | Q_D(QModbusServer); 133 | return d->writeCoils(address, setValue); 134 | } 135 | 136 | Error QModbusServer::writeInputDiscrete(Address address, bool setValue) { 137 | Q_D(QModbusServer); 138 | return d->writeInputDiscrete(address, setValue); 139 | } 140 | Error QModbusServer::writeInputRegisters( 141 | Address address, const QVector &setValues) { 142 | Q_D(QModbusServer); 143 | return d->writeInputRegisters(address, setValues); 144 | } 145 | Error QModbusServer::writeHodingRegisters( 146 | Address address, const QVector &setValues) { 147 | Q_D(QModbusServer); 148 | return d->writeHodingRegisters(address, setValues); 149 | } 150 | 151 | bool QModbusServer::listenAndServe() { 152 | Q_D(QModbusServer); 153 | return d->listenAndServe(); 154 | } 155 | 156 | QModbusServer *createServer(const QString &url, QObject *parent) { 157 | static const QStringList schemaSupported = {"modbus.file", "modbus.tcp"}; 158 | internal::Config config = internal::parseConfig(url); 159 | 160 | bool ok = 161 | std::any_of(schemaSupported.begin(), schemaSupported.end(), 162 | [config](const QString &el) { return config.scheme == el; }); 163 | if (!ok) { 164 | log("", LogLevel::kError, 165 | "unsupported scheme {}, see modbus.file:/// or modbus.tcp:// ", 166 | config.scheme.toStdString()); 167 | return nullptr; 168 | } 169 | 170 | log("", LogLevel::kInfo, "instanced modbus server on {}", url.toStdString()); 171 | if (config.scheme == "modbus.file") { 172 | return createQModbusSerialServer(config.serialName, config.baudRate, 173 | config.dataBits, config.parity, 174 | config.stopBits, parent); 175 | } else if (config.scheme == "modbus.tcp") { 176 | return createQModbusTcpServer(config.port, parent); 177 | } 178 | return nullptr; 179 | } 180 | 181 | } // namespace modbus 182 | -------------------------------------------------------------------------------- /src/tools/modbus_reconnectable_iodevice.cpp: -------------------------------------------------------------------------------- 1 | #include "modbus_client_p.h" 2 | #include "modbus_client_types.h" 3 | #include 4 | #include 5 | #include 6 | 7 | namespace modbus { 8 | 9 | ReconnectableIoDevice::ReconnectableIoDevice(modbus::AbstractIoDevice *iodevice, 10 | QObject *parent) 11 | : QObject(parent), d_ptr(new ReconnectableIoDevicePrivate(iodevice, this)) { 12 | setupEnvironment(); 13 | } 14 | 15 | ReconnectableIoDevice::ReconnectableIoDevice(QObject *parent) 16 | : QObject(parent), d_ptr(nullptr) { 17 | setupEnvironment(); 18 | } 19 | 20 | ReconnectableIoDevice::~ReconnectableIoDevice() { 21 | Q_D(ReconnectableIoDevice); 22 | if (isOpened()) { 23 | close(); 24 | } 25 | if (d->ioDevice_) { 26 | d->ioDevice_->deleteLater(); 27 | } 28 | } 29 | 30 | void ReconnectableIoDevice::setOpenRetryTimes(int retryTimes, int delay) { 31 | Q_D(ReconnectableIoDevice); 32 | 33 | if (retryTimes < 0) { 34 | retryTimes = kBrokenLineReconnection; 35 | } 36 | d->openRetryTimes_ = retryTimes; 37 | d->openRetryTimesBack_ = retryTimes; 38 | 39 | if (delay < 0) { 40 | delay = 0; 41 | } 42 | d->reopenDelay_ = delay; 43 | } 44 | 45 | int ReconnectableIoDevice::openRetryTimes() { 46 | Q_D(ReconnectableIoDevice); 47 | return d->openRetryTimes_; 48 | } 49 | 50 | int ReconnectableIoDevice::openRetryDelay() { 51 | Q_D(ReconnectableIoDevice); 52 | return d->reopenDelay_; 53 | } 54 | 55 | void ReconnectableIoDevice::open() { 56 | Q_D(ReconnectableIoDevice); 57 | 58 | if (!isClosed()) { 59 | log(d->log_prefix_, LogLevel::kInfo, 60 | d->ioDevice_->name() + ": is already opened or opening or closing"); 61 | return; 62 | } 63 | 64 | d->connectionState_.setState(ConnectionState::kOpening); 65 | 66 | d->ioDevice_->open(); 67 | } 68 | 69 | void ReconnectableIoDevice::close() { 70 | Q_D(ReconnectableIoDevice); 71 | d->forceClose_ = true; 72 | closeButNotSetForceCloseFlag(); 73 | } 74 | 75 | void ReconnectableIoDevice::write(const char *data, size_t size) { 76 | Q_D(ReconnectableIoDevice); 77 | d->ioDevice_->write(data, size); 78 | } 79 | 80 | QByteArray ReconnectableIoDevice::readAll() { 81 | Q_D(ReconnectableIoDevice); 82 | return d->ioDevice_->readAll(); 83 | } 84 | 85 | void ReconnectableIoDevice::clear() { 86 | Q_D(ReconnectableIoDevice); 87 | return d->ioDevice_->clear(); 88 | } 89 | 90 | std::string ReconnectableIoDevice::name() { 91 | Q_D(ReconnectableIoDevice); 92 | return d->ioDevice_->name(); 93 | } 94 | 95 | void ReconnectableIoDevice::setPrefix(const QString &prefix) { 96 | Q_D(ReconnectableIoDevice); 97 | d->log_prefix_ = prefix.toStdString(); 98 | } 99 | 100 | void ReconnectableIoDevice::setupEnvironment() { 101 | Q_D(ReconnectableIoDevice); 102 | assert(d->ioDevice_ && "the io device backend is invalid"); 103 | connect(d->ioDevice_, &AbstractIoDevice::opened, this, 104 | &ReconnectableIoDevice::onIoDeviceOpened); 105 | connect(d->ioDevice_, &AbstractIoDevice::closed, this, 106 | &ReconnectableIoDevice::onIoDeviceClosed); 107 | connect(d->ioDevice_, &AbstractIoDevice::error, this, 108 | &ReconnectableIoDevice::onIoDeviceError); 109 | connect(d->ioDevice_, &AbstractIoDevice::bytesWritten, this, 110 | &ReconnectableIoDevice::bytesWritten); 111 | connect(d->ioDevice_, &AbstractIoDevice::readyRead, this, 112 | &ReconnectableIoDevice::readyRead); 113 | 114 | d->connectionState_.setState(ConnectionState::kClosed); 115 | } 116 | 117 | void ReconnectableIoDevice::onIoDeviceOpened() { 118 | Q_D(ReconnectableIoDevice); 119 | d->connectionState_.setState(ConnectionState::kOpened); 120 | d->openRetryTimes_ = d->openRetryTimesBack_; 121 | emit opened(); 122 | } 123 | 124 | void ReconnectableIoDevice::onIoDeviceClosed() { 125 | Q_D(ReconnectableIoDevice); 126 | d->connectionState_.setState(ConnectionState::kClosed); 127 | /** 128 | * closed final,clear all pending request 129 | */ 130 | // clearPendingRequest(); 131 | 132 | /// force close, do not check reconnect 133 | if (d->forceClose_) { 134 | d->forceClose_ = false; 135 | emit error(d->errorString_); 136 | emit closed(); 137 | return; 138 | } 139 | 140 | // check reconnect 141 | if (d->openRetryTimes_ == 0) { 142 | emit error(d->errorString_); 143 | emit closed(); 144 | return; 145 | } 146 | emit connectionIsLostWillReconnect(); 147 | 148 | /// do reconnect 149 | log(d->log_prefix_, LogLevel::kError, 150 | d->ioDevice_->name() + " closed, try reconnect after " + 151 | std::to_string(d->reopenDelay_) + "ms"); 152 | d->openRetryTimes_ > 0 ? --d->openRetryTimes_ : 0; 153 | QTimer::singleShot(d->reopenDelay_, this, &ReconnectableIoDevice::open); 154 | } 155 | 156 | bool ReconnectableIoDevice::isOpened() { 157 | Q_D(ReconnectableIoDevice); 158 | 159 | return d->connectionState_.state() == ConnectionState::kOpened; 160 | } 161 | 162 | bool ReconnectableIoDevice::isClosed() { 163 | Q_D(ReconnectableIoDevice); 164 | 165 | return d->connectionState_.state() == ConnectionState::kClosed; 166 | } 167 | 168 | void ReconnectableIoDevice::onIoDeviceError(const QString &errorString) { 169 | Q_D(ReconnectableIoDevice); 170 | 171 | d->errorString_ = errorString; 172 | 173 | if (d->errorString_.isEmpty()) { 174 | /** 175 | * empty is no error 176 | */ 177 | return; 178 | } 179 | 180 | log(d->log_prefix_, LogLevel::kError, 181 | d->ioDevice_->name() + " " + errorString.toStdString()); 182 | if (isOpened()) { 183 | closeButNotSetForceCloseFlag(); 184 | } else { 185 | onIoDeviceClosed(); 186 | } 187 | } 188 | 189 | void ReconnectableIoDevice::closeButNotSetForceCloseFlag() { 190 | Q_D(ReconnectableIoDevice); 191 | 192 | if (!isOpened()) { 193 | log(d->log_prefix_, LogLevel::kInfo, 194 | d->ioDevice_->name() + ": is already closed or closing or opening"); 195 | return; 196 | } 197 | 198 | d->connectionState_.setState(ConnectionState::kClosing); 199 | d->ioDevice_->close(); 200 | } 201 | 202 | } // namespace modbus 203 | -------------------------------------------------------------------------------- /include/modbus/tools/modbus_server.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_SERVER_H_ 2 | #define __MODBUS_SERVER_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace modbus { 19 | 20 | using BytesBufferPtr = std::shared_ptr; 21 | class AbstractConnection : public QObject { 22 | Q_OBJECT 23 | public: 24 | AbstractConnection(QObject *parent = nullptr) : QObject(parent) { 25 | qRegisterMetaType("modbus::BytesBufferPtr"); 26 | qRegisterMetaType("quintptr"); 27 | } 28 | virtual ~AbstractConnection() {} 29 | virtual quintptr fd() const = 0; 30 | virtual void write(const char *data, size_t size) = 0; 31 | virtual std::string name() const = 0; 32 | virtual std::string fullName() const = 0; 33 | 34 | void setPrefix(const QString &prefix) { log_prefix_ = prefix.toStdString(); } 35 | 36 | const char *prefix() const { return log_prefix_.c_str(); } 37 | signals: 38 | void disconnected(quintptr fd); 39 | void messageArrived(quintptr fd, const BytesBufferPtr &message); 40 | 41 | private: 42 | std::string log_prefix_; 43 | }; 44 | 45 | class AbstractServer : public QObject { 46 | Q_OBJECT 47 | public: 48 | using HandleNewConnFunc = std::function; 49 | 50 | AbstractServer(QObject *parent = nullptr) : QObject(parent) {} 51 | virtual ~AbstractServer() {} 52 | virtual bool listenAndServe() = 0; 53 | 54 | void handleNewConnFunc(const HandleNewConnFunc &functor) { 55 | handleNewConnFunc_ = functor; 56 | } 57 | 58 | void setPrefix(const QString &prefix) { log_prefix_ = prefix.toStdString(); } 59 | 60 | const char *prefix() const { return log_prefix_.c_str(); } 61 | 62 | protected: 63 | HandleNewConnFunc handleNewConnFunc_; 64 | std::string log_prefix_; 65 | }; 66 | 67 | using canWriteSingleBitValueFunc = 68 | std::function; 69 | using canWriteSixteenBitValueFunc = 70 | std::function; 71 | 72 | class QModbusServerPrivate; 73 | class QModbusServer : public QObject { 74 | Q_OBJECT 75 | Q_DECLARE_PRIVATE(QModbusServer) 76 | public: 77 | QModbusServer(AbstractServer *server, QObject *parent = nullptr); 78 | virtual ~QModbusServer(); 79 | 80 | int maxClients() const; 81 | TransferMode transferMode() const; 82 | QList blacklist() const; 83 | ServerAddress serverAddress() const; 84 | 85 | void setMaxClients(int maxClients); 86 | void setTransferMode(TransferMode transferMode); 87 | void addBlacklist(const QString &clientIp); 88 | void setServerAddress(ServerAddress serverAddress); 89 | void enableDump(bool enable); 90 | void setPrefix(const QString &prefix); 91 | 92 | /** 93 | *for write request, 0x05, 0x0f, 0x06,0x16,0x23 94 | *Before writing, check whether it can be written. If writing is allowed, the 95 | *mosbus server will update the value. Otherwise, it will not write, so the 96 | *caller can re-implement these two functions to check whether the client 97 | *request is valid. 98 | * 99 | */ 100 | void setCanWriteSingleBitValueFunc(const canWriteSingleBitValueFunc &func); 101 | void setCanWriteSixteenBitValueFunc(const canWriteSixteenBitValueFunc &func); 102 | 103 | // read write 104 | void handleHoldingRegisters(Address startAddress, Quantity quantity); 105 | // read only 106 | void handleInputRegisters(Address startAddress, Quantity quantity); 107 | // read only 108 | void handleDiscreteInputs(Address startAddress, Quantity quantity); 109 | // read write 110 | void handleCoils(Address startAddress, Quantity quantity); 111 | 112 | // read 113 | bool holdingRegisterValue(Address address, SixteenBitValue *value); 114 | bool inputRegisterValue(Address address, SixteenBitValue *value); 115 | bool coilsValue(Address address); 116 | bool inputDiscreteValue(Address address); 117 | 118 | // write 119 | Error writeCoils(Address address, bool setValue); 120 | Error writeInputDiscrete(Address address, bool setValue); 121 | Error writeInputRegisters(Address address, 122 | const QVector &setValues); 123 | Error writeHodingRegisters(Address address, 124 | const QVector &setValues); 125 | 126 | bool listenAndServe(); 127 | 128 | signals: 129 | // if register of value changed, these signals will emited 130 | void holdingRegisterValueChanged(Address address, 131 | const QVector &values); 132 | void inputRegisterValueChanged(Address address, 133 | const QVector &values); 134 | 135 | // if coils/input discrete value changed,, these signal will emited 136 | void coilsValueChanged(Address address, bool value); 137 | void inputDiscreteValueChanged(Address, bool value); 138 | 139 | void writeCoilsRequested(Address address, bool value); 140 | void writeHodingRegistersRequested(Address address, const ByteArray &values); 141 | 142 | private: 143 | QScopedPointer d_ptr; 144 | }; 145 | 146 | QModbusServer *createQModbusSerialServer( 147 | const QString &serialName, 148 | QSerialPort::BaudRate baudRate = QSerialPort::Baud9600, 149 | QSerialPort::DataBits dataBits = QSerialPort::Data8, 150 | QSerialPort::Parity parity = QSerialPort::NoParity, 151 | QSerialPort::StopBits stopBits = QSerialPort::OneStop, 152 | QObject *parent = nullptr); 153 | 154 | QModbusServer *createQModbusTcpServer(uint16_t port = 502, 155 | QObject *parent = nullptr); 156 | 157 | // url format: 158 | // baud rate:1200/2400/4800/9600/115200 159 | // data bits: 5/6/7/8 160 | // parity:n(NoParity)/e(EvenParity)/o(OddParity) 161 | // stop bits:1/2 162 | // 163 | // modbus.file:///COM1/?9600-8-n-1 164 | // modbus.file:///dev/ttyS0/?9600-8-n-1 165 | // modbus.tcp://:502/ 166 | QModbusServer *createServer(const QString &url, QObject *parent = nullptr); 167 | 168 | } // namespace modbus 169 | 170 | #endif // __MODBUS_SERVER_H_ 171 | -------------------------------------------------------------------------------- /test/modbus_test_rtu_frame_decoder_client_decode.cpp: -------------------------------------------------------------------------------- 1 | #include "base/modbus_frame.h" 2 | #include "modbus/base/modbus.h" 3 | #include "modbus/base/modbus_types.h" 4 | #include "modbus_test_mocker.h" 5 | #include 6 | 7 | using namespace modbus; 8 | 9 | TEST(ModbusRtuFrameDecoder, Construct) { 10 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 11 | decoder.Clear(); 12 | EXPECT_FALSE(decoder.IsDone()); 13 | EXPECT_EQ(decoder.LasError(), Error::kNoError); 14 | } 15 | 16 | TEST(ModbusRtuFrameDecoder, client_decode_readCoils_response_success) { 17 | const ByteArray expect({0x01, 0x01, 0x01, 0x05, 0x91, 0x8b}); 18 | pp::bytes::Buffer buffer; 19 | buffer.Write(expect); 20 | 21 | Adu adu; 22 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 23 | 24 | decoder.Decode(buffer, &adu); 25 | EXPECT_EQ(true, decoder.IsDone()); 26 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 27 | EXPECT_EQ(adu.serverAddress(), 0x01); 28 | EXPECT_EQ(adu.functionCode(), 0x01); 29 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x01, 0x05)); 30 | } 31 | 32 | TEST(ModbusRtuFrameDecoder, client_decode_writeSingleCoil_response_success) { 33 | pp::bytes::Buffer buffer; 34 | buffer.Write("\x01\x05\x00\x05\xff\x00\x9c\x3b", 8); 35 | 36 | Adu adu; 37 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 38 | 39 | decoder.Decode(buffer, &adu); 40 | EXPECT_EQ(true, decoder.IsDone()); 41 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 42 | EXPECT_EQ(adu.serverAddress(), 0x01); 43 | EXPECT_EQ(adu.functionCode(), 0x05); 44 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0xff, 0x00)); 45 | } 46 | 47 | TEST(ModbusRtuFrameDecoder, client_decode_writeMultipleCoils_response_success) { 48 | pp::bytes::Buffer buffer; 49 | buffer.Write("\x01\x0f\x00\x05\x00\x09\x85\xcc", 8); 50 | 51 | Adu adu; 52 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 53 | 54 | decoder.Decode(buffer, &adu); 55 | EXPECT_EQ(true, decoder.IsDone()); 56 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 57 | EXPECT_EQ(adu.serverAddress(), 0x01); 58 | EXPECT_EQ(adu.functionCode(), 0x0f); 59 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0x00, 0x09)); 60 | } 61 | 62 | TEST(ModbusRtuFrameDecoder, client_decode_readInputDiscrete_response_success) { 63 | pp::bytes::Buffer buffer; 64 | buffer.Write("\x01\x02\x01\x05\x61\x8b", 6); 65 | 66 | Adu adu; 67 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 68 | 69 | decoder.Decode(buffer, &adu); 70 | EXPECT_EQ(true, decoder.IsDone()); 71 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 72 | EXPECT_EQ(adu.serverAddress(), 0x01); 73 | EXPECT_EQ(adu.functionCode(), 0x02); 74 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x01, 0x05)); 75 | } 76 | 77 | TEST(ModbusRtuFrameDecoder, 78 | client_decode_readHoldingRegisters_response_success) { 79 | pp::bytes::Buffer buffer; 80 | buffer.Write("\x01\x03\x08\x00\x01\x00\x02\x00\x03\x00\x04\x0d\x14", 13); 81 | 82 | Adu adu; 83 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 84 | 85 | decoder.Decode(buffer, &adu); 86 | EXPECT_EQ(true, decoder.IsDone()); 87 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 88 | EXPECT_EQ(adu.serverAddress(), 0x01); 89 | EXPECT_EQ(adu.functionCode(), 0x03); 90 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x08, 0x00, 0x01, 0x00, 0x02, 91 | 0x00, 0x03, 0x00, 0x04)); 92 | } 93 | 94 | TEST(ModbusRtuFrameDecoder, client_decode_readSingleRegister_response_success) { 95 | pp::bytes::Buffer buffer; 96 | buffer.Write("\x01\x06\x00\x05\x00\x01\x58\x0b", 8); 97 | 98 | Adu adu; 99 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 100 | 101 | decoder.Decode(buffer, &adu); 102 | EXPECT_EQ(true, decoder.IsDone()); 103 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 104 | EXPECT_EQ(adu.serverAddress(), 0x01); 105 | EXPECT_EQ(adu.functionCode(), 0x06); 106 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0x00, 0x01)); 107 | } 108 | 109 | TEST(ModbusRtuFrameDecoder, 110 | client_decode_writeMultipleRegisters_response_success) { 111 | pp::bytes::Buffer buffer; 112 | buffer.Write("\x01\x10\x00\x05\x00\x03\x90\x09", 8); 113 | 114 | Adu adu; 115 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 116 | 117 | decoder.Decode(buffer, &adu); 118 | EXPECT_EQ(true, decoder.IsDone()); 119 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 120 | EXPECT_EQ(adu.serverAddress(), 0x01); 121 | EXPECT_EQ(adu.functionCode(), 0x10); 122 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0x00, 0x03)); 123 | } 124 | 125 | TEST(ModbusRtuFrameDecoder, 126 | client_decode_readWriteMultipleRegisters_response_success) { 127 | pp::bytes::Buffer buffer; 128 | buffer.Write("\x01\x17\x06\x00\x01\x00\x02\x00\x03\xfd\x8b", 11); 129 | 130 | Adu adu; 131 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 132 | 133 | decoder.Decode(buffer, &adu); 134 | EXPECT_EQ(true, decoder.IsDone()); 135 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 136 | EXPECT_EQ(adu.serverAddress(), 0x01); 137 | EXPECT_EQ(adu.functionCode(), 0x17); 138 | EXPECT_THAT(adu.data(), 139 | ::testing::ElementsAre(0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03)); 140 | } 141 | 142 | TEST(ModbusRtuFrameDecoder, client_decode_response_error_response) { 143 | const ByteArray expect({0x01, 0x8f, 0x06, 0xc4, 0x32}); 144 | pp::bytes::Buffer buffer; 145 | buffer.Write(expect); 146 | 147 | Adu adu; 148 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 149 | 150 | decoder.Decode(buffer, &adu); 151 | EXPECT_EQ(true, decoder.IsDone()); 152 | EXPECT_EQ(Error::kSlaveDeviceBusy, decoder.LasError()); 153 | EXPECT_EQ(adu.serverAddress(), 0x01); 154 | EXPECT_EQ(adu.functionCode(), 0x0f); 155 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x06)); 156 | } 157 | 158 | TEST(ModbusRtuFrameDecoder, client_decode_response_crc_error_response) { 159 | const ByteArray expect({0x01, 0x8f, 0x06, 0xc4, 0x33}); 160 | pp::bytes::Buffer buffer; 161 | buffer.Write(expect); 162 | 163 | Adu adu; 164 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 165 | 166 | decoder.Decode(buffer, &adu); 167 | EXPECT_EQ(true, decoder.IsDone()); 168 | EXPECT_EQ(Error::kStorageParityError, decoder.LasError()); 169 | } 170 | 171 | TEST(ModbusRtuFrameDecoder, 172 | client_decode_readWriteMultipleRegisters_response_needmordata) { 173 | // complete frame:\x01\x17\x06\x00\x01\x00\x02\x00\x03\xfd\x8b 174 | pp::bytes::Buffer buffer; 175 | buffer.Write("\x01\x17\x06\x00", 4); 176 | 177 | Adu adu; 178 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 179 | 180 | decoder.Clear(); 181 | 182 | EXPECT_EQ(CheckSizeResult::kNeedMoreData, decoder.Decode(buffer, &adu)); 183 | EXPECT_FALSE(decoder.IsDone()); 184 | } 185 | 186 | TEST(ModbusRtuFrameDecoder, decode_bad_funtioncode) { 187 | pp::bytes::Buffer buffer; 188 | buffer.Write("\x01\x55\x06\x00\x01\x00\x02\x00\x03\xfd\x8b", 11); 189 | 190 | Adu adu; 191 | ModbusRtuFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 192 | 193 | decoder.Clear(); 194 | 195 | decoder.Decode(buffer, &adu); 196 | EXPECT_EQ(true, decoder.IsDone()); 197 | EXPECT_EQ(Error::kIllegalFunctionCode, decoder.LasError()); 198 | } 199 | -------------------------------------------------------------------------------- /test/modbus_test_mbap_frame_decoder_client_decode.cpp: -------------------------------------------------------------------------------- 1 | #include "base/modbus_frame.h" 2 | #include "modbus/base/modbus.h" 3 | #include "modbus/base/modbus_types.h" 4 | #include "modbus_test_mocker.h" 5 | #include 6 | 7 | using namespace modbus; 8 | 9 | TEST(ModbusMbapFrameDecoder, Construct) { 10 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 11 | decoder.Clear(); 12 | EXPECT_FALSE(decoder.IsDone()); 13 | EXPECT_EQ(decoder.LasError(), Error::kNoError); 14 | } 15 | 16 | TEST(ModbusMbapFrameDecoder, client_decode_readCoils_response_success) { 17 | const ByteArray expect( 18 | {0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x01, 0x01, 0x01, 0x05}); 19 | pp::bytes::Buffer buffer; 20 | buffer.Write(expect); 21 | 22 | Adu adu; 23 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 24 | 25 | decoder.Decode(buffer, &adu); 26 | EXPECT_EQ(true, decoder.IsDone()); 27 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 28 | EXPECT_EQ(adu.transactionId(), 0x01); 29 | EXPECT_EQ(adu.serverAddress(), 0x01); 30 | EXPECT_EQ(adu.functionCode(), 0x01); 31 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x01, 0x05)); 32 | } 33 | 34 | TEST(ModbusMbapFrameDecoder, client_decode_writeSingleCoil_response_success) { 35 | pp::bytes::Buffer buffer; 36 | buffer.Write("\x00\x01\x00\x00\x00\x06\x01\x05\x00\x05\xff\x00", 12); 37 | 38 | Adu adu; 39 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 40 | 41 | decoder.Decode(buffer, &adu); 42 | EXPECT_EQ(true, decoder.IsDone()); 43 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 44 | EXPECT_EQ(adu.transactionId(), 0x01); 45 | EXPECT_EQ(adu.serverAddress(), 0x01); 46 | EXPECT_EQ(adu.functionCode(), 0x05); 47 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0xff, 0x00)); 48 | } 49 | 50 | TEST(ModbusMbapFrameDecoder, 51 | client_decode_writeMultipleCoils_response_success) { 52 | pp::bytes::Buffer buffer; 53 | buffer.Write("\x01\x02\x00\x00\x00\x06\x01\x0f\x00\x05\x00\x09", 12); 54 | 55 | Adu adu; 56 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 57 | 58 | decoder.Decode(buffer, &adu); 59 | EXPECT_EQ(true, decoder.IsDone()); 60 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 61 | EXPECT_EQ(adu.transactionId(), 258); 62 | EXPECT_EQ(adu.serverAddress(), 0x01); 63 | EXPECT_EQ(adu.functionCode(), 0x0f); 64 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0x00, 0x09)); 65 | } 66 | 67 | TEST(ModbusMbapFrameDecoder, client_decode_readInputDiscrete_response_success) { 68 | pp::bytes::Buffer buffer; 69 | buffer.Write("\x01\x02\x00\x00\x00\x04\x01\x02\x01\x05", 10); 70 | 71 | Adu adu; 72 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 73 | 74 | decoder.Decode(buffer, &adu); 75 | EXPECT_EQ(true, decoder.IsDone()); 76 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 77 | EXPECT_EQ(adu.transactionId(), 258); 78 | EXPECT_EQ(adu.serverAddress(), 0x01); 79 | EXPECT_EQ(adu.functionCode(), 0x02); 80 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x01, 0x05)); 81 | } 82 | 83 | TEST(ModbusMbapFrameDecoder, 84 | client_decode_readHoldingRegisters_response_success) { 85 | pp::bytes::Buffer buffer; 86 | buffer.Write( 87 | "\x01\x02\x00\x00\x00\x0b\x01\x03\x08\x00\x01\x00\x02\x00\x03\x00\x04", 88 | 17); 89 | 90 | Adu adu; 91 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 92 | 93 | decoder.Decode(buffer, &adu); 94 | EXPECT_EQ(true, decoder.IsDone()); 95 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 96 | EXPECT_EQ(adu.transactionId(), 258); 97 | EXPECT_EQ(adu.serverAddress(), 0x01); 98 | EXPECT_EQ(adu.functionCode(), 0x03); 99 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x08, 0x00, 0x01, 0x00, 0x02, 100 | 0x00, 0x03, 0x00, 0x04)); 101 | } 102 | 103 | TEST(ModbusMbapFrameDecoder, 104 | client_decode_readSingleRegister_response_success) { 105 | pp::bytes::Buffer buffer; 106 | buffer.Write("\x01\x02\x00\x00\x00\x06\x01\x06\x00\x05\x00\x01", 12); 107 | 108 | Adu adu; 109 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 110 | 111 | decoder.Decode(buffer, &adu); 112 | EXPECT_EQ(true, decoder.IsDone()); 113 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 114 | EXPECT_EQ(adu.transactionId(), 258); 115 | EXPECT_EQ(adu.serverAddress(), 0x01); 116 | EXPECT_EQ(adu.functionCode(), 0x06); 117 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0x00, 0x01)); 118 | } 119 | 120 | TEST(ModbusMbapFrameDecoder, 121 | client_decode_writeMultipleRegisters_response_success) { 122 | pp::bytes::Buffer buffer; 123 | buffer.Write("\x01\x02\x00\x00\x00\x06\x01\x10\x00\x05\x00\x03", 12); 124 | 125 | Adu adu; 126 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 127 | 128 | decoder.Decode(buffer, &adu); 129 | EXPECT_EQ(true, decoder.IsDone()); 130 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 131 | EXPECT_EQ(adu.transactionId(), 258); 132 | EXPECT_EQ(adu.serverAddress(), 0x01); 133 | EXPECT_EQ(adu.functionCode(), 0x10); 134 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x00, 0x05, 0x00, 0x03)); 135 | } 136 | 137 | TEST(ModbusMbapFrameDecoder, 138 | client_decode_readWriteMultipleRegisters_response_success) { 139 | pp::bytes::Buffer buffer; 140 | buffer.Write("\x01\x02\x00\x00\x00\x09\x01\x17\x06\x00\x01\x00\x02\x00\x03", 141 | 15); 142 | 143 | Adu adu; 144 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 145 | 146 | decoder.Decode(buffer, &adu); 147 | EXPECT_EQ(true, decoder.IsDone()); 148 | EXPECT_EQ(Error::kNoError, decoder.LasError()); 149 | EXPECT_EQ(adu.transactionId(), 258); 150 | EXPECT_EQ(adu.serverAddress(), 0x01); 151 | EXPECT_EQ(adu.functionCode(), 0x17); 152 | EXPECT_THAT(adu.data(), 153 | ::testing::ElementsAre(0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03)); 154 | } 155 | 156 | TEST(ModbusMbapFrameDecoder, client_decode_response_error_response) { 157 | const ByteArray expect({0x01, 002, 0x00, 0x00, 0x00, 0x03, 0x01, 0x8f, 0x06}); 158 | pp::bytes::Buffer buffer; 159 | buffer.Write(expect); 160 | 161 | Adu adu; 162 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 163 | 164 | decoder.Decode(buffer, &adu); 165 | EXPECT_EQ(true, decoder.IsDone()); 166 | EXPECT_EQ(Error::kSlaveDeviceBusy, decoder.LasError()); 167 | EXPECT_EQ(adu.transactionId(), 258); 168 | EXPECT_EQ(adu.serverAddress(), 0x01); 169 | EXPECT_EQ(adu.functionCode(), 0x0f); 170 | EXPECT_THAT(adu.data(), ::testing::ElementsAre(0x06)); 171 | } 172 | 173 | TEST(ModbusMbapFrameDecoder, 174 | client_decode_readWriteMultipleRegisters_response_needmordata) { 175 | // complete frame:\x01\x17\x06\x00\x01\x00\x02\x00\x03\xfd\x8b 176 | pp::bytes::Buffer buffer; 177 | buffer.Write("\x01\x02\x00\x00\x00\x09\x01\x17\x06\x00", 10); 178 | 179 | Adu adu; 180 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 181 | 182 | decoder.Clear(); 183 | 184 | EXPECT_EQ(CheckSizeResult::kNeedMoreData, decoder.Decode(buffer, &adu)); 185 | EXPECT_FALSE(decoder.IsDone()); 186 | } 187 | 188 | TEST(ModbusMbapFrameDecoder, decode_bad_funtioncode) { 189 | pp::bytes::Buffer buffer; 190 | buffer.Write("\x01\x02\x00\x00\x00\x09\x01\x55\x06\x00\x01\x00\x02\x00\x03", 191 | 15); 192 | 193 | Adu adu; 194 | ModbusMbapFrameDecoder decoder(creatDefaultCheckSizeFuncTableForClient()); 195 | 196 | decoder.Clear(); 197 | 198 | decoder.Decode(buffer, &adu); 199 | EXPECT_TRUE(decoder.IsDone()); 200 | EXPECT_EQ(Error::kIllegalFunctionCode, decoder.LasError()); 201 | } 202 | -------------------------------------------------------------------------------- /include/modbus/base/single_bit_access.h: -------------------------------------------------------------------------------- 1 | #ifndef FUNCTIONS_H 2 | #define FUNCTIONS_H 3 | 4 | #include "modbus.h" 5 | #include "modbus_tool.h" 6 | #include "smart_assert.h" 7 | #include 8 | #include 9 | 10 | namespace modbus { 11 | /** 12 | * modbus data model 13 | * single bit access. 14 | * include Discrete input(r),coil(rw) 15 | * before use SingleBitAccess,must set startAddress, quantity 16 | */ 17 | class SingleBitAccess { 18 | public: 19 | SingleBitAccess() {} 20 | 21 | void setStartAddress(Address startAddress) { startAddress_ = startAddress; } 22 | Address startAddress() const { return startAddress_; } 23 | void setQuantity(Quantity quantity) { quantity_ = quantity; } 24 | Quantity quantity() const { return quantity_; } 25 | 26 | /** 27 | * this will set the value to startAddress 28 | */ 29 | void setValue(bool value) { setValue(startAddress_, value); } 30 | /** 31 | * set value to address 32 | */ 33 | void setValue(Address address, bool value) { valueMap_[address] = value; } 34 | 35 | /** 36 | * Conversion to modbus protocol format 37 | * function code 0x01,0x02 38 | */ 39 | ByteArray marshalReadRequest() const { return marshalAddressQuantity(); } 40 | 41 | ByteArray marshalAddressQuantity() const { 42 | return ByteArray({static_cast(startAddress_ / 256), 43 | static_cast(startAddress_ % 256), 44 | static_cast(quantity_ / 256), 45 | static_cast(quantity_ % 256)}); 46 | } 47 | 48 | bool unmarshalReadRequest(const ByteArray &data) { 49 | size_t size; 50 | auto result = bytesRequired<4>(size, data.data(), data.size()); 51 | if (result != CheckSizeResult::kSizeOk) { 52 | return false; 53 | } 54 | 55 | startAddress_ = data[0] * 256 + data[1]; 56 | quantity_ = data[2] * 256 + data[3]; 57 | return true; 58 | } 59 | 60 | /** 61 | * Conversion to modbus protocol format 62 | * function code 0x05 63 | */ 64 | ByteArray marshalSingleWriteRequest() const { 65 | ByteArray data; 66 | 67 | data.reserve(4); 68 | 69 | auto it = valueMap_.find(startAddress_); 70 | smart_assert(it != valueMap_.end() && "has no value set")(startAddress_); 71 | 72 | data.push_back(startAddress_ / 256); 73 | data.push_back(startAddress_ % 256); 74 | 75 | data.push_back(it->second ? 0xff : 0x00); 76 | data.push_back(0x00); 77 | 78 | return data; 79 | } 80 | 81 | /** 82 | * Conversion to modbus protocol format 83 | * function code 0x0f 84 | */ 85 | ByteArray marshalMultipleWriteRequest() { 86 | ByteArray data; 87 | 88 | data.reserve(48); 89 | 90 | data.push_back(startAddress_ / 256); 91 | data.push_back(startAddress_ % 256); 92 | 93 | data.push_back(quantity_ / 256); 94 | data.push_back(quantity_ % 256); 95 | data.push_back(quantity_ % 8 == 0 ? quantity_ / 8 : quantity_ / 8 + 1); 96 | 97 | Address nextAddress = startAddress_; 98 | Address endAddress = startAddress_ + quantity_; 99 | uint8_t byte = 0; 100 | int offset = 0; 101 | for (; nextAddress < endAddress; nextAddress++) { 102 | auto it = valueMap_.find(nextAddress); 103 | smart_assert(it != valueMap_.end() && 104 | "some value of address not set, bad operation!")( 105 | nextAddress); 106 | 107 | bool value = it->second ? true : false; 108 | byte |= value << offset++; 109 | if (offset == 8) { 110 | offset = 0; 111 | data.push_back(byte); 112 | byte = 0; 113 | } 114 | } 115 | if (quantity_ % 8 != 0) { 116 | data.push_back(byte); 117 | } 118 | 119 | return data; 120 | } 121 | 122 | ByteArray marshalReadResponse() { 123 | auto take = [&](int address, int n) { 124 | std::vector bitValueList; 125 | 126 | for (int i = address; i < address + n; i++) { 127 | int temp = 0; 128 | auto v = valueMap_.find(i); 129 | if (v == valueMap_.end()) { 130 | temp = false; 131 | } else { 132 | temp = v->second ? true : false; 133 | } 134 | bitValueList.push_back(temp); 135 | } 136 | return bitValueList; 137 | }; 138 | 139 | ByteArray data; 140 | 141 | uint8_t bytesNumber = 142 | quantity_ % 8 == 0 ? quantity_ / 8 : quantity_ / 8 + 1; 143 | data.push_back(bytesNumber); 144 | 145 | Address address = startAddress_; 146 | for (int remainingNumber = quantity_; remainingNumber > 0;) { 147 | int numbers = std::min(8, remainingNumber); 148 | auto bitValueList = take(address, numbers); 149 | uint8_t byte = 0; 150 | for (int offset = 0; offset < numbers; offset++) { 151 | byte |= bitValueList[offset] << offset; 152 | } 153 | data.push_back(byte); 154 | remainingNumber -= numbers; 155 | address += numbers; 156 | } 157 | return data; 158 | } 159 | 160 | bool unmarshalReadResponse(const ByteArray &array) { 161 | return unmarshalValueArray(array); 162 | } 163 | 164 | bool unmarshalSingleWriteRequest(const ByteArray &data) { 165 | size_t size; 166 | auto result = bytesRequired<4>(size, data.data(), data.size()); 167 | if (result != CheckSizeResult::kSizeOk) { 168 | return false; 169 | } 170 | startAddress_ = data[0] * 256 + data[1]; 171 | quantity_ = 1; 172 | if (data[2] == 0xff && data[3] == 0x00) { 173 | setValue(true); 174 | } else if (data[2] == 0x00 && data[3] == 0x00) { 175 | setValue(false); 176 | } else { 177 | return false; 178 | } 179 | return true; 180 | } 181 | 182 | bool unmarshalMultipleWriteRequest(const ByteArray &data) { 183 | size_t size; 184 | auto result = 185 | bytesRequiredStoreInArrayIndex<4>(size, data.data(), data.size()); 186 | if (result != CheckSizeResult::kSizeOk) { 187 | return false; 188 | } 189 | startAddress_ = data[0] * 256 + data[1]; 190 | quantity_ = data[2] * 256 + data[3]; 191 | auto valueArray = tool::subArray(data, 4); 192 | return unmarshalValueArray(valueArray); 193 | } 194 | 195 | bool value(Address address) const { 196 | auto it = valueMap_.find(address); 197 | if (it != valueMap_.end()) { 198 | return it->second; 199 | } 200 | 201 | return false; 202 | } 203 | 204 | private: 205 | bool unmarshalValueArray(const ByteArray &array) { 206 | size_t size = 0; 207 | auto result = 208 | bytesRequiredStoreInArrayIndex<0>(size, array.data(), array.size()); 209 | if (result != CheckSizeResult::kSizeOk) { 210 | return false; 211 | } 212 | 213 | ByteArray bitvalues(tool::subArray(array, 1)); 214 | Quantity quantity = quantity_; 215 | Address nextAddress = startAddress_; 216 | for (const auto &n : bitvalues) { 217 | Quantity remainQuantity = quantity >= 8 ? 8 : quantity % 8; 218 | for (int i = 0; i < remainQuantity; i++) { 219 | Address address = nextAddress++; 220 | bool status = n & (0x01 << i); 221 | valueMap_[address] = status; 222 | } 223 | quantity -= remainQuantity; 224 | } 225 | return true; 226 | } 227 | 228 | Address startAddress_ = 0xff; 229 | Quantity quantity_ = 0; 230 | mutable std::unordered_map valueMap_; 231 | }; 232 | 233 | bool processReadSingleBit(const Request &request, const Response &response, 234 | SingleBitAccess *access, 235 | const std::string &log_prefix = ""); 236 | } // namespace modbus 237 | 238 | #endif /* FUNCTIONS_H */ 239 | -------------------------------------------------------------------------------- /include/modbus/tools/modbus_client.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_CLIENT_H_ 2 | #define __MODBUS_CLIENT_H_ 3 | 4 | #include "modbus/base/modbus.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace modbus { 16 | 17 | class AbstractIoDevice : public QObject { 18 | Q_OBJECT 19 | public: 20 | explicit AbstractIoDevice(QObject *parent = nullptr) : QObject(parent) {} 21 | ~AbstractIoDevice() override = default; 22 | virtual void open() = 0; 23 | virtual void close() = 0; 24 | virtual void write(const char *data, size_t size) = 0; 25 | virtual QByteArray readAll() = 0; 26 | virtual void clear() = 0; 27 | virtual std::string name() = 0; 28 | signals: 29 | void opened(); 30 | void closed(); 31 | void error(const QString &errorString); 32 | void bytesWritten(qint64 bytes); 33 | void readyRead(); 34 | }; 35 | 36 | class ReconnectableIoDevicePrivate; 37 | class ReconnectableIoDevice : public QObject { 38 | Q_OBJECT 39 | Q_DECLARE_PRIVATE(ReconnectableIoDevice); 40 | 41 | public: 42 | static const int kBrokenLineReconnection = -1; 43 | explicit ReconnectableIoDevice(QObject *parent = nullptr); 44 | explicit ReconnectableIoDevice(AbstractIoDevice *iodevice, 45 | QObject *parent = nullptr); 46 | ~ReconnectableIoDevice() override; 47 | void setOpenRetryTimes(int retryTimes, int delay); 48 | int openRetryTimes(); 49 | int openRetryDelay(); 50 | 51 | void open(); 52 | void close(); 53 | void write(const char *data, size_t size); 54 | QByteArray readAll(); 55 | void clear(); 56 | std::string name(); 57 | 58 | void setPrefix(const QString &prefix); 59 | 60 | bool isOpened(); 61 | bool isClosed(); 62 | signals: 63 | void opened(); 64 | void closed(); 65 | void error(const QString &errorString); 66 | void bytesWritten(qint64 bytes); 67 | void readyRead(); 68 | void connectionIsLostWillReconnect(); 69 | 70 | private: 71 | void setupEnvironment(); 72 | void onIoDeviceOpened(); 73 | void onIoDeviceClosed(); 74 | 75 | void onIoDeviceError(const QString &errorString); 76 | void closeButNotSetForceCloseFlag(); 77 | 78 | QScopedPointer d_ptr; 79 | }; 80 | 81 | class QModbusClientPrivate; 82 | class QModbusClient : public QObject { 83 | Q_OBJECT 84 | Q_DECLARE_PRIVATE(QModbusClient); 85 | 86 | public: 87 | explicit QModbusClient(AbstractIoDevice *iodevice, QObject *parent = nullptr); 88 | explicit QModbusClient(QObject *parent = nullptr); 89 | ~QModbusClient() override; 90 | 91 | void open(); 92 | void close(); 93 | /** 94 | * if the connection is not opened, the request will dropped 95 | */ 96 | void sendRequest(std::unique_ptr &request); 97 | 98 | /** 99 | *for function code 0x01/0x02 100 | *will emit readSingleBitsFinished signal 101 | */ 102 | void readSingleBits(ServerAddress serverAddress, FunctionCode functionCode, 103 | Address startAddress, Quantity quantity); 104 | 105 | /** 106 | *for function code 0x05 107 | *will emit writeSingleCoilFinished signal 108 | */ 109 | void writeSingleCoil(ServerAddress serverAddress, Address startAddress, 110 | bool value); 111 | 112 | /* 113 | *for function code 0x0f 114 | *will emit writeMultipleCoilsFinished signal 115 | */ 116 | void writeMultipleCoils(ServerAddress serverAddress, Address startAddress, 117 | const QVector &valueList); 118 | 119 | /** 120 | * sixteem bit access, for function code 3/4 121 | * will emit readRegistersFinished signal 122 | */ 123 | void readRegisters(ServerAddress serverAddress, FunctionCode functionCode, 124 | Address startAddress, Quantity quantity); 125 | /** 126 | * for function code 0x06 127 | * will emit writeSingleRegisterFinished signal 128 | */ 129 | void writeSingleRegister(ServerAddress serverAddress, Address address, 130 | const SixteenBitValue &value); 131 | /** 132 | *for function code 0x10 133 | *wiil emit writeMultipleRegistersFinished signal 134 | */ 135 | void writeMultipleRegisters(ServerAddress serverAddress, Address startAddress, 136 | const QVector &valueList); 137 | 138 | /** 139 | *for function code 0x17 140 | *will emit readWriteMultipleRegistersFinished signal 141 | */ 142 | void readWriteMultipleRegisters(ServerAddress serverAddress, 143 | Address readStartAddress, 144 | Quantity readQuantity, 145 | Address writeStartAddress, 146 | const QVector &valueList); 147 | 148 | bool isIdle(); 149 | 150 | bool isClosed(); 151 | bool isOpened(); 152 | 153 | void setTimeout(uint64_t timeout); 154 | uint64_t timeout(); 155 | 156 | void setTransferMode(TransferMode transferMode); 157 | TransferMode transferMode() const; 158 | 159 | void setRetryTimes(int times); 160 | int retryTimes(); 161 | 162 | void setOpenRetryTimes(int retryTimes, int delay = 1000); 163 | int openRetryTimes(); 164 | 165 | int openRetryDelay(); 166 | 167 | void setFrameInterval(int frameInterval); 168 | /** 169 | * After the disconnection, all pending requests will be deleted. So. if the 170 | * short-term reconnection, there should be no pending requests 171 | */ 172 | size_t pendingRequestSize(); 173 | 174 | QString errorString(); 175 | 176 | void setPrefix(const QString &prefix); 177 | 178 | /** 179 | *enable collection diagnosis 180 | *default is disabled 181 | */ 182 | void enableDiagnosis(bool enable); 183 | void enableDump(bool enable); 184 | 185 | RuntimeDiagnosis runtimeDiagnosis() const; 186 | 187 | signals: 188 | void clientOpened(); 189 | void clientClosed(); 190 | void errorOccur(const QString &errorString); 191 | void connectionIsLostWillReconnect(); 192 | void requestFinished(const Request &request, const Response &response); 193 | void readSingleBitsFinished(ServerAddress serverAddress, 194 | FunctionCode functionCode, Address startAddress, 195 | Quantity quantity, const ByteArray &valueList, 196 | Error error); 197 | void writeSingleCoilFinished(ServerAddress serverAddress, Address address, 198 | Error error); 199 | void readRegistersFinished(ServerAddress serverAddress, 200 | FunctionCode functionCode, Address startAddress, 201 | Quantity quantity, const ByteArray &data, 202 | Error error); 203 | void writeSingleRegisterFinished(ServerAddress serverAddress, Address address, 204 | Error error); 205 | 206 | void writeMultipleRegistersFinished(ServerAddress serverAddress, 207 | Address address, Error error); 208 | 209 | void writeMultipleCoilsFinished(ServerAddress serverAddress, Address address, 210 | Error error); 211 | void readWriteMultipleRegistersFinished( 212 | ServerAddress serverAddress, Address readStartAddress, 213 | const QVector &valueList, Error error); 214 | 215 | private: 216 | void runAfter(int delay, const std::function &functor); 217 | void setupEnvironment(); 218 | void initMemberValues(); 219 | void closeNotClearOpenRetrys(); 220 | 221 | void onIoDeviceError(const QString &errorString); 222 | void onIoDeviceBytesWritten(qint16 bytes); 223 | void onIoDeviceReadyRead(); 224 | void onIoDeviceResponseTimeout(); 225 | void clearPendingRequest(); 226 | void processResponseAnyFunctionCode(const Request &request, 227 | const Response &response); 228 | void processFunctionCode(const Request &request, const Response &response); 229 | void processDiagnosis(const Request &request, const Response &response); 230 | 231 | QScopedPointer d_ptr; 232 | }; 233 | 234 | Request createRequest(ServerAddress serverAddress, FunctionCode functionCode, 235 | const any &userData, const ByteArray &data); 236 | 237 | QModbusClient * 238 | newQtSerialClient(const QString &serialName, 239 | QSerialPort::BaudRate baudRate = QSerialPort::Baud9600, 240 | QSerialPort::DataBits dataBits = QSerialPort::Data8, 241 | QSerialPort::Parity parity = QSerialPort::NoParity, 242 | QSerialPort::StopBits stopBits = QSerialPort::OneStop, 243 | QObject *parent = nullptr); 244 | 245 | QModbusClient *newSocketClient(QAbstractSocket::SocketType type, 246 | const QString &hostName, quint16 port, 247 | QObject *parent = nullptr); 248 | // url format: 249 | // baud rate:1200/2400/4800/9600/115200 250 | // data bits: 5/6/7/8 251 | // parity:n(NoParity)/e(EvenParity)/o(OddParity) 252 | // stop bits:1/2 253 | // 254 | // modbus.file:///COM1/?9600-8-n-1 255 | // modbus.file:///dev/ttyS0/?9600-8-n-1 256 | // modbus.tcp://192.168.4.66:502/ 257 | // modbus.udp://192.168.4.66:502/ 258 | QModbusClient *createClient(const QString &url, 259 | QObject *parent = nullptr); 260 | 261 | } // namespace modbus 262 | Q_DECLARE_METATYPE(modbus::Response); 263 | Q_DECLARE_METATYPE(modbus::Request); 264 | Q_DECLARE_METATYPE(modbus::SixteenBitAccess); 265 | Q_DECLARE_METATYPE(modbus::Error); 266 | Q_DECLARE_METATYPE(QVector); 267 | Q_DECLARE_METATYPE(modbus::ByteArray); 268 | 269 | #endif // __MODBUS_CLIENT_H_ 270 | -------------------------------------------------------------------------------- /include/modbus/base/modbus_types.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_TYPES_H_ 2 | #define __MODBUS_TYPES_H_ 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace modbus { 13 | using ByteArray = std::vector; 14 | using ServerAddress = uint8_t; 15 | using CoilAddress = uint16_t; 16 | using RegisterAddress = uint16_t; 17 | using Address = uint16_t; 18 | using Quantity = uint16_t; 19 | 20 | struct SixteenBitValue { 21 | enum class ByteOrder { kNetworkByteOrder, kHostByteOrder }; 22 | 23 | SixteenBitValue(uint8_t chFirst, uint8_t chSecond) 24 | : chFirst_(chFirst), chSecond_(chSecond) {} 25 | SixteenBitValue() {} 26 | explicit SixteenBitValue(uint16_t value) { setUint16(value); } 27 | ~SixteenBitValue() {} 28 | 29 | void setFirstByte(uint8_t byte) { chFirst_ = byte; } 30 | void setSecondByte(uint8_t byte) { chSecond_ = byte; } 31 | 32 | uint8_t firstByte() const { return chFirst_; } 33 | uint8_t secondByte() const { return chSecond_; } 34 | ByteArray twoBytes() const { return ByteArray({chFirst_, chSecond_}); } 35 | 36 | uint16_t toUint16(ByteOrder order = ByteOrder::kHostByteOrder) const { 37 | uint16_t value; 38 | if (order == ByteOrder::kHostByteOrder) { 39 | value = chFirst_ * 256 + chSecond_; 40 | } else { 41 | value = chFirst_ + chSecond_ * 256; 42 | } 43 | return value; 44 | } 45 | 46 | void setUint16(uint16_t value) { 47 | chFirst_ = value / 256; 48 | chSecond_ = value % 256; 49 | } 50 | 51 | std::string toHexString() { 52 | char hex[32] = {0}; 53 | sprintf(hex, "%02x %02x", static_cast(chFirst_), 54 | static_cast(chSecond_)); 55 | return hex; 56 | } 57 | 58 | SixteenBitValue &operator=(uint16_t value) { 59 | setUint16(value); 60 | 61 | return *this; 62 | } 63 | 64 | bool operator==(const SixteenBitValue &value) const { 65 | return value.chFirst_ == chFirst_ && value.chSecond_ == chSecond_; 66 | } 67 | 68 | private: 69 | uint8_t chFirst_ = 0; 70 | uint8_t chSecond_ = 0; 71 | }; 72 | 73 | enum FunctionCode { 74 | kInvalidCode = 0x00, 75 | kReadCoils = 0x01, 76 | kReadInputDiscrete = 0x02, 77 | kReadHoldingRegisters = 0x03, 78 | kReadInputRegister = 0x04, 79 | kWriteSingleCoil = 0x05, 80 | kWriteSingleRegister = 0x06, 81 | kWriteMultipleRegisters = 0x10, 82 | kWriteMultipleCoils = 0x0f, 83 | kReadFileRecords = 0x14, 84 | kWriteFileRecords = 0x15, 85 | kMaskWriteRegister = 0x16, 86 | kReadWriteMultipleRegisters = 0x17, 87 | kReadDeviceIdentificationCode = 0x2b 88 | }; 89 | inline std::ostream &operator<<(std::ostream &output, 90 | const FunctionCode &code) { 91 | switch (code) { 92 | case FunctionCode::kInvalidCode: 93 | output << "invalid function code"; 94 | break; 95 | case FunctionCode::kReadCoils: 96 | output << "read coils"; 97 | break; 98 | case FunctionCode::kReadInputDiscrete: 99 | output << "read input discrete"; 100 | break; 101 | case FunctionCode::kReadHoldingRegisters: 102 | output << "read holding registers"; 103 | break; 104 | case FunctionCode::kReadInputRegister: 105 | output << "read input registers"; 106 | break; 107 | case FunctionCode::kWriteSingleCoil: 108 | output << "write single coil"; 109 | break; 110 | case FunctionCode::kWriteSingleRegister: 111 | output << "write single register"; 112 | break; 113 | case FunctionCode::kWriteMultipleRegisters: 114 | output << "write multiple registers"; 115 | break; 116 | case FunctionCode::kWriteMultipleCoils: 117 | output << "write multiple coils"; 118 | break; 119 | case FunctionCode::kReadFileRecords: 120 | output << "read file records"; 121 | break; 122 | case FunctionCode::kWriteFileRecords: 123 | output << "write file records"; 124 | break; 125 | case FunctionCode::kMaskWriteRegister: 126 | output << "mask write register"; 127 | break; 128 | case FunctionCode::kReadWriteMultipleRegisters: 129 | output << "read/write multiple registers"; 130 | break; 131 | case FunctionCode::kReadDeviceIdentificationCode: 132 | output << "read device identification code"; 133 | break; 134 | default: 135 | output << "function code(" << std::to_string(static_cast(code)) << ")"; 136 | break; 137 | } 138 | 139 | return output; 140 | } 141 | 142 | enum class Error { 143 | kNoError = 0, 144 | kIllegalFunctionCode = 0x01, 145 | kIllegalDataAddress = 0x02, 146 | kIllegalDataValue = 0x03, 147 | kSlaveDeviceFailure = 0x04, 148 | kConfirm = 0x05, 149 | kSlaveDeviceBusy = 0x06, 150 | kStorageParityError = 0x08, 151 | kUnavailableGatewayPath = 0x0a, 152 | kGatewayTargetDeviceResponseLoss = 0x0b, 153 | 154 | /// user defined error, not inlcuded in modbus protocol 155 | kTimeout = 0xd0 156 | }; 157 | inline std::ostream &operator<<(std::ostream &output, const Error &error) { 158 | switch (error) { 159 | case Error::kNoError: 160 | output << "NoError"; 161 | break; 162 | case Error::kIllegalFunctionCode: 163 | output << "Illegal function"; 164 | break; 165 | case Error::kIllegalDataAddress: 166 | output << "Illegal data address"; 167 | break; 168 | case Error::kIllegalDataValue: 169 | output << "Illegal data value"; 170 | break; 171 | case Error::kSlaveDeviceFailure: 172 | output << "Slave device failure"; 173 | break; 174 | case Error::kConfirm: 175 | output << "confirm"; 176 | break; 177 | case Error::kSlaveDeviceBusy: 178 | output << "Slave device is busy"; 179 | break; 180 | case Error::kStorageParityError: 181 | output << "Storage parity error"; 182 | break; 183 | case Error::kUnavailableGatewayPath: 184 | output << "Unavailable gateway path"; 185 | break; 186 | case Error::kGatewayTargetDeviceResponseLoss: 187 | output << "Gateway target device failed to respond"; 188 | break; 189 | case Error::kTimeout: 190 | output << "Timeout"; 191 | break; 192 | default: 193 | output.setstate(std::ios_base::failbit); 194 | } 195 | 196 | return output; 197 | } 198 | enum class TransferMode { kRtu, kAscii, kMbap }; 199 | 200 | enum class LogLevel { kDebug, kWarning, kInfo, kError }; 201 | using LogWriter = std::function; 202 | 203 | class RuntimeDiagnosis { 204 | public: 205 | RuntimeDiagnosis() {} 206 | ~RuntimeDiagnosis() {} 207 | 208 | class ErrorRecord { 209 | public: 210 | ErrorRecord(FunctionCode functionCode, Error error, 211 | const ByteArray &requestFrame) 212 | : functionCode_(functionCode), error_(error), 213 | requestFrame_(requestFrame), occurCount_(1) {} 214 | 215 | FunctionCode functionCode() const { return functionCode_; } 216 | Error error() const { return error_; } 217 | size_t occurrenceCount() const { return occurCount_; } 218 | void incrementOccurCount() { occurCount_++; } 219 | ByteArray requestFrame() const { return requestFrame_; } 220 | 221 | bool operator==(const ErrorRecord &other) { 222 | return other.functionCode_ == functionCode_ && other.error_ == error_ && 223 | other.requestFrame_ == requestFrame_; 224 | } 225 | 226 | private: 227 | FunctionCode functionCode_ = FunctionCode::kInvalidCode; 228 | Error error_ = Error::kNoError; 229 | ByteArray requestFrame_; 230 | size_t occurCount_ = 0; 231 | }; 232 | 233 | using ErrorRecordList = std::vector; 234 | class Server { 235 | public: 236 | Server(ServerAddress serverAddress) : serverAddress_(serverAddress) {} 237 | Server() {} 238 | ServerAddress serverAddress() const { return serverAddress_; } 239 | ErrorRecordList errorRecords() const { return errorRecordList_; } 240 | 241 | void insertErrorRecord(FunctionCode functionCode, Error error, 242 | const ByteArray &requestFrame) { 243 | ErrorRecord errorRecord(functionCode, error, requestFrame); 244 | auto it = std::find(errorRecordList_.begin(), errorRecordList_.end(), 245 | errorRecord); 246 | if (it != errorRecordList_.end()) { 247 | it->incrementOccurCount(); 248 | return; 249 | } 250 | errorRecordList_.push_back(errorRecord); 251 | } 252 | 253 | private: 254 | ServerAddress serverAddress_; 255 | ErrorRecordList errorRecordList_; 256 | }; 257 | 258 | using ServerMap = std::map; 259 | 260 | ServerMap servers() const { return servers_; } 261 | 262 | void insertErrorRecord(ServerAddress serverAddress, FunctionCode functionCode, 263 | Error error, const ByteArray &requestFrame) { 264 | incrementtotalFrameNumbers(); 265 | auto it = servers_.find(serverAddress); 266 | if (it == servers_.end()) { 267 | Server server(serverAddress); 268 | servers_[serverAddress] = server; 269 | it = servers_.find(serverAddress); 270 | } 271 | auto &server = it->second; 272 | server.insertErrorRecord(functionCode, error, requestFrame); 273 | } 274 | 275 | size_t totalFrameNumbers() const { return totalFrameNumbers_; } 276 | 277 | void incrementtotalFrameNumbers() { totalFrameNumbers_++; } 278 | 279 | size_t failedFrameNumbers() const { 280 | size_t number = 0; 281 | for (const auto &el : servers_) { 282 | const auto &server = el.second; 283 | const auto &errorRecords = server.errorRecords(); 284 | for (const auto &errorRecord : errorRecords) { 285 | number += errorRecord.occurrenceCount(); 286 | } 287 | } 288 | return number; 289 | } 290 | 291 | size_t successedFrameNumbers() const { 292 | return totalFrameNumbers() - failedFrameNumbers(); 293 | } 294 | 295 | private: 296 | size_t totalFrameNumbers_ = 0; 297 | ServerMap servers_; 298 | }; 299 | 300 | } // namespace modbus 301 | 302 | namespace std { 303 | template std::string to_string(const T &t) { 304 | std::stringstream s; 305 | 306 | s << t; 307 | return s.str(); 308 | } 309 | } // namespace std 310 | 311 | #endif // __MODBUS_TYPES_H_ 312 | -------------------------------------------------------------------------------- /src/base/modbus_frame.h: -------------------------------------------------------------------------------- 1 | #ifndef __MODBUS_FRAME_H_ 2 | #define __MODBUS_FRAME_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace modbus { 13 | static void appendStdString(ByteArray &array, const std::string &subString) { 14 | array.insert(array.end(), subString.begin(), subString.end()); 15 | } 16 | 17 | inline ByteArray marshalRtuFrame(const ByteArray &data) { 18 | return tool::appendCrc(data); 19 | } 20 | 21 | inline ByteArray marshalAsciiFrame(const ByteArray &data) { 22 | ByteArray ascii; 23 | ByteArray binary = tool::appendLrc(data); 24 | 25 | auto toUpperHexString = [](const ByteArray &data) { 26 | auto hexString = tool::dumpHex(data, ""); 27 | std::transform(hexString.begin(), hexString.end(), hexString.begin(), 28 | ::toupper); 29 | return hexString; 30 | }; 31 | 32 | appendStdString(ascii, ":"); 33 | appendStdString(ascii, toUpperHexString(binary)); 34 | appendStdString(ascii, "\r\n"); 35 | return ascii; 36 | } 37 | 38 | inline std::string dump(TransferMode transferMode, const ByteArray &byteArray) { 39 | return transferMode == TransferMode::kAscii ? tool::dumpRaw(byteArray) 40 | : tool::dumpHex(byteArray); 41 | } 42 | 43 | inline std::string dump(TransferMode transferMode, const char *p, int len) { 44 | return transferMode == TransferMode::kAscii 45 | ? tool::dumpRaw((uint8_t *)p, len) 46 | : tool::dumpHex((uint8_t *)p, len); 47 | } 48 | 49 | inline std::string dump(TransferMode transferMode, const QByteArray &array) { 50 | return transferMode == TransferMode::kAscii 51 | ? tool::dumpRaw((uint8_t *)array.data(), array.size()) 52 | : tool::dumpHex((uint8_t *)array.data(), array.size()); 53 | } 54 | 55 | inline std::string dump(TransferMode transferMode, 56 | const pp::bytes::Buffer &buffer) { 57 | char *p; 58 | int len = buffer.Len(); 59 | buffer.ZeroCopyPeekAt(&p, 0, buffer.Len()); 60 | return transferMode == TransferMode::kAscii 61 | ? tool::dumpRaw((uint8_t *)p, len) 62 | : tool::dumpHex((uint8_t *)p, len); 63 | } 64 | 65 | inline CheckSizeFuncTable creatDefaultCheckSizeFuncTableForClient() { 66 | static const CheckSizeFuncTable table = { 67 | nullptr, 68 | bytesRequiredStoreInArrayIndex<0>, // kReadCoils 0x01 69 | bytesRequiredStoreInArrayIndex<0>, // kReadInputDiscrete 0x02 70 | bytesRequiredStoreInArrayIndex<0>, // kReadHoldingRegisters 0x03 71 | bytesRequiredStoreInArrayIndex<0>, // kReadInputRegister 0x04 72 | bytesRequired<4>, // kWriteSingleCoil 0x05 73 | bytesRequired<4>, // kWriteSingleRegister 0x06 74 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 75 | bytesRequired<4>, // kWriteMultipleCoils = 0x0f, 76 | bytesRequired<4>, // kWriteMultipleRegisters = 0x10, 77 | nullptr, nullptr, nullptr, 78 | nullptr, // kReadFileRecords = 0x14, 79 | nullptr, // kWriteFileRecords = 0x15, 80 | nullptr, // kMaskWriteRegister = 0x16, 81 | bytesRequiredStoreInArrayIndex<0>, // kReadWriteMultipleRegisters = 82 | // 0x17, 83 | nullptr, // kReadDeviceIdentificationCode = 0x2b 84 | }; 85 | return table; 86 | } 87 | 88 | inline CheckSizeFuncTable creatDefaultCheckSizeFuncTableForServer() { 89 | static const CheckSizeFuncTable table = { 90 | nullptr, 91 | bytesRequired<4>, // kReadCoils 0x01 92 | bytesRequired<4>, // kReadInputDiscrete 0x02 93 | bytesRequired<4>, // kReadHoldingRegisters 0x03 94 | bytesRequired<4>, // kReadInputRegister 0x04 95 | bytesRequired<4>, // kWriteSingleCoil 0x05 96 | bytesRequired<4>, // kWriteSingleRegister 0x06 97 | nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, 98 | bytesRequiredStoreInArrayIndex<4>, // kWriteMultipleCoils = 0x0f, 99 | bytesRequiredStoreInArrayIndex<4>, // kWriteMultipleRegisters = 0x10, 100 | nullptr, nullptr, nullptr, 101 | nullptr, // kReadFileRecords = 0x14, 102 | nullptr, // kWriteFileRecords = 0x15, 103 | nullptr, // kMaskWriteRegister = 0x16, 104 | bytesRequiredStoreInArrayIndex<9>, // kReadWriteMultipleRegisters = 105 | // 0x17, 106 | nullptr, // kReadDeviceIdentificationCode = 0x2b 107 | }; 108 | return table; 109 | } 110 | 111 | class ModbusRtuFrameDecoder : public ModbusFrameDecoder { 112 | enum class State { kServerAddress, kFunctionCode, kData, kCrc0, kCrc1, kEnd }; 113 | 114 | public: 115 | explicit ModbusRtuFrameDecoder(const CheckSizeFuncTable &table) 116 | : ModbusFrameDecoder(table) { 117 | Clear(); 118 | } 119 | 120 | ~ModbusRtuFrameDecoder() override = default; 121 | 122 | CheckSizeResult Decode(pp::bytes::Buffer &buffer, Adu *adu) override { 123 | CheckSizeResult result = CheckSizeResult::kNeedMoreData; 124 | while (buffer.Len() > 0 || state_ == State::kEnd) { 125 | switch (state_) { 126 | case State::kServerAddress: { 127 | const auto serverAddress = buffer.ReadByte(); 128 | adu->setServerAddress(serverAddress); 129 | crcCtx_.crc16(static_cast(&serverAddress), 1); 130 | 131 | state_ = State::kFunctionCode; 132 | } break; 133 | case State::kFunctionCode: { 134 | auto functionCode = buffer.ReadByte(); 135 | adu->setFunctionCode(static_cast(functionCode)); 136 | crcCtx_.crc16((const uint8_t *)&functionCode, 1); 137 | 138 | state_ = State::kData; 139 | 140 | function_ = adu->isException() 141 | ? bytesRequired<1> 142 | : checkSizeFuncTable_[adu->functionCode()]; 143 | 144 | if (!function_) { 145 | error_ = Error::kIllegalFunctionCode; 146 | state_ = State::kEnd; 147 | } 148 | 149 | } break; 150 | case State::kData: { 151 | size_t expectSize = 0; 152 | 153 | uint8_t *p; 154 | buffer.ZeroCopyPeekAt(&p, 0, buffer.Len()); 155 | 156 | result = function_(expectSize, p, buffer.Len()); 157 | if (result == CheckSizeResult::kNeedMoreData) { 158 | goto exit_function; 159 | } 160 | // we need crc, 161 | result = CheckSizeResult::kNeedMoreData; 162 | 163 | buffer.ZeroCopyRead(&p, expectSize); 164 | 165 | adu->setData(p, expectSize); 166 | crcCtx_.crc16(p, expectSize); 167 | 168 | state_ = State::kCrc0; 169 | } break; 170 | case State::kCrc0: { 171 | crc_[0] = buffer.ReadByte(); 172 | 173 | state_ = State::kCrc1; 174 | } break; 175 | case State::kCrc1: { 176 | crc_[1] = buffer.ReadByte(); 177 | 178 | uint16_t crc = crcCtx_.end(); 179 | if (crc % 256 != crc_[0] || crc / 256 != crc_[1]) { 180 | error_ = Error::kStorageParityError; 181 | } else if (adu->isException()) { 182 | error_ = Error(adu->data()[0]); 183 | } 184 | 185 | state_ = State::kEnd; 186 | } break; 187 | case State::kEnd: { 188 | result = CheckSizeResult::kSizeOk; 189 | isDone_ = true; 190 | goto exit_function; 191 | } break; 192 | } 193 | } 194 | exit_function: 195 | return result; 196 | } 197 | 198 | bool IsDone() const override { return isDone_; } 199 | 200 | void Clear() override { 201 | state_ = State::kServerAddress; 202 | isDone_ = false; 203 | crcCtx_.clear(); 204 | error_ = Error::kNoError; 205 | } 206 | 207 | Error LasError() const override { return error_; } 208 | 209 | private: 210 | State state_ = State::kServerAddress; 211 | bool isDone_ = false; 212 | uint8_t crc_[2]; 213 | CrcCtx crcCtx_; 214 | Error error_ = Error::kNoError; 215 | CheckSizeFunc function_; 216 | }; 217 | 218 | class ModbusAsciiFrameDecoder : public ModbusFrameDecoder { 219 | enum class State { 220 | kStartChar, 221 | kServerAddress, 222 | kFunctionCode, 223 | kData, 224 | LRC, 225 | kEndChar, 226 | kEnd 227 | }; 228 | 229 | public: 230 | explicit ModbusAsciiFrameDecoder(const CheckSizeFuncTable &table) 231 | : ModbusFrameDecoder(table) { 232 | Clear(); 233 | } 234 | 235 | ~ModbusAsciiFrameDecoder() override = default; 236 | 237 | CheckSizeResult Decode(pp::bytes::Buffer & /**/, Adu *) override { 238 | assert("ascii mode:not support yet"); 239 | 240 | return CheckSizeResult::kSizeOk; 241 | } 242 | 243 | bool IsDone() const override { return isDone_; } 244 | 245 | void Clear() override { 246 | state_ = State::kServerAddress; 247 | isDone_ = false; 248 | crcCtx_.clear(); 249 | error_ = Error::kNoError; 250 | function_ = nullptr; 251 | } 252 | 253 | Error LasError() const override { return error_; } 254 | 255 | private: 256 | State state_ = State::kServerAddress; 257 | bool isDone_ = false; 258 | CrcCtx crcCtx_; 259 | Error error_ = Error::kNoError; 260 | CheckSizeFunc function_; 261 | }; 262 | 263 | class ModbusMbapFrameDecoder : public ModbusFrameDecoder { 264 | enum class State { kMBap, kServerAddress, kFunctionCode, kData, kEnd }; 265 | 266 | public: 267 | explicit ModbusMbapFrameDecoder(const CheckSizeFuncTable &table) 268 | : ModbusFrameDecoder(table) { 269 | Clear(); 270 | } 271 | 272 | ~ModbusMbapFrameDecoder() override = default; 273 | 274 | CheckSizeResult Decode(pp::bytes::Buffer &buffer, Adu *adu) override { 275 | CheckSizeResult result = CheckSizeResult::kNeedMoreData; 276 | while (buffer.Len() > 0 || state_ == State::kEnd) { 277 | switch (state_) { 278 | case State::kMBap: { 279 | if (buffer.Len() < 6) { 280 | goto exit_function; 281 | } 282 | uint8_t *p; 283 | buffer.ZeroCopyRead(&p, 6); 284 | 285 | // transaction id 286 | adu->setTransactionId(p[0] * 256 + p[1]); 287 | 288 | flag_ = p[2] * 256 + p[3]; 289 | len_ = p[4] * 256 + p[5]; 290 | 291 | state_ = State::kServerAddress; 292 | } break; 293 | case State::kServerAddress: { 294 | if (buffer.Len() < len_) { 295 | goto exit_function; 296 | } 297 | 298 | const auto serverAddress = buffer.ReadByte(); 299 | adu->setServerAddress(serverAddress); 300 | 301 | state_ = State::kFunctionCode; 302 | } break; 303 | case State::kFunctionCode: { 304 | auto functionCode = buffer.ReadByte(); 305 | adu->setFunctionCode(static_cast(functionCode)); 306 | 307 | state_ = State::kData; 308 | 309 | function_ = adu->isException() 310 | ? bytesRequired<1> 311 | : checkSizeFuncTable_[adu->functionCode()]; 312 | 313 | if (!function_) { 314 | error_ = Error::kIllegalFunctionCode; 315 | state_ = State::kEnd; 316 | } 317 | } break; 318 | case State::kData: { 319 | size_t expectSize = 0; 320 | 321 | uint8_t *p; 322 | buffer.ZeroCopyPeekAt(&p, 0, buffer.Len()); 323 | 324 | result = function_(expectSize, p, buffer.Len()); 325 | if (result == CheckSizeResult::kNeedMoreData) { 326 | goto exit_function; 327 | } 328 | 329 | buffer.ZeroCopyRead(&p, expectSize); 330 | 331 | adu->setData(p, expectSize); 332 | if (adu->isException()) { 333 | error_ = Error(adu->data()[0]); 334 | } 335 | 336 | state_ = State::kEnd; 337 | } break; 338 | 339 | case State::kEnd: { 340 | result = CheckSizeResult::kSizeOk; 341 | isDone_ = true; 342 | goto exit_function; 343 | } break; 344 | } 345 | } 346 | exit_function: 347 | return result; 348 | } 349 | 350 | bool IsDone() const override { return isDone_; } 351 | 352 | void Clear() override { 353 | state_ = State::kMBap; 354 | isDone_ = false; 355 | error_ = Error::kNoError; 356 | flag_ = 0; 357 | len_ = 0; 358 | } 359 | 360 | Error LasError() const override { return error_; } 361 | 362 | private: 363 | State state_ = State::kMBap; 364 | bool isDone_ = false; 365 | Error error_ = Error::kNoError; 366 | CheckSizeFunc function_; 367 | uint16_t flag_ = 0; 368 | uint16_t len_ = 0; 369 | }; 370 | 371 | class ModbusRtuFrameEncoder : public ModbusFrameEncoder { 372 | public: 373 | ~ModbusRtuFrameEncoder() override = default; 374 | 375 | void Encode(const Adu *adu, pp::bytes::Buffer &buffer) override { 376 | buffer.Write(adu->serverAddress()); 377 | if (adu->isException()) { 378 | buffer.Write(FunctionCode(adu->functionCode() | Adu::kExceptionByte)); 379 | } else { 380 | buffer.Write(adu->functionCode()); 381 | } 382 | buffer.Write(adu->data()); 383 | 384 | uint8_t *data; 385 | int len = buffer.Len(); 386 | buffer.ZeroCopyPeekAt(&data, 0, len); 387 | auto crc = tool::crc16_modbus(data, len); 388 | buffer.Write(crc % 256); 389 | buffer.Write(crc / 256); 390 | } 391 | }; 392 | 393 | class ModbusMbapFrameEncoder : public ModbusFrameEncoder { 394 | public: 395 | ~ModbusMbapFrameEncoder() override = default; 396 | 397 | void Encode(const Adu *adu, pp::bytes::Buffer &buffer) override { 398 | const auto &data = adu->data(); 399 | 400 | buffer.Write(adu->transactionId() / 256); 401 | buffer.Write(adu->transactionId() % 256); 402 | 403 | // protocol id 404 | buffer.Write(0); 405 | buffer.Write(0); 406 | 407 | int size = data.size() + 2; 408 | buffer.Write(size / 256); 409 | buffer.Write(size % 256); 410 | 411 | buffer.Write(adu->serverAddress()); 412 | 413 | if (adu->isException()) { 414 | buffer.Write(FunctionCode(adu->functionCode() | Adu::kExceptionByte)); 415 | } else { 416 | buffer.Write(adu->functionCode()); 417 | } 418 | 419 | buffer.Write(data); 420 | } 421 | }; 422 | 423 | class ModbusAsciiFrameEncoder : public ModbusFrameEncoder { 424 | public: 425 | ~ModbusAsciiFrameEncoder() override = default; 426 | 427 | void Encode(const Adu * /*adu*/, pp::bytes::Buffer & /*buffer*/) override { 428 | static_assert(true, "ascii mode not support yet"); 429 | } 430 | }; 431 | 432 | inline std::unique_ptr 433 | createModbusFrameDecoder(TransferMode mode, const CheckSizeFuncTable &table) { 434 | switch (mode) { 435 | case TransferMode::kRtu: 436 | return std::unique_ptr( 437 | new ModbusRtuFrameDecoder(table)); 438 | case TransferMode::kAscii: 439 | return std::unique_ptr( 440 | new ModbusAsciiFrameDecoder(table)); 441 | case TransferMode::kMbap: 442 | return std::unique_ptr( 443 | new ModbusMbapFrameDecoder(table)); 444 | default: 445 | smart_assert("unsupported modbus transfer mode")(static_cast(mode)); 446 | return nullptr; 447 | } 448 | } 449 | 450 | inline std::unique_ptr 451 | createModbusFrameEncoder(TransferMode mode) { 452 | switch (mode) { 453 | case TransferMode::kRtu: 454 | return std::unique_ptr(new ModbusRtuFrameEncoder()); 455 | case TransferMode::kAscii: 456 | return std::unique_ptr( 457 | new ModbusAsciiFrameEncoder()); 458 | case TransferMode::kMbap: 459 | return std::unique_ptr( 460 | new ModbusMbapFrameEncoder()); 461 | default: 462 | smart_assert("unsupported modbus transfer mode")(static_cast(mode)); 463 | return nullptr; 464 | } 465 | } 466 | 467 | } // namespace modbus 468 | 469 | #endif // __MODBUS_SERIAL_PORT_H_ 470 | -------------------------------------------------------------------------------- /test/modbus_test_server.cpp: -------------------------------------------------------------------------------- 1 | #include "../src/tools/modbus_server_p.h" 2 | #include "bytes/buffer.h" 3 | #include "modbus_frame.h" 4 | #include "modbusserver_client_session.h" 5 | #include "gmock/gmock-spec-builders.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace testing; 18 | using namespace modbus; 19 | 20 | class TestConnection : public AbstractConnection { 21 | Q_OBJECT 22 | public: 23 | TestConnection(QObject *parent = nullptr) : AbstractConnection(parent) { 24 | EXPECT_CALL(*this, fullName()).WillRepeatedly(Invoke([]() { 25 | return "COM1"; 26 | })); 27 | } 28 | ~TestConnection() {} 29 | 30 | MOCK_CONST_METHOD0(fd, quintptr()); 31 | MOCK_METHOD2(write, void(const char *data, size_t size)); 32 | MOCK_CONST_METHOD0(name, std::string()); 33 | MOCK_CONST_METHOD0(fullName, std::string()); 34 | }; 35 | 36 | class TestServer : public AbstractServer { 37 | Q_OBJECT 38 | public: 39 | TestServer(QObject *parent = nullptr) : AbstractServer(parent) {} 40 | ~TestServer() {} 41 | MOCK_METHOD0(listenAndServe, bool()); 42 | }; 43 | 44 | TEST(QModbusServer, constructor) { 45 | TestServer testServer; 46 | QModbusServer server(&testServer); 47 | 48 | EXPECT_EQ(server.maxClients(), 1); 49 | EXPECT_EQ(server.serverAddress(), 0x01); 50 | EXPECT_EQ(server.blacklist().size(), 0); 51 | } 52 | 53 | TEST(QModbusServer, set_get) { 54 | TestServer testServer; 55 | QModbusServer server(&testServer); 56 | 57 | server.setMaxClients(3); 58 | EXPECT_EQ(server.maxClients(), 3); 59 | server.setMaxClients(-1); 60 | EXPECT_EQ(server.maxClients(), 1); 61 | server.addBlacklist("192.168.1.111"); 62 | server.addBlacklist("192.168.1.123"); 63 | EXPECT_THAT(server.blacklist(), 64 | ElementsAre("192.168.1.111", "192.168.1.123")); 65 | 66 | server.setServerAddress(0x01); 67 | EXPECT_EQ(server.serverAddress(), 0x01); 68 | } 69 | 70 | TEST(QModbusServer, testSignles) { 71 | int argc = 1; 72 | char *argv[] = {(char *)"test"}; 73 | QCoreApplication app(argc, argv); 74 | 75 | TestConnection testConn; 76 | AbstractConnection *conn = &testConn; 77 | qRegisterMetaType("BytesBufferPtr"); 78 | QSignalSpy spy(conn, &AbstractConnection::messageArrived); 79 | 80 | BytesBufferPtr requestBuffer(new pp::bytes::Buffer); 81 | testConn.messageArrived(1, requestBuffer); 82 | 83 | EXPECT_EQ(spy.count(), 1); 84 | 85 | QTimer::singleShot(0, [&]() { app.quit(); }); 86 | app.exec(); 87 | } 88 | 89 | TEST(QModbusServer, 90 | recivedRequest_requestServerAddressIsBadAddress_discardTheRequest) { 91 | TestServer server; 92 | QModbusServer modbusServer(&server); 93 | QModbusServerPrivate d(&modbusServer); 94 | 95 | d.setServer(&server); 96 | d.setServerAddress(1); 97 | d.setTransferMode(TransferMode::kRtu); 98 | 99 | pp::bytes::Buffer requestBuffer; 100 | ByteArray raw({0x02, 0x01, 0x00, 0x00, 0x00, 0x01}); 101 | raw = tool::appendCrc(raw); 102 | requestBuffer.Write(raw); 103 | 104 | auto *mockConn = new TestConnection(); 105 | ClientSession session(&d, mockConn, 106 | creatDefaultCheckSizeFuncTableForServer()); 107 | 108 | EXPECT_CALL(*mockConn, write).Times(0); 109 | session.handleModbusRequest(requestBuffer); 110 | } 111 | 112 | TEST(QModbusServer, recivedRequest_needMoreData) { 113 | TestServer server; 114 | QModbusServer modbusServer(&server); 115 | QModbusServerPrivate d(&modbusServer); 116 | 117 | d.setServer(&server); 118 | d.setServerAddress(1); 119 | d.setTransferMode(TransferMode::kRtu); 120 | 121 | pp::bytes::Buffer requestBuffer; 122 | ByteArray raw({0x01}); 123 | requestBuffer.Write(raw); 124 | 125 | auto *mockConn = new TestConnection(); 126 | ClientSession session(&d, mockConn, 127 | creatDefaultCheckSizeFuncTableForServer()); 128 | 129 | EXPECT_CALL(*mockConn, write).Times(0); 130 | session.handleModbusRequest(requestBuffer); 131 | } 132 | 133 | TEST( 134 | QModbusServer, 135 | recivedRequest_theRequestedFunctionCodeIsNotSupported_returnIllegalFunctionCodeError) { 136 | TestServer server; 137 | QModbusServer modbusServer(&server); 138 | QModbusServerPrivate d(&modbusServer); 139 | 140 | d.setServer(&server); 141 | d.setServerAddress(1); 142 | d.setTransferMode(TransferMode::kRtu); 143 | 144 | pp::bytes::Buffer requestBuffer; 145 | ByteArray raw({0x01, 0x01, 0x00, 0x00, 0x00, 0x01}); 146 | raw = tool::appendCrc(raw); 147 | requestBuffer.Write((char *)raw.data(), raw.size()); 148 | 149 | auto *mockConn = new TestConnection(); 150 | ClientSession session(&d, mockConn, 151 | creatDefaultCheckSizeFuncTableForServer()); 152 | 153 | EXPECT_CALL(*mockConn, write).Times(1); 154 | session.handleModbusRequest(requestBuffer); 155 | } 156 | 157 | TEST(QModbusServer, processReadCoils_success) { 158 | TestServer server; 159 | QModbusServer modbusServer(&server); 160 | QModbusServerPrivate d(&modbusServer); 161 | 162 | d.setServerAddress(1); 163 | d.setTransferMode(TransferMode::kRtu); 164 | 165 | d.handleCoils(0x01, 10); 166 | d.writeCoils(0x01, true); 167 | d.writeCoils(0x03, true); 168 | 169 | SingleBitAccess access; 170 | 171 | access.setStartAddress(0x01); 172 | access.setQuantity(0x3); 173 | 174 | Adu request; 175 | Adu response; 176 | 177 | request.setServerAddress(0x01); 178 | request.setFunctionCode(FunctionCode::kReadCoils); 179 | request.setData(access.marshalReadRequest()); 180 | 181 | d.processRequest(&request, &response); 182 | EXPECT_EQ(response.error(), Error::kNoError); 183 | EXPECT_EQ(response.serverAddress(), 0x01); 184 | EXPECT_EQ(response.functionCode(), FunctionCode::kReadCoils); 185 | EXPECT_EQ(response.data(), ByteArray({0x01, 0x05})); 186 | } 187 | 188 | TEST(QModbusServer, processReadCoils_badDataAddress_failed) { 189 | TestServer server; 190 | QModbusServer modbusServer(&server); 191 | QModbusServerPrivate d(&modbusServer); 192 | 193 | d.setServerAddress(1); 194 | d.setTransferMode(TransferMode::kRtu); 195 | 196 | d.handleCoils(0x01, 10); 197 | d.writeCoils(0x01, true); 198 | d.writeCoils(0x03, true); 199 | 200 | Adu request; 201 | Adu response; 202 | 203 | request.setServerAddress(0x01); 204 | request.setFunctionCode(FunctionCode::kReadCoils); 205 | request.setData({0x00, 0x06, 0x00, 0x10}); 206 | 207 | d.processRequest(&request, &response); 208 | EXPECT_EQ(response.error(), Error::kIllegalDataAddress); 209 | EXPECT_EQ(response.isException(), true); 210 | } 211 | 212 | TEST(QModbusServer, processWriteSingleCoils_success) { 213 | TestServer server; 214 | QModbusServer modbusServer(&server); 215 | QModbusServerPrivate d(&modbusServer); 216 | 217 | d.setServerAddress(1); 218 | d.setTransferMode(TransferMode::kRtu); 219 | 220 | d.handleCoils(0x01, 10); 221 | 222 | SingleBitAccess access; 223 | 224 | access.setStartAddress(0x01); 225 | access.setQuantity(0x01); 226 | access.setValue(true); 227 | 228 | Adu request; 229 | Adu response; 230 | 231 | request.setServerAddress(0x01); 232 | request.setFunctionCode(FunctionCode::kWriteSingleCoil); 233 | request.setData({access.marshalSingleWriteRequest()}); 234 | 235 | d.processRequest(&request, &response); 236 | EXPECT_EQ(response.error(), Error::kNoError); 237 | EXPECT_EQ(response.serverAddress(), 0x01); 238 | EXPECT_EQ(response.functionCode(), FunctionCode::kWriteSingleCoil); 239 | EXPECT_EQ(response.data(), ByteArray({0x00, 0x01, 0xff, 0x00})); 240 | } 241 | 242 | TEST(QModbusServer, processWriteSingleCoils_badAddress_Failed) { 243 | TestServer server; 244 | QModbusServer modbusServer(&server); 245 | QModbusServerPrivate d(&modbusServer); 246 | 247 | d.setServerAddress(1); 248 | d.setTransferMode(TransferMode::kRtu); 249 | 250 | d.handleCoils(0x99, 10); 251 | 252 | SingleBitAccess access; 253 | 254 | access.setStartAddress(0x01); 255 | access.setQuantity(0x01); 256 | access.setValue(true); 257 | 258 | Adu request; 259 | Adu response; 260 | 261 | request.setServerAddress(0x01); 262 | request.setFunctionCode(FunctionCode::kWriteSingleCoil); 263 | request.setData(access.marshalSingleWriteRequest()); 264 | 265 | d.processRequest(&request, &response); 266 | EXPECT_EQ(response.error(), Error::kIllegalDataAddress); 267 | EXPECT_EQ(response.isException(), true); 268 | EXPECT_EQ(response.functionCode(), FunctionCode::kWriteSingleCoil); 269 | EXPECT_EQ(response.data(), ByteArray({0x02})); 270 | } 271 | 272 | TEST(QModbusServer, processWriteSingleCoils_badValue_Failed) { 273 | TestServer server; 274 | QModbusServer modbusServer(&server); 275 | QModbusServerPrivate d(&modbusServer); 276 | 277 | d.setServerAddress(1); 278 | d.setTransferMode(TransferMode::kRtu); 279 | 280 | d.handleCoils(0x01, 10); 281 | 282 | Adu request; 283 | Adu response; 284 | 285 | request.setServerAddress(0x01); 286 | request.setFunctionCode(FunctionCode::kWriteSingleCoil); 287 | request.setData(ByteArray({0x00, 0x01, 0xff, 0xff})); 288 | 289 | d.processRequest(&request, &response); 290 | EXPECT_EQ(response.error(), Error::kStorageParityError); 291 | EXPECT_EQ(response.isException(), true); 292 | EXPECT_EQ(response.functionCode(), FunctionCode::kWriteSingleCoil); 293 | EXPECT_EQ(response.data(), ByteArray({0x08})); 294 | } 295 | 296 | TEST(QModbusServer, processWriteSingleCoils_badValue_checkWriteFailed) { 297 | TestServer server; 298 | QModbusServer modbusServer(&server); 299 | QModbusServerPrivate d(&modbusServer); 300 | 301 | d.setServerAddress(1); 302 | d.setTransferMode(TransferMode::kRtu); 303 | d.setCanWriteSingleBitValueFunc( 304 | [&](Address address, bool value) { return Error::kSlaveDeviceBusy; }); 305 | 306 | d.handleCoils(0x01, 10); 307 | 308 | Adu request; 309 | Adu response; 310 | 311 | request.setServerAddress(0x01); 312 | request.setFunctionCode(FunctionCode::kWriteSingleCoil); 313 | request.setData(ByteArray({0x00, 0x01, 0x00, 0x00})); 314 | 315 | d.processRequest(&request, &response); 316 | EXPECT_EQ(response.error(), Error::kSlaveDeviceBusy); 317 | EXPECT_EQ(response.isException(), true); 318 | EXPECT_EQ(response.functionCode(), FunctionCode::kWriteSingleCoil); 319 | EXPECT_EQ(response.data(), ByteArray({0x06})); 320 | } 321 | 322 | TEST(QModbusServer, processWriteMultipleCoils_success) { 323 | TestServer server; 324 | QModbusServer modbusServer(&server); 325 | QModbusServerPrivate d(&modbusServer); 326 | 327 | d.setServerAddress(1); 328 | d.setTransferMode(TransferMode::kRtu); 329 | 330 | d.handleCoils(0x00, 0x10); 331 | 332 | Adu request; 333 | Adu response; 334 | 335 | request.setServerAddress(0x01); 336 | request.setFunctionCode(FunctionCode::kWriteMultipleCoils); 337 | request.setData(ByteArray({0x00, 0x00, 0x00, 0x09, 0x02, 0xff, 0x01})); 338 | 339 | d.processRequest(&request, &response); 340 | EXPECT_EQ(response.error(), Error::kNoError); 341 | EXPECT_EQ(response.isException(), false); 342 | EXPECT_EQ(response.data(), ByteArray({0x00, 0x00, 0x00, 0x09})); 343 | } 344 | 345 | TEST(QModbusServer, processWriteMultipleCoils_failed) { 346 | TestServer server; 347 | QModbusServer modbusServer(&server); 348 | QModbusServerPrivate d(&modbusServer); 349 | 350 | d.setServerAddress(1); 351 | d.setTransferMode(TransferMode::kRtu); 352 | 353 | d.handleCoils(0x00, 0x10); 354 | 355 | Adu request; 356 | Adu response; 357 | 358 | request.setServerAddress(0x01); 359 | request.setFunctionCode(FunctionCode::kWriteMultipleCoils); 360 | request.setData(ByteArray({0x00, 0x00, 0x00, 0x19, 0x02, 0xff, 0x01})); 361 | 362 | d.processRequest(&request, &response); 363 | EXPECT_EQ(response.error(), Error::kIllegalDataAddress); 364 | EXPECT_EQ(response.isException(), true); 365 | EXPECT_EQ(response.data(), ByteArray({uint8_t(Error::kIllegalDataAddress)})); 366 | } 367 | 368 | TEST(QModbusServer, processReadMultipleRegisters_success) { 369 | TestServer server; 370 | QModbusServer modbusServer(&server); 371 | QModbusServerPrivate d(&modbusServer); 372 | 373 | d.setServerAddress(1); 374 | d.setTransferMode(TransferMode::kRtu); 375 | 376 | d.handleInputRegisters(0x00, 0x10); 377 | d.writeInputRegisters(0x00, {SixteenBitValue(0x1234)}); 378 | d.writeInputRegisters(0x01, {SixteenBitValue(0x5678)}); 379 | d.writeInputRegisters(0x02, {SixteenBitValue(0x9876)}); 380 | 381 | Adu request; 382 | Adu response; 383 | 384 | request.setServerAddress(0x01); 385 | request.setFunctionCode(FunctionCode::kReadInputRegister); 386 | request.setData(ByteArray({0x00, 0x00, 0x00, 0x03})); 387 | 388 | d.processRequest(&request, &response); 389 | EXPECT_EQ(response.error(), Error::kNoError); 390 | EXPECT_EQ(response.isException(), false); 391 | EXPECT_EQ(response.data(), 392 | ByteArray({0x06, 0x12, 0x34, 0x56, 0x78, 0x98, 0x76})); 393 | } 394 | 395 | TEST(QModbusServer, processReadMultipleRegisters_badAddress_failed) { 396 | TestServer server; 397 | QModbusServer modbusServer(&server); 398 | QModbusServerPrivate d(&modbusServer); 399 | 400 | d.setServerAddress(1); 401 | d.setTransferMode(TransferMode::kRtu); 402 | 403 | d.handleInputRegisters(0x00, 0x10); 404 | d.writeInputRegisters(0x00, {SixteenBitValue(0x1234)}); 405 | d.writeInputRegisters(0x01, {SixteenBitValue(0x5678)}); 406 | d.writeInputRegisters(0x02, {SixteenBitValue(0x9876)}); 407 | 408 | Adu request; 409 | Adu response; 410 | 411 | request.setServerAddress(0x01); 412 | request.setFunctionCode(FunctionCode::kReadInputRegister); 413 | request.setData(ByteArray({0x00, 0x99, 0x00, 0x03})); 414 | 415 | d.processRequest(&request, &response); 416 | EXPECT_EQ(response.error(), Error::kIllegalDataAddress); 417 | EXPECT_EQ(response.isException(), true); 418 | EXPECT_EQ(response.data(), ByteArray({uint8_t(Error::kIllegalDataAddress)})); 419 | } 420 | 421 | TEST(QModbusServer, processReadMultipleRegisters_badQuantity_failed) { 422 | TestServer server; 423 | QModbusServer modbusServer(&server); 424 | QModbusServerPrivate d(&modbusServer); 425 | 426 | d.setServerAddress(1); 427 | d.setTransferMode(TransferMode::kRtu); 428 | 429 | d.handleInputRegisters(0x00, 0x10); 430 | d.writeInputRegisters(0x00, {SixteenBitValue(0x1234)}); 431 | d.writeInputRegisters(0x01, {SixteenBitValue(0x5678)}); 432 | d.writeInputRegisters(0x02, {SixteenBitValue(0x9876)}); 433 | 434 | Adu request; 435 | Adu response; 436 | 437 | request.setServerAddress(0x01); 438 | request.setFunctionCode(FunctionCode::kReadInputRegister); 439 | request.setData(ByteArray({0x00, 0x08, 0x00, 0x09})); 440 | 441 | d.processRequest(&request, &response); 442 | EXPECT_EQ(response.error(), Error::kIllegalDataAddress); 443 | EXPECT_EQ(response.isException(), true); 444 | EXPECT_EQ(response.data(), ByteArray({uint8_t(Error::kIllegalDataAddress)})); 445 | } 446 | 447 | TEST(QModbusServer, processWriteSingleRegister_success) { 448 | TestServer server; 449 | QModbusServer modbusServer(&server); 450 | QModbusServerPrivate d(&modbusServer); 451 | 452 | d.setServerAddress(1); 453 | d.setTransferMode(TransferMode::kRtu); 454 | 455 | d.handleHoldingRegisters(0x00, 0x10); 456 | d.writeHodingRegisters(0x00, {SixteenBitValue(0x1234)}); 457 | d.writeHodingRegisters(0x01, {SixteenBitValue(0x5678)}); 458 | d.writeHodingRegisters(0x02, {SixteenBitValue(0x9876)}); 459 | 460 | Adu request; 461 | Adu response; 462 | 463 | request.setServerAddress(0x01); 464 | request.setFunctionCode(FunctionCode::kWriteSingleRegister); 465 | request.setData(ByteArray({0x00, 0x08, 0x00, 0x09})); 466 | 467 | d.processRequest(&request, &response); 468 | EXPECT_EQ(response.error(), Error::kNoError); 469 | EXPECT_EQ(response.isException(), false); 470 | EXPECT_EQ(response.data(), ByteArray({0x00, 0x08, 0x00, 0x09})); 471 | } 472 | 473 | TEST(QModbusServer, processWriteSingleRegister_badAddress_failed) { 474 | TestServer server; 475 | QModbusServer modbusServer(&server); 476 | QModbusServerPrivate d(&modbusServer); 477 | 478 | d.setServerAddress(1); 479 | d.setTransferMode(TransferMode::kRtu); 480 | 481 | d.handleHoldingRegisters(0x00, 0x10); 482 | d.writeHodingRegisters(0x00, {SixteenBitValue(0x1234)}); 483 | d.writeHodingRegisters(0x01, {SixteenBitValue(0x5678)}); 484 | d.writeHodingRegisters(0x02, {SixteenBitValue(0x9876)}); 485 | 486 | Adu request; 487 | Adu response; 488 | 489 | request.setServerAddress(0x01); 490 | request.setFunctionCode(FunctionCode::kWriteSingleRegister); 491 | request.setData(ByteArray({0x00, 0x88, 0x00, 0x09})); 492 | 493 | d.processRequest(&request, &response); 494 | EXPECT_EQ(response.error(), Error::kIllegalDataAddress); 495 | EXPECT_EQ(response.isException(), true); 496 | EXPECT_EQ(response.data(), ByteArray({0x02})); 497 | } 498 | 499 | TEST(QModbusServer, processWriteSingleRegister_badValue_failed) { 500 | TestServer server; 501 | QModbusServer modbusServer(&server); 502 | QModbusServerPrivate d(&modbusServer); 503 | 504 | d.setServerAddress(1); 505 | d.setTransferMode(TransferMode::kRtu); 506 | d.setCanWriteSixteenBitValueFunc( 507 | [&](Address address, const SixteenBitValue &value) { 508 | return Error::kIllegalDataValue; 509 | }); 510 | 511 | d.handleHoldingRegisters(0x00, 0x10); 512 | d.writeHodingRegisters(0x00, {SixteenBitValue(0x1234)}); 513 | d.writeHodingRegisters(0x01, {SixteenBitValue(0x5678)}); 514 | d.writeHodingRegisters(0x02, {SixteenBitValue(0x9876)}); 515 | 516 | Adu request; 517 | Adu response; 518 | 519 | request.setServerAddress(0x01); 520 | request.setFunctionCode(FunctionCode::kWriteSingleRegister); 521 | request.setData(ByteArray({0x00, 0x08, 0x00, 0x09})); 522 | 523 | d.processRequest(&request, &response); 524 | EXPECT_EQ(response.error(), Error::kIllegalDataValue); 525 | EXPECT_EQ(response.isException(), true); 526 | EXPECT_EQ(response.data(), ByteArray({0x03})); 527 | } 528 | 529 | TEST(QModbusServer, processWriteMultipleRegisters_success) { 530 | TestServer server; 531 | QModbusServer modbusServer(&server); 532 | QModbusServerPrivate d(&modbusServer); 533 | 534 | QSignalSpy spy(&modbusServer, &QModbusServer::holdingRegisterValueChanged); 535 | QSignalSpy spy2(&modbusServer, &QModbusServer::writeHodingRegistersRequested); 536 | 537 | d.setServerAddress(1); 538 | d.setTransferMode(TransferMode::kRtu); 539 | 540 | d.handleHoldingRegisters(0x00, 0x10); 541 | d.writeHodingRegisters(0x00, {SixteenBitValue(0x1234)}); 542 | d.writeHodingRegisters(0x01, {SixteenBitValue(0x5678)}); 543 | d.writeHodingRegisters(0x02, {SixteenBitValue(0x9876)}); 544 | 545 | Adu request; 546 | Adu response; 547 | 548 | request.setServerAddress(0x01); 549 | request.setFunctionCode(FunctionCode::kWriteMultipleRegisters); 550 | request.setData(ByteArray({0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x01})); 551 | 552 | d.processRequest(&request, &response); 553 | 554 | EXPECT_EQ(response.error(), Error::kNoError); 555 | EXPECT_EQ(response.isException(), false); 556 | EXPECT_EQ(response.data(), ByteArray({0x00, 0x00, 0x00, 0x01})); 557 | EXPECT_EQ(spy.count(), 3); 558 | EXPECT_EQ(spy2.count(), 1); 559 | } 560 | 561 | TEST(QModbusServer, writeValueSixteenValue_success) { 562 | TestServer server; 563 | QModbusServer modbusServer(&server); 564 | QModbusServerPrivate d(&modbusServer); 565 | 566 | QSignalSpy spy(&modbusServer, &QModbusServer::holdingRegisterValueChanged); 567 | 568 | d.setServerAddress(1); 569 | d.setTransferMode(TransferMode::kRtu); 570 | 571 | d.handleHoldingRegisters(0x00, 0x10); 572 | d.writeHodingRegisters(0x00, {SixteenBitValue(0x1234)}); 573 | d.writeHodingRegisters(0x01, {SixteenBitValue(0x5678)}); 574 | d.writeHodingRegisters(0x02, {SixteenBitValue(0x9876)}); 575 | 576 | EXPECT_EQ(spy.count(), 3); 577 | 578 | SixteenBitValue value; 579 | bool ok = d.holdingRegisterValue(0x02, &value); 580 | EXPECT_EQ(ok, true); 581 | EXPECT_EQ(value.toUint16(), SixteenBitValue(0x9876).toUint16()); 582 | } 583 | 584 | #include "modbus_test_server.moc" 585 | --------------------------------------------------------------------------------