Skip to content

Commit 4bd33f5

Browse files
author
GwnDaan
committed
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.
1 parent 65aa380 commit 4bd33f5

File tree

6 files changed

+304
-0
lines changed

6 files changed

+304
-0
lines changed

hardware_integration/CMakeLists.txt

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ if("NTCAN" IN_LIST CAN_DRIVER)
104104
list(APPEND HARDWARE_INTEGRATION_SRC "ntcan_plugin.cpp")
105105
list(APPEND HARDWARE_INTEGRATION_INCLUDE "ntcan_plugin.hpp")
106106
endif()
107+
if("VSCAN" IN_LIST CAN_DRIVER)
108+
list(APPEND HARDWARE_INTEGRATION_SRC "vscan_plugin.cpp")
109+
list(APPEND HARDWARE_INTEGRATION_INCLUDE "vscan_plugin.hpp")
110+
endif()
107111

108112
# Prepend the source directory path to all the source files
109113
prepend(HARDWARE_INTEGRATION_SRC ${HARDWARE_INTEGRATION_SRC_DIR}
@@ -348,6 +352,65 @@ if("NTCAN" IN_LIST CAN_DRIVER)
348352
)
349353
endif()
350354
endif()
355+
if("VSCAN" IN_LIST CAN_DRIVER)
356+
if(MSVC)
357+
# See https://gitlab.kitware.com/cmake/cmake/-/issues/15170
358+
set(CMAKE_SYSTEM_PROCESSOR ${MSVC_CXX_ARCHITECTURE_ID})
359+
endif()
360+
if(DEFINED ENV{VSCAN_API_DIR})
361+
message(
362+
AUTHOR_WARNING
363+
"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."
364+
)
365+
target_include_directories(HardwareIntegration PUBLIC $ENV{VSCAN_API_DIR})
366+
if(WIN32)
367+
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR
368+
STREQUAL "x64")
369+
message(STATUS "Detected Windows x64, linking to VSCAN API x64 library")
370+
target_link_libraries(HardwareIntegration
371+
PRIVATE $ENV{VSCAN_API_DIR}/Win64/vs_can_api.lib)
372+
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR
373+
STREQUAL "X86")
374+
message(STATUS "Detected Windows x86, linking to VSCAN API x86 library")
375+
target_link_libraries(HardwareIntegration
376+
PRIVATE $ENV{VSCAN_API_DIR}/Win32/vs_can_api.lib)
377+
else()
378+
message(
379+
FATAL_ERROR
380+
"VSCAN Selected but no supported processor arch was detected. Only x64 and x86 are supported."
381+
)
382+
endif()
383+
elseif(UNIX)
384+
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR
385+
STREQUAL "x64")
386+
message(STATUS "Detected Linux x64, linking to VSCAN API x64 library")
387+
target_link_libraries(HardwareIntegration
388+
PRIVATE $ENV{VSCAN_API_DIR}/Linux/libvs_can_api.a)
389+
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR
390+
STREQUAL "X86")
391+
message(STATUS "Detected Linux x86, linking to VSCAN API x86 library")
392+
target_link_libraries(
393+
HardwareIntegration PRIVATE $ENV{VSCAN_API_DIR}/Linux
394+
x86-64/libvs_can_api.a)
395+
else()
396+
message(
397+
FATAL_ERROR
398+
"VSCAN Selected but no supported processor arch was detected. Only x64 and x86 are supported."
399+
)
400+
endif()
401+
else()
402+
message(
403+
FATAL_ERROR
404+
"VSCAN Selected but no supported OS was detected. Only Windows and Linux are supported currently."
405+
)
406+
endif()
407+
else()
408+
message(
409+
FATAL_ERROR
410+
"VSCAN Selected but no VSCAN API was found. Set the 'VSCAN_API_DIR' environment variable to the path of the VSCAN SDK."
411+
)
412+
endif()
413+
endif()
351414

352415
# Mark the compiled CAN drivers available to other modules. In the form:
353416
# `ISOBUS_<uppercase CAN_DRIVER>_AVAILABLE` as a preprocessor definition.

hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,8 @@
5050
#include "isobus/hardware_integration/ntcan_plugin.hpp"
5151
#endif
5252

53+
#ifdef ISOBUS_VSCAN_AVAILABLE
54+
#include "isobus/hardware_integration/vscan_plugin.hpp"
55+
#endif
56+
5357
#endif // AVAILABLE_CAN_DRIVERS_HPP
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//================================================================================================
2+
/// @file vscan_plugin.hpp
3+
///
4+
/// @brief An interface for using a VSCOM VSCAN driver.
5+
/// @attention Use of the VSCAN driver is governed in part by their license, and requires you
6+
/// to install their driver first, which in-turn requires you to agree to their terms and conditions.
7+
/// @author Daan Steenbergen
8+
///
9+
/// @copyright 2025 The Open-Agriculture Developers
10+
//================================================================================================
11+
#ifndef VSCAN_PLUGIN_HPP
12+
#define VSCAN_PLUGIN_HPP
13+
14+
#include <string>
15+
#include "vs_can_api.h"
16+
17+
#include "isobus/hardware_integration/can_hardware_plugin.hpp"
18+
#include "isobus/isobus/can_hardware_abstraction.hpp"
19+
#include "isobus/isobus/can_message_frame.hpp"
20+
21+
namespace isobus
22+
{
23+
/// @brief A CAN Driver for VSCOM VSCAN Devices
24+
class VSCANPlugin : public CANHardwarePlugin
25+
{
26+
public:
27+
/// @brief Constructor for the VSCOM VSCAN CAN driver
28+
/// @param[in] channel The COM port or IP address of the VSCAN device to use.
29+
/// @param[in] baudrate The baudrate to use for the CAN connection.
30+
VSCANPlugin(std::string channel, void *baudrate = VSCAN_SPEED_250K);
31+
32+
/// @brief The destructor for VSCANPlugin
33+
virtual ~VSCANPlugin() = default;
34+
35+
/// @brief Returns if the connection with the hardware is valid
36+
/// @returns `true` if connected, `false` if not connected
37+
bool get_is_valid() const override;
38+
39+
/// @brief Closes the connection to the hardware
40+
void close() override;
41+
42+
/// @brief Connects to the hardware you specified in the constructor's channel argument
43+
void open() override;
44+
45+
/// @brief Returns a frame from the hardware (synchronous), or `false` if no frame can be read.
46+
/// @param[in, out] canFrame The CAN frame that was read
47+
/// @returns `true` if a CAN frame was read, otherwise `false`
48+
bool read_frame(isobus::CANMessageFrame &canFrame) override;
49+
50+
/// @brief Writes a frame to the bus (synchronous)
51+
/// @param[in] canFrame The frame to write to the bus
52+
/// @returns `true` if the frame was written, otherwise `false`
53+
bool write_frame(const isobus::CANMessageFrame &canFrame) override;
54+
55+
/// @brief Changes previously set configuration parameters. Only works if the device is not open.
56+
/// @param[in] channel The COM port or IP address of the VSCAN device to use.
57+
/// @param[in] baudrate The baudrate to use for the CAN connection.
58+
/// @returns True if the configuration was changed, otherwise false (if the device is open false will be returned)
59+
bool reconfigure(std::string channel, void *baudrate = VSCAN_SPEED_250K);
60+
61+
private:
62+
/// @brief Parses the error from the status code
63+
/// @param[in] status The status code to parse
64+
/// @returns The error message
65+
std::string parse_error_from_status(VSCAN_STATUS status);
66+
67+
std::string channel; ///< The COM port or IP address of the VSCAN device to use.
68+
void *baudrate; ///< The baudrate to use for the CAN connection.
69+
VSCAN_HANDLE handle; ///< The handle as defined in the NTCAN driver API
70+
VSCAN_STATUS status = VSCAN_ERR_OK; ///< Stores the result of the call to begin CAN communication. Used for is_valid check later.
71+
};
72+
}
73+
74+
#endif // NTCAN_PLUGIN_HPP

hardware_integration/src/ntcan_plugin.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace isobus
4040
if (NTCAN_NO_HANDLE != handle)
4141
{
4242
isobus::CANStackLogger::error("[NTCAN]: Attempting to open a connection that is already open");
43+
return;
4344
}
4445
std::uint32_t mode = 0;
4546
std::int32_t txQueueSize = 8;
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
//================================================================================================
2+
/// @file vscan_plugin.cpp
3+
///
4+
/// @brief An interface for using a VSCOM VSCAN driver.
5+
/// @attention Use of the VSCAN driver is governed in part by their license, and requires you
6+
/// to install their driver first, which in-turn requires you to agree to their terms and conditions.
7+
/// @author Daan Steenbergen
8+
///
9+
/// @copyright 2025 The Open-Agriculture Developers
10+
//================================================================================================
11+
12+
#include "isobus/hardware_integration/vscan_plugin.hpp"
13+
#include "isobus/isobus/can_stack_logger.hpp"
14+
15+
#include <chrono>
16+
#include <thread>
17+
18+
namespace isobus
19+
{
20+
VSCANPlugin::VSCANPlugin(std::string channel, void *baudrate) :
21+
channel(channel),
22+
baudrate(baudrate)
23+
{
24+
}
25+
26+
bool VSCANPlugin::get_is_valid() const
27+
{
28+
return (VSCAN_ERR_OK == status) && (handle > 0);
29+
}
30+
31+
void VSCANPlugin::close()
32+
{
33+
VSCAN_Close(handle);
34+
handle = 0;
35+
}
36+
37+
void VSCANPlugin::open()
38+
{
39+
if (get_is_valid())
40+
{
41+
LOG_ERROR("[VSCAN]: Attempting to open a connection that is already open");
42+
return;
43+
}
44+
45+
VSCAN_API_VERSION version;
46+
status = VSCAN_Ioctl(0, VSCAN_IOCTL_GET_API_VERSION, &version);
47+
if (status != VSCAN_ERR_OK)
48+
{
49+
LOG_ERROR("[VSCAN] Failed to get API version: %s, trying to continue anyway", parse_error_from_status(status).c_str());
50+
}
51+
52+
LOG_DEBUG("[VSCAN] API Version %d.%d.%d", version.Major, version.Minor, version.SubMinor);
53+
54+
// We create a buffer to guarantee the content to be non-const
55+
std::vector<char> channelBuffer(channel.begin(), channel.end());
56+
channelBuffer.push_back('\0');
57+
handle = VSCAN_Open(channelBuffer.data(), VSCAN_MODE_NORMAL);
58+
if (handle <= 0)
59+
{
60+
LOG_ERROR("[VSCAN]: Error trying to open the connection: %s", parse_error_from_status(handle).c_str());
61+
return;
62+
}
63+
64+
status = VSCAN_Ioctl(handle, VSCAN_IOCTL_SET_SPEED, baudrate);
65+
if (VSCAN_ERR_OK != status)
66+
{
67+
LOG_ERROR("[VSCAN]: Error trying to set the baudrate: %s", parse_error_from_status(status).c_str());
68+
close();
69+
return;
70+
}
71+
72+
status = VSCAN_Ioctl(handle, VSCAN_IOCTL_SET_BLOCKING_READ, VSCAN_IOCTL_ON);
73+
if (VSCAN_ERR_OK != status)
74+
{
75+
LOG_ERROR("[VSCAN]: Error trying to set blocking read mode: %s", parse_error_from_status(status).c_str());
76+
close();
77+
return;
78+
}
79+
}
80+
81+
bool VSCANPlugin::read_frame(CANMessageFrame &canFrame)
82+
{
83+
VSCAN_MSG message;
84+
DWORD rv;
85+
bool retVal = false;
86+
87+
status = VSCAN_Read(handle, &message, 1, &rv);
88+
89+
if (VSCAN_ERR_OK == status && rv)
90+
{
91+
canFrame.dataLength = message.Size;
92+
memcpy(canFrame.data, message.Data, message.Size);
93+
canFrame.identifier = message.Id;
94+
canFrame.isExtendedFrame = (message.Flags & VSCAN_FLAGS_EXTENDED);
95+
retVal = true;
96+
}
97+
else
98+
{
99+
LOG_ERROR("[VSCAN]: Error trying to read a frame: %s, closing connection", parse_error_from_status(status).c_str());
100+
close();
101+
}
102+
103+
return retVal;
104+
}
105+
106+
bool VSCANPlugin::write_frame(const CANMessageFrame &canFrame)
107+
{
108+
VSCAN_MSG message;
109+
DWORD rv;
110+
bool retVal = false;
111+
112+
message.Id = canFrame.identifier;
113+
message.Size = canFrame.dataLength;
114+
memcpy(message.Data, canFrame.data, message.Size);
115+
message.Flags = canFrame.isExtendedFrame ? VSCAN_FLAGS_EXTENDED : VSCAN_FLAGS_STANDARD;
116+
117+
status = VSCAN_Write(handle, &message, 1, &rv);
118+
119+
if (VSCAN_ERR_OK == status && rv)
120+
{
121+
status = VSCAN_Flush(handle);
122+
if (VSCAN_ERR_OK == status)
123+
{
124+
retVal = true;
125+
}
126+
else
127+
{
128+
LOG_ERROR("[VSCAN]: Error trying to flush the write buffer: %s, closing connection", parse_error_from_status(status).c_str());
129+
close();
130+
}
131+
}
132+
else
133+
{
134+
LOG_ERROR("[VSCAN]: Error trying to write a frame: %s, closing connection", parse_error_from_status(status).c_str());
135+
close();
136+
}
137+
138+
return retVal;
139+
}
140+
141+
bool VSCANPlugin::reconfigure(std::string channel, void *baudrate)
142+
{
143+
bool retVal = false;
144+
145+
if (!get_is_valid())
146+
{
147+
this->channel = channel;
148+
this->baudrate = baudrate;
149+
retVal = true;
150+
}
151+
return retVal;
152+
}
153+
154+
std::string VSCANPlugin::parse_error_from_status(VSCAN_STATUS status)
155+
{
156+
char errorBuffer[256] = { 0 };
157+
VSCAN_GetErrorString(status, errorBuffer, sizeof(errorBuffer));
158+
return std::string(errorBuffer);
159+
}
160+
}
161+
// namespace isobus

sphinx/source/api/hardware/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ When compiling with CMake, a default CAN driver plug-in will be selected for you
4848
- :code:`-DCAN_DRIVER=TouCAN` for the Rusoku TouCAN (Windows)
4949
- :code:`-DCAN_DRIVER=SYS_TEC` for a SYS TEC sysWORXX USB CAN adapter (Windows)
5050
- :code:`-DCAN_DRIVER=NTCAN` for the NTCAN driver (Windows)
51+
- :code:`-DCAN_DRIVER=VSCAN` for the VSCAN made by VSCOM (Windows/Linux)
5152

5253
Or specify multiple using a semicolon separated list: :code:`-DCAN_DRIVER="<driver1>;<driver2>"`
5354

0 commit comments

Comments
 (0)