Skip to content

Commit

Permalink
feat(hardware): revised ntcan plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
GwnDaan committed Dec 18, 2024
1 parent c9ffdc2 commit 68bc015
Show file tree
Hide file tree
Showing 6 changed files with 289 additions and 2 deletions.
4 changes: 2 additions & 2 deletions examples/transport_layer/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ int main()
#ifndef ISOBUS_VIRTUALCAN_AVAILABLE
std::cout << "This example requires the VirtualCAN plugin to be available. If using CMake, set the `-DCAN_DRIVER=VirtualCAN`." << std::endl;
return -1;
#endif
#else

std::shared_ptr<CANHardwarePlugin> originatorDriver = std::make_shared<VirtualCANPlugin>("test-channel");
std::shared_ptr<CANHardwarePlugin> recipientDriver = std::make_shared<VirtualCANPlugin>("test-channel");
Expand Down Expand Up @@ -175,7 +175,7 @@ int main()
}
std::this_thread::sleep_for(std::chrono::milliseconds(4));
}

#endif
CANHardwareInterface::stop();
return 0;
}
Expand Down
44 changes: 44 additions & 0 deletions hardware_integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ if("SYS_TEC" IN_LIST CAN_DRIVER)
list(APPEND HARDWARE_INTEGRATION_INCLUDE "UsbCanLs.h")
list(APPEND HARDWARE_INTEGRATION_INCLUDE "UsbCanUp.h")
endif()
if("NTCAN" IN_LIST CAN_DRIVER)
list(APPEND HARDWARE_INTEGRATION_SRC "ntcan_plugin.cpp")
list(APPEND HARDWARE_INTEGRATION_INCLUDE "ntcan_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 @@ -304,6 +308,46 @@ if("SYS_TEC" IN_LIST CAN_DRIVER)
)
endif()
endif()
if("NTCAN" 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(WIN32)
if(DEFINED ENV{CanSdkDir})
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR
STREQUAL "x64")
message(STATUS "Detected x64, linking to NTCAN x64 library")
target_link_libraries(HardwareIntegration
PRIVATE $ENV{CanSdkDir}/lib/VC/amd64/ntcan.lib)
target_include_directories(HardwareIntegration
PUBLIC $ENV{CanSdkDir}/include)
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR
STREQUAL "X86")
message(STATUS "Detected x86, linking to NTCAN x86 library")
target_link_libraries(HardwareIntegration
PRIVATE $ENV{CanSdkDir}/lib/VC/i386/ntcan.lib)
target_include_directories(HardwareIntegration
PUBLIC $ENV{CanSdkDir}/include)
else()
message(
FATAL_ERROR
"NTCAN Selected but no supported processor arch was detected. Only x64 and x86 are supported."
)
endif()
else()
message(
FATAL_ERROR
"NTCAN Selected but no NTCAN SDK was found. Set the 'CanSdkDir' environment variable to the path of the NTCAN SDK."
)
endif()
else()
message(
FATAL_ERROR
"NTCAN Selected but no supported OS was detected. Only Windows is supported currently."
)
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 @@ -46,4 +46,8 @@
#include "isobus/hardware_integration/sys_tec_windows_plugin.hpp"
#endif

#ifdef ISOBUS_NTCAN_AVAILABLE
#include "isobus/hardware_integration/ntcan_plugin.hpp"
#endif

#endif // AVAILABLE_CAN_DRIVERS_HPP
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//================================================================================================
/// @file ntcan_plugin.hpp
///
/// @brief An interface for using a ESD NTCAN driver.
/// @attention Use of the NTCAN 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 Alex "Y_Less" Cole
/// @author Daan Steenbergen
///
/// @copyright 2024 The Open-Agriculture Developers
//================================================================================================
#ifndef NTCAN_PLUGIN_HPP
#define NTCAN_PLUGIN_HPP

#include <string>
#include "ntcan.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 ESD NTCAN Devices
class NTCANPlugin : public CANHardwarePlugin
{
public:
/// @brief Constructor for the ESD NTCAN CAN driver
/// @param[in] channel The logical net number assigned to the physical CAN port to use.
/// @param[in] baudrate The baudrate to use for the CAN connection.
explicit NTCANPlugin(int channel, int baudrate = NTCAN_BAUD_250);

/// @brief The destructor for NTCANPlugin
virtual ~NTCANPlugin() = 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 logical net number assigned to the physical CAN port 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(int channel, int baudrate = NTCAN_BAUD_250);

private:
int net; ///< The logical net number assigned to the physical CAN port to use.
int baudrate; ///< The baudrate to use for the CAN connection.
std::uint64_t timestampFreq; ///< The frequency of the timestamp
std::uint64_t timestampOffset; ///< The offset of the timestamps
NTCAN_HANDLE handle = NTCAN_NO_HANDLE; ///< The handle as defined in the NTCAN driver API
NTCAN_RESULT openResult = NTCAN_SUCCESS; ///< Stores the result of the call to begin CAN communication. Used for is_valid check later.
};
}

#endif // NTCAN_PLUGIN_HPP
166 changes: 166 additions & 0 deletions hardware_integration/src/ntcan_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//================================================================================================
/// @file ntcan_plugin.cpp
///
/// @brief An interface for using a ESD NTCAN driver.
/// @attention Use of the NTCAN 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 Alex "Y_Less" Cole
/// @author Daan Steenbergen
///
/// @copyright 2024 The Open-Agriculture Developers
//================================================================================================

#include "isobus/hardware_integration/ntcan_plugin.hpp"
#include "isobus/isobus/can_stack_logger.hpp"

#include <chrono>
#include <thread>

namespace isobus
{
NTCANPlugin::NTCANPlugin(int channel, int baudrate) :
net(channel),
baudrate(baudrate)
{
}

bool NTCANPlugin::get_is_valid() const
{
return (NTCAN_SUCCESS == openResult) && (NTCAN_NO_HANDLE != handle);
}

void NTCANPlugin::close()
{
canClose(handle);
handle = NTCAN_NO_HANDLE;
}

void NTCANPlugin::open()
{
if (NTCAN_NO_HANDLE != handle)
{
isobus::CANStackLogger::error("[NTCAN]: Attempting to open a connection that is already open");
}
std::uint32_t mode = 0;
std::int32_t txQueueSize = 8;
std::int32_t rxQueueSize = 8;
std::int32_t txTimeOut = 100;
std::int32_t rxTimeOut = 1000;

openResult = canOpen(net, mode, txQueueSize, rxQueueSize, txTimeOut, rxTimeOut, &handle);

if (NTCAN_SUCCESS != openResult)
{
isobus::CANStackLogger::error("[NTCAN]: Error trying to open the connection");
return;
}

CAN_IF_STATUS status{ 0 };

openResult = canSetBaudrate(handle, baudrate);
if (NTCAN_SUCCESS != openResult)
{
isobus::CANStackLogger::error("[NTCAN]: Error trying to set the baudrate");
close();
return;
}

openResult = canStatus(handle, &status);
if (NTCAN_SUCCESS != openResult)
{
isobus::CANStackLogger::error("[NTCAN]: Error trying to get the status");
close();
return;
}

if (NTCAN_FEATURE_TIMESTAMP == (status.features & NTCAN_FEATURE_TIMESTAMP))
{
std::uint64_t timestamp = 0;
openResult = canIoctl(handle, NTCAN_IOCTL_GET_TIMESTAMP_FREQ, &timestampFreq);
if (NTCAN_SUCCESS == openResult)
{
openResult = canIoctl(handle, NTCAN_IOCTL_GET_TIMESTAMP, &timestamp);
}
if (NTCAN_SUCCESS == openResult)
{
auto now = std::chrono::system_clock::now();
auto unix = now.time_since_epoch();
long long millis = std::chrono::duration_cast<std::chrono::microseconds>(unix).count();
timestampOffset = millis - timestamp;
}
}

std::int32_t ids = (1 << 11);
openResult = canIdRegionAdd(handle, 0, &ids);
if (NTCAN_SUCCESS == openResult && ids != (1 << 11))
{
openResult = NTCAN_INSUFFICIENT_RESOURCES;
isobus::CANStackLogger::error("[NTCAN]: Error trying to add the standard ID region");
close();
return;
}

ids = (1 << 29);
openResult = canIdRegionAdd(handle, NTCAN_20B_BASE, &ids);
if (NTCAN_SUCCESS == openResult && ids != (1 << 29))
{
openResult = NTCAN_INSUFFICIENT_RESOURCES;
isobus::CANStackLogger::error("[NTCAN]: Error trying to add the extended ID region");
close();
return;
}
}

bool NTCANPlugin::read_frame(isobus::CANMessageFrame &canFrame)
{
NTCAN_RESULT result;
CMSG_T msgCanMessage{ 0 };
bool retVal = false;
std::int32_t count = 1;

result = canReadT(handle, &msgCanMessage, &count, nullptr);

if (NTCAN_SUCCESS == result && 1 == count)
{
canFrame.dataLength = msgCanMessage.len;
memcpy(canFrame.data, msgCanMessage.data, msgCanMessage.len);
canFrame.identifier = (msgCanMessage.id & ((1 << 29) - 1));
canFrame.isExtendedFrame = (NTCAN_20B_BASE == (msgCanMessage.id & NTCAN_20B_BASE));
canFrame.timestamp_us = msgCanMessage.timestamp * 1000000 / timestampFreq + timestampOffset;
retVal = true;
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return retVal;
}

bool NTCANPlugin::write_frame(const isobus::CANMessageFrame &canFrame)
{
NTCAN_RESULT result;
CMSG_T msgCanMessage{ 0 };
std::int32_t count = 1;

msgCanMessage.id = canFrame.isExtendedFrame ? (canFrame.identifier | NTCAN_20B_BASE) : canFrame.identifier;
msgCanMessage.len = canFrame.dataLength;
memcpy(msgCanMessage.data, canFrame.data, canFrame.dataLength);

result = canWriteT(handle, &msgCanMessage, &count, nullptr);

return (NTCAN_SUCCESS == result && 1 == count);
}

bool NTCANPlugin::reconfigure(int channel, int baudrate)
{
bool retVal = false;

if (!get_is_valid())
{
net = channel;
this->baudrate = baudrate;
retVal = true;
}
return retVal;
}
} // 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 @@ -47,6 +47,7 @@ When compiling with CMake, a default CAN driver plug-in will be selected for you
- :code:`-DCAN_DRIVER=WindowsInnoMakerUSB2CAN` for the InnoMaker USB2CAN adapter (Windows)
- :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)

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

Expand Down

0 comments on commit 68bc015

Please sign in to comment.