From 9de44d7461143a5c2bf3fdb7b02f8b9943344959 Mon Sep 17 00:00:00 2001 From: GwnDaan Date: Wed, 8 Jan 2025 12:28:33 +0100 Subject: [PATCH] feat(hw): add VSCAN interface requires you to set the 'VSCAN_API_DIR' environment variable to the API SDK directory that contains the `.h`, `.lib` files. Furthermore, you need to put `vs_can_api.dll` in the same directory as the executable. --- hardware_integration/CMakeLists.txt | 63 +++++++ .../available_can_drivers.hpp | 4 + .../hardware_integration/vscan_plugin.hpp | 74 ++++++++ hardware_integration/src/ntcan_plugin.cpp | 1 + hardware_integration/src/vscan_plugin.cpp | 161 ++++++++++++++++++ sphinx/source/api/hardware/index.rst | 1 + 6 files changed, 304 insertions(+) create mode 100644 hardware_integration/include/isobus/hardware_integration/vscan_plugin.hpp create mode 100644 hardware_integration/src/vscan_plugin.cpp diff --git a/hardware_integration/CMakeLists.txt b/hardware_integration/CMakeLists.txt index c01ba527..5b8cfb23 100644 --- a/hardware_integration/CMakeLists.txt +++ b/hardware_integration/CMakeLists.txt @@ -104,6 +104,10 @@ if("NTCAN" IN_LIST CAN_DRIVER) list(APPEND HARDWARE_INTEGRATION_SRC "ntcan_plugin.cpp") list(APPEND HARDWARE_INTEGRATION_INCLUDE "ntcan_plugin.hpp") endif() +if("VSCAN" IN_LIST CAN_DRIVER) + list(APPEND HARDWARE_INTEGRATION_SRC "vscan_plugin.cpp") + list(APPEND HARDWARE_INTEGRATION_INCLUDE "vscan_plugin.hpp") +endif() # Prepend the source directory path to all the source files prepend(HARDWARE_INTEGRATION_SRC ${HARDWARE_INTEGRATION_SRC_DIR} @@ -348,6 +352,65 @@ if("NTCAN" IN_LIST CAN_DRIVER) ) endif() endif() +if("VSCAN" IN_LIST CAN_DRIVER) + if(MSVC) + # See https://gitlab.kitware.com/cmake/cmake/-/issues/15170 + set(CMAKE_SYSTEM_PROCESSOR ${MSVC_CXX_ARCHITECTURE_ID}) + endif() + if(DEFINED ENV{VSCAN_API_DIR}) + message( + AUTHOR_WARNING + "The VSCAN driver requires vs_can_api.dll to be in the same directory as the executable. You can find this file in the VSCAN API directory." + ) + target_include_directories(HardwareIntegration PUBLIC $ENV{VSCAN_API_DIR}) + if(WIN32) + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR + STREQUAL "x64") + message(STATUS "Detected Windows x64, linking to VSCAN API x64 library") + target_link_libraries(HardwareIntegration + PRIVATE $ENV{VSCAN_API_DIR}/Win64/vs_can_api.lib) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR + STREQUAL "X86") + message(STATUS "Detected Windows x86, linking to VSCAN API x86 library") + target_link_libraries(HardwareIntegration + PRIVATE $ENV{VSCAN_API_DIR}/Win32/vs_can_api.lib) + else() + message( + FATAL_ERROR + "VSCAN Selected but no supported processor arch was detected. Only x64 and x86 are supported." + ) + endif() + elseif(UNIX) + if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR + STREQUAL "x64") + message(STATUS "Detected Linux x64, linking to VSCAN API x64 library") + target_link_libraries(HardwareIntegration + PRIVATE $ENV{VSCAN_API_DIR}/Linux/libvs_can_api.a) + elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR + STREQUAL "X86") + message(STATUS "Detected Linux x86, linking to VSCAN API x86 library") + target_link_libraries( + HardwareIntegration PRIVATE $ENV{VSCAN_API_DIR}/Linux + x86-64/libvs_can_api.a) + else() + message( + FATAL_ERROR + "VSCAN Selected but no supported processor arch was detected. Only x64 and x86 are supported." + ) + endif() + else() + message( + FATAL_ERROR + "VSCAN Selected but no supported OS was detected. Only Windows and Linux are supported currently." + ) + endif() + else() + message( + FATAL_ERROR + "VSCAN Selected but no VSCAN API was found. Set the 'VSCAN_API_DIR' environment variable to the path of the VSCAN SDK." + ) + endif() +endif() # Mark the compiled CAN drivers available to other modules. In the form: # `ISOBUS__AVAILABLE` as a preprocessor definition. diff --git a/hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp b/hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp index 969d310d..7c27f46c 100644 --- a/hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp +++ b/hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp @@ -50,4 +50,8 @@ #include "isobus/hardware_integration/ntcan_plugin.hpp" #endif +#ifdef ISOBUS_VSCAN_AVAILABLE +#include "isobus/hardware_integration/vscan_plugin.hpp" +#endif + #endif // AVAILABLE_CAN_DRIVERS_HPP diff --git a/hardware_integration/include/isobus/hardware_integration/vscan_plugin.hpp b/hardware_integration/include/isobus/hardware_integration/vscan_plugin.hpp new file mode 100644 index 00000000..ef2b6bfd --- /dev/null +++ b/hardware_integration/include/isobus/hardware_integration/vscan_plugin.hpp @@ -0,0 +1,74 @@ +//================================================================================================ +/// @file vscan_plugin.hpp +/// +/// @brief An interface for using a VSCOM VSCAN driver. +/// @attention Use of the VSCAN driver is governed in part by their license, and requires you +/// to install their driver first, which in-turn requires you to agree to their terms and conditions. +/// @author Daan Steenbergen +/// +/// @copyright 2025 The Open-Agriculture Developers +//================================================================================================ +#ifndef VSCAN_PLUGIN_HPP +#define VSCAN_PLUGIN_HPP + +#include +#include "vs_can_api.h" + +#include "isobus/hardware_integration/can_hardware_plugin.hpp" +#include "isobus/isobus/can_hardware_abstraction.hpp" +#include "isobus/isobus/can_message_frame.hpp" + +namespace isobus +{ + /// @brief A CAN Driver for VSCOM VSCAN Devices + class VSCANPlugin : public CANHardwarePlugin + { + public: + /// @brief Constructor for the VSCOM VSCAN CAN driver + /// @param[in] channel The COM port or IP address of the VSCAN device to use. + /// @param[in] baudrate The baudrate to use for the CAN connection. + VSCANPlugin(std::string channel, void *baudrate = VSCAN_SPEED_250K); + + /// @brief The destructor for VSCANPlugin + virtual ~VSCANPlugin() = default; + + /// @brief Returns if the connection with the hardware is valid + /// @returns `true` if connected, `false` if not connected + bool get_is_valid() const override; + + /// @brief Closes the connection to the hardware + void close() override; + + /// @brief Connects to the hardware you specified in the constructor's channel argument + void open() override; + + /// @brief Returns a frame from the hardware (synchronous), or `false` if no frame can be read. + /// @param[in, out] canFrame The CAN frame that was read + /// @returns `true` if a CAN frame was read, otherwise `false` + bool read_frame(isobus::CANMessageFrame &canFrame) override; + + /// @brief Writes a frame to the bus (synchronous) + /// @param[in] canFrame The frame to write to the bus + /// @returns `true` if the frame was written, otherwise `false` + bool write_frame(const isobus::CANMessageFrame &canFrame) override; + + /// @brief Changes previously set configuration parameters. Only works if the device is not open. + /// @param[in] channel The COM port or IP address of the VSCAN device to use. + /// @param[in] baudrate The baudrate to use for the CAN connection. + /// @returns True if the configuration was changed, otherwise false (if the device is open false will be returned) + bool reconfigure(std::string channel, void *baudrate = VSCAN_SPEED_250K); + + private: + /// @brief Parses the error from the status code + /// @param[in] status The status code to parse + /// @returns The error message + std::string parse_error_from_status(VSCAN_STATUS status); + + std::string channel; ///< The COM port or IP address of the VSCAN device to use. + void *baudrate; ///< The baudrate to use for the CAN connection. + VSCAN_HANDLE handle; ///< The handle as defined in the NTCAN driver API + VSCAN_STATUS status = VSCAN_ERR_OK; ///< Stores the result of the call to begin CAN communication. Used for is_valid check later. + }; +} + +#endif // NTCAN_PLUGIN_HPP diff --git a/hardware_integration/src/ntcan_plugin.cpp b/hardware_integration/src/ntcan_plugin.cpp index f4db8042..8a0926a4 100644 --- a/hardware_integration/src/ntcan_plugin.cpp +++ b/hardware_integration/src/ntcan_plugin.cpp @@ -40,6 +40,7 @@ namespace isobus if (NTCAN_NO_HANDLE != handle) { isobus::CANStackLogger::error("[NTCAN]: Attempting to open a connection that is already open"); + return; } std::uint32_t mode = 0; std::int32_t txQueueSize = 8; diff --git a/hardware_integration/src/vscan_plugin.cpp b/hardware_integration/src/vscan_plugin.cpp new file mode 100644 index 00000000..114ac520 --- /dev/null +++ b/hardware_integration/src/vscan_plugin.cpp @@ -0,0 +1,161 @@ +//================================================================================================ +/// @file vscan_plugin.cpp +/// +/// @brief An interface for using a VSCOM VSCAN driver. +/// @attention Use of the VSCAN driver is governed in part by their license, and requires you +/// to install their driver first, which in-turn requires you to agree to their terms and conditions. +/// @author Daan Steenbergen +/// +/// @copyright 2025 The Open-Agriculture Developers +//================================================================================================ + +#include "isobus/hardware_integration/vscan_plugin.hpp" +#include "isobus/isobus/can_stack_logger.hpp" + +#include +#include + +namespace isobus +{ + VSCANPlugin::VSCANPlugin(std::string channel, void *baudrate) : + channel(channel), + baudrate(baudrate) + { + } + + bool VSCANPlugin::get_is_valid() const + { + return (VSCAN_ERR_OK == status) && (handle > 0); + } + + void VSCANPlugin::close() + { + VSCAN_Close(handle); + handle = 0; + } + + void VSCANPlugin::open() + { + if (get_is_valid()) + { + LOG_ERROR("[VSCAN]: Attempting to open a connection that is already open"); + return; + } + + VSCAN_API_VERSION version; + status = VSCAN_Ioctl(0, VSCAN_IOCTL_GET_API_VERSION, &version); + if (status != VSCAN_ERR_OK) + { + LOG_ERROR("[VSCAN] Failed to get API version: %s, trying to continue anyway", parse_error_from_status(status).c_str()); + } + + LOG_DEBUG("[VSCAN] API Version %d.%d.%d", version.Major, version.Minor, version.SubMinor); + + // We create a buffer to guarantee the content to be non-const + std::vector channelBuffer(channel.begin(), channel.end()); + channelBuffer.push_back('\0'); + handle = VSCAN_Open(channelBuffer.data(), VSCAN_MODE_NORMAL); + if (handle <= 0) + { + LOG_ERROR("[VSCAN]: Error trying to open the connection: %s", parse_error_from_status(handle).c_str()); + return; + } + + status = VSCAN_Ioctl(handle, VSCAN_IOCTL_SET_SPEED, baudrate); + if (VSCAN_ERR_OK != status) + { + LOG_ERROR("[VSCAN]: Error trying to set the baudrate: %s", parse_error_from_status(status).c_str()); + close(); + return; + } + + status = VSCAN_Ioctl(handle, VSCAN_IOCTL_SET_BLOCKING_READ, VSCAN_IOCTL_ON); + if (VSCAN_ERR_OK != status) + { + LOG_ERROR("[VSCAN]: Error trying to set blocking read mode: %s", parse_error_from_status(status).c_str()); + close(); + return; + } + } + + bool VSCANPlugin::read_frame(CANMessageFrame &canFrame) + { + VSCAN_MSG message; + DWORD rv; + bool retVal = false; + + status = VSCAN_Read(handle, &message, 1, &rv); + + if (VSCAN_ERR_OK == status && rv) + { + canFrame.dataLength = message.Size; + memcpy(canFrame.data, message.Data, message.Size); + canFrame.identifier = message.Id; + canFrame.isExtendedFrame = (message.Flags & VSCAN_FLAGS_EXTENDED); + retVal = true; + } + else + { + LOG_ERROR("[VSCAN]: Error trying to read a frame: %s, closing connection", parse_error_from_status(status).c_str()); + close(); + } + + return retVal; + } + + bool VSCANPlugin::write_frame(const CANMessageFrame &canFrame) + { + VSCAN_MSG message; + DWORD rv; + bool retVal = false; + + message.Id = canFrame.identifier; + message.Size = canFrame.dataLength; + memcpy(message.Data, canFrame.data, message.Size); + message.Flags = canFrame.isExtendedFrame ? VSCAN_FLAGS_EXTENDED : VSCAN_FLAGS_STANDARD; + + status = VSCAN_Write(handle, &message, 1, &rv); + + if (VSCAN_ERR_OK == status && rv) + { + status = VSCAN_Flush(handle); + if (VSCAN_ERR_OK == status) + { + retVal = true; + } + else + { + LOG_ERROR("[VSCAN]: Error trying to flush the write buffer: %s, closing connection", parse_error_from_status(status).c_str()); + close(); + } + } + else + { + LOG_ERROR("[VSCAN]: Error trying to write a frame: %s, closing connection", parse_error_from_status(status).c_str()); + close(); + } + + return retVal; + } + + bool VSCANPlugin::reconfigure(std::string channel, void *baudrate) + { + bool retVal = false; + + if (!get_is_valid()) + { + this->channel = channel; + this->baudrate = baudrate; + retVal = true; + } + return retVal; + } + + std::string VSCANPlugin::parse_error_from_status(VSCAN_STATUS status) + { + char errorBuffer[256] = { 0 }; + VSCAN_GetErrorString(status, errorBuffer, sizeof(errorBuffer)); + return std::string(errorBuffer); + } +} +// namespace isobus diff --git a/sphinx/source/api/hardware/index.rst b/sphinx/source/api/hardware/index.rst index c019f20d..6f942ce8 100644 --- a/sphinx/source/api/hardware/index.rst +++ b/sphinx/source/api/hardware/index.rst @@ -48,6 +48,7 @@ When compiling with CMake, a default CAN driver plug-in will be selected for you - :code:`-DCAN_DRIVER=TouCAN` for the Rusoku TouCAN (Windows) - :code:`-DCAN_DRIVER=SYS_TEC` for a SYS TEC sysWORXX USB CAN adapter (Windows) - :code:`-DCAN_DRIVER=NTCAN` for the NTCAN driver (Windows) +- :code:`-DCAN_DRIVER=VSCAN` for the VSCAN made by VSCOM (Windows/Linux) Or specify multiple using a semicolon separated list: :code:`-DCAN_DRIVER=";"`