pax_global_header00006660000000000000000000000064151632532540014520gustar00rootroot0000000000000052 comment=b8b37e3915caf5bce93f38c2c6cca71356dcfcab canvenient-1.01/000077500000000000000000000000001516325325400135735ustar00rootroot00000000000000canvenient-1.01/.clang-format000066400000000000000000000014771516325325400161570ustar00rootroot00000000000000# CANopenTerm Format Style Options --- AlignTrailingComments: true AllowShortBlocksOnASingleLine: Always BasedOnStyle: LLVM BraceWrapping: AfterCaseLabel: true AfterClass: false AfterControlStatement: false AfterEnum: true AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: true AfterExternBlock: false BeforeCatch: false BeforeElse: true IndentBraces: true SplitEmptyFunction: false SplitEmptyRecord: true SplitEmptyNamespace: false BeforeLambdaBody: false BeforeWhile: false BreakBeforeBraces: Allman ColumnLimit: 0 IndentCaseLabels: true IndentWidth: 4 MaxEmptyLinesToKeep: 1 PointerAlignment: Left SortIncludes: false SpaceAfterLogicalNot: true SpaceBeforeAssignmentOperators: true SpacesInParentheses: false UseCRLF: false UseTab: Never ... canvenient-1.01/.github/000077500000000000000000000000001516325325400151335ustar00rootroot00000000000000canvenient-1.01/.github/FUNDING.yml000066400000000000000000000000711516325325400167460ustar00rootroot00000000000000buy_me_a_coffee: mupf.dev github: mupfdev ko_fi: mupfdev canvenient-1.01/.github/workflows/000077500000000000000000000000001516325325400171705ustar00rootroot00000000000000canvenient-1.01/.github/workflows/continuous-integration.yml000066400000000000000000000021131516325325400244370ustar00rootroot00000000000000name: Continuous Integration on: push: branches: [ main ] pull_request: branches: [ main ] env: BUILD_TYPE: Release jobs: Linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Install dependencies run: | sudo apt-get update -y -qq sudo apt-get install ninja-build libsocketcan-dev libreadline-dev cppcheck -y -qq - name: Run cppcheck run: | mkdir build cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -B build cppcheck --project=build/compile_commands.json --enable=all --suppress=missingIncludeSystem --suppress=unusedFunction --inconclusive --force --std=c99 --error-exitcode=1 - name: Build CANvenient run: | cmake --build build Windows: needs: Linux runs-on: windows-latest steps: - uses: actions/checkout@v6 - uses: TheMrMilchmann/setup-msvc-dev@v4 with: arch: x64 - name: Build run: | cmake -G "Ninja" -B build -DCMAKE_BUILD_TYPE=Release cmake --build build canvenient-1.01/.gitignore000066400000000000000000000000301516325325400155540ustar00rootroot00000000000000.vs deps* out build tmp canvenient-1.01/CMakeLists.txt000066400000000000000000000025741516325325400163430ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(CANvenient C) set(PROJECT_VERSION "1.0.1") set(PROJECT_VERSION_MAJOR "1") include(${CMAKE_ROOT}/Modules/ExternalProject.cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake") add_library( ${PROJECT_NAME} SHARED ${CMAKE_CURRENT_SOURCE_DIR}/src/CANvenient.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_Ixxat.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_Kvaser.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_PEAK.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_SocketCAN.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_Softing.c ) set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/deps.cmake) target_link_libraries( ${PROJECT_NAME} ${PLATFORM_LIBS} ) if(UNIX) target_link_libraries( ${PROJECT_NAME} socketcan ) set_target_properties( ${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) endif() if (CMAKE_C_COMPILER_ID STREQUAL "MSVC") target_compile_options( ${PROJECT_NAME} PUBLIC /W4 /utf-8 /wd4201 # nonstandard extension used: nameless struct/union ) else() target_compile_options( ${PROJECT_NAME} PUBLIC -Wall -Wextra -Wpedantic ) endif() include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/include ) canvenient-1.01/CMakeSettings.json000066400000000000000000000031701516325325400171700ustar00rootroot00000000000000{ "configurations": [ { "name": "x64-Debug", "generator": "Ninja", "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ], "variables": [ { "name": "BUILD_TESTS", "value": "True", "type": "BOOL" } ] }, { "name": "x64-Release", "generator": "Ninja", "configurationType": "Release", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ] }, { "name": "x86-Debug", "generator": "Ninja", "configurationType": "Debug", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x86" ], "variables": [] }, { "name": "x86-Release", "generator": "Ninja", "configurationType": "RelWithDebInfo", "buildRoot": "${projectDir}\\out\\build\\${name}", "installRoot": "${projectDir}\\out\\install\\${name}", "cmakeCommandArgs": "", "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x86" ], "variables": [] } ] }canvenient-1.01/LICENSE.md000066400000000000000000000020611516325325400151760ustar00rootroot00000000000000# MIT License Copyright 2026 Michael Fitzmayer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. canvenient-1.01/README.md000066400000000000000000000036031516325325400150540ustar00rootroot00000000000000# CANvenient [![Codacy Badge](https://app.codacy.com/project/badge/Grade/cf588c93f8e4453e985bd4eb8464e63a)](https://app.codacy.com/gh/CANopenTerm/CANvenient/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Continuous Integration](https://github.com/CANopenTerm/CANvenient/actions/workflows/continuous-integration.yml/badge.svg)](https://github.com/CANopenTerm/CANvenient/actions/workflows/continuous-integration.yml) CANvenient is an abstraction layer for multiple CAN APIs on Windows and Linux. It provides a unified interface for CAN communication, allowing developers to write code that is portable across different platforms and CAN hardware. ## API Reference The API reference is available at [https://canopenterm.de/canvenient](https://canopenterm.de/canvenient) ## Supported Back-Ends The following back-ends are currently implemented: - Ixxat VCI - Kvaser CANlib - PCAN-Basic - SocketCAN - Softing CAN Layer 2 ## Hardware Contributions Reliable behavior of CANvenient as a CAN abstraction layer depends on validation across a broad range of hardware implementations. Limiting testing to a small set of interfaces increases the risk of vendor-specific inconsistencies and unhandled edge cases. Donations of unused or surplus CAN adapters - such as PEAK PCAN-USB, Ixxat USB-to-CAN, Kvaser Leaf, or comparable devices - enable: - Validation of CANvenient against real hardware - Detection of vendor-specific deviations and timing differences - Improved backend integration across multiple interfaces - Broader regression testing and increased platform coverage CANvenient is an open-source project with no external funding; expanding the set of supported interfaces strengthens portability and ensures consistent behavior across heterogeneous CAN environments. Contributors can initiate hardware donations by opening an issue or contacting the maintainers directly. canvenient-1.01/cmake/000077500000000000000000000000001516325325400146535ustar00rootroot00000000000000canvenient-1.01/cmake/.gitignore000066400000000000000000000000271516325325400166420ustar00rootroot00000000000000* !.gitignore !*.cmake canvenient-1.01/cmake/dep_ixxat.cmake000066400000000000000000000000741516325325400176430ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(Ixxat_devel C) canvenient-1.01/cmake/dep_kvaser.cmake000066400000000000000000000000751516325325400200020ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(Kvaser_devel C) canvenient-1.01/cmake/dep_pcan.cmake000066400000000000000000000000731516325325400174260ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(PCAN_devel C) canvenient-1.01/cmake/dep_softing.cmake000066400000000000000000000000761516325325400201610ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) project(Softing_devel C) canvenient-1.01/cmake/deps.cmake000066400000000000000000000136631516325325400166210ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.16) if(WIN32) # HMS Ixxat VCI C API set(IXXAT_VERSION "4.0.1348.0") set(IXXAT_PLATFORM "x64") set(IXXAT_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/HMS_Ixxat_VCI") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(IXXAT_PLATFORM "x32") endif() set(IXXAT_DEVEL_PKG HMS_Ixxat_VCI-${IXXAT_VERSION}.zip) set(IXXAT_DEVEL_URL https://canopenterm.de/mirror) set(IXXAT_DEVEL_HASH 7a6df069d6acd667631282026252d2b33b7e7507) ExternalProject_Add(Ixxat_devel URL ${IXXAT_DEVEL_URL}/${IXXAT_DEVEL_PKG} URL_HASH SHA1=${IXXAT_DEVEL_HASH} DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps DOWNLOAD_NO_PROGRESS true DOWNLOAD_EXTRACT_TIMESTAMP true TLS_VERIFY true SOURCE_DIR ${IXXAT_PATH}/ BUILD_BYPRODUCTS ${IXXAT_PATH}/sdk/vci/lib/${IXXAT_PLATFORM}/release/vciapi.lib BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping build step." INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${IXXAT_PATH}/sdk/vci/bin/${IXXAT_PLATFORM}/release/vciapi.dll ${CMAKE_CURRENT_BINARY_DIR}/ PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dep_ixxat.cmake" ${IXXAT_PATH}/CMakeLists.txt ) set(IXXAT_INCLUDE_DIR ${IXXAT_PATH}/sdk/vci/inc) set(IXXAT_LIBRARY ${IXXAT_PATH}/sdk/vci/lib/${IXXAT_PLATFORM}/release/vciapi.lib) # Kvaser CANlib API set(KVASER_VERSION "5.51.461") set(KVASER_PLATFORM "x64") set(KVASER_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/Kvaser") set(KVASER_BINDIR "bin_x64") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(KVASER_PLATFORM "MS") set(KVASER_BINDIR "Bin") endif() set(KVASER_DEVEL_PKG Kvaser_Canlib-${KVASER_VERSION}.zip) set(KVASER_DEVEL_URL https://canopenterm.de/mirror) set(KVASER_DEVEL_HASH c73a6ea7cf981b94d0685e561a801dbc840febcc) ExternalProject_Add(Kvaser_devel URL ${KVASER_DEVEL_URL}/${KVASER_DEVEL_PKG} URL_HASH SHA1=${KVASER_DEVEL_HASH} DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps DOWNLOAD_NO_PROGRESS true DOWNLOAD_EXTRACT_TIMESTAMP true TLS_VERIFY true SOURCE_DIR ${KVASER_PATH}/ BUILD_BYPRODUCTS ${KVASER_PATH}/Canlib/Lib/${KVASER_PLATFORM}/canlib32.lib BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping build step." INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${KVASER_PATH}/Canlib/${KVASER_BINDIR}/canlib32.dll ${CMAKE_CURRENT_BINARY_DIR}/ PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dep_kvaser.cmake" ${KVASER_PATH}/CMakeLists.txt ) set(KVASER_INCLUDE_DIR ${KVASER_PATH}/Canlib/INC) set(KVASER_LIBRARY ${KVASER_PATH}/Canlib/Lib/${KVASER_PLATFORM}/canlib32.lib) # PCAN-Basic API set(PCAN_VERSION "5.0.0.1115") set(PCAN_PLATFORM "x64") set(PCAN_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/PCAN-Basic_API_Windows") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(PCAN_PLATFORM "x86") endif() set(PCAN_DEVEL_PKG PCAN-Basic_Windows-${PCAN_VERSION}.zip) set(PCAN_DEVEL_URL https://canopenterm.de/mirror) set(PCAN_DEVEL_HASH aca36ff1a766839ffc698f6d1a1d0870f01e395e) ExternalProject_Add(PCAN_devel URL ${PCAN_DEVEL_URL}/${PCAN_DEVEL_PKG} URL_HASH SHA1=${PCAN_DEVEL_HASH} DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps DOWNLOAD_NO_PROGRESS true DOWNLOAD_EXTRACT_TIMESTAMP true TLS_VERIFY true SOURCE_DIR ${PCAN_PATH}/ BUILD_BYPRODUCTS ${PCAN_PATH}/${PCAN_PLATFORM}/VC_LIB/PCANBasic.lib BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping build step." INSTALL_COMMAND ${CMAKE_COMMAND} -E copy ${PCAN_PATH}/${PCAN_PLATFORM}/PCANBasic.dll ${CMAKE_CURRENT_BINARY_DIR}/ PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dep_pcan.cmake" ${PCAN_PATH}/CMakeLists.txt ) set(PCAN_INCLUDE_DIR ${PCAN_PATH}/Include) set(PCAN_LIBRARY ${PCAN_PATH}/${PCAN_PLATFORM}/VC_LIB/PCANBasic.lib) # Softing CAN Layer 2 set(SOFTING_VERSION "5.23.1") set(SOFTING_PLATFORM "Win64") set(SOFTING_POSTFIX "_64") set(SOFTING_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/Softing") if(CMAKE_SIZEOF_VOID_P EQUAL 4) set(SOFTING_PLATFORM "Win32") set(SOFTING_POSTFIX "") endif() set(SOFTING_DEVEL_PKG Softing_CAN_Layer2-${SOFTING_VERSION}.zip) set(SOFTING_DEVEL_URL https://canopenterm.de/mirror) set(SOFTING_DEVEL_HASH a690c72c326140ae888ad65695fcdd330e86ce0f) ExternalProject_Add(Softing_devel URL ${SOFTING_DEVEL_URL}/${SOFTING_DEVEL_PKG} URL_HASH SHA1=${SOFTING_DEVEL_HASH} DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps DOWNLOAD_NO_PROGRESS true DOWNLOAD_EXTRACT_TIMESTAMP true TLS_VERIFY true SOURCE_DIR ${SOFTING_PATH}/ BUILD_BYPRODUCTS "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL/${SOFTING_PLATFORM}/canL2${SOFTING_POSTFIX}.lib" BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping build step." INSTALL_COMMAND ${CMAKE_COMMAND} -E copy "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL/${SOFTING_PLATFORM}/canL2${SOFTING_POSTFIX}.dll" "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL/${SOFTING_PLATFORM}/CANusbM.dll" "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL/Win64/canchd_64.dll" ${CMAKE_CURRENT_BINARY_DIR}/ PATCH_COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dep_softing.cmake" ${SOFTING_PATH}/CMakeLists.txt ) set(SOFTING_INCLUDE_DIR "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL") set(SOFTING_LIBRARY "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL/${SOFTING_PLATFORM}/canL2${SOFTING_POSTFIX}.lib") set(PLATFORM_DEPS Ixxat_devel Kvaser_devel PCAN_devel Softing_devel ) set(PLATFORM_LIBS ${IXXAT_LIBRARY} ${KVASER_LIBRARY} ${PCAN_LIBRARY} ${SOFTING_LIBRARY} ) include_directories( SYSTEM ${IXXAT_INCLUDE_DIR} SYSTEM ${KVASER_INCLUDE_DIR} SYSTEM ${KVASER_INCLUDE_DIR}/extras SYSTEM ${PCAN_INCLUDE_DIR} SYSTEM ${PCAN_INCLUDE_DIR}/../src/pcan/driver SYSTEM ${PCAN_INCLUDE_DIR}/../src/pcan/lib SYSTEM ${SOFTING_INCLUDE_DIR} ) add_dependencies( ${PROJECT_NAME} Ixxat_devel Kvaser_devel PCAN_devel Softing_devel ) endif() canvenient-1.01/include/000077500000000000000000000000001516325325400152165ustar00rootroot00000000000000canvenient-1.01/include/CANvenient.h000066400000000000000000000043311516325325400173620ustar00rootroot00000000000000/** @file CANvenient.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #ifndef CANVENIENT_H #define CANVENIENT_H #include #define CAN_MAX_INTERFACES 64 #ifdef _WIN32 #define CANVENIENT_API __declspec(dllexport) #include typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef uint32_t canid_t; #elif __linux__ #define CANVENIENT_API __attribute__((visibility("default"))) #include #include typedef __u8 u8; typedef __u16 u16; typedef __u32 u32; typedef __u64 u64; #endif /* * CAN interface baud rate enumeration. */ enum can_baudrate { CAN_BAUD_1M = 0, CAN_BAUD_800K, CAN_BAUD_500K, CAN_BAUD_250K, CAN_BAUD_125K, CAN_BAUD_100K, CAN_BAUD_95K, CAN_BAUD_83K, CAN_BAUD_50K, CAN_BAUD_47K, CAN_BAUD_33K, CAN_BAUD_20K, CAN_BAUD_10K, CAN_BAUD_5K }; #ifndef _CAN_H #define _CAN_H #define CAN_MAX_DLEN 8 #ifdef _WIN32 /* * CAN message frame. * * This is a drop-in replacement for the Linux can_frame struct defined in linux/can.h. */ #pragma pack(push, 1) struct can_frame { canid_t can_id; union { u8 len; u8 can_dlc; }; u8 __pad; u8 __res0; u8 len8_dlc; __declspec(align(8)) u8 data[CAN_MAX_DLEN]; }; #pragma pack(pop) #endif #endif /* _CAN_H */ CANVENIENT_API int can_find_interfaces(void); CANVENIENT_API void can_release_interfaces(void); CANVENIENT_API int can_open(int index, enum can_baudrate baud); CANVENIENT_API void can_close(int index); CANVENIENT_API void can_release(int index); CANVENIENT_API int can_update(int index); CANVENIENT_API int can_get_baudrate(int index, enum can_baudrate* baud); CANVENIENT_API void can_get_error(char* reason_buf, size_t buf_size); CANVENIENT_API int can_get_name(int index, char* name_buf, size_t buf_size); CANVENIENT_API int can_set_baudrate(int index, enum can_baudrate baud); CANVENIENT_API int can_send(int index, struct can_frame* frame); CANVENIENT_API int can_recv(int index, struct can_frame* frame, u64* timestamp); #endif /* CANVENIENT_H */ canvenient-1.01/src/000077500000000000000000000000001516325325400143625ustar00rootroot00000000000000canvenient-1.01/src/CANvenient.c000066400000000000000000000203541516325325400165240ustar00rootroot00000000000000/** @file CANvenient.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include "CANvenient.h" #include "drivers/CANvenient_internal.h" #include "drivers/CANvenient_Ixxat.h" #include "drivers/CANvenient_Kvaser.h" #include "drivers/CANvenient_SocketCAN.h" #include "drivers/CANvenient_Softing.h" #include "drivers/CANvenient_PEAK.h" struct can_iface can_interface[CAN_MAX_INTERFACES] = {0}; char can_error_reason[1024] = {0}; CANVENIENT_API int can_find_interfaces(void) { int status; status = ixxat_find_interfaces(); if (status < 0) { return status; } status = peak_find_interfaces(); if (status < 0) { return status; } status = kvaser_find_interfaces(); if (status < 0) { return status; } status = socketcan_find_interfaces(); if (status < 0) { return status; } return softing_find_interfaces(); } CANVENIENT_API void can_release_interfaces(void) { for (int i = 0; i < CAN_MAX_INTERFACES; i++) { can_release(i); } } CANVENIENT_API int can_open(int index, enum can_baudrate baud) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } can_interface[index].baudrate = baud; switch (can_interface[index].vendor) { case CAN_VENDOR_IXXAT: return ixxat_open(index); case CAN_VENDOR_KVASER: return kvaser_open(index); case CAN_VENDOR_PEAK: return peak_open(index); case CAN_VENDOR_SOCKETCAN: return socketcan_open(index); case CAN_VENDOR_SOFTING: return softing_open(index); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); return -1; } } CANVENIENT_API void can_close(int index) { if (index < 0 || index >= CAN_MAX_INTERFACES) { return; } switch (can_interface[index].vendor) { case CAN_VENDOR_IXXAT: { ixxat_close(index); break; } case CAN_VENDOR_KVASER: kvaser_close(index); break; case CAN_VENDOR_PEAK: peak_close(index); break; case CAN_VENDOR_SOCKETCAN: socketcan_close(index); break; case CAN_VENDOR_SOFTING: softing_close(index); break; default: case CAN_VENDOR_NONE: break; } } CANVENIENT_API void can_release(int index) { if (index < 0 || index >= CAN_MAX_INTERFACES) { return; } can_close(index); can_interface[index].vendor = CAN_VENDOR_NONE; can_interface[index].opened = 0; if (can_interface[index].name) { free(can_interface[index].name); can_interface[index].name = NULL; } if (can_interface[index].internal) { free(can_interface[index].internal); can_interface[index].internal = NULL; } } CANVENIENT_API int can_update(int index) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } switch (can_interface[index].vendor) { case CAN_VENDOR_IXXAT: return ixxat_update(index); case CAN_VENDOR_KVASER: return kvaser_update(index); case CAN_VENDOR_PEAK: return peak_update(index); case CAN_VENDOR_SOCKETCAN: return socketcan_update(index); break; case CAN_VENDOR_SOFTING: return softing_update(index); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); return -1; } } CANVENIENT_API int can_get_baudrate(int index, enum can_baudrate* baud) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } else if (!baud) { set_error_reason("Output parameter is NULL."); return -1; } else { *baud = can_interface[index].baudrate; return 0; } } CANVENIENT_API void can_get_error(char* reason_buf, size_t buf_size) { if (NULL == reason_buf || buf_size == 0) { return; } snprintf(reason_buf, buf_size, "%s", can_error_reason); } CANVENIENT_API int can_get_name(int index, char* name_buf, size_t buf_size) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } else if (NULL == can_interface[index].name) { set_error_reason("CAN interface name is not set."); return -1; } else if (NULL == name_buf) { set_error_reason("Output parameter is NULL."); return -1; } else { snprintf(name_buf, buf_size, "%s", can_interface[index].name); return 0; } } CANVENIENT_API int can_set_baudrate(int index, enum can_baudrate baud) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } else if (baud < CAN_BAUD_1M || baud > CAN_BAUD_5K) { set_error_reason("Invalid baudrate specified."); return -1; } else if (! can_interface[index].name) { set_error_reason("CAN interface name is not set."); return -1; } switch (can_interface[index].vendor) { case CAN_VENDOR_IXXAT: return ixxat_set_baudrate(index, baud); case CAN_VENDOR_KVASER: return kvaser_set_baudrate(index, baud); case CAN_VENDOR_PEAK: return peak_set_baudrate(index, baud); case CAN_VENDOR_SOCKETCAN: return socketcan_set_baudrate(index, baud); case CAN_VENDOR_SOFTING: return softing_set_baudrate(index, baud); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); return -1; } } CANVENIENT_API int can_send(int index, struct can_frame* frame) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } switch (can_interface[index].vendor) { case CAN_VENDOR_IXXAT: return ixxat_send(index, frame); case CAN_VENDOR_KVASER: return kvaser_send(index, frame); case CAN_VENDOR_PEAK: return peak_send(index, frame); case CAN_VENDOR_SOCKETCAN: return socketcan_send(index, frame); case CAN_VENDOR_SOFTING: return softing_send(index, frame); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); return -1; } } CANVENIENT_API int can_recv(int index, struct can_frame* frame, u64* timestamp) { if (index < 0 || index >= CAN_MAX_INTERFACES) { set_error_reason("Channel index is out-of-range."); return -1; } switch (can_interface[index].vendor) { case CAN_VENDOR_IXXAT: return ixxat_recv(index, frame, timestamp); case CAN_VENDOR_KVASER: return kvaser_recv(index, frame, timestamp); case CAN_VENDOR_PEAK: return peak_recv(index, frame, timestamp); case CAN_VENDOR_SOCKETCAN: return socketcan_recv(index, frame, timestamp); case CAN_VENDOR_SOFTING: return softing_recv(index, frame, timestamp); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); return -1; } } int find_free_interface_slot(u32* index) { for (u32 i = 0; i < CAN_MAX_INTERFACES; i++) { if (NULL == can_interface[i].name) { *index = i; return 0; } } *index = CAN_MAX_INTERFACES; /* No free slot found */ return -1; } void set_error_reason(const char* reason) { snprintf(can_error_reason, sizeof(can_error_reason), "%s", reason); } canvenient-1.01/src/drivers/000077500000000000000000000000001516325325400160405ustar00rootroot00000000000000canvenient-1.01/src/drivers/CANvenient_Ixxat.c000066400000000000000000000423441516325325400213620ustar00rootroot00000000000000/** @file CANvenient_Ixxat.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include #include "CANvenient.h" #include "CANvenient_internal.h" #ifdef _WIN32 #include #include #include typedef struct ixxat_ctx { VCIID device_id; u32 bus_no; ICanChannel* pChannel; IFifoReader* pReader; IFifoWriter* pWriter; } ixxat_ctx_t; static void ixxat_baudrate_to_btr(enum can_baudrate baud, u8* bt0, u8* bt1); #endif int ixxat_find_interfaces(void) { #ifdef _WIN32 IVciDeviceManager* pDevMan = NULL; IVciEnumDevice* pEnum = NULL; HRESULT hr; hr = VciGetDeviceManager(&pDevMan); if (SUCCEEDED(hr) && (pDevMan)) { hr = pDevMan->lpVtbl->EnumDevices(pDevMan, &pEnum); if (SUCCEEDED(hr) && (pEnum)) { VCIDEVICEINFO devInfo; u32 fetched = 0; while (S_OK == pEnum->lpVtbl->Next(pEnum, 1, &devInfo, &fetched) && fetched > 0) { IVciDevice* pDevice = NULL; IBalObject* pBal = NULL; hr = pDevMan->lpVtbl->OpenDevice(pDevMan, &devInfo.VciObjectId, &pDevice); if (SUCCEEDED(hr) && (pDevice)) { hr = pDevice->lpVtbl->OpenComponent(pDevice, &CLSID_VCIBAL, &IID_IBalObject, (PVOID*)&pBal); if (SUCCEEDED(hr) && (pBal)) { BALFEATURES features; hr = pBal->lpVtbl->GetFeatures(pBal, &features); if (SUCCEEDED(hr)) { for (u32 bus = 0; bus < features.BusSocketCount; bus++) { if (features.BusSocketType[bus]) { ixxat_ctx_t* ctx; char socket_name[256]; size_t name_len; u32 free_index; u32 k; int already_registered = 0; snprintf(socket_name, sizeof(socket_name), "%s CAN%u", devInfo.Description, bus); for (k = 0; k < CAN_MAX_INTERFACES; k++) { if (can_interface[k].name && strcmp(can_interface[k].name, socket_name) == 0) { already_registered = 1; break; } } if (already_registered) { continue; } if (0 != find_free_interface_slot(&free_index)) { /* No free slot available: skip. */ continue; } name_len = strnlen(socket_name, sizeof(socket_name)); can_interface[free_index].name = (char*)malloc(name_len + 1); if (NULL == can_interface[free_index].name) { break; } ctx = (ixxat_ctx_t*)malloc(sizeof(ixxat_ctx_t)); if (NULL == ctx) { free(can_interface[free_index].name); can_interface[free_index].name = NULL; break; } ctx->device_id = devInfo.VciObjectId; ctx->bus_no = bus; ctx->pChannel = NULL; ctx->pReader = NULL; ctx->pWriter = NULL; snprintf(can_interface[free_index].name, name_len + 1, "%s", socket_name); can_interface[free_index].vendor = CAN_VENDOR_IXXAT; can_interface[free_index].opened = 0; can_interface[free_index].baudrate = CAN_BAUD_1M; can_interface[free_index].internal = ctx; } } } pBal->lpVtbl->Release(pBal); } pDevice->lpVtbl->Release(pDevice); } } pEnum->lpVtbl->Release(pEnum); } pDevMan->lpVtbl->Release(pDevMan); } #endif return 0; } int ixxat_open(int index) { #ifdef _WIN32 ixxat_ctx_t* ctx; IVciDeviceManager* pDevMan = NULL; IVciDevice* pDevice = NULL; IBalObject* pBal = NULL; ICanControl* pControl = NULL; ICanSocket* pSocket = NULL; ICanChannel* pChannel = NULL; CANINITLINE initLine; HRESULT hr; u8 bt0; u8 bt1; ctx = (ixxat_ctx_t*)can_interface[index].internal; if (NULL == ctx) { set_error_reason("Internal error: context is NULL."); return -1; } hr = VciGetDeviceManager(&pDevMan); if (FAILED(hr) || NULL == pDevMan) { set_error_reason("Failed to get device manager."); return -1; } hr = pDevMan->lpVtbl->OpenDevice(pDevMan, &ctx->device_id, &pDevice); pDevMan->lpVtbl->Release(pDevMan); if (FAILED(hr) || NULL == pDevice) { set_error_reason("Failed to open device."); return -1; } hr = pDevice->lpVtbl->OpenComponent(pDevice, &CLSID_VCIBAL, &IID_IBalObject, (PVOID*)&pBal); pDevice->lpVtbl->Release(pDevice); if (FAILED(hr) || NULL == pBal) { return -1; } hr = pBal->lpVtbl->OpenSocket(pBal, ctx->bus_no, &IID_ICanControl, (PVOID*)&pControl); if (SUCCEEDED(hr) && NULL != pControl) { ixxat_baudrate_to_btr(can_interface[index].baudrate, &bt0, &bt1); initLine.bOpMode = CAN_OPMODE_STANDARD | CAN_OPMODE_EXTENDED | CAN_OPMODE_ERRFRAME; initLine.bReserved = 0; initLine.bBtReg0 = bt0; initLine.bBtReg1 = bt1; pControl->lpVtbl->InitLine(pControl, &initLine); pControl->lpVtbl->StartLine(pControl); pControl->lpVtbl->Release(pControl); } hr = pBal->lpVtbl->OpenSocket(pBal, ctx->bus_no, &IID_ICanSocket, (PVOID*)&pSocket); pBal->lpVtbl->Release(pBal); if (FAILED(hr) || NULL == pSocket) { set_error_reason("Failed to open CAN socket."); return -1; } hr = pSocket->lpVtbl->CreateChannel(pSocket, FALSE, &pChannel); pSocket->lpVtbl->Release(pSocket); if (FAILED(hr) || NULL == pChannel) { set_error_reason("Failed to create CAN channel."); return -1; } hr = pChannel->lpVtbl->Initialize(pChannel, 1024, 128); if (FAILED(hr)) { pChannel->lpVtbl->Release(pChannel); set_error_reason("Failed to initialize CAN channel."); return -1; } hr = pChannel->lpVtbl->Activate(pChannel); if (FAILED(hr)) { pChannel->lpVtbl->Release(pChannel); set_error_reason("Failed to activate CAN channel."); return -1; } pChannel->lpVtbl->GetReader(pChannel, &ctx->pReader); pChannel->lpVtbl->GetWriter(pChannel, &ctx->pWriter); ctx->pChannel = pChannel; can_interface[index].opened = 1; return 0; #else set_error_reason("Ixxat driver is only supported on Windows."); (void)index; return -1; #endif } void ixxat_close(int index) { #ifdef _WIN32 ixxat_ctx_t* ctx = (ixxat_ctx_t*)can_interface[index].internal; if (ctx) { if (ctx->pWriter) { ctx->pWriter->lpVtbl->Release(ctx->pWriter); ctx->pWriter = NULL; } if (ctx->pReader) { ctx->pReader->lpVtbl->Release(ctx->pReader); ctx->pReader = NULL; } if (ctx->pChannel) { ctx->pChannel->lpVtbl->Deactivate(ctx->pChannel); ctx->pChannel->lpVtbl->Release(ctx->pChannel); ctx->pChannel = NULL; } } #else set_error_reason("Ixxat driver is only supported on Windows."); (void)index; #endif } int ixxat_update(int index) { #ifdef _WIN32 ixxat_ctx_t* ctx; IVciDeviceManager* pDevMan = NULL; IVciDevice* pDevice = NULL; HRESULT hr; ctx = (ixxat_ctx_t*)can_interface[index].internal; if (NULL == ctx) { return -1; } hr = VciGetDeviceManager(&pDevMan); if (FAILED(hr) || NULL == pDevMan) { can_release(index); return -1; } hr = pDevMan->lpVtbl->OpenDevice(pDevMan, &ctx->device_id, &pDevice); pDevMan->lpVtbl->Release(pDevMan); if (FAILED(hr) || NULL == pDevice) { can_release(index); return -1; } pDevice->lpVtbl->Release(pDevice); return 0; #else set_error_reason("Ixxat driver is only supported on Windows."); (void)index; return -1; #endif } int ixxat_set_baudrate(int index, enum can_baudrate baud) { #ifdef _WIN32 ixxat_ctx_t* ctx = (ixxat_ctx_t*)can_interface[index].internal; IVciDeviceManager* pDevMan = NULL; IVciDevice* pDevice = NULL; IBalObject* pBal = NULL; ICanControl* pControl = NULL; ICanSocket* pSocket = NULL; ICanChannel* pChannel = NULL; CANINITLINE initLine; HRESULT hr; u8 bt0; u8 bt1; if (NULL == ctx) { set_error_reason("Internal error: context is NULL."); return -1; } /* Release existing channel resources. */ if (ctx->pWriter) { ctx->pWriter->lpVtbl->Release(ctx->pWriter); ctx->pWriter = NULL; } if (ctx->pReader) { ctx->pReader->lpVtbl->Release(ctx->pReader); ctx->pReader = NULL; } if (ctx->pChannel) { ctx->pChannel->lpVtbl->Deactivate(ctx->pChannel); ctx->pChannel->lpVtbl->Release(ctx->pChannel); ctx->pChannel = NULL; } /* Stop the CAN line before changing baudrate. */ hr = VciGetDeviceManager(&pDevMan); if (SUCCEEDED(hr) && NULL != pDevMan) { hr = pDevMan->lpVtbl->OpenDevice(pDevMan, &ctx->device_id, &pDevice); if (SUCCEEDED(hr) && NULL != pDevice) { hr = pDevice->lpVtbl->OpenComponent(pDevice, &CLSID_VCIBAL, &IID_IBalObject, (PVOID*)&pBal); if (SUCCEEDED(hr) && NULL != pBal) { hr = pBal->lpVtbl->OpenSocket(pBal, ctx->bus_no, &IID_ICanControl, (PVOID*)&pControl); if (SUCCEEDED(hr) && NULL != pControl) { pControl->lpVtbl->StopLine(pControl); pControl->lpVtbl->ResetLine(pControl); pControl->lpVtbl->Release(pControl); pControl = NULL; } pBal->lpVtbl->Release(pBal); pBal = NULL; } pDevice->lpVtbl->Release(pDevice); pDevice = NULL; } pDevMan->lpVtbl->Release(pDevMan); pDevMan = NULL; } hr = VciGetDeviceManager(&pDevMan); if (FAILED(hr) || NULL == pDevMan) { set_error_reason("Failed to get device manager."); return -1; } hr = pDevMan->lpVtbl->OpenDevice(pDevMan, &ctx->device_id, &pDevice); pDevMan->lpVtbl->Release(pDevMan); if (FAILED(hr) || NULL == pDevice) { set_error_reason("Failed to open device."); return -1; } hr = pDevice->lpVtbl->OpenComponent(pDevice, &CLSID_VCIBAL, &IID_IBalObject, (PVOID*)&pBal); pDevice->lpVtbl->Release(pDevice); if (FAILED(hr) || NULL == pBal) { set_error_reason("Failed to open BAL component."); return -1; } hr = pBal->lpVtbl->OpenSocket(pBal, ctx->bus_no, &IID_ICanControl, (PVOID*)&pControl); if (SUCCEEDED(hr) && NULL != pControl) { ixxat_baudrate_to_btr(baud, &bt0, &bt1); initLine.bOpMode = CAN_OPMODE_STANDARD | CAN_OPMODE_EXTENDED | CAN_OPMODE_ERRFRAME; initLine.bReserved = 0; initLine.bBtReg0 = bt0; initLine.bBtReg1 = bt1; pControl->lpVtbl->InitLine(pControl, &initLine); pControl->lpVtbl->StartLine(pControl); pControl->lpVtbl->Release(pControl); } hr = pBal->lpVtbl->OpenSocket(pBal, ctx->bus_no, &IID_ICanSocket, (PVOID*)&pSocket); pBal->lpVtbl->Release(pBal); if (FAILED(hr) || NULL == pSocket) { set_error_reason("Failed to open CAN socket."); return -1; } hr = pSocket->lpVtbl->CreateChannel(pSocket, FALSE, &pChannel); pSocket->lpVtbl->Release(pSocket); if (FAILED(hr) || NULL == pChannel) { set_error_reason("Failed to create CAN channel."); return -1; } hr = pChannel->lpVtbl->Initialize(pChannel, 1024, 128); if (FAILED(hr)) { pChannel->lpVtbl->Release(pChannel); set_error_reason("Failed to initialize CAN channel."); return -1; } hr = pChannel->lpVtbl->Activate(pChannel); if (FAILED(hr)) { pChannel->lpVtbl->Release(pChannel); set_error_reason("Failed to activate CAN channel."); return -1; } pChannel->lpVtbl->GetReader(pChannel, &ctx->pReader); pChannel->lpVtbl->GetWriter(pChannel, &ctx->pWriter); ctx->pChannel = pChannel; can_interface[index].baudrate = baud; return 0; #else set_error_reason("Ixxat driver is only supported on Windows."); (void)index; (void)baud; return -1; #endif } int ixxat_send(int index, struct can_frame* frame) { #ifdef _WIN32 ixxat_ctx_t* ctx = (ixxat_ctx_t*)can_interface[index].internal; CANMSG msg = {0}; HRESULT hr; int i; if (NULL == ctx || NULL == ctx->pWriter) { set_error_reason("Channel not open."); return -1; } msg.dwMsgId = frame->can_id; msg.uMsgInfo.Bytes.bType = CAN_MSGTYPE_DATA; msg.uMsgInfo.Bytes.bFlags = (frame->can_dlc & CAN_MSGFLAGS_DLC); for (i = 0; i < frame->can_dlc && i < 8; i += 1) { msg.abData[i] = frame->data[i]; } hr = ctx->pWriter->lpVtbl->PutDataEntry(ctx->pWriter, &msg); if (FAILED(hr)) { set_error_reason("Failed to send CAN message."); return -1; } return 0; #else set_error_reason("Ixxat driver is only supported on Windows."); (void)index; (void)frame; return -1; #endif } int ixxat_recv(int index, struct can_frame* frame, u64* timestamp) { #ifdef _WIN32 ixxat_ctx_t* ctx = (ixxat_ctx_t*)can_interface[index].internal; CANMSG msg = {0}; HRESULT hr; int i; if (NULL == ctx || NULL == ctx->pReader) { set_error_reason("Channel not open."); return -1; } hr = ctx->pReader->lpVtbl->GetDataEntry(ctx->pReader, &msg); if (S_OK != hr) { set_error_reason("No message available."); return -1; } if (CAN_MSGTYPE_DATA != msg.uMsgInfo.Bytes.bType) { set_error_reason("No message available."); return -1; } frame->can_id = msg.dwMsgId; frame->can_dlc = msg.uMsgInfo.Bytes.bFlags & CAN_MSGFLAGS_DLC; *timestamp = (u64)msg.dwTime; for (i = 0; i < frame->can_dlc && i < 8; i += 1) { frame->data[i] = msg.abData[i]; } return 0; #else set_error_reason("Ixxat driver is only supported on Windows."); (void)index; (void)frame; (void)timestamp; return -1; #endif } #ifdef _WIN32 static void ixxat_baudrate_to_btr(enum can_baudrate baud, u8* bt0, u8* bt1) { switch (baud) { case CAN_BAUD_800K: *bt0 = CAN_BT0_800KB; *bt1 = CAN_BT1_800KB; break; case CAN_BAUD_500K: *bt0 = CAN_BT0_500KB; *bt1 = CAN_BT1_500KB; break; case CAN_BAUD_250K: *bt0 = CAN_BT0_250KB; *bt1 = CAN_BT1_250KB; break; case CAN_BAUD_125K: *bt0 = CAN_BT0_125KB; *bt1 = CAN_BT1_125KB; break; case CAN_BAUD_100K: case CAN_BAUD_95K: *bt0 = CAN_BT0_100KB; *bt1 = CAN_BT1_100KB; break; case CAN_BAUD_83K: *bt0 = 0x0B; *bt1 = 0x14; break; case CAN_BAUD_50K: case CAN_BAUD_47K: *bt0 = CAN_BT0_50KB; *bt1 = CAN_BT1_50KB; break; case CAN_BAUD_33K: case CAN_BAUD_20K: *bt0 = CAN_BT0_20KB; *bt1 = CAN_BT1_20KB; break; case CAN_BAUD_10K: *bt0 = CAN_BT0_10KB; *bt1 = CAN_BT1_10KB; break; case CAN_BAUD_5K: *bt0 = CAN_BT0_5KB; *bt1 = CAN_BT1_5KB; break; case CAN_BAUD_1M: default: *bt0 = CAN_BT0_1000KB; *bt1 = CAN_BT1_1000KB; break; } } #endif canvenient-1.01/src/drivers/CANvenient_Ixxat.h000066400000000000000000000010511516325325400213550ustar00rootroot00000000000000/** @file CANvenient_Ixxat.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include "CANvenient.h" int ixxat_find_interfaces(void); int ixxat_open(int index); void ixxat_close(int index); int ixxat_update(int index); int ixxat_set_baudrate(int index, enum can_baudrate baud); int ixxat_send(int index, struct can_frame* frame); int ixxat_recv(int index, struct can_frame* frame, u64* timestamp); canvenient-1.01/src/drivers/CANvenient_Kvaser.c000066400000000000000000000174371516325325400215250ustar00rootroot00000000000000/** @file CANvenient_Kvaser.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include #include #include "CANvenient.h" #include "CANvenient_internal.h" #ifdef _WIN32 #include #include typedef struct { int channel; CanHandle handle; } kvaser_channel_info_t; static long baudrate_to_canlib(enum can_baudrate baud); static const char* get_canlib_error_text(canStatus status); #endif int kvaser_find_interfaces(void) { #ifdef _WIN32 int channel_count = 0; canStatus status; canInitializeLibrary(); status = canGetNumberOfChannels(&channel_count); if (status < 0) { /* It's fine to have no channels, just return 0. */ return 0; } if (channel_count > CAN_MAX_INTERFACES) { channel_count = CAN_MAX_INTERFACES; } for (int i = 0; i < channel_count; i++) { char device_name[256]; kvaser_channel_info_t* ch_info; u32 free_index; u32 k; int already_registered = 0; status = canGetChannelData(i, canCHANNELDATA_DEVDESCR_ASCII, device_name, sizeof(device_name)); if (status < 0) { set_error_reason(get_canlib_error_text(status)); return -1; } device_name[sizeof(device_name) - 1] = '\0'; for (k = 0; k < CAN_MAX_INTERFACES; k++) { if (can_interface[k].name && strcmp(can_interface[k].name, device_name) == 0) { already_registered = 1; break; } } if (already_registered) { continue; } if (0 != find_free_interface_slot(&free_index)) { /* No free slot available: stop. */ break; } ch_info = (kvaser_channel_info_t*)malloc(sizeof(kvaser_channel_info_t)); if (NULL == ch_info) { set_error_reason("Memory allocation failed."); return -1; } ch_info->channel = i; ch_info->handle = canINVALID_HANDLE; can_interface[free_index].name = (char*)malloc(strnlen(device_name, sizeof(device_name)) + 1); if (NULL == can_interface[free_index].name) { set_error_reason("Memory allocation failed."); free(ch_info); return -1; } strncpy_s(can_interface[free_index].name, strnlen(device_name, sizeof(device_name)) + 1, device_name, _TRUNCATE); can_interface[free_index].internal = ch_info; can_interface[free_index].vendor = CAN_VENDOR_KVASER; can_interface[free_index].opened = 0; can_interface[free_index].baudrate = CAN_BAUD_1M; } #endif return 0; } int kvaser_open(int index) { #ifdef _WIN32 kvaser_channel_info_t* ch_info = (kvaser_channel_info_t*)can_interface[index].internal; CanHandle hnd; canStatus status; hnd = canOpenChannel(ch_info->channel, 0); if (hnd < 0) { set_error_reason(get_canlib_error_text((canStatus)hnd)); return -1; } status = canSetBusParams(hnd, baudrate_to_canlib(can_interface[index].baudrate), 0, 0, 0, 0, 0); if (status < 0) { canClose(hnd); set_error_reason(get_canlib_error_text(status)); return -1; } status = canBusOn(hnd); if (status < 0) { canClose(hnd); set_error_reason(get_canlib_error_text(status)); return -1; } ch_info->handle = hnd; can_interface[index].opened = 1; return 0; #else set_error_reason("Kvaser driver is only supported on Windows."); (void)index; return -1; #endif } void kvaser_close(int index) { #ifdef _WIN32 kvaser_channel_info_t* ch_info = (kvaser_channel_info_t*)can_interface[index].internal; if (canINVALID_HANDLE == ch_info->handle) { return; } canBusOff(ch_info->handle); canClose(ch_info->handle); ch_info->handle = canINVALID_HANDLE; can_interface[index].opened = 0; #else set_error_reason("Kvaser driver is only supported on Windows."); (void)index; #endif } int kvaser_update(int index) { #ifdef _WIN32 kvaser_channel_info_t* ch_info; canStatus status; char device_name[256]; ch_info = (kvaser_channel_info_t*)can_interface[index].internal; if (NULL == ch_info) { return -1; } status = canGetChannelData(ch_info->channel, canCHANNELDATA_DEVDESCR_ASCII, device_name, sizeof(device_name)); if (status < 0) { can_release(index); return -1; } return 0; #else set_error_reason("Kvaser driver is only supported on Windows."); (void)index; return -1; #endif } int kvaser_set_baudrate(int index, enum can_baudrate baud) { #ifdef _WIN32 can_close(index); can_interface[index].baudrate = baud; return can_open(index, baud); #else set_error_reason("Kvaser driver is only supported on Windows."); (void)index; (void)baud; return -1; #endif } int kvaser_send(int index, struct can_frame* frame) { #ifdef _WIN32 kvaser_channel_info_t* ch_info = (kvaser_channel_info_t*)can_interface[index].internal; canStatus status; status = canWrite(ch_info->handle, (long)frame->can_id, frame->data, frame->can_dlc, canMSG_STD); if (status < 0) { set_error_reason(get_canlib_error_text(status)); return -1; } return 0; #else set_error_reason("Kvaser driver is only supported on Windows."); (void)index; (void)frame; return -1; #endif } int kvaser_recv(int index, struct can_frame* frame, u64* timestamp) { #ifdef _WIN32 kvaser_channel_info_t* ch_info = (kvaser_channel_info_t*)can_interface[index].internal; canStatus status; long id; unsigned int dlc; unsigned int flag; unsigned long time; status = canRead(ch_info->handle, &id, frame->data, &dlc, &flag, &time); if (status < 0) { if (canERR_NOMSG != status) { set_error_reason(get_canlib_error_text(status)); } return -1; } frame->can_id = (canid_t)id; frame->can_dlc = (u8)dlc; *timestamp = (u64)time * 1000ULL; /* ms to µs */ return 0; #else set_error_reason("Kvaser driver is only supported on Windows."); (void)index; (void)frame; (void)timestamp; return -1; #endif } #ifdef _WIN32 static long baudrate_to_canlib(enum can_baudrate baud) { switch (baud) { case CAN_BAUD_1M: return canBITRATE_1M; case CAN_BAUD_800K: return canBITRATE_1M; /* no 800K predefined, fall back to 1M */ case CAN_BAUD_500K: return canBITRATE_500K; case CAN_BAUD_250K: return canBITRATE_250K; case CAN_BAUD_125K: return canBITRATE_125K; case CAN_BAUD_100K: return canBITRATE_100K; case CAN_BAUD_95K: return canBITRATE_83K; /* closest predefined */ case CAN_BAUD_83K: return canBITRATE_83K; case CAN_BAUD_50K: return canBITRATE_50K; case CAN_BAUD_47K: return canBITRATE_50K; /* closest predefined */ case CAN_BAUD_33K: return canBITRATE_62K; /* closest predefined */ case CAN_BAUD_20K: return canBITRATE_10K; /* closest predefined */ case CAN_BAUD_10K: return canBITRATE_10K; case CAN_BAUD_5K: return canBITRATE_10K; /* closest predefined */ default: return canBITRATE_1M; } } static const char* get_canlib_error_text(canStatus status) { static char error_buf[256]; canGetErrorText(status, error_buf, (unsigned int)sizeof(error_buf)); return error_buf; } #endif /* _WIN32 */ canvenient-1.01/src/drivers/CANvenient_Kvaser.h000066400000000000000000000011201516325325400215100ustar00rootroot00000000000000/** @file CANvenient_Kvaser.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include "CANvenient.h" int kvaser_find_interfaces(void); int kvaser_open(int index); int kvaser_open_fd(int index); void kvaser_close(int index); int kvaser_update(int index); int kvaser_set_baudrate(int index, enum can_baudrate baud); int kvaser_send(int index, struct can_frame* frame); int kvaser_recv(int index, struct can_frame* frame, u64* timestamp); canvenient-1.01/src/drivers/CANvenient_PEAK.c000066400000000000000000000353011516325325400210000ustar00rootroot00000000000000/** @file CANvenient_PEAK.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include #ifdef _WIN32 #include #include static char* lookup_bus_name(DWORD device_type); static char* lookup_error_string(TPCANStatus pcan_status); static TPCANBaudrate lookup_pcan_baudrate(enum can_baudrate baud); #endif #include "CANvenient.h" #include "CANvenient_internal.h" int peak_find_interfaces(void) { #ifdef _WIN32 u32 ch_count = 0; TPCANChannelInformation* pcan_ch_info; TPCANStatus pcan_status; char bus_name[16] = {0}; /* PEAK-System. */ pcan_status = CAN_GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS_COUNT, &ch_count, sizeof(u32)); if (PCAN_ERROR_OK != pcan_status) { set_error_reason(lookup_error_string(pcan_status)); return -1; } if (ch_count > CAN_MAX_INTERFACES) { ch_count = CAN_MAX_INTERFACES; } pcan_ch_info = (TPCANChannelInformation*)malloc(sizeof(TPCANChannelInformation) * ch_count); if (NULL == pcan_ch_info) { set_error_reason("Memory allocation failed."); return -1; } pcan_status = CAN_GetValue(PCAN_NONEBUS, PCAN_ATTACHED_CHANNELS, pcan_ch_info, sizeof(TPCANChannelInformation) * ch_count); if (PCAN_ERROR_OK != pcan_status) { set_error_reason(lookup_error_string(pcan_status)); free(pcan_ch_info); pcan_ch_info = NULL; return -1; } for (u32 i = 0; i < ch_count; i++) { size_t name_len; u32 free_index; u32 k; int already_registered = 0; snprintf(bus_name, sizeof(bus_name), "%s", lookup_bus_name(pcan_ch_info[i].channel_handle)); for (k = 0; k < CAN_MAX_INTERFACES; k++) { if (can_interface[k].name && strcmp(can_interface[k].name, bus_name) == 0) { already_registered = 1; break; } } if (already_registered) { continue; } if (0 != find_free_interface_slot(&free_index)) { /* No free slot available: stop. */ break; } can_interface[free_index].internal = malloc(sizeof(TPCANChannelInformation)); if (! can_interface[free_index].internal) { set_error_reason("Memory allocation failed."); free(pcan_ch_info); return -1; } name_len = strnlen(bus_name, sizeof(bus_name)); can_interface[free_index].name = (char*)malloc(name_len + 1); if (NULL == can_interface[free_index].name) { set_error_reason("Memory allocation failed."); free(can_interface[free_index].internal); can_interface[free_index].internal = NULL; free(pcan_ch_info); return -1; } /* Set interface properties */ snprintf(can_interface[free_index].name, name_len + 1, "%s", bus_name); memcpy(can_interface[free_index].internal, &pcan_ch_info[i], sizeof(TPCANChannelInformation)); can_interface[free_index].vendor = CAN_VENDOR_PEAK; can_interface[free_index].opened = 0; can_interface[free_index].baudrate = CAN_BAUD_1M; } free(pcan_ch_info); #endif return 0; } int peak_open(int index) { #ifdef _WIN32 TPCANHandle pcan_ch = ((TPCANChannelInformation*)can_interface[index].internal)->channel_handle; TPCANStatus pcan_status; pcan_status = CAN_Initialize(pcan_ch, lookup_pcan_baudrate(can_interface[index].baudrate), 0, 0, 0); if (PCAN_ERROR_OK != pcan_status) { set_error_reason(lookup_error_string(pcan_status)); return -1; } else { can_interface[index].opened = 1; return 0; } #else set_error_reason("PEAK driver is only supported on Windows."); (void)index; return -1; #endif } void peak_close(int index) { #ifdef _WIN32 TPCANStatus pcan_status; pcan_status = CAN_Uninitialize(((TPCANChannelInformation*)can_interface[index].internal)->channel_handle); if (PCAN_ERROR_OK != pcan_status) { set_error_reason(lookup_error_string(pcan_status)); } #else set_error_reason("PEAK driver is only supported on Windows."); (void)index; #endif } int peak_update(int index) { #ifdef _WIN32 TPCANStatus pcan_status; if (NULL == can_interface[index].internal) { return -1; } pcan_status = CAN_GetStatus(((TPCANChannelInformation*)can_interface[index].internal)->channel_handle); if (PCAN_ERROR_ILLHW == pcan_status) { can_release(index); return -1; } else { return 0; } #else set_error_reason("PEAK driver is only supported on Windows."); (void)index; return -1; #endif } int peak_set_baudrate(int index, enum can_baudrate baud) { #ifdef _WIN32 can_close(index); can_interface[index].baudrate = baud; return can_open(index, baud); #else set_error_reason("PEAK driver is only supported on Windows."); (void)index; (void)baud; return -1; #endif } int peak_send(int index, struct can_frame* frame) { #ifdef _WIN32 TPCANHandle pcan_ch = ((TPCANChannelInformation*)can_interface[index].internal)->channel_handle; TPCANStatus pcan_status; TPCANMsg pcan_frame = {0}; pcan_frame.ID = frame->can_id; pcan_frame.LEN = frame->can_dlc; pcan_frame.MSGTYPE = PCAN_MESSAGE_STANDARD; for (int i = 0; i < 8; i += 1) { pcan_frame.DATA[i] = frame->data[i]; } pcan_status = CAN_Write(pcan_ch, &pcan_frame); if (PCAN_ERROR_OK != pcan_status) { set_error_reason(lookup_error_string(pcan_status)); return -1; } return 0; #else set_error_reason("PEAK driver is only supported on Windows."); (void)index; (void)frame; return -1; #endif } int peak_recv(int index, struct can_frame* frame, u64* timestamp) { #ifdef _WIN32 TPCANHandle pcan_ch = ((TPCANChannelInformation*)can_interface[index].internal)->channel_handle; TPCANStatus pcan_status; TPCANMsg pcan_frame = {0}; TPCANTimestamp pcan_timestamp = {0}; pcan_status = CAN_Read(pcan_ch, &pcan_frame, &pcan_timestamp); if (PCAN_ERROR_OK != pcan_status) { set_error_reason(lookup_error_string(pcan_status)); return -1; } frame->can_id = pcan_frame.ID; frame->can_dlc = pcan_frame.LEN; *timestamp = pcan_timestamp.micros + (1000ULL * pcan_timestamp.millis) + (0x100000000ULL * 1000ULL * pcan_timestamp.millis_overflow); for (int i = 0; i < 8; i += 1) { frame->data[i] = pcan_frame.DATA[i]; } return 0; #else set_error_reason("PEAK driver is only supported on Windows."); (void)index; (void)frame; (void)timestamp; return -1; #endif } #ifdef _WIN32 static TPCANBaudrate lookup_pcan_baudrate(enum can_baudrate baud) { switch (baud) { default: case CAN_BAUD_1M: return PCAN_BAUD_1M; case CAN_BAUD_800K: return PCAN_BAUD_800K; case CAN_BAUD_500K: return PCAN_BAUD_500K; case CAN_BAUD_250K: return PCAN_BAUD_250K; case CAN_BAUD_125K: return PCAN_BAUD_125K; case CAN_BAUD_100K: return PCAN_BAUD_100K; case CAN_BAUD_95K: return PCAN_BAUD_95K; case CAN_BAUD_83K: return PCAN_BAUD_83K; case CAN_BAUD_50K: return PCAN_BAUD_50K; case CAN_BAUD_47K: return PCAN_BAUD_47K; case CAN_BAUD_33K: return PCAN_BAUD_33K; case CAN_BAUD_20K: return PCAN_BAUD_20K; case CAN_BAUD_10K: return PCAN_BAUD_10K; case CAN_BAUD_5K: return PCAN_BAUD_5K; } } static char* lookup_bus_name(DWORD device_type) { switch (device_type) { default: case PCAN_NONEBUS: return "PCAN-NONEBUS"; case PCAN_PCIBUS1: return "PCAN-PCI 1"; case PCAN_PCIBUS2: return "PCAN-PCI 2"; case PCAN_PCIBUS3: return "PCAN-PCI 3"; case PCAN_PCIBUS4: return "PCAN-PCI 4"; case PCAN_PCIBUS5: return "PCAN-PCI 5"; case PCAN_PCIBUS6: return "PCAN-PCI 6"; case PCAN_PCIBUS7: return "PCAN-PCI 7"; case PCAN_PCIBUS8: return "PCAN-PCI 8"; case PCAN_PCIBUS9: return "PCAN-PCI 9"; case PCAN_PCIBUS10: return "PCAN-PCI 10"; case PCAN_PCIBUS11: return "PCAN-PCI 11"; case PCAN_PCIBUS12: return "PCAN-PCI 12"; case PCAN_PCIBUS13: return "PCAN-PCI 13"; case PCAN_PCIBUS14: return "PCAN-PCI 14"; case PCAN_PCIBUS15: return "PCAN-PCI 15"; case PCAN_PCIBUS16: return "PCAN-PCI 16"; case PCAN_USBBUS1: return "PCAN-USB 1"; case PCAN_USBBUS2: return "PCAN-USB 2"; case PCAN_USBBUS3: return "PCAN-USB 3"; case PCAN_USBBUS4: return "PCAN-USB 4"; case PCAN_USBBUS5: return "PCAN-USB 5"; case PCAN_USBBUS6: return "PCAN-USB 6"; case PCAN_USBBUS7: return "PCAN-USB 7"; case PCAN_USBBUS8: return "PCAN-USB 8"; case PCAN_USBBUS9: return "PCAN-USB 9"; case PCAN_USBBUS10: return "PCAN-USB 10"; case PCAN_USBBUS11: return "PCAN-USB 11"; case PCAN_USBBUS12: return "PCAN-USB 12"; case PCAN_USBBUS13: return "PCAN-USB 13"; case PCAN_USBBUS14: return "PCAN-USB 14"; case PCAN_USBBUS15: return "PCAN-USB 15"; case PCAN_USBBUS16: return "PCAN-USB 16"; case PCAN_LANBUS1: return "PCAN-LAN 1"; case PCAN_LANBUS2: return "PCAN-LAN 2"; case PCAN_LANBUS3: return "PCAN-LAN 3"; case PCAN_LANBUS4: return "PCAN-LAN 4"; case PCAN_LANBUS5: return "PCAN-LAN 5"; case PCAN_LANBUS6: return "PCAN-LAN 6"; case PCAN_LANBUS7: return "PCAN-LAN 7"; case PCAN_LANBUS8: return "PCAN-LAN 8"; case PCAN_LANBUS9: return "PCAN-LAN 9"; case PCAN_LANBUS10: return "PCAN-LAN 10"; case PCAN_LANBUS11: return "PCAN-LAN 11"; case PCAN_LANBUS12: return "PCAN-LAN 12"; case PCAN_LANBUS13: return "PCAN-LAN 13"; case PCAN_LANBUS14: return "PCAN-LAN 14"; case PCAN_LANBUS15: return "PCAN-LAN 15"; case PCAN_LANBUS16: return "PCAN-LAN 16"; } } static char* lookup_error_string(TPCANStatus pcan_status) { static char error_buf[1024]; size_t offset = 0; int found = 0; size_t i; /* Some PCAN status codes share bits (e.g. ILLHW, ILLNET, ILLCLIENT all overlap with HWINUSE / NETINUSE). Using a (value, mask) pair lets us match each code exactly without false positives. */ static const struct { TPCANStatus value; TPCANStatus mask; const char* msg; } error_table[] = { {PCAN_ERROR_XMTFULL, PCAN_ERROR_XMTFULL, "Transmit buffer in CAN controller is full."}, {PCAN_ERROR_OVERRUN, PCAN_ERROR_OVERRUN, "CAN controller was read too late."}, {PCAN_ERROR_BUSLIGHT, PCAN_ERROR_BUSLIGHT, "Bus error: an error counter reached the 'light' limit."}, {PCAN_ERROR_BUSHEAVY, PCAN_ERROR_BUSHEAVY, "Bus error: an error counter reached the 'heavy' limit."}, {PCAN_ERROR_BUSPASSIVE, PCAN_ERROR_BUSPASSIVE, "Bus error: the CAN controller is error passive."}, {PCAN_ERROR_BUSOFF, PCAN_ERROR_BUSOFF, "Bus error: the CAN controller is in bus-off state."}, {PCAN_ERROR_QRCVEMPTY, PCAN_ERROR_QRCVEMPTY, "Receive queue is empty."}, {PCAN_ERROR_QOVERRUN, PCAN_ERROR_QOVERRUN, "Receive queue was read too late."}, {PCAN_ERROR_QXMTFULL, PCAN_ERROR_QXMTFULL, "Transmit queue is full."}, {PCAN_ERROR_REGTEST, PCAN_ERROR_REGTEST, "Test of the CAN controller hardware registers failed (no hardware found)."}, {PCAN_ERROR_NODRIVER, PCAN_ERROR_NODRIVER, "Driver not loaded."}, /* HWINUSE (0x0400), NETINUSE (0x0800), ILLHW (0x1400), ILLNET (0x1800) and ILLCLIENT (0x1C00) all share bits within the 0x1C00 mask. Each entry is therefore matched against that common mask so that only the intended code is reported. */ {PCAN_ERROR_HWINUSE, PCAN_ERROR_ILLCLIENT, "PCAN-Hardware already in use by a PCAN-Net."}, {PCAN_ERROR_NETINUSE, PCAN_ERROR_ILLCLIENT, "A PCAN-Client is already connected to the PCAN-Net."}, {PCAN_ERROR_ILLHW, PCAN_ERROR_ILLCLIENT, "PCAN-Hardware handle is invalid."}, {PCAN_ERROR_ILLNET, PCAN_ERROR_ILLCLIENT, "PCAN-Net handle is invalid."}, {PCAN_ERROR_ILLCLIENT, PCAN_ERROR_ILLCLIENT, "PCAN-Client handle is invalid."}, {PCAN_ERROR_RESOURCE, PCAN_ERROR_RESOURCE, "Resource (FIFO, Client, timeout) cannot be created."}, {PCAN_ERROR_ILLPARAMTYPE, PCAN_ERROR_ILLPARAMTYPE, "Invalid parameter."}, {PCAN_ERROR_ILLPARAMVAL, PCAN_ERROR_ILLPARAMVAL, "Invalid parameter value."}, {PCAN_ERROR_UNKNOWN, PCAN_ERROR_UNKNOWN, "Unknown error."}, {PCAN_ERROR_ILLDATA, PCAN_ERROR_ILLDATA, "Invalid data, function, or action."}, {PCAN_ERROR_ILLMODE, PCAN_ERROR_ILLMODE, "Driver object state is wrong for the attempted operation."}, {PCAN_ERROR_CAUTION, PCAN_ERROR_CAUTION, "Operation succeeded but with irregularities."}, {PCAN_ERROR_INITIALIZE, PCAN_ERROR_INITIALIZE, "Channel is not initialized."}, {PCAN_ERROR_ILLOPERATION, PCAN_ERROR_ILLOPERATION, "Invalid operation."}, }; if (PCAN_ERROR_OK == pcan_status) { return "No error. Success."; } error_buf[0] = '\0'; for (i = 0; i < sizeof(error_table) / sizeof(error_table[0]); i++) { if ((pcan_status & error_table[i].mask) == error_table[i].value) { if (found) { offset += (size_t)snprintf(error_buf + offset, sizeof(error_buf) - offset, "; "); } offset += (size_t)snprintf(error_buf + offset, sizeof(error_buf) - offset, "%s", error_table[i].msg); found = 1; } } if (! found) { return "Unknown error."; } return error_buf; } #endif /* _WIN32 */ canvenient-1.01/src/drivers/CANvenient_PEAK.h000066400000000000000000000010761516325325400210070ustar00rootroot00000000000000/** @file CANvenient_PEAK.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include "CANvenient.h" int peak_find_interfaces(void); int peak_open(int index); int peak_open_fd(int index); void peak_close(int index); int peak_update(int index); int peak_set_baudrate(int index, enum can_baudrate baud); int peak_send(int index, struct can_frame* frame); int peak_recv(int index, struct can_frame* frame, u64* timestamp); canvenient-1.01/src/drivers/CANvenient_SocketCAN.c000066400000000000000000000225101516325325400220300ustar00rootroot00000000000000/** @file CANvenient_SocketCAN.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include #include #include void socketcan_close(int index); #ifdef __linux__ #include #include #include #include #include #include #include #include #include #endif #include "CANvenient.h" #include "CANvenient_internal.h" int socketcan_find_interfaces(void) { #ifdef __linux__ DIR* dir; struct dirent* entry; /* Open /sys/class/net to enumerate network interfaces */ dir = opendir("/sys/class/net"); if (NULL == dir) { return -1; } /* Scan through network interfaces */ while ((entry = readdir(dir))) { char path[512]; FILE* type_file; int if_type; size_t name_len; u32 free_index; /* Skip . and .. */ if (entry->d_name[0] == '.') { continue; } /* Check if this is a CAN interface by reading the type */ snprintf(path, sizeof(path), "/sys/class/net/%s/type", entry->d_name); type_file = fopen(path, "r"); if (NULL == type_file) { continue; } if (fscanf(type_file, "%d", &if_type) != 1) { fclose(type_file); continue; } fclose(type_file); /* ARPHRD_CAN = 280 (CAN interface) */ if (if_type != 280) { continue; } /* Skip if this interface is already registered */ { u32 k; int already_registered = 0; for (k = 0; k < CAN_MAX_INTERFACES; k++) { if (can_interface[k].name && strcmp(can_interface[k].name, entry->d_name) == 0) { already_registered = 1; break; } } if (already_registered) { continue; } } /* Find a free slot in the interface array */ if (0 != find_free_interface_slot(&free_index)) { /* No free slot available: skip. */ continue; } /* Allocate and copy interface name */ name_len = strnlen(entry->d_name, 256); can_interface[free_index].name = (char*)malloc(name_len + 1); if (NULL == can_interface[free_index].name) { closedir(dir); return -1; } /* Set interface properties */ snprintf(can_interface[free_index].name, name_len + 1, "%.*s", (int)name_len, entry->d_name); can_interface[free_index].vendor = CAN_VENDOR_SOCKETCAN; can_interface[free_index].opened = 0; can_interface[free_index].baudrate = CAN_BAUD_1M; { int* sock_fd = (int*)malloc(sizeof(int)); if (NULL == sock_fd) { closedir(dir); return -1; } *sock_fd = -1; can_interface[free_index].internal = sock_fd; /* CAN socket. */ } } closedir(dir); #endif return 0; } int socketcan_open(int index) { #ifdef __linux__ if (can_interface[index].name) { struct sockaddr_can addr; struct ifreq ifr; int buffer_size = 1024 * 1024; /* 1MB */ int enable_timestamp = 1; int* can_socket = can_interface[index].internal; if (can_interface[index].opened) { socketcan_close(index); } *can_socket = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (*can_socket < 0) { set_error_reason("socketcan_open: failed to create socket"); return -1; } setsockopt(*can_socket, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)); setsockopt(*can_socket, SOL_SOCKET, SO_TIMESTAMP, &enable_timestamp, sizeof(enable_timestamp)); strcpy(ifr.ifr_name, can_interface[index].name); if (ioctl(*can_socket, SIOCGIFINDEX, &ifr) < 0) { set_error_reason("socketcan_open: ioctl SIOCGIFINDEX failed"); return -1; } addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(*can_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) { set_error_reason("socketcan_open: bind failed"); return 1; } struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 1000; /* 1 ms receive timeout */ setsockopt(*can_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); can_interface[index].opened = 1; return 0; } else { set_error_reason("socketcan_open: invalid CAN interface"); return -1; } #else (void)index; return -1; #endif } void socketcan_close(int index) { #ifdef __linux__ if (can_interface[index].name && can_interface[index].opened) { int* can_socket = can_interface[index].internal; close(*can_socket); *can_socket = -1; can_interface[index].opened = 0; } #else (void)index; #endif } int socketcan_update(int index) { #ifdef __linux__ struct ifreq ifr; int tmp_sock; if (NULL == can_interface[index].name) { return -1; } tmp_sock = socket(AF_CAN, SOCK_RAW, CAN_RAW); if (tmp_sock < 0) { return -1; } strcpy(ifr.ifr_name, can_interface[index].name); if (ioctl(tmp_sock, SIOCGIFINDEX, &ifr) < 0) { close(tmp_sock); can_release(index); return -1; } close(tmp_sock); return 0; #else set_error_reason("SocketCAN driver is only supported on Linux."); (void)index; return -1; #endif } int socketcan_set_baudrate(int index, enum can_baudrate baud) { #ifdef __linux__ u32 bitrate = 1000000; switch (baud) { case CAN_BAUD_1M: bitrate = 1000000; break; case CAN_BAUD_800K: bitrate = 800000; break; case CAN_BAUD_500K: bitrate = 500000; break; case CAN_BAUD_250K: bitrate = 250000; break; case CAN_BAUD_125K: bitrate = 125000; break; case CAN_BAUD_100K: bitrate = 100000; break; case CAN_BAUD_95K: bitrate = 95000; break; case CAN_BAUD_83K: bitrate = 83000; break; case CAN_BAUD_50K: bitrate = 50000; break; case CAN_BAUD_47K: bitrate = 47000; break; case CAN_BAUD_33K: bitrate = 33000; break; case CAN_BAUD_20K: bitrate = 20000; break; case CAN_BAUD_10K: bitrate = 10000; break; case CAN_BAUD_5K: bitrate = 5000; break; } return can_set_bitrate(can_interface[index].name, bitrate); #else (void)index; (void)baud; return -1; #endif } int socketcan_send(int index, struct can_frame* frame) { #ifdef __linux__ int* can_socket = can_interface[index].internal; long num_bytes; struct timespec ts = {0}; nanosleep(&ts, NULL); if (can_interface[index].vendor != CAN_VENDOR_SOCKETCAN) { set_error_reason("socketcan_send: invalid CAN interface"); return -1; } num_bytes = write(*can_socket, frame, sizeof(struct can_frame)); ts.tv_sec = 0; ts.tv_nsec = 1000000; // 1 ms = 1,000,000 ns nanosleep(&ts, NULL); if (-1 == num_bytes) { set_error_reason("socketcan_send: write failed"); return -1; } return 0; #else (void)index; (void)frame; return -1; #endif } int socketcan_recv(int index, struct can_frame* frame, u64* timestamp) { #ifdef __linux__ struct msghdr msg; struct iovec iov; char ctrlmsg[CMSG_SPACE(sizeof(struct timeval))]; struct cmsghdr* cmsg; const struct timeval* tv; int nbytes; if (can_interface[index].vendor != CAN_VENDOR_SOCKETCAN) { set_error_reason("socketcan_recv: invalid CAN interface"); return -1; } iov.iov_base = frame; iov.iov_len = sizeof(struct can_frame); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = &ctrlmsg; msg.msg_controllen = sizeof(ctrlmsg); msg.msg_flags = 0; nbytes = recvmsg(*(int*)can_interface[index].internal, &msg, 0); if (nbytes < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { set_error_reason("socketcan_recv: timeout, no data received"); } else { set_error_reason("socketcan_recv: recvmsg failed"); } return -1; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SO_TIMESTAMP) { tv = (struct timeval*)CMSG_DATA(cmsg); *timestamp = tv->tv_sec * 1000000ULL + tv->tv_usec; break; } } return 0; #else (void)index; (void)frame; (void)timestamp; return -1; #endif } canvenient-1.01/src/drivers/CANvenient_SocketCAN.h000066400000000000000000000011111516325325400220270ustar00rootroot00000000000000/** @file CANvenient_SocketCAN.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include "CANvenient.h" int socketcan_find_interfaces(void); int socketcan_open(int index); void socketcan_close(int index); int socketcan_update(int index); int socketcan_set_baudrate(int index, enum can_baudrate baud); int socketcan_send(int index, struct can_frame* frame); int socketcan_recv(int index, struct can_frame* frame, u64* timestamp); canvenient-1.01/src/drivers/CANvenient_Softing.c000066400000000000000000000245471516325325400217030ustar00rootroot00000000000000/** @file CANvenient_Softing.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include #include #include "CANvenient.h" #include "CANvenient_internal.h" #ifdef _WIN32 #include #include #include #define CAN_EFF_FLAG 0x80000000U typedef struct softing_ctx { CAN_HANDLE hChannel; char channel_name[MAXLENCHNAME]; } softing_ctx_t; static double softing_baudrate_to_kbps(enum can_baudrate baud); #endif int softing_find_interfaces(void) { #ifdef _WIN32 unsigned long buf_size_needed = 0; unsigned long num_channels = 0; CHDSNAPSHOT* channels = NULL; unsigned long i; CANL2_get_all_CAN_channels(0, &buf_size_needed, &num_channels, NULL); if (0 == num_channels || 0 == buf_size_needed) { return 0; } channels = (CHDSNAPSHOT*)malloc(buf_size_needed); if (NULL == channels) { set_error_reason("Memory allocation failed."); return -1; } if (CANL2_OK != CANL2_get_all_CAN_channels(buf_size_needed, &buf_size_needed, &num_channels, channels)) { set_error_reason("Failed to enumerate Softing CAN channels."); free(channels); return -1; } for (i = 0; i < num_channels; i++) { softing_ctx_t* ctx; size_t name_len; u32 free_index; u32 k; int already_registered = 0; for (k = 0; k < CAN_MAX_INTERFACES; k++) { if (can_interface[k].name && strcmp(can_interface[k].name, channels[i].ChannelName) == 0) { already_registered = 1; break; } } if (already_registered) { continue; } if (0 != find_free_interface_slot(&free_index)) { break; } ctx = (softing_ctx_t*)malloc(sizeof(softing_ctx_t)); if (NULL == ctx) { set_error_reason("Memory allocation failed."); free(channels); return -1; } ctx->hChannel = 0; strncpy_s(ctx->channel_name, MAXLENCHNAME, channels[i].ChannelName, _TRUNCATE); ctx->channel_name[MAXLENCHNAME - 1] = '\0'; name_len = strnlen(channels[i].ChannelName, MAXLENCHNAME); can_interface[free_index].name = (char*)malloc(name_len + 1); if (NULL == can_interface[free_index].name) { set_error_reason("Memory allocation failed."); free(ctx); free(channels); return -1; } snprintf(can_interface[free_index].name, name_len + 1, "%.*s", (int)name_len, channels[i].ChannelName); can_interface[free_index].internal = ctx; can_interface[free_index].vendor = CAN_VENDOR_SOFTING; can_interface[free_index].opened = 0; can_interface[free_index].baudrate = CAN_BAUD_1M; } free(channels); #endif return 0; } int softing_open(int index) { #ifdef _WIN32 softing_ctx_t* ctx = (softing_ctx_t*)can_interface[index].internal; L2CONFIG cfg; int ret; if (NULL == ctx) { set_error_reason("Internal error: context is NULL."); return -1; } ret = INIL2_initialize_channel(&ctx->hChannel, ctx->channel_name); if (ret) { switch (ret) { case -1000: set_error_reason("Invalid channel handle."); break; case -1002: set_error_reason("Too many channels opened."); break; case -1003: set_error_reason("Version conflict: incompatible driver or DLL."); break; case -1004: set_error_reason("Firmware download error (may be a DPRAM access error)."); break; case -1005: set_error_reason("CAN USB DLL (canusbm.dll) not found or could not be loaded."); break; default: set_error_reason("Unknown error while initializing Softing CAN channel."); break; } return -1; } cfg.fBaudrate = softing_baudrate_to_kbps(can_interface[index].baudrate); cfg.s32Prescaler = GET_FROM_SCIM; cfg.s32Tseg1 = GET_FROM_SCIM; cfg.s32Tseg2 = GET_FROM_SCIM; cfg.s32Sjw = GET_FROM_SCIM; cfg.s32Sam = GET_FROM_SCIM; cfg.s32AccCodeStd = GET_FROM_SCIM; cfg.s32AccMaskStd = GET_FROM_SCIM; cfg.s32AccCodeXtd = GET_FROM_SCIM; cfg.s32AccMaskXtd = GET_FROM_SCIM; cfg.s32OutputCtrl = GET_FROM_SCIM; cfg.bEnableAck = GET_FROM_SCIM; cfg.bEnableErrorframe = GET_FROM_SCIM; cfg.hEvent = NULL; ret = CANL2_initialize_fifo_mode(ctx->hChannel, &cfg); if (CANL2_OK != ret) { char err_msg[128]; snprintf(err_msg, sizeof(err_msg), "Failed to configure Softing CAN channel (code: %d).", ret); set_error_reason(err_msg); INIL2_close_channel(ctx->hChannel); ctx->hChannel = 0; return -1; } ret = CANL2_start_chip(ctx->hChannel); if (CANL2_OK != ret) { set_error_reason("Failed to start Softing CAN chip."); INIL2_close_channel(ctx->hChannel); ctx->hChannel = 0; return -1; } can_interface[index].opened = 1; return 0; #else set_error_reason("Softing driver is only supported on Windows."); (void)index; return -1; #endif } void softing_close(int index) { #ifdef _WIN32 softing_ctx_t* ctx = (softing_ctx_t*)can_interface[index].internal; if (NULL == ctx) { return; } if (ctx->hChannel) { INIL2_close_channel(ctx->hChannel); ctx->hChannel = 0; } can_interface[index].opened = 0; #else set_error_reason("Softing driver is only supported on Windows."); (void)index; #endif } int softing_update(int index) { #ifdef _WIN32 const softing_ctx_t* ctx; unsigned long buf_size_needed = 0; unsigned long num_channels = 0; CHDSNAPSHOT* channels = NULL; unsigned long i; int found = 0; ctx = (const softing_ctx_t*)can_interface[index].internal; if (NULL == ctx) { return -1; } CANL2_get_all_CAN_channels(0, &buf_size_needed, &num_channels, NULL); if (0 == num_channels || 0 == buf_size_needed) { can_release(index); return -1; } channels = (CHDSNAPSHOT*)malloc(buf_size_needed); if (NULL == channels) { return -1; } if (CANL2_OK != CANL2_get_all_CAN_channels(buf_size_needed, &buf_size_needed, &num_channels, channels)) { free(channels); can_release(index); return -1; } for (i = 0; i < num_channels; i++) { if (0 == strncmp(ctx->channel_name, channels[i].ChannelName, MAXLENCHNAME)) { found = 1; break; } } free(channels); if (! found) { can_release(index); return -1; } return 0; #else set_error_reason("Softing driver is only supported on Windows."); (void)index; return -1; #endif } int softing_set_baudrate(int index, enum can_baudrate baud) { #ifdef _WIN32 softing_close(index); can_interface[index].baudrate = baud; return softing_open(index); #else set_error_reason("Softing driver is only supported on Windows."); (void)index; (void)baud; return -1; #endif } int softing_send(int index, struct can_frame* frame) { #ifdef _WIN32 softing_ctx_t* ctx = (softing_ctx_t*)can_interface[index].internal; unsigned long ident; int xtd; int ret; if (NULL == ctx || 0 == ctx->hChannel) { set_error_reason("Channel not open."); return -1; } if (frame->can_id & CAN_EFF_FLAG) { ident = frame->can_id & ~CAN_EFF_FLAG; xtd = 1; } else { ident = frame->can_id; xtd = 0; } ret = CANL2_send_data(ctx->hChannel, ident, xtd, (int)frame->can_dlc, frame->data); if (CANL2_OK != ret) { set_error_reason("Failed to send CAN message."); return -1; } return 0; #else set_error_reason("Softing driver is only supported on Windows."); (void)index; (void)frame; return -1; #endif } int softing_recv(int index, struct can_frame* frame, u64* timestamp) { #ifdef _WIN32 softing_ctx_t* ctx = (softing_ctx_t*)can_interface[index].internal; PARAM_STRUCT param; int ret; if (NULL == ctx || 0 == ctx->hChannel) { set_error_reason("Channel not open."); return -1; } memset(¶m, 0, sizeof(param)); ret = CANL2_read_ac(ctx->hChannel, ¶m); if (CANL2_RA_NO_DATA == ret) { set_error_reason("No message available."); return -1; } else if (ret < 0) { set_error_reason("Failed to receive CAN message."); return -1; } if (CANL2_RA_DATAFRAME == ret || CANL2_RA_XTD_DATAFRAME == ret) { frame->can_id = (CANL2_RA_XTD_DATAFRAME == ret) ? (param.Ident | CAN_EFF_FLAG) : param.Ident; frame->can_dlc = (u8)param.DataLength; *timestamp = (u64)param.Time; for (int i = 0; i < param.DataLength && i < 8; i++) { frame->data[i] = param.RCV_data[i]; } return 0; } set_error_reason("No message available."); return -1; #else set_error_reason("Softing driver is only supported on Windows."); (void)index; (void)frame; (void)timestamp; return -1; #endif } #ifdef _WIN32 static double softing_baudrate_to_kbps(enum can_baudrate baud) { switch (baud) { case CAN_BAUD_800K: return 800.0; case CAN_BAUD_500K: return 500.0; case CAN_BAUD_250K: return 250.0; case CAN_BAUD_125K: return 125.0; case CAN_BAUD_100K: return 100.0; case CAN_BAUD_95K: return 95.238; case CAN_BAUD_83K: return 83.333; case CAN_BAUD_50K: return 50.0; case CAN_BAUD_47K: return 47.619; case CAN_BAUD_33K: return 33.333; case CAN_BAUD_20K: return 20.0; case CAN_BAUD_10K: return 10.0; case CAN_BAUD_5K: return 5.0; case CAN_BAUD_1M: default: return 1000.0; } } #endif canvenient-1.01/src/drivers/CANvenient_Softing.h000066400000000000000000000011311516325325400216700ustar00rootroot00000000000000/** @file CANvenient_Softing.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include "CANvenient.h" int softing_find_interfaces(void); int softing_open(int index); int softing_open_fd(int index); void softing_close(int index); int softing_update(int index); int softing_set_baudrate(int index, enum can_baudrate baud); int softing_send(int index, struct can_frame* frame); int softing_recv(int index, struct can_frame* frame, u64* timestamp); canvenient-1.01/src/drivers/CANvenient_internal.h000066400000000000000000000016071516325325400221030ustar00rootroot00000000000000/** @file CANvenient_internal.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #ifndef CANVENIENT_INTERNAL_H #define CANVENIENT_INTERNAL_H /* * CAN interface vendor enumeration. */ enum can_vendor { CAN_VENDOR_NONE = 0, CAN_VENDOR_IXXAT, CAN_VENDOR_KVASER, CAN_VENDOR_PEAK, CAN_VENDOR_SOCKETCAN, CAN_VENDOR_SOFTING }; /* * CAN interface. */ struct can_iface { u8 opened; /* 0 = closed, 1 = opened */ char* name; enum can_baudrate baudrate; enum can_vendor vendor; void* internal; }; extern struct can_iface can_interface[CAN_MAX_INTERFACES]; extern char can_error_reason[1024]; int find_free_interface_slot(u32* index); void set_error_reason(const char* reason); #endif /* CANVENIENT_INTERNAL_H */ canvenient-1.01/src/drivers/CANvenient_template.c000066400000000000000000000041671516325325400221010ustar00rootroot00000000000000/** @file CANvenient_template.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #ifdef _WIN32 #endif #include "CANvenient.h" #include "CANvenient_internal.h" int template_find_interfaces(void) { #ifdef _WIN32 #endif return 0; } int template_open(int index) { #ifdef _WIN32 set_error_reason("template driver is not supported yet."); (void)index; return -1; #else set_error_reason("template driver is only supported on Windows."); (void)index; return -1; #endif } void template_close(int index) { #ifdef _WIN32 set_error_reason("template driver is not supported yet."); (void)index; #else set_error_reason("template driver is only supported on Windows."); (void)index; #endif } int template_update(int index) { #ifdef _WIN32 set_error_reason("template driver is not supported yet."); (void)index; return -1; #else set_error_reason("template driver is only supported on Windows."); (void)index; return -1; #endif } int template_set_baudrate(int index, enum can_baudrate baud) { #ifdef _WIN32 set_error_reason("template driver is not supported yet."); (void)index; (void)baud; return -1; #else set_error_reason("template driver is only supported on Windows."); (void)index; (void)baud; return -1; #endif } int template_send(int index, struct can_frame* frame) { #ifdef _WIN32 set_error_reason("template driver is not supported yet."); (void)index; (void)frame; return -1; #else set_error_reason("template driver is only supported on Windows."); (void)index; (void)frame; return -1; #endif } int template_recv(int index, struct can_frame* frame, u64* timestamp) { #ifdef _WIN32 set_error_reason("template driver is not supported yet."); (void)index; (void)frame; (void)timestamp; return -1; #else set_error_reason("template driver is only supported on Windows."); (void)index; (void)frame; (void)timestamp; return -1; #endif } canvenient-1.01/src/drivers/CANvenient_template.h000066400000000000000000000011421516325325400220740ustar00rootroot00000000000000/** @file CANvenient_template.h * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include "CANvenient.h" int template_find_interfaces(void); int template_open(int index); int template_open_fd(int index); void template_close(int index); int template_update(int index); int template_set_baudrate(int index, enum can_baudrate baud); int template_send(int index, struct can_frame* frame); int template_recv(int index, struct can_frame* frame, u64* timestamp); canvenient-1.01/src/tests/000077500000000000000000000000001516325325400155245ustar00rootroot00000000000000canvenient-1.01/src/tests/example.c000066400000000000000000000021171516325325400173240ustar00rootroot00000000000000/** @file example.c * * CANvenient is an abstraction layer for multiple CAN APIs on * Windows and Linux. * * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. * SPDX-License-Identifier: MIT * **/ #include #include #include int main() { if (0 == can_find_interfaces()) { for (int i = 0; i < CAN_MAX_INTERFACES; i++) { char name[256] = {0}; can_get_name(i, name, sizeof(name)); if (name[0] != '\0') { if (0 == can_open(i, CAN_BAUD_1M)) { printf("Opened CAN interface: %s\n", name); } else { char error_reason[256] = {0}; can_get_error(error_reason, sizeof(error_reason)); printf("Failed to open CAN interface %s: %s\n", name, error_reason); } } } } /* Implicitly calls can_close() for all opened interfaces. */ can_release_interfaces(); return EXIT_SUCCESS; }