├── .clang-format ├── .github └── workflows │ ├── cmake.yml │ ├── cpack.yml │ └── gtest.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Doxyfile ├── FUNDING.yml ├── LICENSE ├── README.md ├── cmake ├── cpack.cmake └── libsockcanppConfig.cmake ├── include ├── CMakeLists.txt ├── CanDriver.hpp ├── CanId.hpp ├── CanMessage.hpp └── exceptions │ ├── CanCloseException.hpp │ ├── CanException.hpp │ ├── CanInitException.hpp │ └── InvalidSocketException.hpp ├── libsockcanpp.pc.in ├── src ├── CMakeLists.txt └── CanDriver.cpp ├── test ├── CMakeLists.txt ├── app │ ├── CMakeLists.txt │ └── src │ │ └── Main.cpp └── unit │ ├── .gitignore │ ├── CMakeLists.txt │ └── src │ ├── CanId_Tests.cpp │ └── main.cpp └── toolchains ├── arm64-toolchain.cmake ├── armhf-toolchain.cmake ├── i686-toolchain.cmake ├── ppc-toolchain.cmake ├── ppc64-toolchain.cmake └── x86_64-toolchain.cmake /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: 0 2 | AlignConsecutiveAssignments: true 3 | AllowShortBlocksOnASingleLine: true 4 | AllowShortFunctionsOnASingleLine: All 5 | AllowShortLambdasOnASingleLine: All 6 | AllowShortLoopsOnASingleLine: true 7 | BasedOnStyle: Google 8 | BreakBeforeBraces: Custom 9 | BraceWrapping: 10 | #AfterCaseLabel: Never 11 | AfterClass: false 12 | AfterControlStatement: false 13 | AfterEnum: false 14 | AfterFunction: false 15 | AfterNamespace: false 16 | AfterStruct: false 17 | AfterUnion: false 18 | AfterExternBlock: false 19 | BeforeCatch: false 20 | BeforeElse: false 21 | IndentBraces: false 22 | SplitEmptyRecord: false 23 | SplitEmptyNamespace: false 24 | BreakBeforeBinaryOperators: None 25 | BreakBeforeBraces: Attach 26 | BreakBeforeTernaryOperators: false 27 | BreakConstructorInitializers: AfterColon 28 | BreakInheritanceList: AfterColon 29 | ColumnLimit: 160 30 | CompactNamespaces: false 31 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 32 | ConstructorInitializerIndentWidth: 4 33 | Cpp11BracedListStyle: false 34 | FixNamespaceComments: true 35 | IncludeBlocks: Regroup 36 | IndentCaseLabels: true 37 | #IndentGotoLabels: false # Clang 10 38 | IndentPPDirectives: BeforeHash 39 | IndentWidth: 4 40 | Language: Cpp 41 | MaxEmptyLinesToKeep: 1 42 | NamespaceIndentation: All 43 | PointerAlignment: Left 44 | SortIncludes: true 45 | SortUsingDeclarations: true 46 | SpaceAfterCStyleCast: false 47 | SpaceAfterLogicalNot: false 48 | SpaceAfterTemplateKeyword: false 49 | SpaceBeforeAssignmentOperators: true 50 | SpaceBeforeCpp11BracedList: true 51 | SpaceBeforeCtorInitializerColon: false 52 | SpaceBeforeInheritanceColon: false 53 | SpaceBeforeParens: ControlStatements 54 | SpaceBeforeRangeBasedForLoopColon: true 55 | #SpaceInEmptyBlock: true # Clang 10 56 | SpaceInEmptyParentheses: false 57 | SpacesBeforeTrailingComments: 1 58 | SpacesInAngles: false 59 | SpacesInCStyleCastParentheses: false 60 | SpacesInContainerLiterals: true 61 | SpacesInParentheses: false 62 | SpacesInSquareBrackets: false 63 | Standard: Auto 64 | TabWidth: 4 65 | UseTab: Never -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: Build Debug 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Debug 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Install system dependencies 22 | if: runner.os == 'Linux' 23 | run: | 24 | sudo apt update 25 | sudo apt-get update 26 | sudo apt-get -y install can-utils libsocketcan-dev 27 | while read -r cmd 28 | do 29 | eval sudo $cmd 30 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') 31 | 32 | - uses: actions/checkout@v3 33 | 34 | - name: Configure CMake 35 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 36 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 37 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 38 | 39 | - name: Build 40 | # Build your program with the given configuration 41 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/cpack.yml: -------------------------------------------------------------------------------- 1 | name: Build and Pack 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Install system dependencies 22 | if: runner.os == 'Linux' 23 | run: | 24 | sudo apt update 25 | sudo apt-get update 26 | sudo apt-get -y install can-utils libsocketcan-dev 27 | while read -r cmd 28 | do 29 | eval sudo $cmd 30 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') 31 | 32 | - uses: actions/checkout@v3 33 | 34 | - name: Configure CMake 35 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 36 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 37 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 38 | 39 | - name: Build 40 | # Build your program with the given configuration 41 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 42 | 43 | - name: Pack 44 | working-directory: ${{github.workspace}}/build 45 | # Execute tests defined by the CMake configuration. 46 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 47 | run: cpack . 48 | 49 | -------------------------------------------------------------------------------- /.github/workflows/gtest.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Debug 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 16 | # You can convert this to a matrix build if you need cross-platform coverage. 17 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Install system dependencies 22 | if: runner.os == 'Linux' 23 | run: | 24 | sudo apt update 25 | sudo apt -y install can-utils libsocketcan-dev googletest 26 | while read -r cmd 27 | do 28 | eval sudo $cmd 29 | done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') 30 | 31 | - uses: actions/checkout@v3 32 | - uses: MarkusJx/googletest-installer@v1.1 33 | 34 | - name: Configure CMake 35 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 36 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 37 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON 38 | 39 | - name: Build 40 | # Build your program with the given configuration 41 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 42 | 43 | - name: Test 44 | working-directory: ${{github.workspace}}/build 45 | # Execute tests defined by the CMake configuration. 46 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 47 | run: ctest -C ${{env.BUILD_TYPE}} 48 | 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################# 2 | ### .GITIGNORE FOR SOCKETCANPP ### 3 | ################################################# 4 | 5 | ### 6 | # Build directories 7 | ### 8 | build*/ 9 | 10 | ### 11 | # Documentation (Doxygen) 12 | ### 13 | html/ 14 | 15 | ### 16 | # cpack packages 17 | ### 18 | packages/ 19 | 20 | ### 21 | # .vscode 22 | ### 23 | .vscode/ 24 | 25 | ### 26 | # Binaries 27 | ### 28 | *.a 29 | *.so 30 | *.lib 31 | *.bin 32 | *.exe 33 | *.dll -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "doxygen-awesome-css"] 2 | path = doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23) 2 | 3 | project(sockcanpp LANGUAGES CXX VERSION 1.5.0) 4 | 5 | option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) 6 | option(BUILD_TESTS "Build the tests" OFF) 7 | 8 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 9 | 10 | option(sockcanpp_CONCEPT_SUPPORT "Allow higher level language support" ON) 11 | 12 | if (sockcanpp_CONCEPT_SUPPORT) 13 | message(STATUS "Enabling support for higher level language features") 14 | set(CMAKE_CXX_STANDARD 20) 15 | else() 16 | set(CMAKE_CXX_STANDARD 11) 17 | endif() 18 | 19 | add_compile_options( 20 | -Wall 21 | -Werror 22 | -Wpedantic 23 | -Wno-unknown-pragmas 24 | ) 25 | 26 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") 27 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s") 28 | 29 | include(GNUInstallDirs) 30 | 31 | if (BUILD_SHARED_LIBS STREQUAL "ON") 32 | add_library(${PROJECT_NAME}) 33 | set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) 34 | set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") 35 | else() 36 | add_library(${PROJECT_NAME} STATIC) 37 | endif() 38 | 39 | ### 40 | # If BUILD_TESTS is set to ON, a static test library with the name of the project suffixed with "_test" will be created 41 | ### 42 | if(BUILD_TESTS STREQUAL "ON") 43 | add_library(${PROJECT_NAME}_test STATIC) 44 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/) 45 | endif() 46 | 47 | add_subdirectory(include) 48 | add_subdirectory(src) 49 | 50 | install(TARGETS ${PROJECT_NAME} 51 | EXPORT ${PROJECT_NAME}Targets 52 | FILE_SET HEADERS 53 | ) 54 | 55 | install(EXPORT ${PROJECT_NAME}Targets 56 | FILE ${PROJECT_NAME}Targets.cmake 57 | NAMESPACE ${PROJECT_NAME}:: 58 | DESTINATION lib/cmake/${PROJECT_NAME} 59 | ) 60 | 61 | include(CMakePackageConfigHelpers) 62 | write_basic_package_version_file( 63 | "lib${PROJECT_NAME}ConfigVersion.cmake" 64 | VERSION ${${PROJECT_NAME}_VERSION} 65 | COMPATIBILITY AnyNewerVersion 66 | ) 67 | 68 | install(FILES "cmake/lib${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}ConfigVersion.cmake" 69 | DESTINATION lib/cmake/${PROJECT_NAME}) 70 | 71 | 72 | set(prefix ${CMAKE_INSTALL_PREFIX}) 73 | set(exec_prefix ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) 74 | set(includedir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}) 75 | set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) 76 | 77 | configure_file(libsockcanpp.pc.in ${CMAKE_BINARY_DIR}/libsockcanpp.pc @ONLY) 78 | # Install pkg-config files 79 | install(FILES ${CMAKE_BINARY_DIR}/libsockcanpp.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 80 | 81 | include(cmake/cpack.cmake) 82 | 83 | ### 84 | # Docs target 85 | ### 86 | add_custom_target("libsockcan_docs" COMMENT "Create Doxygen documentation") 87 | add_custom_command( 88 | TARGET "libsockcan_docs" 89 | POST_BUILD 90 | COMMENT "Generate Doxygen documentation for publication or reading" 91 | COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile 92 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 93 | ) 94 | -------------------------------------------------------------------------------- /Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.9.1 2 | 3 | #--------------------------------------------------------------------------- 4 | # Project related configuration options 5 | #--------------------------------------------------------------------------- 6 | DOXYFILE_ENCODING = UTF-8 7 | PROJECT_NAME = "Libsockcanpp" 8 | PROJECT_NUMBER = 9 | PROJECT_BRIEF = "A complete C++ wrapper around socketcan." 10 | PROJECT_LOGO = 11 | OUTPUT_DIRECTORY = 12 | CREATE_SUBDIRS = YES 13 | ALLOW_UNICODE_NAMES = YES 14 | OUTPUT_LANGUAGE = English 15 | OUTPUT_TEXT_DIRECTION = None 16 | BRIEF_MEMBER_DESC = YES 17 | REPEAT_BRIEF = YES 18 | ABBREVIATE_BRIEF = "The $name class" \ 19 | "The $name widget" \ 20 | "The $name file" \ 21 | is \ 22 | provides \ 23 | specifies \ 24 | contains \ 25 | represents \ 26 | a \ 27 | an \ 28 | the 29 | ALWAYS_DETAILED_SEC = NO 30 | INLINE_INHERITED_MEMB = NO 31 | FULL_PATH_NAMES = YES 32 | STRIP_FROM_PATH = 33 | STRIP_FROM_INC_PATH = 34 | SHORT_NAMES = NO 35 | JAVADOC_AUTOBRIEF = NO 36 | JAVADOC_BANNER = NO 37 | QT_AUTOBRIEF = NO 38 | MULTILINE_CPP_IS_BRIEF = NO 39 | PYTHON_DOCSTRING = YES 40 | INHERIT_DOCS = YES 41 | SEPARATE_MEMBER_PAGES = NO 42 | TAB_SIZE = 4 43 | ALIASES = 44 | OPTIMIZE_OUTPUT_FOR_C = YES 45 | OPTIMIZE_OUTPUT_JAVA = NO 46 | OPTIMIZE_FOR_FORTRAN = NO 47 | OPTIMIZE_OUTPUT_VHDL = NO 48 | OPTIMIZE_OUTPUT_SLICE = NO 49 | EXTENSION_MAPPING = 50 | MARKDOWN_SUPPORT = YES 51 | TOC_INCLUDE_HEADINGS = 5 52 | AUTOLINK_SUPPORT = YES 53 | BUILTIN_STL_SUPPORT = NO 54 | CPP_CLI_SUPPORT = YES 55 | SIP_SUPPORT = NO 56 | IDL_PROPERTY_SUPPORT = YES 57 | DISTRIBUTE_GROUP_DOC = NO 58 | GROUP_NESTED_COMPOUNDS = NO 59 | SUBGROUPING = YES 60 | INLINE_GROUPED_CLASSES = NO 61 | INLINE_SIMPLE_STRUCTS = NO 62 | TYPEDEF_HIDES_STRUCT = NO 63 | LOOKUP_CACHE_SIZE = 0 64 | NUM_PROC_THREADS = 1 65 | #--------------------------------------------------------------------------- 66 | # Build related configuration options 67 | #--------------------------------------------------------------------------- 68 | EXTRACT_ALL = YES 69 | EXTRACT_PRIVATE = YES 70 | EXTRACT_PRIV_VIRTUAL = YES 71 | EXTRACT_PACKAGE = YES 72 | EXTRACT_STATIC = YES 73 | EXTRACT_LOCAL_CLASSES = YES 74 | EXTRACT_LOCAL_METHODS = YES 75 | EXTRACT_ANON_NSPACES = YES 76 | RESOLVE_UNNAMED_PARAMS = YES 77 | HIDE_UNDOC_MEMBERS = NO 78 | HIDE_UNDOC_CLASSES = NO 79 | HIDE_FRIEND_COMPOUNDS = NO 80 | HIDE_IN_BODY_DOCS = NO 81 | INTERNAL_DOCS = NO 82 | CASE_SENSE_NAMES = YES 83 | HIDE_SCOPE_NAMES = NO 84 | HIDE_COMPOUND_REFERENCE= NO 85 | SHOW_INCLUDE_FILES = YES 86 | SHOW_GROUPED_MEMB_INC = NO 87 | FORCE_LOCAL_INCLUDES = NO 88 | INLINE_INFO = YES 89 | SORT_MEMBER_DOCS = YES 90 | SORT_BRIEF_DOCS = NO 91 | SORT_MEMBERS_CTORS_1ST = NO 92 | SORT_GROUP_NAMES = NO 93 | SORT_BY_SCOPE_NAME = NO 94 | STRICT_PROTO_MATCHING = NO 95 | GENERATE_TODOLIST = YES 96 | GENERATE_TESTLIST = YES 97 | GENERATE_BUGLIST = YES 98 | GENERATE_DEPRECATEDLIST= YES 99 | ENABLED_SECTIONS = 100 | MAX_INITIALIZER_LINES = 30 101 | SHOW_USED_FILES = YES 102 | SHOW_FILES = YES 103 | SHOW_NAMESPACES = YES 104 | FILE_VERSION_FILTER = 105 | LAYOUT_FILE = 106 | CITE_BIB_FILES = 107 | #--------------------------------------------------------------------------- 108 | # Configuration options related to warning and progress messages 109 | #--------------------------------------------------------------------------- 110 | QUIET = NO 111 | WARNINGS = YES 112 | WARN_IF_UNDOCUMENTED = YES 113 | WARN_IF_DOC_ERROR = YES 114 | WARN_NO_PARAMDOC = NO 115 | WARN_AS_ERROR = NO 116 | WARN_FORMAT = "$file:$line: $text" 117 | WARN_LOGFILE = 118 | #--------------------------------------------------------------------------- 119 | # Configuration options related to the input files 120 | #--------------------------------------------------------------------------- 121 | INPUT = README.md include/ include/exceptions/ src/ test/ 122 | INPUT_ENCODING = UTF-8 123 | FILE_PATTERNS = *.c \ 124 | *.cc \ 125 | *.cxx \ 126 | *.cpp \ 127 | *.c++ \ 128 | *.java \ 129 | *.ii \ 130 | *.ixx \ 131 | *.ipp \ 132 | *.i++ \ 133 | *.inl \ 134 | *.idl \ 135 | *.ddl \ 136 | *.odl \ 137 | *.h \ 138 | *.hh \ 139 | *.hxx \ 140 | *.hpp \ 141 | *.h++ \ 142 | *.cs \ 143 | *.d \ 144 | *.php \ 145 | *.php4 \ 146 | *.php5 \ 147 | *.phtml \ 148 | *.inc \ 149 | *.m \ 150 | *.markdown \ 151 | *.md \ 152 | *.mm \ 153 | *.dox \ 154 | *.py \ 155 | *.pyw \ 156 | *.f90 \ 157 | *.f95 \ 158 | *.f03 \ 159 | *.f08 \ 160 | *.f18 \ 161 | *.f \ 162 | *.for \ 163 | *.vhd \ 164 | *.vhdl \ 165 | *.ucf \ 166 | *.qsf \ 167 | *.ice 168 | RECURSIVE = NO 169 | EXCLUDE = 170 | EXCLUDE_SYMLINKS = NO 171 | EXCLUDE_PATTERNS = 172 | EXCLUDE_SYMBOLS = 173 | EXAMPLE_PATH = 174 | EXAMPLE_PATTERNS = * 175 | EXAMPLE_RECURSIVE = NO 176 | IMAGE_PATH = 177 | INPUT_FILTER = 178 | FILTER_PATTERNS = 179 | FILTER_SOURCE_FILES = NO 180 | FILTER_SOURCE_PATTERNS = 181 | USE_MDFILE_AS_MAINPAGE = README.md 182 | #--------------------------------------------------------------------------- 183 | # Configuration options related to source browsing 184 | #--------------------------------------------------------------------------- 185 | SOURCE_BROWSER = YES 186 | INLINE_SOURCES = YES 187 | STRIP_CODE_COMMENTS = YES 188 | REFERENCED_BY_RELATION = YES 189 | REFERENCES_RELATION = YES 190 | REFERENCES_LINK_SOURCE = YES 191 | SOURCE_TOOLTIPS = YES 192 | USE_HTAGS = NO 193 | VERBATIM_HEADERS = YES 194 | CLANG_ASSISTED_PARSING = YES 195 | CLANG_ADD_INC_PATHS = YES 196 | CLANG_OPTIONS = 197 | CLANG_DATABASE_PATH = 198 | #--------------------------------------------------------------------------- 199 | # Configuration options related to the alphabetical class index 200 | #--------------------------------------------------------------------------- 201 | ALPHABETICAL_INDEX = YES 202 | IGNORE_PREFIX = 203 | #--------------------------------------------------------------------------- 204 | # Configuration options related to the HTML output 205 | #--------------------------------------------------------------------------- 206 | GENERATE_HTML = YES 207 | HTML_OUTPUT = html 208 | HTML_FILE_EXTENSION = .html 209 | HTML_HEADER = 210 | HTML_FOOTER = 211 | HTML_STYLESHEET = 212 | HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css 213 | HTML_EXTRA_FILES = 214 | HTML_COLORSTYLE_HUE = 220 215 | HTML_COLORSTYLE_SAT = 100 216 | HTML_COLORSTYLE_GAMMA = 80 217 | HTML_TIMESTAMP = NO 218 | HTML_DYNAMIC_MENUS = YES 219 | HTML_DYNAMIC_SECTIONS = NO 220 | HTML_INDEX_NUM_ENTRIES = 100 221 | GENERATE_DOCSET = NO 222 | DOCSET_FEEDNAME = "Doxygen generated docs" 223 | DOCSET_BUNDLE_ID = eu.simonc.EndlesshReportGen 224 | DOCSET_PUBLISHER_ID = eu.simonc.EndlesshReportGen 225 | DOCSET_PUBLISHER_NAME = Simon Cahill 226 | GENERATE_HTMLHELP = NO 227 | CHM_FILE = 228 | HHC_LOCATION = 229 | GENERATE_CHI = NO 230 | CHM_INDEX_ENCODING = 231 | BINARY_TOC = NO 232 | TOC_EXPAND = NO 233 | GENERATE_QHP = NO 234 | QCH_FILE = 235 | QHP_NAMESPACE = org.doxygen.Project 236 | QHP_VIRTUAL_FOLDER = doc 237 | QHP_CUST_FILTER_NAME = 238 | QHP_CUST_FILTER_ATTRS = 239 | QHP_SECT_FILTER_ATTRS = 240 | QHG_LOCATION = 241 | GENERATE_ECLIPSEHELP = NO 242 | ECLIPSE_DOC_ID = org.doxygen.Project 243 | DISABLE_INDEX = NO 244 | GENERATE_TREEVIEW = NO 245 | ENUM_VALUES_PER_LINE = 4 246 | TREEVIEW_WIDTH = 250 247 | EXT_LINKS_IN_WINDOW = NO 248 | HTML_FORMULA_FORMAT = png 249 | FORMULA_FONTSIZE = 10 250 | FORMULA_TRANSPARENT = YES 251 | FORMULA_MACROFILE = 252 | USE_MATHJAX = NO 253 | MATHJAX_FORMAT = HTML-CSS 254 | MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 255 | MATHJAX_EXTENSIONS = 256 | MATHJAX_CODEFILE = 257 | SEARCHENGINE = YES 258 | SERVER_BASED_SEARCH = NO 259 | EXTERNAL_SEARCH = NO 260 | SEARCHENGINE_URL = 261 | SEARCHDATA_FILE = searchdata.xml 262 | EXTERNAL_SEARCH_ID = 263 | EXTRA_SEARCH_MAPPINGS = 264 | #--------------------------------------------------------------------------- 265 | # Configuration options related to the LaTeX output 266 | #--------------------------------------------------------------------------- 267 | GENERATE_LATEX = NO 268 | LATEX_OUTPUT = latex 269 | LATEX_CMD_NAME = 270 | MAKEINDEX_CMD_NAME = makeindex 271 | LATEX_MAKEINDEX_CMD = makeindex 272 | COMPACT_LATEX = NO 273 | PAPER_TYPE = a4 274 | EXTRA_PACKAGES = 275 | LATEX_HEADER = 276 | LATEX_FOOTER = 277 | LATEX_EXTRA_STYLESHEET = 278 | LATEX_EXTRA_FILES = 279 | PDF_HYPERLINKS = YES 280 | USE_PDFLATEX = YES 281 | LATEX_BATCHMODE = NO 282 | LATEX_HIDE_INDICES = NO 283 | LATEX_SOURCE_CODE = NO 284 | LATEX_BIB_STYLE = plain 285 | LATEX_TIMESTAMP = NO 286 | LATEX_EMOJI_DIRECTORY = 287 | #--------------------------------------------------------------------------- 288 | # Configuration options related to the RTF output 289 | #--------------------------------------------------------------------------- 290 | GENERATE_RTF = NO 291 | RTF_OUTPUT = rtf 292 | COMPACT_RTF = NO 293 | RTF_HYPERLINKS = NO 294 | RTF_STYLESHEET_FILE = 295 | RTF_EXTENSIONS_FILE = 296 | RTF_SOURCE_CODE = NO 297 | #--------------------------------------------------------------------------- 298 | # Configuration options related to the man page output 299 | #--------------------------------------------------------------------------- 300 | GENERATE_MAN = NO 301 | MAN_OUTPUT = man 302 | MAN_EXTENSION = .3 303 | MAN_SUBDIR = 304 | MAN_LINKS = NO 305 | #--------------------------------------------------------------------------- 306 | # Configuration options related to the XML output 307 | #--------------------------------------------------------------------------- 308 | GENERATE_XML = NO 309 | XML_OUTPUT = xml 310 | XML_PROGRAMLISTING = YES 311 | XML_NS_MEMB_FILE_SCOPE = NO 312 | #--------------------------------------------------------------------------- 313 | # Configuration options related to the DOCBOOK output 314 | #--------------------------------------------------------------------------- 315 | GENERATE_DOCBOOK = NO 316 | DOCBOOK_OUTPUT = docbook 317 | DOCBOOK_PROGRAMLISTING = NO 318 | #--------------------------------------------------------------------------- 319 | # Configuration options for the AutoGen Definitions output 320 | #--------------------------------------------------------------------------- 321 | GENERATE_AUTOGEN_DEF = NO 322 | #--------------------------------------------------------------------------- 323 | # Configuration options related to the Perl module output 324 | #--------------------------------------------------------------------------- 325 | GENERATE_PERLMOD = NO 326 | PERLMOD_LATEX = NO 327 | PERLMOD_PRETTY = YES 328 | PERLMOD_MAKEVAR_PREFIX = 329 | #--------------------------------------------------------------------------- 330 | # Configuration options related to the preprocessor 331 | #--------------------------------------------------------------------------- 332 | ENABLE_PREPROCESSING = YES 333 | MACRO_EXPANSION = NO 334 | EXPAND_ONLY_PREDEF = NO 335 | SEARCH_INCLUDES = YES 336 | INCLUDE_PATH = 337 | INCLUDE_FILE_PATTERNS = 338 | PREDEFINED = 339 | EXPAND_AS_DEFINED = 340 | SKIP_FUNCTION_MACROS = YES 341 | #--------------------------------------------------------------------------- 342 | # Configuration options related to external references 343 | #--------------------------------------------------------------------------- 344 | TAGFILES = 345 | GENERATE_TAGFILE = 346 | ALLEXTERNALS = NO 347 | EXTERNAL_GROUPS = YES 348 | EXTERNAL_PAGES = YES 349 | #--------------------------------------------------------------------------- 350 | # Configuration options related to the dot tool 351 | #--------------------------------------------------------------------------- 352 | CLASS_DIAGRAMS = YES 353 | DIA_PATH = 354 | HIDE_UNDOC_RELATIONS = YES 355 | HAVE_DOT = YES 356 | DOT_NUM_THREADS = 0 357 | DOT_FONTNAME = Helvetica 358 | DOT_FONTSIZE = 10 359 | DOT_FONTPATH = 360 | CLASS_GRAPH = YES 361 | COLLABORATION_GRAPH = YES 362 | GROUP_GRAPHS = YES 363 | UML_LOOK = NO 364 | UML_LIMIT_NUM_FIELDS = 10 365 | DOT_UML_DETAILS = NO 366 | DOT_WRAP_THRESHOLD = 17 367 | TEMPLATE_RELATIONS = NO 368 | INCLUDE_GRAPH = YES 369 | INCLUDED_BY_GRAPH = YES 370 | CALL_GRAPH = NO 371 | CALLER_GRAPH = NO 372 | GRAPHICAL_HIERARCHY = YES 373 | DIRECTORY_GRAPH = YES 374 | DOT_IMAGE_FORMAT = png 375 | INTERACTIVE_SVG = NO 376 | DOT_PATH = 377 | DOTFILE_DIRS = 378 | MSCFILE_DIRS = 379 | DIAFILE_DIRS = 380 | PLANTUML_JAR_PATH = 381 | PLANTUML_CFG_FILE = 382 | PLANTUML_INCLUDE_PATH = 383 | DOT_GRAPH_MAX_NODES = 50 384 | MAX_DOT_GRAPH_DEPTH = 0 385 | DOT_TRANSPARENT = NO 386 | DOT_MULTI_TARGETS = NO 387 | GENERATE_LEGEND = YES 388 | DOT_CLEANUP = YES 389 | -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: SimonCahill 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libsockcanpp 2 | 3 | Welcome to the documentation for libsockcanpp!
4 | libsockcanpp is a socketcan wrapper library for C++, aimed to be easy to use without any fuss. 5 | 6 | [![Build](https://github.com/SimonCahill/libsockcanpp/actions/workflows/cmake.yml/badge.svg)](https://github.com/SimonCahill/libsockcanpp/actions/workflows/cmake.yml) 7 | 8 | # Useful Links 9 | 10 | - GitHub repo: [https://github.com/SimonCahill/libsockcanpp](https://github.com/SimonCahill/libsockcanpp) 11 | 12 | # Getting Started 13 | 14 | ## Building 15 | 16 | > [!NOTE] 17 | > **C++11** SUPPORT: 18 | > This library supports modern C++ features, such as concepts in certain places. 19 | > If your project cannot support C++20 features, you can force the C++ standard back by setting 20 | > `-Dsockcanpp_CONCEPT_SUPPORT=OFF` in the command-line or `set(sockcanpp_CONCEPT_SUPPORT OFF CACHE BOOL "Force C++ standard back to 11")` 21 | > in your CMakeLists.txt. 22 | 23 | libsockcanpp was designed with use in CMake projects, but it can also easily be integrated into existing Makefile projects, as long as cmake is present on the build system. 24 | 25 | 1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 26 | 2) create build directory: `mkdir build && cd build` 27 | 3) generate build files: `cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchains/desired-toolchain.cmake` 28 | 4) building library: `make -j` 29 | 30 | ## Incorporating into Cmake projects: 31 | 32 | 1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 33 | 2) add the following to CMakeLists.txt 34 | ```cmake 35 | if (NOT TARGET sockcanpp) 36 | # IF you need C++11 support: 37 | # set(sockcanpp_CONCEPT_SUPPORT OFF CACHE BOOL "Force C++ standard back to 11") 38 | 39 | add_subdirectory(/path/to/libsockcanpprepo ${CMAKE_CURRENT_BUILD_DIR}/libsockcanpp) 40 | endif() 41 | 42 | # ... 43 | 44 | target_link_libraries( 45 | # ... 46 | sockcanpp 47 | ) 48 | ``` 49 | 3) generate and build 50 | 4) ??? profit 51 | 52 | ## Using the CAN driver 53 | 54 | libsockcanpp provides a simple interface to socketcan, which is represented by the CanDriver class.
55 | `CanDriver` handles the setup of the socket; **it does not however setup the CAN interface!** 56 | 57 | ### Instantiating CanDriver 58 | 59 | The example below describes how to instantiate a new instance of CanDriver.
60 | You can create as many instances as required, the resources are `free`'d once the instance has gone out of scope or been deleted. 61 | 62 | The following parameters are required for correct instantiation: 63 | 64 | 1) CAN interface: [v]can[0-?] 65 | 2) CAN protocol: see [linux/can.h](https://github.com/linux-can/can-utils/blob/master/include/linux/can.h) for options 66 | 3) (optional) CAN sender ID (arbitration ID) 67 | 68 | The following exceptions may be thrown if something went wrong during initialisation: 69 | 70 | - @see CanInitException 71 | - if socketcan failed to initlialise 72 | - if ioctl failed 73 | - if socket binding failed 74 | 75 | CanDriver is fully thread-safe and can be used in multi-threaded applications. 76 | 77 | ```cpp 78 | #include 79 | 80 | using sockcanpp::CanDriver; 81 | 82 | int main() { 83 | CanDriver canDriver("can0", CAN_RAW[, 0xbad]); 84 | 85 | return 0; 86 | } 87 | ``` 88 | 89 | ### Using CAN IDs 90 | 91 | libsockcanpp provides a first-class datatype, @see CanId, which acts as an integer which can be either 11 or 29 bits in size.
92 | The @see CanId type is used through libsockcanpp to provide a semi fool-proof method of using CAN arbitration IDs without the pitfalls of using traditional 32-bit integers. 93 | 94 | CanId supports the following operations: 95 | - bitwise AND/OR 96 | - casting to: [u]int16, [u]int32 97 | - basic comparison: 98 | - equal to 99 | - not equal to 100 | - greater than (or equal to) 101 | - less than (or equal to) 102 | - arithmetic: 103 | - addition 104 | - subtraction 105 | 106 | ### Sending CAN frames 107 | 108 | Sending CAN frames with sockcanpp is as easy as you could imagine. 109 | 110 | 1) instantiate a @see CanDriver object 111 | 2) create a @see CanMessage 112 | 3) send message 113 | 114 | ```cpp 115 | #include 116 | 117 | using sockcanpp::CanDriver; 118 | using sockcanpp::CanId; 119 | using sockcanpp::CanMessage; 120 | 121 | void sendCanFrameExample() { 122 | CanDriver canDriver("can0", CAN_RAW[, 0xd00d]); 123 | 124 | CanMessage messageToSend(0 /*send with default ID*/, "8 bytes!" /* the data */); 125 | 126 | auto sentByteCount = canDriver.sendMessage(messageToSend[, false /* don't force extended ID */]); 127 | 128 | printf("Sent %d bytes via CAN!\n", sentByteCount); 129 | } 130 | 131 | void sendMultipleFramesExample() { 132 | CanDriver canDriver("can1", CAN_RAW[, 0 /* no default ID */]); 133 | 134 | queue messageQueue = { 135 | CanMessage(0x269, "somedata"), 136 | Canmessage(0x1e9, "moredata") 137 | }; 138 | 139 | auto sentByteCount = canDriver.sendMessageQueue(messageQueue[, milliseconds(20) /* delay between frames */[, false /* don't force extended ID */]]); 140 | 141 | printf("Sent %d bytes via CAN!\n", sentByteCount); 142 | 143 | } 144 | ``` 145 | 146 | ### Receiving messages via CAN 147 | 148 | Receiving CAN messages is almost as simple as sending them! Firstly, check if there are any messages in the buffer, then pull them out; either one-by-one, or all at once! 149 | 150 | ```cpp 151 | #include 152 | 153 | using sockcanpp::CanDriver; 154 | using sockcanpp::CanId; 155 | using sockcanpp::CanMessage; 156 | 157 | void receiveCanFramesExample() { 158 | CanDriver canDriver("can2", CAN_RAW[, 0 /* no default ID */]); 159 | 160 | if (canDriver.waitForMessages([milliseconds(3000) /* timeout */])) { 161 | // read a single message 162 | CanMessage receivedMessage = canDriver.readMessage(); 163 | 164 | // read all available messages 165 | queue receivedMessages = canDriver.readQueuedMessages(); 166 | 167 | // handle CAN frames 168 | } 169 | } 170 | ``` 171 | -------------------------------------------------------------------------------- /cmake/cpack.cmake: -------------------------------------------------------------------------------- 1 | # these are cache variables, so they could be overwritten with -D, 2 | set(CPACK_PACKAGE_NAME lib${PROJECT_NAME} 3 | CACHE STRING "The resulting package name" 4 | ) 5 | 6 | # which is useful in case of packing only selected components instead of the whole thing 7 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A socketcan wrapper library for C++" 8 | CACHE STRING "Package description for the package metadata" 9 | ) 10 | 11 | set(CPACK_VERBATIM_VARIABLES YES) 12 | 13 | set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) 14 | SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/packages") 15 | 16 | # https://unix.stackexchange.com/a/11552/254512 17 | #set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/some")#/${CMAKE_PROJECT_VERSION}") 18 | 19 | set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 20 | set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 21 | set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 22 | 23 | set(CPACK_PACKAGE_CONTACT "contact@simonc.eu") 24 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Simon Cahill") 25 | 26 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") 27 | set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") 28 | 29 | # package name for deb. If set, then instead of some-application-0.9.2-Linux.deb 30 | # you'll get some-application_0.9.2_amd64.deb (note the underscores too) 31 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 32 | 33 | # that is if you want every group to have its own package, 34 | # although the same will happen if this is not set (so it defaults to ONE_PER_GROUP) 35 | # and CPACK_DEB_COMPONENT_INSTALL is set to YES 36 | set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP) 37 | 38 | # without this you won't be able to pack only specified component 39 | set(CPACK_DEB_COMPONENT_INSTALL YES) 40 | 41 | set(CPACK_GENERATOR TGZ DEB TZ STGZ RPM ZIP) 42 | 43 | include(CPack) -------------------------------------------------------------------------------- /cmake/libsockcanppConfig.cmake: -------------------------------------------------------------------------------- 1 | # include(CMakeFindDependencyMacro) 2 | # find_dependency(xxx 2.0) 3 | include(${CMAKE_CURRENT_LIST_DIR}/libsockcanppTargets.cmake) -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.23) 2 | 3 | target_sources(${PROJECT_NAME} 4 | PUBLIC FILE_SET HEADERS 5 | BASE_DIRS ${CMAKE_CURRENT_LIST_DIR} 6 | FILES 7 | CanDriver.hpp 8 | CanId.hpp 9 | CanMessage.hpp 10 | exceptions/CanCloseException.hpp 11 | exceptions/CanException.hpp 12 | exceptions/CanInitException.hpp 13 | exceptions/InvalidSocketException.hpp 14 | ) 15 | 16 | if (TARGET sockcanpp_test) 17 | target_sources(sockcanpp_test 18 | PUBLIC FILE_SET HEADERS 19 | BASE_DIRS ${CMAKE_CURRENT_LIST_DIR} 20 | FILES 21 | CanDriver.hpp 22 | CanId.hpp 23 | CanMessage.hpp 24 | exceptions/CanCloseException.hpp 25 | exceptions/CanException.hpp 26 | exceptions/CanInitException.hpp 27 | exceptions/InvalidSocketException.hpp 28 | ) 29 | endif() 30 | -------------------------------------------------------------------------------- /include/CanDriver.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanDriver.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the declarations for the SocketCAN wrapper in C++. 5 | * @version 0.1 6 | * @date 2020-07-01 7 | * 8 | * @copyright Copyright (c) 2020-2025 Simon Cahill 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | #ifndef LIBSOCKCANPP_INCLUDE_CANDRIVER_HPP 24 | #define LIBSOCKCANPP_INCLUDE_CANDRIVER_HPP 25 | 26 | ////////////////////////////// 27 | // SYSTEM INCLUDES // 28 | ////////////////////////////// 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | ////////////////////////////// 36 | // LOCAL INCLUDES // 37 | ////////////////////////////// 38 | #include "CanId.hpp" 39 | #include "CanMessage.hpp" 40 | 41 | /** 42 | * @brief Main library namespace. 43 | * 44 | * This namespace contains the library's main code. 45 | */ 46 | namespace sockcanpp { 47 | 48 | using namespace std::chrono_literals; 49 | 50 | using std::chrono::microseconds; 51 | using std::chrono::milliseconds; 52 | using std::chrono::nanoseconds; 53 | using std::mutex; 54 | using std::string; 55 | using std::queue; 56 | using std::unordered_map; 57 | 58 | using filtermap_t = unordered_map; 59 | 60 | /** 61 | * @brief CanDriver class; handles communication via CAN. 62 | * 63 | * This class provides the means of easily communicating with other devices via CAN in C++. 64 | * 65 | * @remarks 66 | * This class may be inherited by other applications and modified to suit your needs. 67 | */ 68 | class CanDriver { 69 | public: // +++ Static +++ 70 | static constexpr int32_t CAN_MAX_DATA_LENGTH = 8; //!< The maximum amount of bytes allowed in a single CAN frame 71 | static constexpr int32_t CAN_SOCK_RAW = CAN_RAW; //!< The raw CAN protocol 72 | static constexpr int32_t CAN_SOCK_SEVEN = 7; //!< A separate CAN protocol, used by certain embedded device OEMs. 73 | 74 | public: // +++ Constructor / Destructor +++ 75 | CanDriver(const string& canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); //!< Constructor 76 | CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); 77 | CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId = 0); 78 | CanDriver() = default; 79 | virtual ~CanDriver() { uninitialiseSocketCan(); } //!< Destructor 80 | 81 | public: // +++ Getter / Setter +++ 82 | CanDriver& setDefaultSenderId(const CanId& id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID 83 | 84 | CanId getDefaultSenderId() const { return this->_defaultSenderId; } //!< Gets the default sender ID 85 | 86 | filtermap_t getFilterMask() const { return this->_canFilterMask; } //!< Gets the filter mask used by this instance 87 | 88 | int32_t getMessageQueueSize() const { return this->_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() 89 | int32_t getSocketFd() const { return this->_socketFd; } //!< The socket file descriptor used by this instance. 90 | 91 | string getCanInterface() const { return this->_canInterface; } //!< The CAN interface used by this instance. 92 | 93 | public: // +++ I/O +++ 94 | virtual bool waitForMessages(microseconds timeout = 3000us); //!< Waits for CAN messages to appear 95 | virtual bool waitForMessages(milliseconds timeout = 3000ms); //!< Waits for CAN messages to appear 96 | virtual bool waitForMessages(nanoseconds timeout = 3000ns); //!< Waits for CAN messages to appear 97 | 98 | virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus 99 | 100 | virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message 101 | virtual ssize_t sendMessageQueue(queue& messages, microseconds delay = 20us, bool forceExtended = false); //!< Attempts to send a queue of messages 102 | virtual ssize_t sendMessageQueue(queue& messages, milliseconds delay = 20ms, bool forceExtended = false); //!< Attempts to send a queue of messages 103 | virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay = 20ns, bool forceExtended = false); //!< Attempts to send a queue of messages 104 | 105 | virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus 106 | 107 | public: // +++ Socket Management +++ 108 | virtual void allowCanFdFrames(const bool enabled = true) const; //!< Sets the CAN FD frame option for the interface 109 | #ifdef CANXL_XLF 110 | virtual void allowCanXlFrames(const bool enabled = true) const; //!< Sets the CAN XL frame option for the interface 111 | #endif // CANXL_XLF 112 | virtual void joinCanFilters() const; //!< Configures the socket to join the CAN filters 113 | virtual void setCanFilterMask(const int32_t mask, const CanId& filterId); //!< Attempts to set a new CAN filter mask to the interface 114 | virtual void setCanFilters(const filtermap_t& filters); //!< Sets the CAN filters for the interface 115 | virtual void setErrorFilter(const bool enabled = true) const; //!< Sets the error filter for the interface 116 | virtual void setReceiveOwnMessages(const bool enabled = true) const; //!< Sets the receive own messages option for the interface 117 | 118 | protected: // +++ Socket Management +++ 119 | virtual void initialiseSocketCan(); //!< Initialises socketcan 120 | virtual void uninitialiseSocketCan(); //!< Uninitialises socketcan 121 | 122 | private: // +++ Member Functions +++ 123 | virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard 124 | 125 | private: // +++ Variables +++ 126 | 127 | CanId _defaultSenderId; //!< The ID to send messages with if no other ID was set. 128 | 129 | filtermap_t _canFilterMask; //!< The bit mask used to filter CAN messages 130 | 131 | int32_t _canProtocol; //!< The protocol used when communicating via CAN 132 | int32_t _socketFd{-1}; //!< The CAN socket file descriptor 133 | int32_t _queueSize{0}; ///!< The size of the message queue read by waitForMessages() 134 | bool _canReadQueueSize{true}; ///!< Is the queue size available 135 | 136 | //!< Mutex for thread-safety. 137 | mutex _lock{}; 138 | mutex _lockSend{}; 139 | 140 | string _canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) 141 | 142 | }; 143 | 144 | /** 145 | * @brief Formats a std string object. 146 | * 147 | * @remarks Yoinked from https://github.com/Beatsleigher/liblogpp :) 148 | * 149 | * @tparam Args The formatting argument types. 150 | * @param format The format string. 151 | * @param args The format arguments (strings must be converted to C-style strings!) 152 | * 153 | * @return string The formatted string. 154 | */ 155 | template 156 | string formatString(const string& format, Args... args) { 157 | using std::unique_ptr; 158 | auto stringSize = snprintf(nullptr, 0, format.c_str(), args...) + 1; // +1 for \0 159 | unique_ptr buffer(new char[stringSize]); 160 | 161 | snprintf(buffer.get(), stringSize, format.c_str(), args...); 162 | 163 | return string(buffer.get(), buffer.get() + stringSize - 1); // std::string handles termination for us. 164 | } 165 | 166 | } 167 | 168 | #endif // LIBSOCKCANPP_INCLUDE_CANDRIVER_HPP 169 | -------------------------------------------------------------------------------- /include/CanId.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanId.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of a value-type representing a CAN identifier. 5 | * @version 0.1 6 | * @date 2020-07-01 7 | * 8 | * @copyright Copyright (c) 2020-2025 Simon Cahill 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | #ifndef LIBSOCKPP_INCLUDE_CANID_HPP 24 | #define LIBSOCKPP_INCLUDE_CANID_HPP 25 | 26 | ////////////////////////////// 27 | // SYSTEM INCLUDES // 28 | ////////////////////////////// 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #if __cpp_concepts >= 201907 37 | template 38 | concept Stringable = requires(Str s) { { s.data() + s.size() } -> std::convertible_to; }; 39 | 40 | template 41 | concept CChar = requires(CharArr c) { std::is_same_v; }; 42 | 43 | template 44 | concept Integral = requires(Int i) { std::is_integral_v; }; 45 | 46 | template 47 | concept ConvertibleToCanId = Stringable || Integral || CChar; 48 | #endif 49 | 50 | namespace sockcanpp { 51 | 52 | using std::bitset; 53 | using std::error_code; 54 | using std::exception; 55 | using std::generic_category; 56 | using std::system_error; 57 | 58 | /** 59 | * @brief Represents a CAN ID in a simple and easy-to-use manner. 60 | */ 61 | struct CanId { 62 | public: // +++ Constructors +++ 63 | constexpr CanId() = default; 64 | constexpr CanId(const canid_t id): m_identifier(id) { } 65 | constexpr CanId(const int32_t id): m_identifier(id) { } 66 | 67 | #if __cpp_concepts >= 201907 68 | template 69 | CanId(const T& id) { m_identifier = std::stoul(id.data(), nullptr, 16); } 70 | #endif // __cpp_concepts >= 201907 71 | 72 | CanId(const char* id) { m_identifier = std::stoul(id, nullptr, 16); } 73 | 74 | public: // +++ Operators +++ 75 | constexpr canid_t operator *() const { return m_identifier; } //!< Returns the raw CAN ID value. 76 | 77 | #pragma region "Conversions" 78 | constexpr operator int16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } 79 | constexpr operator uint16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } 80 | constexpr operator int32_t() const { return m_identifier & CAN_ERR_MASK; } 81 | constexpr operator canid_t() const { return m_identifier & CAN_ERR_MASK; } 82 | #pragma endregion 83 | 84 | #pragma region "Bitwise Operators" 85 | template 86 | constexpr CanId operator &(const T x) const { return m_identifier & x; } //!< Performs a bitwise AND operation on this ID and another. 87 | constexpr CanId operator &(const CanId& x) const { return m_identifier & x.m_identifier; } //!< Performs a bitwise AND operation on this ID and another. 88 | 89 | template 90 | constexpr CanId operator |(const T x) const { return m_identifier | x; } //!< Performs a bitwise OR operation on this ID and a 16-bit integer. 91 | constexpr CanId operator |(const CanId& x) const { return m_identifier | x.m_identifier; } //!< Performs a bitwise OR operation on this ID and another. 92 | 93 | template 94 | constexpr CanId operator ^(const T x) const { return m_identifier ^ x; } //!< Performs a bitwise XOR operation on this ID and a 16-bit integer. 95 | constexpr CanId operator ^(const CanId& x) const { return m_identifier ^ x.m_identifier; } //!< Performs a bitwise XOR operation on this ID and another. 96 | 97 | constexpr CanId operator ~() const { return ~m_identifier; } //!< Performs a bitwise NOT operation on this ID. 98 | 99 | template 100 | constexpr CanId operator <<(const T x) const { return m_identifier << x; } //!< Shifts this ID to the left by a 16-bit integer. 101 | constexpr CanId operator <<(const CanId& x) const { return m_identifier << x.m_identifier; } //!< Shifts this ID to the left by another. 102 | 103 | template 104 | constexpr CanId operator >>(const T x) const { return m_identifier >> x; } //!< Shifts this ID to the right by a 16-bit integer. 105 | constexpr CanId operator >>(const CanId& x) const { return m_identifier >> x.m_identifier; } //!< Shifts this ID to the right by another. 106 | 107 | template 108 | CanId operator <<=(const T x) { return m_identifier <<= x; } //!< Shifts this ID to the left by a 16-bit integer. 109 | CanId operator <<=(const CanId& x) { return m_identifier <<= x.m_identifier; } //!< Shifts this ID to the left by another. 110 | 111 | template 112 | CanId operator >>=(const T x) { return m_identifier >>= x; } //!< Shifts this ID to the right by a 16-bit integer. 113 | CanId operator >>=(const CanId& x) { return m_identifier >>= x.m_identifier; } //!< Shifts this ID to the right by another. 114 | 115 | #pragma endregion 116 | 117 | #pragma region "Comparison Operators" 118 | constexpr bool operator ==(const CanId& x) const { return m_identifier == x.m_identifier; } //!< Compares this ID to another. 119 | 120 | template 121 | constexpr bool operator ==(const T x) const { return m_identifier == static_cast(x); } //!< Compares this ID to another. 122 | 123 | constexpr bool operator !=(const CanId& x) const { return m_identifier != x.m_identifier; } //!< Compares this ID to another. 124 | 125 | template 126 | constexpr bool operator !=(const T x) const { return m_identifier != static_cast(x); } //!< Compares this ID to another. 127 | 128 | template 129 | constexpr bool operator <(T x) const { return static_cast(x) < m_identifier; } //!< Compares this ID to another. 130 | 131 | template 132 | constexpr bool operator >(T x) const { return static_cast(x) > m_identifier; } //!< Compares this ID to a 32-bit integer. 133 | 134 | template 135 | constexpr bool operator <=(const T x) const { return x.m_identifier <= m_identifier; } //!< Compares this ID to another. 136 | 137 | template 138 | constexpr bool operator >=(const T x) const { return x.m_identifier >= m_identifier; } //!< Compares this ID to another. 139 | #pragma endregion 140 | 141 | #pragma region "Assignment Operators" 142 | template 143 | constexpr CanId operator =(const T val) { return CanId(val); } //!< Assigns a new integer to this CanID 144 | 145 | #if __cpp_concepts >= 201907 146 | template 147 | CanId operator =(const T& val) { 148 | return operator=(std::stoul(val.data(), nullptr, 16)); 149 | } 150 | #endif // __cpp_concepts >= 201907 151 | 152 | constexpr CanId operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. 153 | 154 | template 155 | constexpr CanId operator |=(const T x) { return m_identifier |= x; } //!< Performs a bitwise OR operation on this ID and another. 156 | 157 | template 158 | constexpr CanId operator &=(const T x) { return m_identifier &= x; } //!< Performs a bitwise AND operation on this ID and another. 159 | 160 | template 161 | constexpr CanId operator ^=(const T x) { return m_identifier ^= x; } //!< Performs a bitwise XOR operation on this ID and another. 162 | #pragma endregion 163 | 164 | #pragma region "Arithmetic Operators" 165 | template 166 | constexpr CanId operator +(const T x) const { return m_identifier + x; } 167 | 168 | template 169 | constexpr CanId operator +=(const T x) { return m_identifier += x; } 170 | 171 | template 172 | constexpr CanId operator -=(const T x) { return m_identifier -= x; } 173 | 174 | template 175 | constexpr CanId operator -(const T x) const { return m_identifier - x; } 176 | 177 | template 178 | constexpr CanId operator *(const T x) const { return m_identifier * x; } 179 | 180 | template 181 | constexpr CanId operator *= (const T x) { return m_identifier *= x; } 182 | 183 | template 184 | constexpr CanId operator /(const T x) const { return m_identifier / x; } 185 | 186 | template 187 | constexpr CanId operator /=(const T x) { return m_identifier /= x; } 188 | 189 | template 190 | constexpr CanId operator %(const T x) const { return m_identifier % x; } 191 | 192 | template 193 | constexpr CanId operator %=(const T x) { return m_identifier %= x; } 194 | #pragma endregion 195 | 196 | public: // +++ Validity Checks +++ 197 | /** 198 | * @brief Indicates whether or not a given integer is a valid CAN identifier. 199 | * 200 | * @param value The integer to check. 201 | * 202 | * @return true If value is a valid CAN identifier. 203 | * @return false Otherwise. 204 | */ 205 | template 206 | static constexpr bool isValidIdentifier(T value) { 207 | return static_cast(value) <= CAN_EFF_MASK; 208 | } 209 | 210 | /** 211 | * @brief Indicates whether or not a given integer contains the error frame flag or not. 212 | * 213 | * @param value The integer to check. 214 | * 215 | * @return true If value has the error frame flag (bit) set to 1. 216 | * @return false Otherwise. 217 | */ 218 | template 219 | static constexpr bool isErrorFrame(T value) { 220 | return static_cast(value) & CAN_ERR_FLAG; 221 | } 222 | 223 | /** 224 | * @brief Indicates whether the received frame is a remote transmission request. 225 | * 226 | * @param value The integer to check. 227 | * 228 | * @return true If the frame is a remote transmission request. 229 | * @return false Otherwise. 230 | */ 231 | template 232 | static constexpr bool isRemoteTransmissionRequest(T value) { 233 | return static_cast(value) & CAN_RTR_FLAG; 234 | } 235 | 236 | /** 237 | * @brief Indicates whether or not a given integer is an extended frame ID. 238 | * 239 | * @param value The integer to check. 240 | * 241 | * @return true If the frame is in the extended format. 242 | * @return false Otherwise. 243 | */ 244 | template 245 | static constexpr bool isExtendedFrame(T value) { 246 | return static_cast(value) & CAN_EFF_FLAG; 247 | } 248 | 249 | public: // +++ Getters +++ 250 | constexpr bool hasErrorFrameFlag() const { return isErrorFrame(m_identifier); } //!< Indicates whether or not this ID is an error frame. 251 | constexpr bool hasRtrFrameFlag() const { return isRemoteTransmissionRequest(m_identifier); } //!< Indicates whether or not this ID is a remote transmission request. 252 | constexpr bool isStandardFrameId() const { return !isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is a standard frame ID. 253 | constexpr bool isExtendedFrameId() const { return isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is an extended frame ID. 254 | 255 | public: // +++ Equality Checks +++ 256 | constexpr bool equals(const CanId& otherId) const { return m_identifier == otherId.m_identifier; } //!< Compares this ID to another. 257 | 258 | private: // +++ Variables +++ 259 | uint32_t m_identifier = 0; 260 | }; 261 | 262 | /** 263 | * @brief Implements a hash function for the CanId type. 264 | */ 265 | struct CanIdHasher { 266 | public: 267 | size_t operator()(const CanId& id) const { return std::hash()(*id); } 268 | }; 269 | 270 | } 271 | 272 | #endif // LIBSOCKPP_INCLUDE_CANID_HPP 273 | -------------------------------------------------------------------------------- /include/CanMessage.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanMessage.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of a CAN message representation in C++. 5 | * @version 0.1 6 | * @date 2020-07-01 7 | * 8 | * @copyright Copyright (c) 2020-2025 Simon Cahill 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | #ifndef LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP 24 | #define LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP 25 | 26 | ////////////////////////////// 27 | // SYSTEM INCLUDES // 28 | ////////////////////////////// 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | ////////////////////////////// 38 | // LOCAL INCLUDES // 39 | ////////////////////////////// 40 | #include "CanId.hpp" 41 | 42 | namespace sockcanpp { 43 | 44 | using std::error_code; 45 | using std::generic_category; 46 | using std::memcpy; 47 | using std::string; 48 | using std::system_error; 49 | 50 | /** 51 | * @brief Represents a CAN message that was received. 52 | */ 53 | class CanMessage { 54 | public: // +++ Constructor / Destructor +++ 55 | CanMessage(const struct can_frame frame): 56 | _canIdentifier(frame.can_id), _frameData((const char*)frame.data, frame.can_dlc), _rawFrame(frame) { } 57 | 58 | CanMessage(const CanId canId, const string& frameData): _canIdentifier(canId), _frameData(frameData) { 59 | if (frameData.size() > CAN_MAX_DLEN) { 60 | throw system_error(error_code(0xbadd1c, generic_category()), "Payload too big!"); 61 | } 62 | 63 | struct can_frame rawFrame{}; 64 | rawFrame.can_id = canId; 65 | std::copy(frameData.begin(), frameData.end(), rawFrame.data); 66 | rawFrame.can_dlc = frameData.size(); 67 | 68 | _rawFrame = rawFrame; 69 | } 70 | 71 | virtual ~CanMessage() {} 72 | 73 | public: // +++ Getters +++ 74 | const CanId getCanId() const { return this->_canIdentifier; } 75 | const string getFrameData() const { return this->_frameData; } 76 | const can_frame getRawFrame() const { return this->_rawFrame; } 77 | 78 | private: 79 | CanId _canIdentifier; 80 | 81 | string _frameData; 82 | 83 | struct can_frame _rawFrame; 84 | }; 85 | 86 | } 87 | 88 | #endif // LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP 89 | -------------------------------------------------------------------------------- /include/exceptions/CanCloseException.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanCloseException.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of an exception that may be thrown when an error occurs while closing a CAN socket. 5 | * @version 0.1 6 | * @date 2020-07-02 7 | * 8 | * @copyright Copyright (c) 2020 Simon Cahill 9 | * 10 | * Copyright 2020 Simon Cahill 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | #ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP 26 | #define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP 27 | 28 | #include 29 | #include 30 | 31 | namespace sockcanpp { namespace exceptions { 32 | 33 | using std::exception; 34 | using std::string; 35 | 36 | /** 37 | * @brief An exception that may be thrown when an error occurs while closing a CAN socket. 38 | */ 39 | class CanCloseException: public exception { 40 | public: // +++ Constructor / Destructor +++ 41 | CanCloseException(string message): _message(message) {} 42 | ~CanCloseException() {} 43 | 44 | public: // +++ Overrides +++ 45 | const char* what() const noexcept { return _message.c_str(); } 46 | 47 | private: 48 | string _message; 49 | }; 50 | 51 | } /* exceptions */ } /* sockcanpp */ 52 | 53 | #endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP 54 | -------------------------------------------------------------------------------- /include/exceptions/CanException.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanException.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of a general-purpose exception which may be thrown when an error occurs when performing IO on a CAN socket. 5 | * @version 0.1 6 | * @date 2020-07-02 7 | * 8 | * @copyright Copyright (c) 2020 Simon Cahill 9 | * 10 | * Copyright 2020 Simon Cahill 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | #ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP 26 | #define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP 27 | 28 | #include 29 | #include 30 | 31 | namespace sockcanpp { namespace exceptions { 32 | 33 | using std::exception; 34 | using std::string; 35 | 36 | /** 37 | * @brief An exception that may be thrown when an error occurs while closing a CAN socket. 38 | */ 39 | class CanException: public exception { 40 | public: // +++ Constructor / Destructor +++ 41 | CanException(const string& message, int32_t socket): m_socket(socket), m_message(message) {} 42 | ~CanException() {} 43 | 44 | public: // +++ Overrides +++ 45 | const char* what() const noexcept { return m_message.c_str(); } 46 | 47 | public: // +++ Getter +++ 48 | const int32_t getSocket() const { return m_socket; } 49 | 50 | private: 51 | int32_t m_socket; 52 | 53 | string m_message; 54 | }; 55 | 56 | } /* exceptions */ } /* sockcanpp */ 57 | 58 | #endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP 59 | -------------------------------------------------------------------------------- /include/exceptions/CanInitException.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanInitException.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of an exception that is thrown when a CAN socket couldn't be inintialised. 5 | * @version 0.1 6 | * @date 2020-07-02 7 | * 8 | * @copyright Copyright (c) 2020 Simon Cahill 9 | * 10 | * Copyright 2020 Simon Cahill 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | #ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP 26 | #define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP 27 | 28 | #include 29 | #include 30 | 31 | namespace sockcanpp { namespace exceptions { 32 | 33 | using std::exception; 34 | using std::string; 35 | 36 | /** 37 | * @brief An exception that may be thrown when an error occurred while initialising a CAN socket. 38 | */ 39 | class CanInitException: public exception { 40 | public: // +++ Constructor / Destructor +++ 41 | CanInitException(string message): _message(message) { } 42 | virtual ~CanInitException() { } 43 | 44 | public: // +++ Override +++ 45 | const char* what() const noexcept { return _message.c_str(); } 46 | 47 | private: 48 | string _message; 49 | }; 50 | 51 | } /* exceptions */ } /* sockcanpp */ 52 | 53 | #endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP 54 | -------------------------------------------------------------------------------- /include/exceptions/InvalidSocketException.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file InvalidSocketException.hpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of an exception that may be thrown when an invalid CAN socket is detected. 5 | * @version 0.1 6 | * @date 2020-07-02 7 | * 8 | * @copyright Copyright (c) 2020 Simon Cahill 9 | * 10 | * Copyright 2020 Simon Cahill 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | #ifndef LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP 26 | #define LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP 27 | 28 | #include 29 | #include 30 | 31 | namespace sockcanpp { namespace exceptions { 32 | 33 | using std::exception; 34 | using std::string; 35 | 36 | /** 37 | * @brief An exception that may be thrown when an error occurs while closing a CAN socket. 38 | */ 39 | class InvalidSocketException: public exception { 40 | public: // +++ Constructor / Destructor +++ 41 | InvalidSocketException(const string& message, int32_t socket): m_socket(socket), m_message(message) {} 42 | ~InvalidSocketException() {} 43 | 44 | public: // +++ Overrides +++ 45 | const char* what() const noexcept { return m_message.c_str(); } 46 | 47 | public: // +++ Getter +++ 48 | const int32_t getSocket() const { return m_socket; } 49 | 50 | private: 51 | int32_t m_socket; 52 | 53 | string m_message; 54 | }; 55 | 56 | } /* exceptions */ } /* sockcanpp */ 57 | 58 | #endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP 59 | -------------------------------------------------------------------------------- /libsockcanpp.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@prefix@ 2 | exec_prefix=@exec_prefix@ 3 | libdir=@libdir@ 4 | includedir=@includedir@ 5 | 6 | Name: @PROJECT_NAME@ 7 | Description: A C++ user-space CAN bus driver for Linux (using socketCAN). 8 | Version: @PROJECT_VERSION@ 9 | Libs: -L${libdir} -l@PROJECT_NAME@ 10 | Cflags: -I${includedir} 11 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | target_sources(${PROJECT_NAME} 2 | PRIVATE 3 | ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp 4 | ) 5 | 6 | if (TARGET sockcanpp_test) 7 | target_sources(sockcanpp_test 8 | PRIVATE 9 | ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp 10 | ) 11 | endif() 12 | 13 | add_compile_options( 14 | -Wno-unknown-pragmas 15 | ) 16 | -------------------------------------------------------------------------------- /src/CanDriver.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanDriver.cpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief 5 | * @version 0.1 6 | * @date 2020-07-01 7 | * 8 | * @copyright Copyright (c) 2020-2025 Simon Cahill 9 | * 10 | * Licensed under the Apache License, Version 2.0 (the "License"); 11 | * you may not use this file except in compliance with the License. 12 | * You may obtain a copy of the License at 13 | * 14 | * http://www.apache.org/licenses/LICENSE-2.0 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under the License is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See the License for the specific language governing permissions and 20 | * limitations under the License. 21 | */ 22 | 23 | ////////////////////////////// 24 | // SYSTEM INCLUDES // 25 | ////////////////////////////// 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | ////////////////////////////// 46 | // LOCAL INCLUDES // 47 | ////////////////////////////// 48 | #include "CanDriver.hpp" 49 | #include "CanId.hpp" 50 | #include "CanMessage.hpp" 51 | #include "exceptions/CanCloseException.hpp" 52 | #include "exceptions/CanException.hpp" 53 | #include "exceptions/CanInitException.hpp" 54 | #include "exceptions/InvalidSocketException.hpp" 55 | 56 | namespace sockcanpp { 57 | 58 | using exceptions::CanCloseException; 59 | using exceptions::CanException; 60 | using exceptions::CanInitException; 61 | using exceptions::InvalidSocketException; 62 | 63 | using std::mutex; 64 | using std::queue; 65 | using std::string; 66 | using std::strncpy; 67 | using std::unique_lock; 68 | using std::unique_ptr; 69 | using std::chrono::milliseconds; 70 | using std::this_thread::sleep_for; 71 | using std::vector; 72 | 73 | ////////////////////////////////////// 74 | // PUBLIC IMPLEMENTATION // 75 | ////////////////////////////////////// 76 | 77 | 78 | #pragma region "Object Construction" 79 | CanDriver::CanDriver(const string& canInterface, int32_t canProtocol, const CanId defaultSenderId): 80 | CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) { } 81 | 82 | CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): 83 | CanDriver(canInterface, canProtocol, filtermap_t{{0, filterMask}}, defaultSenderId) { } 84 | 85 | CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId): 86 | _defaultSenderId(defaultSenderId), _canFilterMask(filters), _canProtocol(canProtocol), _canInterface(canInterface) { 87 | initialiseSocketCan(); 88 | } 89 | #pragma endregion 90 | 91 | #pragma region "I / O" 92 | /** 93 | * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. 94 | * 95 | * @param timeout The time (in µsec) to wait before timing out. 96 | * 97 | * @return true If messages are available on the bus. 98 | * @return false Otherwise. 99 | */ 100 | bool CanDriver::waitForMessages(microseconds timeout/* = microseconds(3000)*/) { 101 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 102 | 103 | unique_lock locky(_lock); 104 | 105 | fd_set readFileDescriptors; 106 | timeval waitTime{0, static_cast(timeout.count())}; 107 | 108 | FD_ZERO(&readFileDescriptors); 109 | FD_SET(_socketFd, &readFileDescriptors); 110 | const auto fdsAvailable = select(_socketFd + 1, &readFileDescriptors, nullptr, nullptr, &waitTime); 111 | 112 | int32_t bytesAvailable{0}; 113 | const auto retCode = ioctl(_socketFd, FIONREAD, &bytesAvailable); 114 | if (retCode == 0) { 115 | _queueSize = static_cast(std::ceil(bytesAvailable / sizeof(can_frame))); 116 | } else { 117 | _queueSize = 0; 118 | // vcan interfaces don't support FIONREAD. So fall back to 119 | // using alternate implementation in readQueuedMessages(). 120 | _canReadQueueSize = false; 121 | } 122 | 123 | return fdsAvailable > 0; 124 | } 125 | 126 | /** 127 | * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. 128 | * 129 | * @param timeout The time (in millis) to wait before timing out. 130 | * 131 | * @return true If messages are available on the bus. 132 | * @return false Otherwise. 133 | */ 134 | bool CanDriver::waitForMessages(milliseconds timeout) { return waitForMessages(std::chrono::duration_cast(timeout)); } 135 | 136 | /** 137 | * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. 138 | * 139 | * @param timeout The time (in nanoseconds) to wait before timing out. 140 | * 141 | * @return true If messages are available on the bus. 142 | * @return false Otherwise. 143 | */ 144 | bool CanDriver::waitForMessages(nanoseconds timeout/* = nanoseconds(3000)*/) { return waitForMessages(std::chrono::duration_cast(timeout)); } 145 | 146 | /** 147 | * @brief Attempts to read a message from the associated CAN bus. 148 | * 149 | * @return CanMessage The message read from the bus. 150 | */ 151 | CanMessage CanDriver::readMessage() { return readMessageLock(); } 152 | 153 | /** 154 | * @brief readMessage deadlock guard, attempts to read a message from the associated CAN bus. 155 | * 156 | * @return CanMessage The message read from the bus. 157 | */ 158 | CanMessage CanDriver::readMessageLock(bool const lock) { 159 | unique_ptr> locky{nullptr}; 160 | 161 | if (lock) { locky = unique_ptr>{new unique_lock{_lock}}; } 162 | 163 | if (0 > _socketFd) { throw InvalidSocketException("Invalid socket!", _socketFd); } 164 | 165 | ssize_t readBytes{0}; 166 | can_frame canFrame{}; 167 | 168 | readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); 169 | 170 | if (0 > readBytes) { throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); } 171 | 172 | return CanMessage{canFrame}; 173 | } 174 | 175 | /** 176 | * @brief Attempts to send a CAN message on the associated bus. 177 | * 178 | * @param message The message to be sent. 179 | * @param forceExtended Whether or not to force use of an extended ID. 180 | * 181 | * @return ssize_t The amount of bytes sent on the bus. 182 | */ 183 | ssize_t CanDriver::sendMessage(const CanMessage& message, bool forceExtended) { 184 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 185 | 186 | unique_lock locky(_lockSend); 187 | 188 | ssize_t bytesWritten = 0; 189 | 190 | if (message.getFrameData().size() > CAN_MAX_DATA_LENGTH) { 191 | throw CanException(formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH), _socketFd); 192 | } 193 | 194 | auto canFrame = message.getRawFrame(); 195 | 196 | if (forceExtended || ((uint32_t)message.getCanId() > CAN_SFF_MASK)) { canFrame.can_id |= CAN_EFF_FLAG; } 197 | 198 | bytesWritten = write(_socketFd, (const void*)&canFrame, sizeof(canFrame)); 199 | 200 | if (bytesWritten == -1) { throw CanException(formatString("FAILED to write data to socket! Error: %d => %s", errno, strerror(errno)), _socketFd); } 201 | 202 | return bytesWritten; 203 | } 204 | 205 | /** 206 | * @brief Attempts to send a queue of messages on the associated CAN bus. 207 | * 208 | * @param messages A queue containing the messages to be sent. 209 | * @param delay If greater than 0, will delay the sending of the next message. 210 | * @param forceExtended Whether or not to force use of an extended ID. 211 | * 212 | * @return int32_t The total amount of bytes sent. 213 | */ 214 | ssize_t CanDriver::sendMessageQueue(queue& messages, microseconds delay, bool forceExtended) { return sendMessageQueue(messages, std::chrono::duration_cast(delay), forceExtended); } 215 | 216 | /** 217 | * @brief Attempts to send a queue of messages on the associated CAN bus. 218 | * 219 | * @param messages A queue containing the messages to be sent. 220 | * @param delay If greater than 0, will delay the sending of the next message. 221 | * @param forceExtended Whether or not to force use of an extended ID. 222 | * 223 | * @return int32_t The total amount of bytes sent. 224 | */ 225 | ssize_t CanDriver::sendMessageQueue(queue& messages, milliseconds delay, bool forceExtended) { return sendMessageQueue(messages, std::chrono::duration_cast(delay), forceExtended); } 226 | 227 | /** 228 | * @brief Attempts to send a queue of messages on the associated CAN bus. 229 | * 230 | * @param messages A queue containing the messages to be sent. 231 | * @param delay If greater than 0, will delay the sending of the next message. 232 | * @param forceExtended Whether or not to force use of an extended ID. 233 | * 234 | * @return int32_t The total amount of bytes sent. 235 | */ 236 | ssize_t CanDriver::sendMessageQueue(queue& messages, nanoseconds delay, bool forceExtended) { 237 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 238 | 239 | ssize_t totalBytesWritten = 0; 240 | 241 | while (!messages.empty()) { 242 | totalBytesWritten += sendMessage(messages.front(), forceExtended); 243 | messages.pop(); 244 | 245 | if (delay.count() > 0) { 246 | sleep_for(delay); 247 | } 248 | } 249 | 250 | return totalBytesWritten; 251 | } 252 | 253 | /** 254 | * @brief Attempts to read all messages stored in the buffer for the associated CAN bus. 255 | * 256 | * @return queue A queue containing the messages read from the bus buffer. 257 | */ 258 | queue CanDriver::readQueuedMessages() { 259 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 260 | 261 | unique_lock locky{_lock}; 262 | queue messages{}; 263 | 264 | if (_canReadQueueSize) { 265 | for (int32_t i = _queueSize; 0 < i; --i) { 266 | messages.emplace(readMessageLock(false)); 267 | } 268 | } else { 269 | // If the interface doesn't support FIONREAD, fall back 270 | // to reading until data exhausted. 271 | bool more{true}; 272 | 273 | do { 274 | ssize_t readBytes; 275 | can_frame canFrame{}; 276 | readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); 277 | if (readBytes >= 0) { 278 | messages.emplace(canFrame); 279 | } else if (errno == EAGAIN) { 280 | more = false; 281 | } else { 282 | throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); 283 | } 284 | } while (more); 285 | } 286 | 287 | return messages; 288 | } 289 | 290 | /** 291 | * @brief Sets the CAN FD frame option for the interface. 292 | * 293 | * This option allows the current driver instance to receive CAN FD frames. 294 | * 295 | * @param enabled Whether or not to enable the CAN FD frame option. 296 | */ 297 | void CanDriver::allowCanFdFrames(const bool enabled/* = true*/) const { 298 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 299 | 300 | int32_t canFdFrames = enabled ? 1 : 0; 301 | 302 | if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canFdFrames, sizeof(canFdFrames)) == -1) { 303 | throw CanInitException(formatString("FAILED to set CAN FD frames on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); 304 | } 305 | } 306 | 307 | #ifdef CANXL_XLF 308 | /** 309 | * @brief Sets the CAN XL option for the interface. 310 | * 311 | * This option allows the current driver instance to send/receive CAN XL frames. 312 | * 313 | * @param enabled Whether or not to enable the CAN XL option. 314 | */ 315 | void CanDriver::allowCanXlFrames(const bool enabled/* = true*/) const { 316 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 317 | 318 | int32_t canXlFrames = enabled ? 1 : 0; 319 | 320 | 321 | if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canXlFrames, sizeof(canXlFrames)) == -1) { 322 | throw CanInitException(formatString("FAILED to set CAN XL frames on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); 323 | } 324 | } 325 | #endif // CANXL_XLF 326 | 327 | /** 328 | * @brief Configures the socket to join the CAN filters. 329 | * This is especially required, when using inverted CAN filters. 330 | * 331 | * Source: https://stackoverflow.com/a/57680496/2921426 332 | */ 333 | void CanDriver::joinCanFilters() const { 334 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 335 | 336 | int32_t joinFilters = 1; 337 | 338 | if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS, &joinFilters, sizeof(joinFilters)) == -1) { 339 | throw CanInitException(formatString("FAILED to join CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); 340 | } 341 | } 342 | 343 | /** 344 | * @brief Attempts to set the filter mask for the associated CAN bus. 345 | * 346 | * @param mask The bit mask to apply. 347 | * @param filterId The ID to filter on. 348 | */ 349 | void CanDriver::setCanFilterMask(const int32_t mask, const CanId& filterId) { setCanFilters({{filterId, static_cast(mask)}}); } 350 | 351 | /** 352 | * @brief Sets multiple CAN filters for the associated CAN bus. 353 | * 354 | * @param filters A map containing the filters to apply. 355 | */ 356 | void CanDriver::setCanFilters(const filtermap_t& filters) { 357 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 358 | 359 | unique_lock locky(_lock); 360 | vector canFilters{}; 361 | 362 | // Structured bindings only available with C++17 363 | #if __cplusplus >= 201703L 364 | for (const auto [id, filter] : filters) { 365 | canFilters.push_back({*id, filter}); 366 | } 367 | #else 368 | for (const auto& filterPair : filters) { 369 | canFilters.push_back({*filterPair.first, filterPair.second}); 370 | } 371 | #endif 372 | 373 | if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, canFilters.data(), canFilters.size() * sizeof(can_filter)) == -1) { 374 | throw CanInitException(formatString("FAILED to set CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); 375 | } 376 | } 377 | 378 | /** 379 | * @brief Sets the error filter for the associated CAN bus. 380 | * 381 | * @param enabled Whether or not to enable the error filter. 382 | */ 383 | void CanDriver::setErrorFilter(const bool enabled/* = true*/) const { 384 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 385 | 386 | int32_t errorFilter = enabled ? 1 : 0; 387 | 388 | if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &errorFilter, sizeof(errorFilter)) == -1) { 389 | throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); 390 | } 391 | } 392 | 393 | /** 394 | * @brief Sets the receive own messages option for the associated CAN bus. 395 | * 396 | * This option allows the socket to receive its own messages. 397 | * 398 | * @param enabled Whether or not to enable the receive own messages option. 399 | */ 400 | void CanDriver::setReceiveOwnMessages(const bool enabled) const { 401 | if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } 402 | 403 | int32_t receiveOwnMessages = enabled ? 1 : 0; 404 | 405 | if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &receiveOwnMessages, sizeof(receiveOwnMessages)) == -1) { 406 | throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); 407 | } 408 | } 409 | #pragma endregion 410 | 411 | ////////////////////////////////////// 412 | // PROTECTED IMPLEMENTATION // 413 | ////////////////////////////////////// 414 | 415 | #pragma region Socket Management 416 | 417 | /** 418 | * @brief Initialises the underlying CAN socket. 419 | */ 420 | void CanDriver::initialiseSocketCan() { 421 | struct sockaddr_can address{}; 422 | struct ifreq ifaceRequest{}; 423 | int64_t fdOptions{0}; 424 | int32_t tmpReturn{0}; 425 | 426 | _socketFd = socket(PF_CAN, SOCK_RAW, _canProtocol); 427 | 428 | if (_socketFd == -1) { 429 | throw CanInitException(formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno))); 430 | } 431 | 432 | std::copy(_canInterface.begin(), _canInterface.end(), ifaceRequest.ifr_name); 433 | 434 | if ((tmpReturn = ioctl(_socketFd, SIOCGIFINDEX, &ifaceRequest)) == -1) { 435 | throw CanInitException(formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", _canInterface.c_str(), errno, 436 | strerror(errno))); 437 | } 438 | 439 | fdOptions = fcntl(_socketFd, F_GETFL); 440 | fdOptions |= O_NONBLOCK; 441 | tmpReturn = fcntl(_socketFd, F_SETFL, fdOptions); 442 | 443 | address.can_family = AF_CAN; 444 | address.can_ifindex = ifaceRequest.ifr_ifindex; 445 | 446 | setCanFilters(_canFilterMask); 447 | 448 | if ((tmpReturn = bind(_socketFd, (struct sockaddr*)&address, sizeof(address))) == -1) { 449 | throw CanInitException(formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno))); 450 | } 451 | } 452 | 453 | /** 454 | * @brief Closes the underlying CAN socket. 455 | */ 456 | void CanDriver::uninitialiseSocketCan() { 457 | unique_lock locky(_lock); 458 | 459 | if (_socketFd <= 0) { throw CanCloseException("Cannot close invalid socket!"); } 460 | 461 | if (close(_socketFd) == -1) { throw CanCloseException(formatString("FAILED to close CAN socket! Error: %d => %s", errno, strerror(errno))); } 462 | 463 | _socketFd = -1; 464 | } 465 | #pragma endregion 466 | 467 | } // namespace sockcanpp 468 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | add_subdirectory(app) 4 | add_subdirectory(unit) -------------------------------------------------------------------------------- /test/app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(testlibsockcanpp LANGUAGES CXX VERSION 1.0.0) 4 | set(TARGET_NAME testsockcanpp.bin) 5 | 6 | set(CMAKE_CXX_STANDARD 14) 7 | 8 | include_directories( 9 | include/ 10 | ) 11 | 12 | if (NOT TARGET sockcanpp) 13 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) 14 | endif() 15 | 16 | file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) 17 | 18 | add_executable(${TARGET_NAME} ${FILES}) 19 | 20 | target_link_libraries( 21 | # Binary 22 | ${TARGET_NAME} 23 | 24 | # Libs 25 | sockcanpp_test 26 | -lm 27 | 28 | -static 29 | -static-libstdc++ 30 | -static-libgcc 31 | ) -------------------------------------------------------------------------------- /test/app/src/Main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Main.cpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the implementation of a test application using this library. 5 | * @version 0.1 6 | * @date 2020-07-02 7 | * 8 | * @copyright Copyright (c) 2020 Simon Cahill 9 | * 10 | * Copyright 2020 Simon Cahill 11 | * 12 | * Licensed under the Apache License, Version 2.0 (the "License"); 13 | * you may not use this file except in compliance with the License. 14 | * You may obtain a copy of the License at 15 | * 16 | * http://www.apache.org/licenses/LICENSE-2.0 17 | * 18 | * Unless required by applicable law or agreed to in writing, software 19 | * distributed under the License is distributed on an "AS IS" BASIS, 20 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21 | * See the License for the specific language governing permissions and 22 | * limitations under the License. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | using namespace std::chrono_literals; 34 | 35 | using sockcanpp::CanDriver; 36 | using sockcanpp::CanId; 37 | using sockcanpp::exceptions::CanException; 38 | using sockcanpp::exceptions::CanInitException; 39 | using sockcanpp::exceptions::InvalidSocketException; 40 | using sockcanpp::CanMessage; 41 | 42 | using std::cerr; 43 | using std::cout; 44 | using std::endl; 45 | using std::string; 46 | 47 | void printHelp(string); 48 | 49 | int main(int32_t argCount, char** argValues) { 50 | int32_t desiredCanSocket = 0; 51 | string canInterface; 52 | 53 | if (argCount > 2) { 54 | for (int32_t i = 1; i < argCount; i++) { 55 | string arg{argValues[i]}; 56 | if (arg == "--help" || arg == "-h") { 57 | printHelp(argValues[0]); 58 | return 0; 59 | } else if (arg == "-protocol") { 60 | desiredCanSocket = atoi(argValues[i + 1]); 61 | i += 1; 62 | continue; 63 | } else if (arg == "-iface") { 64 | canInterface = (argValues[i + 1]); 65 | i += 1; 66 | continue; 67 | } 68 | } 69 | } 70 | 71 | if (desiredCanSocket <= 0) 72 | desiredCanSocket = CanDriver::CAN_SOCK_RAW; 73 | if (canInterface == "") 74 | canInterface = "can0"; 75 | 76 | CanDriver* canDriver{nullptr}; 77 | try { 78 | canDriver = new CanDriver(canInterface, desiredCanSocket); 79 | } catch (CanInitException& ex) { 80 | cerr << "An error occurred while initialising CanDriver: " << ex.what() << endl; 81 | if (canDriver) { delete canDriver; } 82 | return -1; 83 | } 84 | 85 | while (true) { 86 | printf("Writing test message:\n"); 87 | try { canDriver->sendMessage(CanMessage(0x555, "abcdefg8")); } 88 | catch (CanException& ex) { cerr << "Failed to send test message! " << ex.what() << endl; } 89 | catch (InvalidSocketException& ex) { cerr << "Failed to send test message! " << ex.what() << endl; } 90 | 91 | printf("Reading messages\n"); 92 | if (!canDriver->waitForMessages(3000ns)) continue; 93 | 94 | cout << "Reading queue..." << endl; 95 | auto canMessages = canDriver->readQueuedMessages(); 96 | while (!canMessages.empty()) { 97 | auto msg = canMessages.front(); 98 | canMessages.pop(); 99 | 100 | cout << "CAN ID: " << (int32_t)msg.getCanId() << endl 101 | << "CAN data: "; 102 | for (auto byte : msg.getFrameData()) 103 | cout << std::hex << byte << " "; 104 | cout << endl; 105 | } 106 | } 107 | } 108 | 109 | void printHelp(string appname) { 110 | cout << appname << endl << endl 111 | << "-h\t\tPrints this menu" << endl 112 | << "--help\t\tPrints this menu" << endl 113 | << "-protocol " << endl 114 | << "-iface " << endl; 115 | } 116 | -------------------------------------------------------------------------------- /test/unit/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /test/unit/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(libsockcanpp_unittests LANGUAGES CXX VERSION 0.1) 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | find_package(GTest REQUIRED) 8 | 9 | include_directories( 10 | # none as of now 11 | ) 12 | 13 | if (NOT TARGET sockcanpp) 14 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) 15 | endif() 16 | 17 | file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) 18 | 19 | add_compile_options( 20 | -Wall 21 | -Wextra 22 | -Wpedantic 23 | -Werror 24 | -Wno-unknown-pragmas 25 | ) 26 | 27 | add_executable(${PROJECT_NAME} ${SOURCES}) 28 | 29 | target_link_libraries( 30 | ${PROJECT_NAME} 31 | 32 | sockcanpp_test 33 | ${GTEST_LIBRARIES} 34 | pthread 35 | ) 36 | 37 | gtest_discover_tests(${PROJECT_NAME}) -------------------------------------------------------------------------------- /test/unit/src/CanId_Tests.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CanId_Tests.cpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains all the unit tests for the CanId structure. 5 | * @version 0.1 6 | * @date 2024-05-29 7 | * 8 | * @copyright Copyright (c) 2024 Simon Cahill and Contributors. 9 | */ 10 | 11 | #include 12 | 13 | #include 14 | 15 | using sockcanpp::CanId; 16 | 17 | using std::string; 18 | 19 | TEST(CanIdTests, CanId_invalidId_ExpectFalse) { 20 | ASSERT_FALSE(CanId::isValidIdentifier(-1)); 21 | } 22 | 23 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId) { 24 | ASSERT_TRUE(CanId::isValidIdentifier(0x123)); 25 | } 26 | 27 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId) { 28 | ASSERT_TRUE(CanId::isValidIdentifier(0x123456)); 29 | } 30 | 31 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCast) { 32 | CanId id(0x123); 33 | ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); 34 | } 35 | 36 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCast) { 37 | CanId id(0x123456); 38 | ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); 39 | } 40 | 41 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCast) { 42 | CanId id(0x123); 43 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 44 | } 45 | 46 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCast) { 47 | CanId id(0x123456); 48 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 49 | } 50 | 51 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToInt16) { 52 | CanId id(0x123); 53 | ASSERT_TRUE(CanId::isValidIdentifier((int16_t)id)); 54 | } 55 | 56 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToUint16) { 57 | CanId id(0x123); 58 | ASSERT_TRUE(CanId::isValidIdentifier((uint16_t)id)); 59 | } 60 | 61 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToInt32) { 62 | CanId id(0x123); 63 | ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); 64 | } 65 | 66 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToInt16) { 67 | CanId id(0x123); 68 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 69 | } 70 | 71 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToUint16) { 72 | CanId id(0x123); 73 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 74 | } 75 | 76 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToInt32) { 77 | CanId id(0x123); 78 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 79 | } 80 | 81 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToInt16) { 82 | CanId id(0x123456); 83 | ASSERT_TRUE(CanId::isValidIdentifier((int16_t)id)); 84 | } 85 | 86 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToUint16) { 87 | CanId id(0x123456); 88 | ASSERT_TRUE(CanId::isValidIdentifier((uint16_t)id)); 89 | } 90 | 91 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToInt32) { 92 | CanId id(0x123456); 93 | ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); 94 | } 95 | 96 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToInt16) { 97 | CanId id(0x123456); 98 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 99 | } 100 | 101 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToUint16) { 102 | CanId id(0x123456); 103 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 104 | } 105 | 106 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToInt32) { 107 | CanId id(0x123456); 108 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 109 | } 110 | 111 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToUint32) { 112 | CanId id(0x123); 113 | ASSERT_TRUE(CanId::isValidIdentifier((uint32_t)id)); 114 | } 115 | 116 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToUint32) { 117 | CanId id(0x123456); 118 | ASSERT_TRUE(CanId::isValidIdentifier((uint32_t)id)); 119 | } 120 | 121 | TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToUint32) { 122 | CanId id(0x123); 123 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 124 | } 125 | 126 | TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToUint32) { 127 | CanId id(0x123456); 128 | ASSERT_TRUE(CanId::isValidIdentifier(id)); 129 | } 130 | 131 | TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue) { 132 | auto id = 0xe0000abc; 133 | ASSERT_TRUE(CanId::isErrorFrame(id)); 134 | } 135 | 136 | TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse) { 137 | auto id = 0x123; 138 | ASSERT_FALSE(CanId::isErrorFrame(id)); 139 | } 140 | 141 | TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue_ExplicitCast) { 142 | auto id = 0xe0000abc; 143 | ASSERT_TRUE(CanId::isErrorFrame((int32_t)id)); 144 | } 145 | 146 | TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse_ExplicitCast) { 147 | auto id = 0x123; 148 | ASSERT_FALSE(CanId::isErrorFrame((int32_t)id)); 149 | } 150 | 151 | TEST(CanIdTests, CanId_isExtendedFrame_ExpectTrue) { 152 | auto id = 0xe0000abc; 153 | ASSERT_TRUE(CanId::isExtendedFrame(id)); 154 | } 155 | 156 | TEST(CanIdTests, CanId_isExtendedFrame_ExpectFalse) { 157 | auto id = 0x123; 158 | ASSERT_FALSE(CanId::isExtendedFrame(id)); 159 | } 160 | 161 | TEST(CanIdTests, CanId_isRtr_ExpectTrue) { 162 | auto id = 0x40000000; 163 | ASSERT_TRUE(CanId::isRemoteTransmissionRequest(id)); 164 | } 165 | 166 | TEST(CanIdTests, CanId_isRtr_ExpectFalse) { 167 | auto id = 0x123; 168 | ASSERT_FALSE(CanId::isRemoteTransmissionRequest(id)); 169 | } 170 | 171 | // Test constexpr 172 | TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue_Constexpr) { 173 | constexpr auto id = 0xe0000abc; 174 | ASSERT_TRUE(CanId::isErrorFrame(id)); 175 | } 176 | 177 | TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse_Constexpr) { 178 | constexpr auto id = 0x123; 179 | ASSERT_FALSE(CanId::isErrorFrame(id)); 180 | } 181 | 182 | // Test implicit operators 183 | // Implicit operators strip out control bits 184 | 185 | TEST(CanIdTests, CanId_ImplicitOperatorInt32_ExpectTrue) { 186 | CanId id(0x123); 187 | ASSERT_EQ((int32_t)id, 0x123); 188 | } 189 | 190 | TEST(CanIdTests, CanId_ImplicitOperatorInt16_ExpectTrue) { 191 | CanId id(0x123); 192 | ASSERT_EQ((int16_t)id, 0x123); 193 | } 194 | 195 | TEST(CanIdTests, CanId_ImplicitOperatorUint16_ExpectTrue) { 196 | CanId id(0x123); 197 | ASSERT_EQ((uint16_t)id, 0x123); 198 | } 199 | 200 | TEST(CanIdTests, CanId_ImplicitOperatorUint32_ExpectTrue) { 201 | CanId id(0x123); 202 | ASSERT_EQ((uint32_t)id, 0x123); 203 | } 204 | 205 | // Test implicit operators with control bits set; these should be stripped. 206 | TEST(CanIdTests, CanId_ImplicitOperatorInt32_ExpectTrue_ControlBits) { 207 | CanId id(0x12345678); 208 | ASSERT_EQ((int32_t)id, 0x12345678); 209 | } 210 | 211 | TEST(CanIdTests, CanId_ImplicitOperatorInt16_ExpectTrue_ControlBits) { 212 | CanId id(0x12345678); 213 | ASSERT_EQ((int16_t)id, 0x5678); 214 | } 215 | 216 | TEST(CanIdTests, CanId_ImplicitOperatorUint16_ExpectTrue_ControlBits) { 217 | CanId id(0x12345678); 218 | ASSERT_EQ((uint16_t)id, 0x5678); 219 | } 220 | 221 | TEST(CanIdTests, CanId_ImplicitOperatorUint32_ExpectTrue_ControlBits) { 222 | CanId id(0x12345678); 223 | ASSERT_EQ((uint32_t)id, 0x12345678); 224 | } 225 | 226 | // Test arithmetic operators 227 | TEST(CanIdTests, CanId_ArithmeticOperatorPlus_ExpectTrue) { 228 | CanId id(0x123); 229 | ASSERT_EQ(id + 0x123, 0x246); 230 | } 231 | 232 | TEST(CanIdTests, CanId_ArithmeticOperatorMinus_ExpectTrue) { 233 | CanId id(0x123); 234 | ASSERT_EQ(id - 0x123, 0); 235 | } 236 | 237 | TEST(CanIdTests, CanId_ArithmeticOperatorPlusEquals_ExpectTrue) { 238 | CanId id(0x123); 239 | id += 0x123; 240 | ASSERT_EQ(id, 0x246); 241 | } 242 | 243 | TEST(CanIdTests, CanId_ArithmeticOperatorMinusEquals_ExpectTrue) { 244 | CanId id(0x123); 245 | id -= 0x123; 246 | ASSERT_EQ(id, 0); 247 | } 248 | 249 | TEST(CanIdTests, CanId_ArithmeticOperatorStar_ExpectTrue) { 250 | CanId id(0x123); 251 | ASSERT_EQ(id * 2, 0x246); 252 | } 253 | 254 | TEST(CanIdTests, CanId_ArithmeticOperatorSlash_ExpectTrue) { 255 | CanId id(0x246); 256 | ASSERT_EQ(id / 2, 0x123); 257 | } 258 | 259 | TEST(CanIdTests, CanId_ArithmeticOperatorStarEquals_ExpectTrue) { 260 | CanId id(0x123); 261 | id *= 2; 262 | ASSERT_EQ(id, 0x246); 263 | } 264 | 265 | TEST(CanIdTests, CanId_ArithmeticOperatorSlashEquals_ExpectTrue) { 266 | CanId id(0x246); 267 | id /= 2; 268 | ASSERT_EQ(id, 0x123); 269 | } 270 | 271 | TEST(CanIdTests, CanId_ArithmeticOperatorModulo_ExpectTrue) { 272 | CanId id(0x123); 273 | ASSERT_EQ(id % 2, 1); 274 | } 275 | 276 | TEST(CanIdTests, CanId_ArithmeticOperatorModuloEquals_ExpectTrue) { 277 | CanId id(0x123); 278 | id %= 2; 279 | ASSERT_EQ(id, 1); 280 | } 281 | 282 | #if __cpp_concepts >= 201907 283 | 284 | TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue) { 285 | string id{"0x123"}; 286 | 287 | EXPECT_NO_THROW(CanId canId(id)); 288 | EXPECT_NO_THROW(CanId canId{id}); 289 | EXPECT_NO_THROW(CanId canId = id; (void)canId;); 290 | EXPECT_NO_THROW(CanId canId{"0x123"}); 291 | 292 | CanId canId(id); 293 | ASSERT_EQ(canId, 0x123); 294 | } 295 | 296 | TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue_ExplicitCast) { 297 | string id{"0x123"}; 298 | 299 | EXPECT_NO_THROW(CanId canId(static_cast(id.c_str()))); 300 | EXPECT_NO_THROW(CanId canId = static_cast(id.c_str()); (void)canId;); 301 | EXPECT_NO_THROW(CanId canId = static_cast(id.c_str()); (void)canId;); 302 | 303 | CanId canId(static_cast(id.c_str())); 304 | ASSERT_EQ(canId, 0x123); 305 | } 306 | 307 | TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue_ImplicitCast) { 308 | string id{"0x123"}; 309 | 310 | EXPECT_NO_THROW(CanId canId = id; (void)canId;); 311 | EXPECT_NO_THROW(CanId canId = id; (void)canId;); 312 | 313 | CanId canId = id; 314 | ASSERT_EQ(canId, 0x123); 315 | } 316 | 317 | TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectError) { 318 | string id{"hello_world"}; 319 | 320 | EXPECT_THROW(CanId canId(id), std::invalid_argument); 321 | EXPECT_THROW(CanId canId = id; (void)canId;, std::invalid_argument); 322 | EXPECT_THROW(CanId canId = id; (void)canId;, std::invalid_argument); 323 | } 324 | 325 | #endif // __cpp_concepts >= 201907 -------------------------------------------------------------------------------- /test/unit/src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file main.cpp 3 | * @author Simon Cahill (contact@simonc.eu) 4 | * @brief Contains the main entry point for the unit tests. 5 | * @version 0.1 6 | * @date 2024-05-29 7 | * 8 | * @copyright Copyright (c) 2024 Simon Cahill and Contributors. 9 | */ 10 | 11 | #include 12 | 13 | int main(int argc, char **argv) { 14 | ::testing::InitGoogleTest(&argc, argv); 15 | return RUN_ALL_TESTS(); 16 | } -------------------------------------------------------------------------------- /toolchains/arm64-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_SYSTEM_PROCESSOR arm) 3 | 4 | ### 5 | # Set binary paths 6 | ### 7 | set(tools /usr/bin) 8 | set(cross aarch64-linux-gnu) 9 | 10 | set(CMAKE_C_COMPILER ${tools}/${cross}-gcc) 11 | set(CMAKE_CXX_COMPILER ${tools}/${cross}-g++) 12 | set(CMAKE_LD ${tools}/${cross}-ld) 13 | set(CMAKE_STRIP ${tools}/${cross}-strip) 14 | set(CMAKE_AR ${tools}/${cross}-ar) 15 | 16 | ### 17 | # Set source and lib paths 18 | ### 19 | -------------------------------------------------------------------------------- /toolchains/armhf-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_SYSTEM_PROCESSOR arm) 3 | 4 | ### 5 | # Set binary paths 6 | ### 7 | set(tools /usr/bin) 8 | set(cross arm-linux-gnueabihf) 9 | 10 | set(CMAKE_C_COMPILER ${tools}/${cross}-gcc) 11 | set(CMAKE_CXX_COMPILER ${tools}/${cross}-g++) 12 | set(CMAKE_LD ${tools}/${cross}-ld) 13 | set(CMAKE_STRIP ${tools}/${cross}-strip) 14 | set(CMAKE_AR ${tools}/${cross}-ar) 15 | 16 | ### 17 | # Set source and lib paths 18 | ### 19 | 20 | #include_directories(SYSTEM ) 21 | -------------------------------------------------------------------------------- /toolchains/i686-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_SYSTEM_PROCESSOR i686) 3 | 4 | ### 5 | # Set binary paths 6 | ### 7 | set(tools /usr/bin) 8 | set(cross i686-linux-gnu) 9 | 10 | set(CMAKE_C_COMPILER ${tools}/${cross}-gcc) 11 | set(CMAKE_CXX_COMPILER ${tools}/${cross}-g++) 12 | set(CMAKE_LD ${tools}/${cross}-ld) 13 | set(CMAKE_STRIP ${tools}/${cross}-strip) 14 | set(CMAKE_AR ${tools}/${cross}-ar) 15 | 16 | ### 17 | # Set source and lib paths 18 | ### 19 | 20 | #include_directories(SYSTEM ) 21 | -------------------------------------------------------------------------------- /toolchains/ppc-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_SYSTEM_PROCESSOR powerpc) 3 | 4 | ### 5 | # Set binary paths 6 | ### 7 | set(tools /usr/bin) 8 | set(cross powerpc-linux-gnu) 9 | 10 | set(CMAKE_C_COMPILER ${tools}/${cross}-gcc) 11 | set(CMAKE_CXX_COMPILER ${tools}/${cross}-g++) 12 | set(CMAKE_LD ${tools}/${cross}-ld) 13 | set(CMAKE_STRIP ${tools}/${cross}-strip) 14 | set(CMAKE_AR ${tools}/${cross}-ar) 15 | 16 | add_definitions( 17 | -DPOWERPC 18 | ) 19 | 20 | ### 21 | # Set source and lib paths 22 | ### 23 | 24 | #include_directories(SYSTEM ) 25 | -------------------------------------------------------------------------------- /toolchains/ppc64-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_SYSTEM_PROCESSOR powerpc64) 3 | 4 | ### 5 | # Set binary paths 6 | ### 7 | set(tools /usr/bin) 8 | set(cross arm-linux-gnueabihf) 9 | 10 | set(CMAKE_C_COMPILER ${tools}/${cross}-gcc) 11 | set(CMAKE_CXX_COMPILER ${tools}/${cross}-g++) 12 | set(CMAKE_LD ${tools}/${cross}-ld) 13 | set(CMAKE_STRIP ${tools}/${cross}-strip) 14 | set(CMAKE_AR ${tools}/${cross}-ar) 15 | 16 | ### 17 | # Set source and lib paths 18 | ### 19 | 20 | #include_directories(SYSTEM ) 21 | -------------------------------------------------------------------------------- /toolchains/x86_64-toolchain.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_SYSTEM_NAME Linux) 2 | set(CMAKE_SYSTEM_PROCESSOR x86_64) 3 | 4 | ### 5 | # Set binary paths 6 | ### 7 | set(tools /usr/bin) 8 | set(cross x86_64-linux-gnu) 9 | 10 | set(CMAKE_C_COMPILER ${tools}/${cross}-gcc) 11 | set(CMAKE_CXX_COMPILER ${tools}/${cross}-g++) 12 | set(CMAKE_LD ${tools}/${cross}-ld) 13 | set(CMAKE_STRIP ${tools}/${cross}-strip) 14 | set(CMAKE_AR ${tools}/${cross}-ar) 15 | 16 | ### 17 | # Set source and lib paths 18 | ### 19 | 20 | #include_directories(SYSTEM ) 21 | --------------------------------------------------------------------------------