├── .clang-format ├── .cmake-format ├── .github └── workflows │ ├── documentation.yaml │ ├── install.yml │ ├── macos.yml │ ├── standalone.yml │ ├── style.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── all └── CMakeLists.txt ├── cmake ├── Asio2.cmake ├── Boost.cmake ├── CPM.cmake └── tools.cmake ├── codecov.yaml ├── documentation ├── CMakeLists.txt ├── Doxyfile ├── conf.py └── pages │ └── about.dox ├── include └── modbuscpp │ ├── modbus.hpp │ └── modbuscpp │ ├── adu.hpp │ ├── asio2.hpp │ ├── bit-read.hpp │ ├── bit-read.inline.hpp │ ├── bit-write.hpp │ ├── constants.hpp │ ├── data-table.hpp │ ├── data-table.inline.hpp │ ├── exception.hpp │ ├── logger.hpp │ ├── operation.hpp │ ├── register-read.hpp │ ├── register-read.inline.hpp │ ├── register-write.hpp │ ├── request-handler.hpp │ ├── request.hpp │ ├── response.hpp │ ├── server.hpp │ ├── struct.hpp │ ├── types.hpp │ └── utilities.hpp ├── source ├── adu.cpp ├── bit-read.cpp ├── bit-write.cpp ├── data-table.cpp ├── logger.cpp ├── operation.cpp ├── register-read.cpp ├── register-write.cpp ├── request-handler.cpp ├── request.cpp ├── response.cpp └── server.cpp ├── standalone ├── CMakeLists.txt └── source │ ├── client.cpp │ └── server.cpp └── test ├── CMakeLists.txt └── source ├── main.cpp └── meta.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Chromium 4 | AccessModifierOffset: '-2' 5 | AlignTrailingComments: 'true' 6 | AlignConsecutiveDeclarations: true 7 | AlignOperands: true 8 | 9 | AllowAllParametersOfDeclarationOnNextLine: 'false' 10 | AlwaysBreakTemplateDeclarations: 'No' 11 | BreakBeforeBraces: Attach 12 | ColumnLimit: '80' 13 | ConstructorInitializerAllOnOneLineOrOnePerLine: 'true' 14 | IndentPPDirectives: AfterHash 15 | IndentWidth: '2' 16 | # NamespaceIndentation: All 17 | BreakBeforeBinaryOperators: All 18 | BreakBeforeTernaryOperators: 'true' 19 | UseTab: Never 20 | ... 21 | -------------------------------------------------------------------------------- /.cmake-format: -------------------------------------------------------------------------------- 1 | format: 2 | tab_size: 2 3 | line_width: 100 4 | dangle_parens: true 5 | 6 | parse: 7 | additional_commands: 8 | cpmaddpackage: 9 | pargs: 10 | nargs: '*' 11 | flags: [] 12 | spelling: CPMAddPackage 13 | kwargs: &cpmaddpackagekwargs 14 | NAME: 1 15 | FORCE: 1 16 | VERSION: 1 17 | GIT_TAG: 1 18 | DOWNLOAD_ONLY: 1 19 | GITHUB_REPOSITORY: 1 20 | GITLAB_REPOSITORY: 1 21 | GIT_REPOSITORY: 1 22 | SVN_REPOSITORY: 1 23 | SVN_REVISION: 1 24 | SOURCE_DIR: 1 25 | DOWNLOAD_COMMAND: 1 26 | FIND_PACKAGE_ARGUMENTS: 1 27 | NO_CACHE: 1 28 | GIT_SHALLOW: 1 29 | URL: 1 30 | URL_HASH: 1 31 | URL_MD5: 1 32 | DOWNLOAD_NAME: 1 33 | DOWNLOAD_NO_EXTRACT: 1 34 | HTTP_USERNAME: 1 35 | HTTP_PASSWORD: 1 36 | OPTIONS: + 37 | cpmfindpackage: 38 | pargs: 39 | nargs: '*' 40 | flags: [] 41 | spelling: CPMFindPackage 42 | kwargs: *cpmaddpackagekwargs 43 | packageproject: 44 | pargs: 45 | nargs: '*' 46 | flags: [] 47 | spelling: packageProject 48 | kwargs: 49 | NAME: 1 50 | VERSION: 1 51 | INCLUDE_DIR: 1 52 | INCLUDE_DESTINATION: 1 53 | BINARY_DIR: 1 54 | COMPATIBILITY: 1 55 | VERSION_HEADER: 1 56 | DEPENDENCIES: + 57 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yaml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | build: 10 | name: Build and publish documentation 11 | runs-on: macos-latest 12 | steps: 13 | - uses: actions/checkout@v1 14 | 15 | - name: Install dependencies 16 | run: | 17 | brew install boost 18 | brew install doxygen 19 | pip3 install jinja2 Pygments 20 | 21 | - name: Build 22 | run: | 23 | cmake -Hdocumentation -Bbuild 24 | cmake --build build --target GenerateDocs 25 | 26 | - name: Publish 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | github_token: ${{ secrets.GITHUB_TOKEN }} 30 | publish_dir: ./build/doxygen/html 31 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: Install 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CTEST_OUTPUT_ON_FAILURE: 1 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v1 21 | 22 | #- name: Install dependencies 23 | #run: | 24 | #sudo add-apt-repository ppa:mhier/libboost-latest 25 | #sudo apt-get update 26 | #sudo apt-get install libboost1.74 libboost1.74-dev -y 27 | 28 | - name: build and install library 29 | run: | 30 | BOOST_ROOT=$BOOST_ROOT_1_72_0 cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release 31 | sudo cmake --build build --target install 32 | rm -rf build 33 | 34 | - name: configure 35 | run: BOOST_ROOT=$BOOST_ROOT_1_72_0 cmake -Htest -Bbuild -DTEST_INSTALLED_VERSION=1 36 | 37 | - name: build 38 | run: cmake --build build --config Debug -j4 39 | 40 | - name: test 41 | run: | 42 | cd build 43 | ctest --build-config Debug 44 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: MacOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CTEST_OUTPUT_ON_FAILURE: 1 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: macos-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v1 21 | 22 | - name: Install dependencies 23 | run: | 24 | brew install boost 25 | 26 | - name: configure 27 | run: cmake -Htest -Bbuild 28 | 29 | - name: build 30 | run: cmake --build build --config Debug -j4 31 | 32 | - name: test 33 | run: | 34 | cd build 35 | ctest --build-config Debug 36 | -------------------------------------------------------------------------------- /.github/workflows/standalone.yml: -------------------------------------------------------------------------------- 1 | name: Standalone 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | 19 | #- name: Install dependencies 20 | #run: | 21 | #sudo add-apt-repository ppa:mhier/libboost-latest 22 | #sudo apt-get update 23 | #sudo apt-get install libboost1.74 libboost1.74-dev -y 24 | 25 | - name: configure 26 | run: BOOST_ROOT=$BOOST_ROOT_1_72_0 cmake -Hstandalone -Bbuild 27 | 28 | - name: build 29 | run: cmake --build build -j4 30 | 31 | - name: run 32 | run: | 33 | echo -ne "\n" | ./build/server 34 | -------------------------------------------------------------------------------- /.github/workflows/style.yml: -------------------------------------------------------------------------------- 1 | name: Style 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: macos-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | 19 | - name: Install format dependencies 20 | run: | 21 | brew install clang-format boost 22 | pip3 install cmake_format==0.6.11 pyyaml 23 | 24 | - name: configure 25 | run: cmake -Htest -Bbuild 26 | 27 | - name: check style 28 | run: cmake --build build --target check-format 29 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: Ubuntu 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CTEST_OUTPUT_ON_FAILURE: 1 13 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - uses: actions/checkout@v1 22 | 23 | #- name: Install dependencies 24 | #run: | 25 | #sudo add-apt-repository ppa:mhier/libboost-latest 26 | #sudo apt-get update 27 | #sudo apt-get install libboost1.74 libboost1.74-dev -y 28 | 29 | - name: configure 30 | run: BOOST_ROOT=$BOOST_ROOT_1_72_0 cmake -Htest -Bbuild -DENABLE_TEST_COVERAGE=1 31 | 32 | - name: build 33 | run: cmake --build build --config Debug -j4 34 | 35 | - name: test 36 | run: | 37 | cd build 38 | ctest --build-config Debug 39 | 40 | - name: collect code coverage 41 | run: bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" 42 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | env: 12 | CTEST_OUTPUT_ON_FAILURE: 1 13 | 14 | jobs: 15 | build: 16 | 17 | runs-on: windows-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v1 21 | 22 | - name: Export boost environment 23 | run: "echo ::set-env name=BOOST_ROOT::%BOOST_ROOT_1_72_0%" 24 | shell: cmd 25 | 26 | - name: configure 27 | run: cmake -Htest -Bbuild 28 | 29 | - name: build 30 | run: cmake --build build --config Debug -j4 31 | 32 | - name: test 33 | run: | 34 | cd build 35 | ctest --build-config Debug 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build* 2 | /.vscode 3 | .DS_Store 4 | tags 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | project( 4 | modbuscpp 5 | VERSION 1.0 6 | LANGUAGES CXX 7 | ) 8 | 9 | # ---- Include guards ---- 10 | if(${CMAKE_BUILD_TYPE} MATCHES Debug) 11 | add_definitions(-DDEBUG_ON) 12 | endif() 13 | 14 | if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR) 15 | message( 16 | FATAL_ERROR 17 | "In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there." 18 | ) 19 | endif() 20 | 21 | # ---- Add dependencies via CPM ---- 22 | include(cmake/CPM.cmake) 23 | 24 | # Utils 25 | CPMAddPackage( 26 | NAME PackageProject.cmake 27 | GITHUB_REPOSITORY TheLartians/PackageProject.cmake 28 | VERSION 1.3 29 | ) 30 | 31 | CPMAddPackage( 32 | NAME GroupSourcesByFolder.cmake 33 | GITHUB_REPOSITORY TheLartians/GroupSourcesByFolder.cmake 34 | VERSION 1.0 35 | ) 36 | 37 | # Threads 38 | find_package(Threads REQUIRED) 39 | 40 | # Asio2 41 | include(cmake/Asio2.cmake) 42 | 43 | # FMT 44 | CPMAddPackage( 45 | NAME fmt 46 | GITHUB_REPOSITORY fmtlib/fmt 47 | GIT_TAG 6.2.1 48 | ) 49 | 50 | # Struc 51 | CPMAddPackage( 52 | NAME struc 53 | GITHUB_REPOSITORY rayandrews/struc 54 | VERSION 1 55 | GIT_TAG 235db327aeec3a83c9204033e3a7b0a74c866151 56 | ) 57 | 58 | # Asio2 59 | include(cmake/Asio2.cmake) 60 | 61 | # ---- Add source files ---- 62 | 63 | # Note: globbing sources is considered bad practice as CMake's generators may not detect new files 64 | # automatically. Keep that in mind when changing files, or explicitly mention them here. 65 | set(headers 66 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbus.hpp 67 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/asio2.hpp 68 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/struct.hpp 69 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/data-table.hpp 70 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/data-table.inline.hpp 71 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/constants.hpp 72 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/exception.hpp 73 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/logger.hpp 74 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/operation.hpp 75 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/types.hpp 76 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/utilities.hpp 77 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/adu.hpp 78 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/request.hpp 79 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/response.hpp 80 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/bit-read.hpp 81 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/bit-read.inline.hpp 82 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/bit-write.hpp 83 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/register-read.hpp 84 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/register-read.inline.hpp 85 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/register-write.hpp 86 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/request-handler.hpp 87 | ${CMAKE_CURRENT_SOURCE_DIR}/include/modbuscpp/modbuscpp/server.hpp 88 | ) 89 | 90 | set(sources 91 | ${CMAKE_CURRENT_SOURCE_DIR}/source/data-table.cpp 92 | ${CMAKE_CURRENT_SOURCE_DIR}/source/logger.cpp 93 | ${CMAKE_CURRENT_SOURCE_DIR}/source/operation.cpp 94 | ${CMAKE_CURRENT_SOURCE_DIR}/source/adu.cpp 95 | ${CMAKE_CURRENT_SOURCE_DIR}/source/request.cpp 96 | ${CMAKE_CURRENT_SOURCE_DIR}/source/response.cpp 97 | ${CMAKE_CURRENT_SOURCE_DIR}/source/bit-read.cpp 98 | ${CMAKE_CURRENT_SOURCE_DIR}/source/bit-write.cpp 99 | ${CMAKE_CURRENT_SOURCE_DIR}/source/register-write.cpp 100 | ${CMAKE_CURRENT_SOURCE_DIR}/source/register-read.cpp 101 | ${CMAKE_CURRENT_SOURCE_DIR}/source/request-handler.cpp 102 | ${CMAKE_CURRENT_SOURCE_DIR}/source/server.cpp 103 | ) 104 | 105 | # ---- Create library ---- 106 | add_library(modbuscpp ${headers} ${sources}) 107 | groupsourcesbyfolder(modbuscpp) 108 | 109 | set_target_properties(modbuscpp PROPERTIES CXX_STANDARD 17) 110 | 111 | # being a cross-platform target, we enforce standards conformance on MSVC 112 | target_compile_options(modbuscpp PUBLIC "$<$:/permissive->") 113 | 114 | # Link dependencies 115 | target_link_libraries(modbuscpp PRIVATE Threads::Threads ${Boost_LIBRARIES} fmt struc asio2) 116 | 117 | target_include_directories( 118 | modbuscpp PUBLIC $ 119 | $ 120 | ) 121 | 122 | if(Boost_FOUND) 123 | target_include_directories(modbuscpp PUBLIC $) 124 | endif() 125 | 126 | # ---- Create an installable target ---- 127 | # this allows users to install and find the library via `find_package()`. 128 | 129 | # the location where the project's version header will be placed should match the project's regular 130 | # header paths 131 | string(TOLOWER ${PROJECT_NAME}/version.hpp VERSION_HEADER_LOCATION) 132 | 133 | packageProject( 134 | NAME fmt 135 | VERSION 6.2.1 136 | BINARY_DIR ${PROJECT_BINARY_DIR} 137 | INCLUDE_DIR ${fmt_SOURCE_DIR}/include 138 | INCLUDE_DESTINATION include/fmt-6.2.1 139 | ) 140 | 141 | packageProject( 142 | NAME struc 143 | VERSION 1 144 | BINARY_DIR ${PROJECT_BINARY_DIR} 145 | INCLUDE_DIR ${struc_SOURCE_DIR}/include 146 | INCLUDE_DESTINATION include/struc-1.0.0 147 | ) 148 | 149 | packageProject( 150 | NAME asio2 151 | VERSION 1 152 | BINARY_DIR ${PROJECT_BINARY_DIR} 153 | INCLUDE_DIR ${asio2_SOURCE_DIR} 154 | INCLUDE_DESTINATION include/asio2-1.0.0 155 | ) 156 | 157 | packageProject( 158 | NAME ${PROJECT_NAME} 159 | VERSION ${PROJECT_VERSION} 160 | BINARY_DIR ${PROJECT_BINARY_DIR} 161 | INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include 162 | INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION} 163 | VERSION_HEADER "${VERSION_HEADER_LOCATION}" 164 | DEPENDENCIES "fmt;struc;asio2" 165 | ) 166 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2020 Ray Andrew 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/rayandrews/modbus-cpp/workflows/MacOS/badge.svg)](https://github.com/rayandrews/modbus-cpp/actions) 2 | [![Actions Status](https://github.com/rayandrews/modbus-cpp/workflows/Windows/badge.svg)](https://github.com/rayandrews/modbus-cpp/actions) 3 | [![Actions Status](https://github.com/rayandrews/modbus-cpp/workflows/Ubuntu/badge.svg)](https://github.com/rayandrews/modbus-cpp/actions) 4 | [![Actions Status](https://github.com/rayandrews/modbus-cpp/workflows/Style/badge.svg)](https://github.com/rayandrews/modbus-cpp/actions) 5 | [![Actions Status](https://github.com/rayandrews/modbus-cpp/workflows/Install/badge.svg)](https://github.com/rayandrews/modbus-cpp/actions) 6 | [![codecov](https://codecov.io/gh/rayandrews/modbus-cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/rayandrews/modbus-cpp) 7 | 8 | # ModbusC++ (modbus-cpp) 9 | 10 | Modbus master and slave implementation in C++ using Boost Asio 11 | 12 | ## Supported Functions 13 | 14 | - Read Coils (0x01) 15 | - Read Discrete Inputs (0x02) 16 | - Read Holding Registers (0x03) 17 | - Read Input Registers (0x04) 18 | - Write Single Coil (0x05) 19 | - Write Single Register (0x06) 20 | - Write Multiple Coils (0x0F) 21 | - Write Multiple Registers (0x10) 22 | - Mask Write Register (0x16) 23 | - Read/Write Multiple Registers (0x17) 24 | 25 | ## Usage 26 | 27 | ### Modbus slave (server) 28 | 29 | See [server.cpp](standalone/source/server.cpp) 30 | 31 | ### Modbus master (client) 32 | 33 | See [client.cpp](standalone/source/client.cpp) 34 | 35 | ## TODOs 36 | 37 | - [ ] Add tests 38 | - [ ] Complete modbus client/master 39 | - [ ] ... 40 | 41 | ## Authors 42 | 43 | Ray Andrew 44 | 45 | ## Acknowledgement 46 | 47 | - [PyModbus](https://github.com/riptideio/pymodbus) 48 | - [Libmodbus](https://github.com/stephane/libmodbus) 49 | 50 | ## LICENSE 51 | 52 | [MIT](LICENSE) 53 | -------------------------------------------------------------------------------- /all/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # this script adds all subprojects to a single build to allow IDEs understand the full project 2 | # structure. 3 | 4 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 5 | 6 | project(BuildAll LANGUAGES CXX) 7 | 8 | add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../standalone ${CMAKE_BINARY_DIR}/standalone) 9 | add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../test ${CMAKE_BINARY_DIR}/test) 10 | add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../documentation ${CMAKE_BINARY_DIR}/documentation) 11 | -------------------------------------------------------------------------------- /cmake/Asio2.cmake: -------------------------------------------------------------------------------- 1 | # only activate tools for top level project 2 | if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 3 | return() 4 | endif() 5 | 6 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 7 | include(${CMAKE_CURRENT_LIST_DIR}/Boost.cmake) 8 | 9 | CPMAddPackage( 10 | NAME asio2 11 | GITHUB_REPOSITORY zhllxt/asio2 12 | VERSION 1 13 | GIT_TAG ce6ac7f6ba2a931a19b9abb7627d12cfaeed8ab1 14 | ) 15 | 16 | if(asio2_ADDED) 17 | add_library(asio2 INTERFACE) 18 | 19 | target_include_directories( 20 | asio2 INTERFACE $ $ 21 | ) 22 | endif() 23 | -------------------------------------------------------------------------------- /cmake/Boost.cmake: -------------------------------------------------------------------------------- 1 | # only activate tools for top level project 2 | if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 3 | return() 4 | endif() 5 | 6 | option(BOOST_STATIC "Boost static library" OFF) 7 | 8 | set(Boost_USE_STATIC_LIBS ${BOOST_STATIC}) # only find static libs 9 | set(Boost_USE_DEBUG_LIBS OFF) # ignore debug libs and 10 | set(Boost_USE_RELEASE_LIBS ON) # only find release libs 11 | set(Boost_USE_MULTITHREADED ON) 12 | set(Boost_USE_STATIC_RUNTIME OFF) 13 | find_package(Boost 1.70.0 REQUIRED COMPONENTS system) 14 | 15 | # fix attempt from @al-cheb 16 | # https://github.com/actions/virtual-environments/issues/370#issuecomment-586209745 17 | if(WIN32) 18 | add_definitions( 19 | -DBOOST_ALL_NO_LIB -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_IOSTREAMS_DYN_LINK 20 | -DBOOST_THREAD_DYN_LINK 21 | ) 22 | endif() 23 | -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | set(CPM_DOWNLOAD_VERSION 0.27.2) 2 | 3 | if(CPM_SOURCE_CACHE) 4 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 5 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 6 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 7 | else() 8 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | endif() 10 | 11 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 12 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 13 | file(DOWNLOAD 14 | https://github.com/TheLartians/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 15 | ${CPM_DOWNLOAD_LOCATION} 16 | ) 17 | endif() 18 | 19 | include(${CPM_DOWNLOAD_LOCATION}) 20 | -------------------------------------------------------------------------------- /cmake/tools.cmake: -------------------------------------------------------------------------------- 1 | # this file contains a list of tools that can be activated and downloaded on-demand each tool is 2 | # enabled during configuration by passing an additional `-DUSE_=` argument to CMake 3 | 4 | # only activate tools for top level project 5 | if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 6 | return() 7 | endif() 8 | 9 | include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake) 10 | 11 | # enables sanitizers support using the the `USE_SANITIZER` flag available values are: Address, 12 | # Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined' 13 | if(USE_SANITIZER OR USE_STATIC_ANALYZER) 14 | CPMAddPackage( 15 | NAME StableCoder-cmake-scripts 16 | GITHUB_REPOSITORY StableCoder/cmake-scripts 17 | GIT_TAG 3d2d5a9fb26f0ce24e3e4eaeeff686ec2ecfb3fb 18 | ) 19 | 20 | if(USE_SANITIZER) 21 | include(${StableCoder-cmake-scripts_SOURCE_DIR}/sanitizers.cmake) 22 | endif() 23 | 24 | if(USE_STATIC_ANALYZER) 25 | if("clang-tidy" IN_LIST USE_STATIC_ANALYZER) 26 | set(CLANG_TIDY 27 | ON 28 | CACHE INTERNAL "" FORCE 29 | ) 30 | else() 31 | set(CLANG_TIDY 32 | OFF 33 | CACHE INTERNAL "" FORCE 34 | ) 35 | endif() 36 | if("iwyu" IN_LIST USE_STATIC_ANALYZER) 37 | set(IWYU 38 | ON 39 | CACHE INTERNAL "" FORCE 40 | ) 41 | else() 42 | set(IWYU 43 | OFF 44 | CACHE INTERNAL "" FORCE 45 | ) 46 | endif() 47 | if("cppcheck" IN_LIST USE_STATIC_ANALYZER) 48 | set(CPPCHECK 49 | ON 50 | CACHE INTERNAL "" FORCE 51 | ) 52 | else() 53 | set(CPPCHECK 54 | OFF 55 | CACHE INTERNAL "" FORCE 56 | ) 57 | endif() 58 | 59 | include(${StableCoder-cmake-scripts_SOURCE_DIR}/tools.cmake) 60 | 61 | clang_tidy(-format-style=file -checks=* -header-filter='${CMAKE_SOURCE_DIR}/*') 62 | include_what_you_use(-Xiwyu) 63 | cppcheck(--enable=warning,performance,portability,missingInclude 64 | --template="[{severity}][{id}] {message} {callstack} \(On {file}:{line}\)" 65 | --suppress=missingIncludeSystem --quiet --verbose --force 66 | ) 67 | # clang_tidy(${CLANG_TIDY_ARGS}) include_what_you_use(${IWYU_ARGS}) cppcheck(${CPPCHECK_ARGS}) 68 | endif() 69 | endif() 70 | 71 | # enables CCACHE support through the USE_CCACHE flag possible values are: YES, NO or equivalent 72 | if(USE_CCACHE) 73 | CPMAddPackage( 74 | NAME Ccache.cmake 75 | GITHUB_REPOSITORY TheLartians/Ccache.cmake 76 | VERSION 1.1 77 | ) 78 | endif() 79 | -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "test" 3 | - "**/_deps/**" 4 | - "**/usr/**" 5 | - "*.yaml" 6 | 7 | comment: 8 | require_changes: true 9 | -------------------------------------------------------------------------------- /documentation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 2 | 3 | project(modbuscpp_docs) 4 | 5 | # ---- Dependencies ---- 6 | 7 | include(../cmake/CPM.cmake) 8 | 9 | include(../cmake/Asio2.cmake) 10 | 11 | CPMAddPackage( 12 | NAME fmt 13 | GITHUB_REPOSITORY fmtlib/fmt 14 | GIT_TAG 6.2.1 15 | ) 16 | 17 | CPMAddPackage( 18 | NAME struc 19 | GITHUB_REPOSITORY rayandrews/struc 20 | VERSION 1 21 | GIT_TAG 235db327aeec3a83c9204033e3a7b0a74c866151 22 | ) 23 | 24 | CPMAddPackage(NAME modbuscpp SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/..) 25 | 26 | CPMAddPackage( 27 | NAME MCSS 28 | DOWNLOAD_ONLY YES 29 | # patched version until https://github.com/mosra/m.css/pull/171 is resolved 30 | GITHUB_REPOSITORY TheLartians/m.css 31 | GIT_TAG 1bf162b96d5bfefc9967a80cef138f1270ffa415 32 | ) 33 | 34 | # ---- Doxygen variables ---- 35 | 36 | # set Doxyfile variables 37 | set(DOXYGEN_PROJECT_NAME modbuscpp) 38 | set(DOXYGEN_PROJECT_VERSION ${modbuscpp_VERSION}) 39 | set(DOXYGEN_PROJECT_ROOT "${CMAKE_CURRENT_LIST_DIR}/..") 40 | set(DOXYGEN_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doxygen") 41 | 42 | configure_file(${CMAKE_CURRENT_LIST_DIR}/Doxyfile ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 43 | 44 | configure_file(${CMAKE_CURRENT_LIST_DIR}/conf.py ${CMAKE_CURRENT_BINARY_DIR}/conf.py) 45 | 46 | add_custom_target( 47 | GenerateDocs 48 | ${CMAKE_COMMAND} -E make_directory "${DOXYGEN_OUTPUT_DIRECTORY}" 49 | COMMAND "${MCSS_SOURCE_DIR}/documentation/doxygen.py" "${CMAKE_CURRENT_BINARY_DIR}/conf.py" 50 | COMMAND echo "Docs written to: ${DOXYGEN_OUTPUT_DIRECTORY}" 51 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 52 | ) 53 | -------------------------------------------------------------------------------- /documentation/Doxyfile: -------------------------------------------------------------------------------- 1 | # Configuration for Doxygen for use with CMake 2 | # Only options that deviate from the default are included 3 | # To create a new Doxyfile containing all available options, call `doxygen -g` 4 | 5 | # Get Project name and version from CMake 6 | PROJECT_NAME = @DOXYGEN_PROJECT_NAME@ 7 | PROJECT_NUMBER = @DOXYGEN_PROJECT_VERSION@ 8 | 9 | # Add sources 10 | INPUT = @DOXYGEN_PROJECT_ROOT@/README.md @DOXYGEN_PROJECT_ROOT@/include @DOXYGEN_PROJECT_ROOT@/documentation/pages 11 | EXTRACT_ALL = YES 12 | RECURSIVE = YES 13 | OUTPUT_DIRECTORY = @DOXYGEN_OUTPUT_DIRECTORY@ 14 | 15 | # Use the README as a main page 16 | USE_MDFILE_AS_MAINPAGE = @DOXYGEN_PROJECT_ROOT@/README.md 17 | 18 | # set relative include paths 19 | FULL_PATH_NAMES = YES 20 | STRIP_FROM_PATH = @DOXYGEN_PROJECT_ROOT@/include @DOXYGEN_PROJECT_ROOT@ 21 | 22 | # We use m.css to generate the html documentation, so we only need XML output 23 | GENERATE_XML = YES 24 | GENERATE_HTML = NO 25 | GENERATE_LATEX = NO 26 | XML_PROGRAMLISTING = NO 27 | CREATE_SUBDIRS = NO 28 | 29 | # Include all directories, files and namespaces in the documentation 30 | # Disable to include only explicitly documented objects 31 | M_SHOW_UNDOCUMENTED = YES 32 | -------------------------------------------------------------------------------- /documentation/conf.py: -------------------------------------------------------------------------------- 1 | DOXYFILE = 'Doxyfile' 2 | 3 | LINKS_NAVBAR1 = [ 4 | (None, 'pages', [(None, 'about')]), 5 | (None, 'namespaces', []), 6 | ] 7 | 8 | # Add your own navbar links using the code below. 9 | # To find the valid link names, you can inspect the URL of a generated documentation site. 10 | 11 | # LINKS_NAVBAR1 = [ 12 | # (None, 'pages', [(None, 'about')]), 13 | # (None, 'namespaces', [(None, 'namespacegreeter')]), 14 | # ] 15 | # 16 | # LINKS_NAVBAR2 = [ 17 | # (None, 'annotated', [(None, 'classgreeter_1_1_greeter')]), 18 | # (None, 'files', [(None, 'greeter_8h')]), 19 | # ] 20 | -------------------------------------------------------------------------------- /documentation/pages/about.dox: -------------------------------------------------------------------------------- 1 | /** @page about About 2 | @section doc ModernCppStarter Documentation 3 | This is the auto-generated documentation for the initial project of the ModernCppStater. 4 | It shows how we can use Doxygen to automatically build a browsable documentation for your projects. 5 | */ 6 | -------------------------------------------------------------------------------- /include/modbuscpp/modbus.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_HPP_ 2 | #define LIB_MODBUS_MODBUS_HPP_ 3 | 4 | /** @file modbus.hpp 5 | * @brief Modbus header file 6 | */ 7 | 8 | #pragma GCC system_header 9 | 10 | #if defined(WIN32) || defined(_WIN32) \ 11 | || defined(__WIN32) && !defined(__CYGWIN__) 12 | # undef exception_code 13 | #endif 14 | 15 | #include "modbuscpp/asio2.hpp" 16 | #include "modbuscpp/struct.hpp" 17 | 18 | #include "modbuscpp/constants.hpp" 19 | #include "modbuscpp/types.hpp" 20 | 21 | #include "modbuscpp/exception.hpp" 22 | 23 | #include "modbuscpp/utilities.hpp" 24 | 25 | #include "modbuscpp/logger.hpp" 26 | 27 | #include "modbuscpp/data-table.hpp" 28 | #include "modbuscpp/data-table.inline.hpp" 29 | 30 | #include "modbuscpp/operation.hpp" 31 | 32 | #include "modbuscpp/adu.hpp" 33 | #include "modbuscpp/request.hpp" 34 | #include "modbuscpp/response.hpp" 35 | 36 | // Functions implementation 37 | #include "modbuscpp/bit-read.hpp" 38 | #include "modbuscpp/bit-read.inline.hpp" 39 | 40 | #include "modbuscpp/bit-write.hpp" 41 | 42 | #include "modbuscpp/register-read.hpp" 43 | #include "modbuscpp/register-read.inline.hpp" 44 | 45 | #include "modbuscpp/register-write.hpp" 46 | 47 | #include "modbuscpp/request-handler.hpp" 48 | 49 | #include "modbuscpp/server.hpp" 50 | 51 | #endif // LIB_MODBUS_MODBUS_HPP_ 52 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/adu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_ADU_HPP_ 2 | #define LIB_MODBUS_MODBUS_ADU_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "constants.hpp" 13 | #include "types.hpp" 14 | #include "utilities.hpp" 15 | 16 | namespace modbus { 17 | // forward declarations 18 | namespace internal { 19 | class adu; 20 | } 21 | 22 | namespace internal { 23 | /** 24 | * @brief ADU base class 25 | * 26 | * @author Ray Andrew 27 | * @ingroup Modbus/Internal 28 | * 29 | * ADU structure for TCP: 30 | * - Header (7 bytes) 31 | * --- Transaction (2 bytes) 32 | * --- Protocol (2 bytes) 33 | * --- Length (2 bytes) [ unit identifier + PDU ] 34 | * --- Unit (1 byte) 35 | * - PDU 36 | * --- Function (1 byte) 37 | * --- Rest of data... (N byte) 38 | */ 39 | class adu { 40 | public: 41 | /** 42 | * Initializer 43 | */ 44 | struct initializer_t { 45 | /** 46 | * Transaction 47 | */ 48 | std::uint16_t transaction; 49 | /** 50 | * Unit 51 | */ 52 | std::uint8_t unit; 53 | }; 54 | 55 | /** 56 | * ADU constructor 57 | */ 58 | explicit adu() noexcept; 59 | 60 | /** 61 | * ADU constructor 62 | * 63 | * @param function_code modbus function code 64 | * @param transaction transaction id 65 | * @param unit unit id 66 | */ 67 | explicit adu(std::uint8_t function_code, 68 | std::uint16_t transaction = 0x00, 69 | std::uint8_t unit = 0x00) noexcept; 70 | 71 | /** 72 | * ADU constructor 73 | * 74 | * @param function modbus function 75 | * @param transaction transaction id 76 | * @param unit unit id 77 | */ 78 | explicit adu(constants::function_code function, 79 | std::uint16_t transaction = 0x00, 80 | std::uint8_t unit = 0x00) noexcept; 81 | 82 | /** 83 | * ADU constructor 84 | * 85 | * @param function_code modbus function code 86 | * @param initializer initializer 87 | */ 88 | explicit adu(std::uint8_t function_code, 89 | const initializer_t& initializer) noexcept; 90 | 91 | /** 92 | * ADU constructor 93 | * 94 | * @param function modbus function 95 | * @param initializer initializer 96 | */ 97 | explicit adu(constants::function_code function, 98 | const initializer_t& initializer) noexcept; 99 | 100 | /** 101 | * ADU constructor 102 | * 103 | * @param function_code modbus function code 104 | * @param m_header header struct 105 | */ 106 | explicit adu(std::uint8_t function_code, const header_t& m_header) noexcept; 107 | 108 | /** 109 | * ADU constructor 110 | * 111 | * @param function modbus function 112 | * @param m_header header struct 113 | */ 114 | explicit adu(constants::function_code function, 115 | const header_t& m_header) noexcept; 116 | 117 | public: 118 | /** 119 | * Encode packet 120 | * 121 | * @return packet that has been encoded 122 | */ 123 | virtual packet_t encode() = 0; 124 | 125 | /** 126 | * Decode packet 127 | * 128 | * @param packet packet to be decoded 129 | */ 130 | virtual void decode(std::string_view packet); 131 | 132 | /** 133 | * Decode packet 134 | * 135 | * @param packet packet to be decoded 136 | */ 137 | virtual void decode(const packet_t& packet) = 0; 138 | 139 | public: 140 | /** Getter */ 141 | /** 142 | * Get function code 143 | * 144 | * @return function code 145 | */ 146 | inline constants::function_code function() const { return function_; } 147 | 148 | /** 149 | * Get transaction id 150 | * 151 | * @return transaction id 152 | */ 153 | inline std::uint16_t transaction() const { return transaction_; } 154 | 155 | /** 156 | * Get length 157 | * 158 | * @return length 159 | */ 160 | inline std::uint16_t length() const { return length_; } 161 | 162 | /** 163 | * Get unit id 164 | * 165 | * @return unit id 166 | */ 167 | inline std::uint8_t unit() const { return unit_; } 168 | 169 | /** 170 | * Get header 171 | * 172 | * @return header 173 | */ 174 | header_t header() const { return {transaction(), length(), unit()}; } 175 | 176 | /** Setter */ 177 | 178 | /** 179 | * Iniitalize ADU 180 | * 181 | * @param initializer initializer 182 | * 183 | * @return instance of ADU 184 | */ 185 | adu& initialize(const initializer_t& initializer); 186 | 187 | /** 188 | * Set header 189 | * 190 | * @param m_header header struct 191 | * 192 | * @return instance of ADU 193 | */ 194 | adu& header(const header_t& m_header); 195 | 196 | /** 197 | * Set transaction id 198 | * 199 | * @param new_transaction new transaction id 200 | * 201 | * @return instance of ADU 202 | */ 203 | adu& transaction(std::uint16_t new_transaction); 204 | 205 | /** 206 | * Calculate length of ADU given PDU length 207 | * 208 | * @param pdu_length PDU length 209 | * 210 | * @return instance of ADU 211 | */ 212 | adu& calc_length(std::uint16_t pdu_length); 213 | 214 | /** 215 | * Set length of ADU 216 | * 217 | * @param new_length new length 218 | * 219 | * @return instance of ADU 220 | */ 221 | adu& length(std::uint16_t new_length); 222 | 223 | /** 224 | * Set unit id 225 | * 226 | * @param new_unit new unit id 227 | * 228 | * @return instance of ADU 229 | */ 230 | adu& unit(std::uint8_t new_unit); 231 | 232 | /** Operator */ 233 | /** 234 | * Equality operator 235 | * 236 | * @param other other ADU 237 | * 238 | * @return true if transaction_id is equal to other transaction_id 239 | */ 240 | bool operator==(const adu& other) const; 241 | 242 | /** 243 | * Less-than operator 244 | * 245 | * @param other other ADU 246 | * 247 | * @return true if transaction_id is less than other transaction_id 248 | */ 249 | bool operator<(const adu& other) const; 250 | 251 | /** 252 | * More-than operator 253 | * 254 | * @param other other ADU 255 | * 256 | * @return true if transaction_id is more than other transaction_id 257 | */ 258 | bool operator>(const adu& other) const; 259 | 260 | /** 261 | * Dump to string 262 | * 263 | * @param os ostream 264 | * 265 | * @return ostream 266 | */ 267 | virtual std::ostream& dump(std::ostream& os) const; 268 | 269 | /** 270 | * Ostream operator 271 | * 272 | * @param os ostream 273 | * @param adu adu instance 274 | * 275 | * @return stream 276 | */ 277 | template 278 | inline friend ostream& operator<<(ostream& os, const adu& obj) { 279 | return obj.dump(os); 280 | } 281 | 282 | protected: 283 | /** 284 | * Decode packet header 285 | * 286 | * @param packet packet to be decoded 287 | * 288 | * @return instance of ADU 289 | */ 290 | void decode_header(const packet_t& packet); 291 | 292 | /** 293 | * Get header packet 294 | * 295 | * @return packet consists of header 296 | */ 297 | packet_t header_packet(); 298 | 299 | /** 300 | * Response size 301 | * 302 | * @param pdu_length PDU length 303 | * 304 | * @return ADU length 305 | */ 306 | inline static constexpr std::uint16_t calc_adu_length( 307 | std::uint16_t data_length) { 308 | /** header_length (containing unit_id) + 1 (function) + data_length */ 309 | return header_length + 1 + data_length; 310 | } 311 | 312 | public: 313 | /** 314 | * Header length 315 | */ 316 | static constexpr typename packet_t::size_type header_length = 7; 317 | 318 | protected: 319 | /** 320 | * Protocol ID 321 | */ 322 | static constexpr std::uint16_t protocol = constants::tcp_protocol; 323 | 324 | /** 325 | * Length index 326 | */ 327 | static constexpr typename packet_t::size_type length_idx = 4; 328 | /** 329 | * Max length 330 | */ 331 | static constexpr typename packet_t::size_type max_length 332 | = constants::max_adu_length; 333 | /** 334 | * Max PDU length 335 | */ 336 | static constexpr typename packet_t::size_type max_pdu_size 337 | = max_length - header_length; 338 | /** 339 | * Header struct with function format 340 | */ 341 | static constexpr std::string_view header_func_format = "HHHBB"; 342 | 343 | protected: 344 | /** 345 | * Function 346 | */ 347 | constants::function_code function_; 348 | /** 349 | * Function code 350 | */ 351 | std::underlying_type_t function_code_; 352 | /** 353 | * Transaction id 354 | */ 355 | std::uint16_t transaction_; 356 | /** 357 | * Length of packet [ unit identifier + PDU ] 358 | */ 359 | std::uint16_t length_; 360 | /** 361 | * Unit id 362 | */ 363 | std::uint8_t unit_; 364 | }; 365 | } // namespace internal 366 | } // namespace modbus 367 | 368 | #endif // LIB_MODBUS_MODBUS_ADU_HPP_ 369 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/asio2.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_ASIO2_HPP_ 2 | #define LIB_MODBUS_ASIO2_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #endif // LIB_MODBUS_ASIO2_HPP_ 11 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/bit-read.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_BIT_READ_HPP_ 2 | #define LIB_MODBUS_MODBUS_BIT_READ_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include "constants.hpp" 13 | #include "types.hpp" 14 | 15 | #include "data-table.hpp" 16 | #include "data-table.inline.hpp" 17 | 18 | #include "adu.hpp" 19 | #include "request.hpp" 20 | #include "response.hpp" 21 | 22 | namespace modbus { 23 | namespace request { 24 | /** 25 | * base request read bits class 26 | * 27 | * @author Ray Andrew 28 | * @date August 2020 29 | * 30 | * Encode, decode, and execute read coils request 31 | * 32 | * Structure : 33 | * [ (Header...) ] 34 | * [ Function (1 byte) ] 35 | * [ Starting Address (2 bytes) ] 36 | * [ Quantity of bits (2 bytes) ] 37 | */ 38 | template class base_read_bits 39 | : public internal::request { 40 | public: 41 | /** 42 | * request::base_read_bits constructor 43 | * 44 | * @param address address requested 45 | * @param count count requested 46 | */ 47 | explicit base_read_bits(const address_t& address = address_t{}, 48 | const read_num_bits_t& count 49 | = read_num_bits_t{}) noexcept; 50 | 51 | /** 52 | * Encode read bits packet from given data 53 | * 54 | * @return packet format 55 | */ 56 | virtual packet_t encode() override; 57 | 58 | /** 59 | * Decode read bits packet 60 | * 61 | * @param packet packet to decode 62 | * 63 | * @return packet format 64 | */ 65 | virtual void decode(const packet_t& packet) override; 66 | 67 | /** 68 | * Encode read bits packet 69 | * 70 | * @return packet format 71 | */ 72 | virtual typename internal::response::pointer execute( 73 | table* data_table) override; 74 | 75 | /** 76 | * Byte count 77 | * 78 | * @return byte count 79 | */ 80 | std::uint16_t byte_count() const; 81 | 82 | /** 83 | * Get response size for error checking on client 84 | * 85 | * @return response size 86 | */ 87 | inline virtual typename packet_t::size_type response_size() const override; 88 | 89 | /** 90 | * Get address 91 | * 92 | * @return address 93 | */ 94 | inline const address_t& address() const { return address_; } 95 | 96 | /** 97 | * Get count 98 | * 99 | * @return count 100 | */ 101 | inline const read_num_bits_t& count() const { return count_; } 102 | 103 | /** 104 | * Dump to string 105 | * 106 | * @param os ostream 107 | * 108 | * @return ostream 109 | */ 110 | virtual std::ostream& dump(std::ostream& os) const override; 111 | 112 | private: 113 | /** 114 | * Data length (4 bytes) 115 | */ 116 | static constexpr std::uint16_t data_length = 4; 117 | /** 118 | * Address 119 | */ 120 | address_t address_; 121 | /** 122 | * Number of bits 123 | */ 124 | read_num_bits_t count_; 125 | /** 126 | * Struct format 127 | */ 128 | static constexpr std::string_view format = "HH"; 129 | }; 130 | 131 | using read_coils = base_read_bits; 132 | using read_discrete_inputs 133 | = base_read_bits; 134 | } // namespace request 135 | 136 | namespace response { 137 | /** 138 | * base response read bits class 139 | * 140 | * @author Ray Andrew 141 | * @date August 2020 142 | * 143 | * Encode and decode read bits response 144 | * 145 | * Structure : 146 | * [ (Header...) ] 147 | * [ Function (1 byte) ] 148 | * [ Byte count = N (1 byte) ] 149 | * [ Bits (n = N or N + 1 bytes) ] 150 | */ 151 | template class base_read_bits 152 | : public internal::response { 153 | public: 154 | /** 155 | * Create std::unique_ptr of response::read_bits 156 | * 157 | * @return std::unique_ptr of response::read_bits 158 | */ 159 | MAKE_STD_UNIQUE(base_read_bits) 160 | 161 | /** 162 | * response::read_bits constructor 163 | * 164 | * @param request read coils request pointer 165 | * @param data_table data table 166 | */ 167 | explicit base_read_bits(const request::base_read_bits* request, 168 | table* data_table = nullptr) noexcept; 169 | 170 | /** 171 | * Encode packet 172 | * 173 | * @return packet format 174 | */ 175 | virtual packet_t encode() override; 176 | 177 | /** 178 | * Decode stage passed packet 179 | * 180 | * @param packet packet to parse 181 | */ 182 | virtual void decode_passed(const packet_t& packet) override; 183 | 184 | /** 185 | * Dump to string 186 | * 187 | * @param os ostream 188 | * 189 | * @return ostream 190 | */ 191 | virtual std::ostream& dump(std::ostream& os) const override; 192 | 193 | /** 194 | * Byte count 195 | * 196 | * @return byte count 197 | */ 198 | inline std::uint16_t byte_count() const { return count_; } 199 | 200 | /** 201 | * Get bits 202 | */ 203 | inline const block::bits::container_type& bits() const { return bits_; } 204 | 205 | private: 206 | /** 207 | * Request pointer 208 | */ 209 | const request::base_read_bits* request_; 210 | /** 211 | * Byte count 212 | */ 213 | std::uint16_t count_; 214 | /** 215 | * Slice of data from block of bits from data table 216 | */ 217 | block::bits::container_type bits_; 218 | /** 219 | * Struct format 220 | */ 221 | static constexpr std::string_view format = "B"; 222 | }; 223 | 224 | using read_coils = base_read_bits; 225 | using read_discrete_inputs 226 | = base_read_bits; 227 | } // namespace response 228 | } // namespace modbus 229 | 230 | #endif // LIB_MODBUS_MODBUS_BIT_READ_HPP_ 231 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/bit-read.inline.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_BIT_READ_INLINE_HPP_ 2 | #define LIB_MODBUS_MODBUS_BIT_READ_INLINE_HPP_ 3 | 4 | #include "bit-read.hpp" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include "exception.hpp" 11 | #include "logger.hpp" 12 | #include "operation.hpp" 13 | #include "utilities.hpp" 14 | 15 | namespace modbus { 16 | namespace request { 17 | template 18 | base_read_bits::base_read_bits( 19 | const address_t& address, 20 | const read_num_bits_t& count) noexcept 21 | : internal::request{function_code}, address_{address}, count_{count} {} 22 | 23 | template 24 | std::uint16_t base_read_bits::byte_count() const { 25 | std::uint16_t byte_count = static_cast(count_()) / 8; 26 | std::uint16_t remainder = static_cast(count_()) % 8; 27 | 28 | if (remainder) 29 | byte_count++; 30 | return byte_count; 31 | } 32 | 33 | template 34 | typename packet_t::size_type base_read_bits::response_size() 35 | const { 36 | return calc_adu_length(1 + byte_count()); 37 | } 38 | 39 | template 40 | packet_t base_read_bits::encode() { 41 | if (!address_.validate() || !count_.validate()) { 42 | throw ex::bad_data(); 43 | } 44 | 45 | calc_length(data_length); 46 | packet_t packet = header_packet(); 47 | packet.reserve(header_length + 1 + data_length); 48 | packet_t pdu = struc::pack(fmt::format(">{}", format), address_(), count_()); 49 | packet.insert(packet.end(), pdu.begin(), pdu.end()); 50 | return packet; 51 | } 52 | 53 | template 54 | void base_read_bits::decode(const packet_t& packet) { 55 | try { 56 | if (packet.at(header_length) != utilities::to_underlying(function_code)) { 57 | throw ex::bad_data(); 58 | } 59 | 60 | decode_header(packet); 61 | struc::unpack(fmt::format(">{}", format), packet.data() + header_length + 1, 62 | address_.ref(), count_.ref()); 63 | } catch (...) { 64 | throw ex::server_device_failure(function(), header()); 65 | } 66 | } 67 | } // namespace request 68 | 69 | namespace response { 70 | template 71 | base_read_bits::base_read_bits( 72 | const request::base_read_bits* request, 73 | table* data_table) noexcept 74 | : internal::response{function_code, request->header(), data_table}, 75 | request_{request} { 76 | initialize({request_->transaction(), request_->unit()}); 77 | } 78 | 79 | template 80 | void base_read_bits::decode_passed(const packet_t& packet) { 81 | try { 82 | if (packet.size() != request_->response_size()) { 83 | throw ex::bad_data(); 84 | } 85 | 86 | packet_t::size_type byte_idx = header_length + 1; 87 | count_ = static_cast(packet[byte_idx]); 88 | 89 | if (count_ != request_->count().get()) { 90 | throw ex::bad_data(); 91 | } 92 | 93 | block::bits::container_type buffer 94 | = op::unpack_bits(packet.begin() + byte_idx + 1, packet.end()); 95 | 96 | if (buffer.size() != request_->count().get()) { 97 | throw ex::bad_data(); 98 | } 99 | 100 | bits_.swap(buffer); 101 | } catch (...) { 102 | throw ex::bad_data(); 103 | } 104 | } 105 | } // namespace response 106 | } // namespace modbus 107 | 108 | #endif // LIB_MODBUS_MODBUS_BIT_READ_INLINE_HPP_ 109 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/bit-write.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_BIT_WRITE_HPP_ 2 | #define LIB_MODBUS_MODBUS_BIT_WRITE_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "constants.hpp" 14 | #include "types.hpp" 15 | 16 | #include "data-table.hpp" 17 | 18 | #include "adu.hpp" 19 | #include "request.hpp" 20 | #include "response.hpp" 21 | 22 | namespace modbus { 23 | // forward declarations 24 | namespace request { 25 | class write_single_coil; 26 | class write_multiple_coils; 27 | } // namespace request 28 | 29 | namespace response { 30 | class write_single_coil; 31 | class write_multiple_coils; 32 | } // namespace response 33 | 34 | namespace request { 35 | /** 36 | * request write single coil class 37 | * 38 | * @author Ray Andrew 39 | * @date August 2020 40 | * 41 | * Encode, decode, and execute write singe coil request 42 | * 43 | * Structure: 44 | * [ (Header...) ] 45 | * [ Function (1 byte) ] 46 | * [ Output Address (2 bytes) ] 47 | * [ Output Value (2 bytes) ] 48 | */ 49 | class write_single_coil : public internal::request { 50 | public: 51 | /** 52 | * request::write_single_coil constructor 53 | * 54 | * @param address output address 55 | * @param value coil value 56 | */ 57 | explicit write_single_coil(const address_t& address = address_t{}, 58 | value::bits value = value::bits::on) noexcept; 59 | 60 | /** 61 | * 62 | * Encode write single_coil packet from given data 63 | * 64 | * @return packet format 65 | */ 66 | virtual packet_t encode() override; 67 | 68 | /** 69 | * Decode write single coil packet 70 | * 71 | * @param data data to be appended 72 | * 73 | * @return packet format 74 | */ 75 | virtual void decode(const packet_t& data) override; 76 | 77 | /** 78 | * Encode write bits packet 79 | * 80 | * @return packet format 81 | */ 82 | virtual typename internal::response::pointer execute( 83 | table* data_table) override; 84 | 85 | /** 86 | * Get response size for error checking on client 87 | * 88 | * @return response size 89 | */ 90 | inline virtual typename packet_t::size_type response_size() const override; 91 | 92 | /** 93 | * Dump to string 94 | * 95 | * @param os ostream 96 | * 97 | * @return ostream 98 | */ 99 | virtual std::ostream& dump(std::ostream& os) const override; 100 | 101 | public: 102 | /** 103 | * Get address 104 | * 105 | * @return address 106 | */ 107 | inline const address_t& address() const { return address_; } 108 | 109 | /** 110 | * Get value 111 | * 112 | * @return value 113 | */ 114 | inline value::bits value() const { return value_; } 115 | 116 | private: 117 | /** 118 | * Data length (4 bytes) 119 | */ 120 | static constexpr std::uint16_t data_length = 4; 121 | /** 122 | * Address 123 | */ 124 | address_t address_; 125 | /** 126 | * Value 127 | */ 128 | value::bits value_; 129 | /** 130 | * Struct format 131 | */ 132 | static constexpr std::string_view format = "HH"; 133 | }; 134 | 135 | /** 136 | * request write multiple coils class 137 | * 138 | * @author Ray Andrew 139 | * @date August 2020 140 | * 141 | * Encode, decode, and execute write multiple coils request 142 | * 143 | * Structure: 144 | * [ (Header...) ] 145 | * [ Function (1 byte) ] 146 | * [ Starting Address (2 bytes) ] 147 | * [ Quantity of outputs (2 bytes) ] 148 | * [ Byte count N (1 byte) ] 149 | * [ Output value (N x 1 bytes) ] 150 | */ 151 | class write_multiple_coils : public internal::request { 152 | public: 153 | /** 154 | * request::write_multiple_coils constructor 155 | * 156 | * @param address output address 157 | * @param count count 158 | * @param values coil values 159 | */ 160 | explicit write_multiple_coils( 161 | const address_t& address = address_t{}, 162 | const write_num_bits_t& count = write_num_bits_t{}, 163 | std::initializer_list values = {}) noexcept; 164 | 165 | /** 166 | * 167 | * Encode write single_coil packet from given data 168 | * 169 | * @return packet format 170 | */ 171 | virtual packet_t encode() override; 172 | 173 | /** 174 | * Decode write single coil packet 175 | * 176 | * @param data data to be appended 177 | * 178 | * @return packet format 179 | */ 180 | virtual void decode(const packet_t& data) override; 181 | 182 | /** 183 | * Encode write bits packet 184 | * 185 | * @return packet format 186 | */ 187 | virtual typename internal::response::pointer execute( 188 | table* data_table) override; 189 | 190 | /** 191 | * Get response size for error checking on client 192 | * 193 | * @return response size 194 | */ 195 | inline virtual typename packet_t::size_type response_size() const override; 196 | 197 | /** 198 | * Byte count 199 | * 200 | * @return byte count 201 | */ 202 | std::uint8_t byte_count() const; 203 | 204 | /** 205 | * Dump to string 206 | * 207 | * @param os ostream 208 | * 209 | * @return ostream 210 | */ 211 | virtual std::ostream& dump(std::ostream& os) const override; 212 | 213 | public: 214 | /** 215 | * Get address 216 | * 217 | * @return address 218 | */ 219 | inline const address_t& address() const { return address_; } 220 | 221 | /** 222 | * Get address 223 | * 224 | * @return address 225 | */ 226 | inline const write_num_bits_t& count() const { return count_; } 227 | 228 | /** 229 | * Get value 230 | * 231 | * @return value 232 | */ 233 | inline const block::bits::container_type& values() const { return values_; } 234 | 235 | private: 236 | /** 237 | * Get data length 238 | * 239 | * Except Function size + header 240 | * 241 | * @return data length 242 | */ 243 | inline std::uint16_t data_length() const { return 4 + 1 + byte_count_; } 244 | 245 | private: 246 | /** 247 | * Address 248 | */ 249 | address_t address_; 250 | /** 251 | * Count 252 | */ 253 | write_num_bits_t count_; 254 | /** 255 | * Value 256 | */ 257 | block::bits::container_type values_; 258 | /** 259 | * Byte count 260 | */ 261 | std::uint8_t byte_count_; 262 | /** 263 | * Struct format 264 | */ 265 | static constexpr std::string_view format = "HHB"; 266 | }; 267 | } // namespace request 268 | 269 | namespace response { 270 | /** 271 | * response write single coil class 272 | * 273 | * @author Ray Andrew 274 | * @date August 2020 275 | * 276 | * Structure: 277 | * [ (Header...) ] 278 | * [ Function (1 byte) ] 279 | * [ Output Address (2 bytes) ] 280 | * [ Output Value (2 bytes) ] 281 | */ 282 | class write_single_coil : public internal::response { 283 | public: 284 | /** 285 | * Create std::unique_ptr of response::write_single_coil 286 | * 287 | * @return std::unique_ptr of response::write_single_coil 288 | */ 289 | MAKE_STD_UNIQUE(write_single_coil) 290 | 291 | /** 292 | * response::write_single_coil constructor 293 | * 294 | * @param request read coils request pointer 295 | * @param data_table data table 296 | */ 297 | explicit write_single_coil(const request::write_single_coil* request, 298 | table* data_table = nullptr) noexcept; 299 | 300 | /** 301 | * Encode packet 302 | * 303 | * @return packet format 304 | */ 305 | virtual packet_t encode() override; 306 | 307 | /** 308 | * Decode stage passed packet 309 | * 310 | * @param packet packet to parse 311 | */ 312 | virtual void decode_passed(const packet_t& packet) override; 313 | 314 | /** 315 | * Dump to string 316 | * 317 | * @param os ostream 318 | * 319 | * @return ostream 320 | */ 321 | virtual std::ostream& dump(std::ostream& os) const override; 322 | 323 | private: 324 | /** 325 | * Request pointer 326 | */ 327 | const request::write_single_coil* request_; 328 | /** 329 | * Value 330 | */ 331 | value::bits value_; 332 | /** 333 | * Data length (4 bytes) 334 | */ 335 | static constexpr std::uint16_t data_length = 4; 336 | /** 337 | * Struct format 338 | */ 339 | static constexpr std::string_view format = "HH"; 340 | }; 341 | 342 | /** 343 | * response write multiple coils class 344 | * 345 | * @author Ray Andrew 346 | * @date August 2020 347 | * 348 | * Structure: 349 | * [ (Header...) ] 350 | * [ Function (1 byte) ] 351 | * [ Starting Address (2 bytes) ] 352 | * [ Quantity of Outputs (2 bytes) ] 353 | */ 354 | class write_multiple_coils : public internal::response { 355 | public: 356 | /** 357 | * Create std::unique_ptr of response::write_multiple_coils 358 | * 359 | * @return std::unique_ptr of response::write_multiple_coils 360 | */ 361 | MAKE_STD_UNIQUE(write_multiple_coils) 362 | 363 | /** 364 | * response::write_multiple_coils constructor 365 | * 366 | * @param request read coils request pointer 367 | * @param data_table data table 368 | */ 369 | explicit write_multiple_coils(const request::write_multiple_coils* request, 370 | table* data_table = nullptr) noexcept; 371 | 372 | /** 373 | * Encode packet 374 | * 375 | * @return packet format 376 | */ 377 | virtual packet_t encode() override; 378 | 379 | /** 380 | * Decode stage passed packet 381 | * 382 | * @param packet packet to parse 383 | */ 384 | virtual void decode_passed(const packet_t& packet) override; 385 | 386 | /** 387 | * Dump to string 388 | * 389 | * @param os ostream 390 | * 391 | * @return ostream 392 | */ 393 | virtual std::ostream& dump(std::ostream& os) const override; 394 | 395 | /** 396 | * Get address 397 | * 398 | * @return address 399 | */ 400 | inline const address_t& address() const { return address_; } 401 | 402 | /** 403 | * Get address 404 | * 405 | * @return address 406 | */ 407 | inline const write_num_bits_t& count() const { return count_; } 408 | 409 | private: 410 | /** 411 | * Request pointer 412 | */ 413 | const request::write_multiple_coils* request_; 414 | /** 415 | * Address 416 | */ 417 | address_t address_; 418 | /** 419 | * Count 420 | */ 421 | write_num_bits_t count_; 422 | /** 423 | * Data length (4 bytes) 424 | */ 425 | static constexpr std::uint16_t data_length = 4; 426 | /** 427 | * Struct format 428 | */ 429 | static constexpr std::string_view format = "HH"; 430 | }; 431 | } // namespace response 432 | } // namespace modbus 433 | 434 | #endif // LIB_MODBUS_MODBUS_BIT_WRITE_HPP_ 435 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/constants.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_CONSTANTS_HPP_ 2 | #define LIB_MODBUS_MODBUS_CONSTANTS_HPP_ 3 | 4 | #include 5 | #include 6 | 7 | #if defined(WIN32) || defined(_WIN32) \ 8 | || defined(__WIN32) && !defined(__CYGWIN__) 9 | # undef exception_code 10 | #endif 11 | 12 | namespace modbus { 13 | namespace constants { 14 | /** 15 | * \author Ray Andrew 16 | * \ingroup Modbus 17 | * @brief is a strongly typed enum class representing the function of Modbus 18 | */ 19 | enum class function_code : std::uint8_t { 20 | min = 0x00, 21 | read_coils = 0x01, 22 | read_discrete_inputs = 0x02, 23 | read_holding_registers = 0x03, 24 | read_input_registers = 0x04, 25 | write_single_coil = 0x05, 26 | write_single_register = 0x06, 27 | read_exception_status = 0x07, 28 | diagnostics = 0x08, 29 | write_multiple_coils = 0x0F, 30 | write_multiple_registers = 0x10, 31 | read_file_record = 0x14, 32 | write_file_record = 0x15, 33 | mask_write_register = 0x16, 34 | read_write_multiple_registers = 0x17, 35 | read_fifo_queue = 0x18, 36 | encapsulated_interface_transport = 0x2B, 37 | max = 0x2C, 38 | }; 39 | 40 | /** 41 | * \author Ray Andrew 42 | * \ingroup Modbus 43 | * @brief is a strongly typed enum class representing the exception of Modbus 44 | * and internal lib 45 | */ 46 | enum class exception_code : std::uint8_t { 47 | min = 0x00, /**< helper for checking modbus::exception value */ 48 | 49 | /** modbus exception */ 50 | illegal_function = 0x01, 51 | illegal_data_address, 52 | illegal_data_value, 53 | server_device_failure, 54 | acknowledge, 55 | server_device_busy, 56 | negative_acknowledge, 57 | memory_parity_error, 58 | undef, 59 | gateway_path_unavailable, 60 | gateway_target_device_failed_to_respond, 61 | 62 | /** Internal exception */ 63 | bad_data, /*<< data is not sent properly, bad request, bad response */ 64 | bad_data_size, /*<< bad data size provided (can be out of bound, buffer size 65 | is lesser than expected, etc) */ 66 | connection_problem, /*<< connection problem because because of timed 67 | out */ 68 | bad_exception, /*<< unknown exception */ 69 | 70 | /** helper */ 71 | no_exception, /*<< No exception status */ 72 | max /*<< helper for checking modbus::exeception value */ 73 | }; 74 | 75 | static constexpr std::uint16_t max_adu_length = 260; 76 | static constexpr std::uint16_t tcp_protocol = 0x00; 77 | static constexpr std::uint16_t max_num_bits_read = 0x07D0; 78 | static constexpr std::uint16_t max_num_regs_read = 0x007D; 79 | static constexpr std::uint16_t max_num_bits_write = 0x07B0; 80 | static constexpr std::uint16_t max_num_regs_write = 0x007B; 81 | static constexpr std::uint16_t max_address = 0xFFFF; 82 | } // namespace constants 83 | 84 | namespace value { 85 | enum class bits : std::uint16_t { off = 0x0000, on = 0xFF00 }; 86 | } // namespace value 87 | 88 | inline constexpr bool check_function(std::uint8_t function) { 89 | return static_cast(constants::function_code::min) < function 90 | && function < static_cast(constants::function_code::max); 91 | } 92 | 93 | inline constexpr bool check_function(constants::function_code function) { 94 | return (function != constants::function_code::min) 95 | && (function != constants::function_code::max); 96 | } 97 | 98 | inline constexpr bool check_exception(std::uint8_t exception) { 99 | return static_cast(constants::exception_code::min) < exception 100 | && exception 101 | < static_cast(constants::exception_code::max); 102 | } 103 | 104 | inline constexpr bool check_function(constants::exception_code exception) { 105 | return (exception != constants::exception_code::min) 106 | && (exception != constants::exception_code::max); 107 | } 108 | 109 | inline constexpr bool check_bits_value(std::uint16_t value) { 110 | return (value == static_cast(value::bits::off)) 111 | || (value == static_cast(value::bits::on)); 112 | } 113 | 114 | inline constexpr const char* function_code_str(constants::function_code code) { 115 | switch (code) { 116 | case constants::function_code::read_coils: 117 | return "read coils"; 118 | case constants::function_code::read_discrete_inputs: 119 | return "read discrete inputs"; 120 | case constants::function_code::read_holding_registers: 121 | return "read holding registers"; 122 | case constants::function_code::read_input_registers: 123 | return "read input registers"; 124 | case constants::function_code::write_single_coil: 125 | return "write single coil"; 126 | case constants::function_code::write_single_register: 127 | return "write single register"; 128 | case constants::function_code::read_exception_status: 129 | return "read exception status"; 130 | case constants::function_code::diagnostics: 131 | return "diagnostics"; 132 | case constants::function_code::write_multiple_coils: 133 | return "write multiple coils"; 134 | case constants::function_code::write_multiple_registers: 135 | return "write multiple registers"; 136 | case constants::function_code::read_file_record: 137 | return "read file record"; 138 | case constants::function_code::write_file_record: 139 | return "write file record"; 140 | case constants::function_code::mask_write_register: 141 | return "mask write register"; 142 | case constants::function_code::read_write_multiple_registers: 143 | return "read write multiple registers"; 144 | case constants::function_code::read_fifo_queue: 145 | return "read fifo queue"; 146 | case constants::function_code::encapsulated_interface_transport: 147 | return "encapsulated interface transport"; 148 | default: 149 | return "Unknown"; 150 | } 151 | } 152 | } // namespace modbus 153 | 154 | #endif // LIB_MODBUS_MODBUS_CONSTANTS_HPP_ 155 | -------------------------------------------------------------------------------- /include/modbuscpp/modbuscpp/data-table.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LIB_MODBUS_MODBUS_DATA_TABLE_HPP_ 2 | #define LIB_MODBUS_MODBUS_DATA_TABLE_HPP_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "types.hpp" 12 | #include "utilities.hpp" 13 | 14 | namespace modbus { 15 | // forward declarations 16 | namespace block { 17 | template