diff --git a/examples/transport_layer/main.cpp b/examples/transport_layer/main.cpp index d38621447..f940d8248 100644 --- a/examples/transport_layer/main.cpp +++ b/examples/transport_layer/main.cpp @@ -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 originatorDriver = std::make_shared("test-channel"); std::shared_ptr recipientDriver = std::make_shared("test-channel"); @@ -175,7 +175,7 @@ int main() } std::this_thread::sleep_for(std::chrono::milliseconds(4)); } - +#endif CANHardwareInterface::stop(); return 0; } diff --git a/hardware_integration/CMakeLists.txt b/hardware_integration/CMakeLists.txt index 1836b0225..c01ba527f 100644 --- a/hardware_integration/CMakeLists.txt +++ b/hardware_integration/CMakeLists.txt @@ -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} @@ -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__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 55a76a330..969d310dd 100644 --- a/hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp +++ b/hardware_integration/include/isobus/hardware_integration/available_can_drivers.hpp @@ -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 diff --git a/hardware_integration/include/isobus/hardware_integration/ntcan_plugin.hpp b/hardware_integration/include/isobus/hardware_integration/ntcan_plugin.hpp new file mode 100644 index 000000000..76d7b4e9d --- /dev/null +++ b/hardware_integration/include/isobus/hardware_integration/ntcan_plugin.hpp @@ -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 +#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 diff --git a/hardware_integration/src/ntcan_plugin.cpp b/hardware_integration/src/ntcan_plugin.cpp new file mode 100644 index 000000000..f4db80421 --- /dev/null +++ b/hardware_integration/src/ntcan_plugin.cpp @@ -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 +#include + +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, ×tampFreq); + if (NTCAN_SUCCESS == openResult) + { + openResult = canIoctl(handle, NTCAN_IOCTL_GET_TIMESTAMP, ×tamp); + } + if (NTCAN_SUCCESS == openResult) + { + auto now = std::chrono::system_clock::now(); + auto unix = now.time_since_epoch(); + long long millis = std::chrono::duration_cast(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 diff --git a/sphinx/source/api/hardware/index.rst b/sphinx/source/api/hardware/index.rst index 682ef97d1..c019f20d4 100644 --- a/sphinx/source/api/hardware/index.rst +++ b/sphinx/source/api/hardware/index.rst @@ -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=";"`