Skip to content

Commit

Permalink
feat(hw): add VSCAN interface
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
GwnDaan committed Jan 8, 2025
1 parent 2b8a739 commit 9de44d7
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 0 deletions.
63 changes: 63 additions & 0 deletions hardware_integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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_<uppercase CAN_DRIVER>_AVAILABLE` as a preprocessor definition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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 <string>
#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
1 change: 1 addition & 0 deletions hardware_integration/src/ntcan_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
161 changes: 161 additions & 0 deletions hardware_integration/src/vscan_plugin.cpp
Original file line number Diff line number Diff line change
@@ -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 <chrono>
#include <thread>

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<char> 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
1 change: 1 addition & 0 deletions sphinx/source/api/hardware/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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="<driver1>;<driver2>"`

Expand Down

0 comments on commit 9de44d7

Please sign in to comment.