From 1abe999523a017a6abf8f74d9ca9cf17e7221eda Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 19 Feb 2026 14:15:43 +0100 Subject: [PATCH 01/63] Create outline of build for CanVendorSystec --- CMakeLists.txt | 6 +++++ cmake/systec.cmake | 14 ++++++++++ src/include/CanVendorSystec.h | 32 ++++++++++++++++++++++ src/main/CanDevice.cpp | 7 +++++ src/main/CanVendorSystec.cpp | 51 +++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+) create mode 100644 cmake/systec.cmake create mode 100644 src/include/CanVendorSystec.h create mode 100644 src/main/CanVendorSystec.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bf8de3e0..4fea0ae0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,12 @@ if (UNIX) src/main/CanVendorSocketCan.cpp src/main/CanVendorSocketCanSystec.cpp ) +else() + include(cmake/systec.cmake) + include_directories(${SYSTEC_PATH_HEADERS}) + list(APPEND VENDOR_SOURCES + src/main/CanVendorSystec.cpp + ) endif() if (NOT DEFINED CAN_MODULE_MAIN_ONLY) diff --git a/cmake/systec.cmake b/cmake/systec.cmake new file mode 100644 index 00000000..e60fc3c9 --- /dev/null +++ b/cmake/systec.cmake @@ -0,0 +1,14 @@ +#----- +#SYSTEC USB-CANmodul Utility Disk +#----- +if( NOT DEFINED ENV{SYSTEC_PATH_HEADERS} OR NOT DEFINED ENV{SYSTEC_PATH_LIBS} ) + message( FATAL_ERROR "unable to determine SYSTEC USB-CANmodule Utility Disk headers and library paths from environment variables SYSTEC_PATH_HEADERS [$ENV{SYSTEC_PATH_HEADERS}] SYSTEC_PATH_LIBS [$ENV{SYSTEC_PATH_LIBS}]") +else() + message( STATUS "using SYSTEC USB-CANmodule Utility Disk headers and library paths from environment variables SYSTEC_PATH_HEADERS [$ENV{SYSTEC_PATH_HEADERS}] SYSTEC_PATH_LIBS [$ENV{SYSTEC_PATH_LIBS}]") +endif() +include_directories($ENV{SYSTEC_PATH_HEADERS}) +set(SYSTEC_LIB_PATH $ENV{SYSTEC_PATH_LIBS}) +if(NOT TARGET libusbcan64) + add_library(libusbcan64 SHARED IMPORTED) + set_property(TARGET libusbcan64 PROPERTY IMPORTED_LOCATION $ENV{SYSTEC_PATH_LIBS}/USBCAN64.lib) +endif() \ No newline at end of file diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h new file mode 100644 index 00000000..6644a26d --- /dev/null +++ b/src/include/CanVendorSystec.h @@ -0,0 +1,32 @@ +#ifndef SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ +#define SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ + +#include + +#include "CanDevice.h" + +/** + * @struct CanVendorSystec + * @brief Represents a SocketCAN Systec specific implementation of a CanDevice. + * + * This class provides the implementation for interacting with a SocketCAN + * Systec device. It extends the CanVendorSocketCan class and overrides the + * necessary methods to open, close, and send CAN frames using the SocketCAN + * interface. It provides a custom mechanishm for handling BUS_OFF errors, + * restarting the interface instead of using the built-in restart mechanism of + * SocketCan due to a kernel-panic bug on Systec linux module. + */ +struct CanVendorSystec : CanDevice { + explicit CanVendorSystec(const CanDeviceArguments& args); + ~CanVendorSystec() { vendor_close(); } + + private: + CanReturnCode vendor_open() noexcept override; + CanReturnCode vendor_close() noexcept override; + CanReturnCode vendor_send(const CanFrame& frame) noexcept override; + CanDiagnostics vendor_diagnostics() noexcept override; + + // std::unique_ptr m_can_vendor_socketcan; +}; + +#endif // SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ diff --git a/src/main/CanDevice.cpp b/src/main/CanDevice.cpp index 204b70ea..0f6265bd 100644 --- a/src/main/CanDevice.cpp +++ b/src/main/CanDevice.cpp @@ -13,6 +13,8 @@ #ifndef _WIN32 #include "CanVendorSocketCan.h" #include "CanVendorSocketCanSystec.h" +#else +#include "CanVendorSystec.h" #endif /** @@ -163,6 +165,11 @@ std::unique_ptr CanDevice::create( LOG(Log::DBG, CanLogIt::h()) << "Creating SocketCAN Systec CAN device"; return std::make_unique(configuration); } +#else + if (vendor == "systec") { + LOG(Log::DBG, CanLogIt::h()) << "Creating Systec CAN device for Windows"; + return std::make_unique(configuration); + } #endif if (vendor == "anagate") { diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp new file mode 100644 index 00000000..175e9407 --- /dev/null +++ b/src/main/CanVendorSystec.cpp @@ -0,0 +1,51 @@ +/** © Copyright CERN, 2015. All rights not expressly granted are reserved. + * + * STCanScap.cpp + * + * Created on: Jul 21, 2011 + * Based on work by vfilimon + * Rework and logging done by Piotr Nikiel + * mludwig at cern dot ch + * + * This file is part of Quasar. + * + * Quasar is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public Licence as published by + * the Free Software Foundation, either version 3 of the Licence. + * + * Quasar is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public Licence for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Quasar. If not, see . + */ + +#include "CanVendorSystec.h" + +#include +#include +#include + +CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) + : CanDevice("systec", args) { + if (!args.config.bus_name.has_value()) { + throw std::invalid_argument("Missing required configuration parameters"); + } +} + +CanReturnCode CanVendorSystec::vendor_open() noexcept { + return CanReturnCode::internal_api_error; +} + +CanReturnCode CanVendorSystec::vendor_close() noexcept { + return CanReturnCode::internal_api_error; +}; +CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { + return CanReturnCode::internal_api_error; +}; +CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { + CanDiagnostics diagnostics{}; + return diagnostics; +}; \ No newline at end of file From caf83771b05625ca90669894a9ae03bf4b9863bb Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 19 Feb 2026 16:07:40 +0100 Subject: [PATCH 02/63] Outline of Systec implementation for Windows --- src/include/CanVendorSystec.h | 31 ++- src/main/CanVendorSystec.cpp | 450 +++++++++++++++++++++++++++++++++- 2 files changed, 474 insertions(+), 7 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 6644a26d..94d8c323 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -2,8 +2,18 @@ #define SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ #include - +#include "tchar.h" +#include "Winsock2.h" +#include "windows.h" +#include +#include "usbcan32.h" +#include +#include +#include //NOLINT +#include "CanDiagnostics.h" +#include "CanVendorLoopback.h" #include "CanDevice.h" +#include /** * @struct CanVendorSystec @@ -19,12 +29,27 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } - - private: + // should it be a friend or static? STCanScan.h uses a static... + friend DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec); + + private: + bool m_CanScanThreadShutdownFlag = true; + tUcanHandle m_UcanHandle; + int m_moduleNumber; + int m_channelNumber; + HANDLE m_hReceiveThread; + DWORD m_idReceiveThread; CanReturnCode vendor_open() noexcept override; CanReturnCode vendor_close() noexcept override; CanReturnCode vendor_send(const CanFrame& frame) noexcept override; CanDiagnostics vendor_diagnostics() noexcept override; + + CanReturnCode init_can_port(); + // CanReturnCode close_can_port(); + + + // TODO i don't like this too much + inline static std::unordered_map m_handleMap = {}; // std::unique_ptr m_can_vendor_socketcan; }; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 175e9407..94fcd178 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -27,25 +27,467 @@ #include #include #include +#include +#include CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args) { if (!args.config.bus_name.has_value()) { throw std::invalid_argument("Missing required configuration parameters"); } + + // TODO trim possible can prefix use hardcoded value + int handleNumber = std::stoi(args.config.bus_name.value()); + m_moduleNumber = handleNumber / 2; + m_channelNumber = handleNumber % 2; +} + + +/** + * We create and fill initializationParameters, to pass it to openCanPort + */ + /* static */ tUcanInitCanParam createInitializationParameters( unsigned int baudRate ){ + tUcanInitCanParam initializationParameters; + initializationParameters.m_dwSize = sizeof(initializationParameters); // size of this struct + initializationParameters.m_bMode = kUcanModeNormal; // normal operation mode + initializationParameters.m_bBTR0 = HIBYTE( baudRate ); // baudrate + initializationParameters.m_bBTR1 = LOBYTE( baudRate ); + initializationParameters.m_bOCR = 0x1A; // standard output + initializationParameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages + initializationParameters.m_dwACR = USBCAN_ACR_ALL; + initializationParameters.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; + initializationParameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + initializationParameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + + return ( initializationParameters ); +} + +CanReturnCode CanVendorSystec::init_can_port() { + BYTE systecCallReturn = USBCAN_SUCCESSFUL; + tUcanHandle canModuleHandle; + + // check if USB-CANmodul already is initialized + auto pos = m_handleMap.find(m_moduleNumber); + if (pos == m_handleMap.end()) { // module not in use + systecCallReturn = ::UcanInitHardwareEx(&canModuleHandle, m_moduleNumber, 0, 0); + if (systecCallReturn != USBCAN_SUCCESSFUL ) { + LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systecCallReturn << std::dec << "]"; + ::UcanDeinitHardware(canModuleHandle); + return CanReturnCode::unknown_open_error; + } + m_handleMap[m_moduleNumber] = canModuleHandle; + } else { // find existing handle of module + canModuleHandle = pos->second; + LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; + } + + unsigned int baudRate = USBCAN_BAUD_125kBit; + switch (args().config.bitrate.value_or(0)) { + case 50000: baudRate = USBCAN_BAUD_50kBit; break; + case 100000: baudRate = USBCAN_BAUD_100kBit; break; + case 125000: baudRate = USBCAN_BAUD_125kBit; break; + case 250000: baudRate = USBCAN_BAUD_250kBit; break; + case 500000: baudRate = USBCAN_BAUD_500kBit; break; + case 1000000: baudRate = USBCAN_BAUD_1MBit; break; + default: { + LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baudRate << "]"; + } + } + auto initializationParameters = createInitializationParameters(baudRate); + + systecCallReturn = ::UcanInitCanEx2(canModuleHandle, m_channelNumber, &initializationParameters); + if ( systecCallReturn != USBCAN_SUCCESSFUL ) { + LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systecCallReturn << std::dec << "]"; + ::UcanDeinitCanEx(canModuleHandle, m_channelNumber); + return CanReturnCode::unknown_open_error; + } + + m_UcanHandle = canModuleHandle; + LOG(Log::INF, CanLogIt::h()) << "Successfully opened CAN port on module " << m_moduleNumber << ", channel " << m_channelNumber; + return CanReturnCode::success; +} + + + +// forward declaration: TODO delete me +std::string STcanGetErrorText( long errCode ); + +/** + * thread to supervise port activity + */ +DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec) +{ + BYTE status; + tCanMsgStruct readCanMessage; + CanVendorSystec *vendorPointer = reinterpret_cast(pCanVendorSystec); + // TODO was previously vendorPointer, can this thread see CanLogIt::h()? idk + LOG(Log::DBG, CanLogIt::h()) << "CanScanControlThread Started. m_CanScanThreadShutdownFlag = [" << vendorPointer->m_CanScanThreadShutdownFlag <<"]"; + while (vendorPointer->m_CanScanThreadShutdownFlag) { + status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, 0); + // seems to always be USBCAN_WARN_NODATA... + if (status != USBCAN_WARN_NODATA) LOG(Log::DBG, CanLogIt::h()) << STcanGetErrorText(status); + LOG(Log::TRC, CanLogIt::h()) << STcanGetErrorText(status); + if (status == USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Got flags" << std::hex << (long) readCanMessage.m_bFF; + if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) { + LOG(Log::ERR, CanLogIt::h()) << "TODO REMOVE ME USBCA_MSG_FF_RTR"; + continue; + } + + // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); + + std::vector data(8); + for (int i = 0; i < 8; i++) + data[i] = readCanMessage.m_bData[i]; + // id, data, flags + CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); + // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" + + LOG(Log::ERR, CanLogIt::h()) << "Received response yay with message " << std::string(canMsgCopy.message().data()); + vendorPointer->received(canMsgCopy); + // vendorPointer->m_statistics.onReceive( readCanMessage.m_bDLC ); + // vendorPointer->m_statistics.setTimeSinceReceived(); + + // we can reset the reconnectionTimeout here, since we have received a message + // vendorPointer->resetTimeoutOnReception(); + } + else if (status == USBCAN_WARN_NODATA) { + // pass for now + // // TODO for now, we always connect, TODO reimplement the full logic. yucky... + // LOG(Log::INF, CanLogIt::h()) << "Reconnecting the handle and channel etc"; + + // // deinit single bus and reopen + // UcanDeinitCanEx ( vendorPointer->m_UcanHandle, (BYTE )vendorPointer->m_channelNumber ); + // // setCanHandleInUse( vendorPointer->m_moduleNumber, false); + // // TODO is it right to comment the line below? it's not exactly the same as "port is in use" + // // vendorPointer->m_handleMap.erase(vendorPointer->m_moduleNumber); + // // TODO the UcanDeinitCanEx should be encapulsated in init_can_port, not here. + // auto returnCode = vendorPointer->init_can_port(); // TODO snake_case... + + // LOG(Log::INF, CanLogIt::h()) << "reinit return code" << std::hex << long(returnCode); + // Sleep( 100 ); // ms + } + else { + // TODO + // vendorPointer->sendErrorCode(status); + } + } + ExitThread(0); + return 0; } CanReturnCode CanVendorSystec::vendor_open() noexcept { - return CanReturnCode::internal_api_error; + + auto returnCode = init_can_port(); + + // TODO set time since opened equivalent... + // m_statistics.setTimeSinceOpened(); + + // TODO we need to start a thread which somehow makes sure received frames are passed to the receiver callback(s)... + // that thread needs to call received(CanFrame(...)) + // see anagate_receive and see how it registered as a callback in the CAN api... + // basically, we should copy CanScanControlThread + + // After the canboard is configured and started, we start the scan control thread + + // TODO reenable thread... + m_hReceiveThread = CreateThread(NULL, 0, CanScanControlThread, this, 0, &m_idReceiveThread); + + if (NULL == m_hReceiveThread) { + LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; + return CanReturnCode::unknown_open_error; + } + + return returnCode; } CanReturnCode CanVendorSystec::vendor_close() noexcept { - return CanReturnCode::internal_api_error; + // TODO what if the return code is not success? + m_CanScanThreadShutdownFlag = false; + DWORD result = WaitForSingleObject(m_hReceiveThread, INFINITE); //Shut down can scan thread + UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channelNumber); + LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ <<" closed successfully"; + return CanReturnCode::success; }; + CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { - return CanReturnCode::internal_api_error; + // bool CanVendorSystec::sendMessage(short cobID, unsigned char len, unsigned char *message, bool rtr) + bool rtr = frame.is_remote_request(); + uint32_t len = frame.length(); + char *message = frame.message().data(); + short cobID = frame.id(); // is this the same as cobid? i don't know... + + // TODO we can't just use message as it's now a vector of chars not a char* + + LOG(Log::DBG, CanLogIt::h()) << "Sending message: [" << ( message == 0 ? "" : (const char *) message) << "], cobID: [" << cobID << "], Message Length: [" << static_cast(len) << "]"; + + tCanMsgStruct canMsgToBeSent; + BYTE Status; + + canMsgToBeSent.m_dwID = cobID; + canMsgToBeSent.m_bDLC = len; + canMsgToBeSent.m_bFF = 0; + if (rtr) { + canMsgToBeSent.m_bFF = USBCAN_MSG_FF_RTR; + } + int messageLengthToBeProcessed; + //If there is more than 8 characters to process, we process 8 of them in this iteration of the loop + if (len > 8) { + messageLengthToBeProcessed = 8; + LOG(Log::DBG, CanLogIt::h()) << "The length is more then 8 bytes, adjust to 8, ignore >8. len= " << len; + } else { + //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop + messageLengthToBeProcessed = len; + if (len < 8) { + LOG(Log::DBG, CanLogIt::h())<< "The length is less then 8 bytes, process only. len= " << len; + } + } + canMsgToBeSent.m_bDLC = messageLengthToBeProcessed; + memcpy(canMsgToBeSent.m_bData, message, messageLengthToBeProcessed); + // MLOG(TRC,this) << "Channel Number: [" << m_channelNumber << "], cobID: [" << canMsgToBeSent.m_dwID << "], Message Length: [" << static_cast(canMsgToBeSent.m_bDLC) << "]"; + Status = UcanWriteCanMsgEx(m_UcanHandle, m_channelNumber, &canMsgToBeSent, NULL); + if ( Status != USBCAN_SUCCESSFUL ) { + LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message."; + + // switch( m_reconnectCondition ){ + // case CanModule::ReconnectAutoCondition::sendFail: { + // LOG(Log::WRN, CanLogIt::h()) << " detected a sendFail, triggerCounter= " << m_triggerCounter + // << " failedSendCounter= " << m_failedSendCounter; + // m_triggerCounter--; + // break; + // } + // case CanModule::ReconnectAutoCondition::never: + // default:{ + // m_triggerCounter = m_failedSendCounter; + // break; + // } + // } + + // switch ( m_reconnectAction ){ + // case CanModule::ReconnectAction::allBusesOnBridge: { + // if ( m_triggerCounter <= 0 ){ + // LOG(Log::INF, CanLogIt::h()) << " reconnect condition " << (int) m_reconnectCondition + // << reconnectConditionString(m_reconnectCondition) + // << " triggered action " << (int) m_reconnectAction + // << reconnectActionString(m_reconnectAction); + + // CanVendorSystec::reconnectAllPorts( m_UcanHandle ); + // m_triggerCounter = m_failedSendCounter; + // } + // break; + // } + // case CanModule::ReconnectAction::singleBus: { + // if ( m_triggerCounter <= 0 ){ + // LOG(Log::INF, CanLogIt::h()) << " reconnect condition " << (int) m_reconnectCondition + // << reconnectConditionString(m_reconnectCondition) + // << " triggered action " << (int) m_reconnectAction + // << reconnectActionString(m_reconnectAction); + // openCanPort( createInitializationParameters( m_baudRate )); + // LOG(Log::INF, CanLogIt::h())<< "reconnect one CAN port m_UcanHandle= " << (int) m_UcanHandle; + // m_triggerCounter = m_failedSendCounter; + // } + // break; + // } + // default: { + // break; + // } + // } + return CanReturnCode::unknown_send_error; + } else { + // why is it requesting so few sends... hmmm + LOG(Log::ERR, CanLogIt::h()) << "Sent message on systec successfully!!!"; + // m_statistics.onTransmit( canMsgToBeSent.m_bDLC ); + // m_statistics.setTimeSinceTransmitted(); + return CanReturnCode::success; + } + // return sendErrorCode(Status); }; CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { + // TODO, look at getPortStatus, use UcanGetStatus CanDiagnostics diagnostics{}; + tStatusStruct status; + // TODO check return code of these functions... + UcanGetStatusEx(m_UcanHandle, m_channelNumber, &status); + WORD can_status = status.m_wCanStatus; + diagnostics.state = (can_status == 0) ? "ERROR_ACTIVE" : "ERROR_TODO"; // TODO look at page 101 of the manual + // and use more useful states... + // the function has a return code separate from the status... + // or Ex version for specific channel? + tUcanMsgCountInfo msg_count_info; + UcanGetMsgCountInfoEx(m_UcanHandle, m_channelNumber, &msg_count_info); + + diagnostics.tx = msg_count_info.m_wSentMsgCount; + diagnostics.rx = msg_count_info.m_wRecvdMsgCount; + + DWORD tx_error, rx_error; + UcanGetCanErrorCounter(m_UcanHandle, m_channelNumber, &tx_error, &rx_error); + diagnostics.tx_error = tx_error; + diagnostics.rx_error = rx_error; + + // DWORD module_time; + // UcanGetModuleTime(m_UcanHandle, &module_time); + return diagnostics; -}; \ No newline at end of file +}; + +/** + * error text specific to STcan according to table24 + * I am just copying the whole descriptions from the doc, verbatim, wtf. + * you get some shakespeare from it. + */ +std::string STcanGetErrorText( long errCode ){ + switch( errCode ){ + case USBCAN_SUCCESSFUL: return("success"); + + case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this\ + case the term resource means memory and handles provided by the Windows OS"); + + case USBCAN_ERR_MAXMODULES: return("An application has tried to open more than 64 USB-CANmodul devices.\ + The standard version of the DLL only supports up to 64 USB-CANmodul\ + devices at the same time. This error also appears if several applications\ + try to access more than 64 USB-CANmodul devices. For example,\ + application 1 has opened 60 modules, application 2 has opened 4\ + modules and application 3 wants to open a module. Application 3\ + receives this error code."); + + case USBCAN_ERR_HWINUSE: return("An application tries to initialize an USB-CANmodul with the given device\ + number. If this module has already been initialized by its own or by\ + another application, this error code is returned."); + + case USBCAN_ERR_ILLVERSION: return("This error code returns if the firmware version of the USB-CANmodul is\ + not compatible to the software version of the DLL. In this case, install\ + the latest driver for the USB-CANmodul. Furthermore make sure that\ + the latest firmware version is programmed to the USB-CANmodul."); + + case USBCAN_ERR_ILLHW: return("This error code returns if an USB-CANmodul with the given device\ + number is not found. If the function UcanInitHardware() or\ + UcanInitHardwareEx() has been called with the device number\ + USBCAN_ANY_MODULE, and the error code appears, it indicates that\ + no module is connected to the PC or all connected modules are already\ + in use."); + + case USBCAN_ERR_ILLHANDLE: return("This error code returns if a function received an incorrect USBCAN\ + handle. The function first checks which USB-CANmodul is related to this\ + handle. This error occurs if no device belongs this handle."); + + case USBCAN_ERR_ILLPARAM: return("This error code returns if a wrong parameter is passed to the function.\ + For example, the value NULL has been passed to a pointer variable\ + instead of a valid address."); + + case USBCAN_ERR_BUSY: return("This error code occurs if several threads are accessing an\ + USB-CANmodul within a single application. After the other threads have\ + finished their tasks, the function may be called again."); + + case USBCAN_ERR_TIMEOUT: return("This error code occurs if the function transmits a command to the\ + USB-CANmodul but no reply is returned. To solve this problem, close\ + the application, disconnect the USB-CANmodul, and connect it again."); + + case USBCAN_ERR_IOFAILED: return("This error code occurs if the communication to the kernel driver was\ + interrupted. This happens, for example, if the USB-CANmodul is\ + disconnected during transferring data or commands to the\ + USB-CANmodul."); + + case USBCAN_ERR_DLL_TXFULL: return("The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks\ + if the transmit buffer within the DLL has enough capacity to store new\ + CAN messages. If the buffer is full, this error code returns. The CAN\ + message passed to these functions will not be written into the transmit\ + buffer in order to protect other CAN messages against overwriting. The\ + size of the transmit buffer is configurable (refer to function\ + UcanInitCanEx() and structure tUcanInitCanParam)."); + + case USBCAN_ERR_MAXINSTANCES: return("A maximum amount of 64 applications are able to have access to the\ + DLL. If more applications attempting to access to the DLL, this error\ + code is returned. In this case, it is not possible to use an\ + USB-CANmodul by this application."); + + case USBCAN_ERR_CANNOTINIT: return("This error code returns if an application tries to call an API function\ + which only can be called in software state CAN_INIT but the current\ + software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for\ + detailed information."); + + case USBCAN_ERR_DISCONNECT: return("This error code occurs if an API function was called for an\ + USB-CANmodul that was plugged-off from the computer recently."); + + case USBCAN_ERR_ILLCHANNEL: return("This error code is returned if an extended function of the DLL is called\ + with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."); + + case USBCAN_ERR_ILLHWTYPE: return("This error code occurs if an extended function of the DLL was called for\ + a hardware which does not support the feature."); + + case USBCAN_ERRCMD_NOTEQU: return("This error code occurs during communication between the PC and an\ + USB-CANmodul. The PC sends a command to the USB-CANmodul,\ + then the module executes the command and returns a response to the\ + PC. This error code returns if the reply does not correspond to the command."); + + case USBCAN_ERRCMD_REGTST: return("The software tests the CAN controller on the USB-CANmodul when the\ + CAN interface is initialized. Several registers of the CAN controller are\ + checked. This error code returns if an error appears during this register test."); + + case USBCAN_ERRCMD_ILLCMD: return("This error code returns if the USB-CANmodul receives a non-defined\ + command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."); + + case USBCAN_ERRCMD_EEPROM: return("The USB-CANmodul has a built-in EEPROM. This EEPROM contains\ + several configurations, e.g. the device number and the serial number. If\ + an error occurs while reading these values, this error code is returned."); + + case USBCAN_ERRCMD_ILLBDR: return("The USB-CANmodul has been initialized with an invalid baud rate (refer\ + to section 4.3.4)."); + + case USBCAN_ERRCMD_NOTINIT: return("It was tried to access a CAN-channel of a multi-channel\ + USB-CANmodul that was not initialized."); + + case USBCAN_ERRCMD_ALREADYINIT: return("The accessed CAN-channel of a multi-channel USB-CANmodul was\ + already initialized"); + + case USBCAN_ERRCMD_ILLSUBCMD: return("An internal error occurred within the DLL. In this case an unknown sub-\ + command was called instead of a main command (e.g. for the cyclic CAN message-feature)."); + + case USBCAN_ERRCMD_ILLIDX: return("An internal error occurred within the DLL. In this case an invalid index\ + for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."); + + case USBCAN_ERRCMD_RUNNING: return("The caller tries to define a new list of cyclic CAN messages but this\ + feature was already started. For defining a new list, it is necessary to stop the feature beforehand."); + + case USBCAN_WARN_NODATA: return("If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns\ + with this warning, it is an indication that the receive buffer contains no CAN messages."); + + case USBCAN_WARN_SYS_RXOVERRUN: return("This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the\ + receive buffer within the kernel driver runs over. The function\ + nevertheless returns a valid CAN message. It also indicates that at least\ + one CAN message are lost. However, it does not indicate the position of the lost CAN messages."); + + case USBCAN_WARN_DLL_RXOVERRUN: return("The DLL automatically requests CAN messages from the\ + USB-CANmodul and stores the messages into a buffer of the DLL. If\ + more CAN messages are received than the DLL buffer size allows, this\ + error code returns and CAN messages are lost. However, it does not\ + indicate the position of the lost CAN messages. The size of the receive\ + buffer is configurable (refer to function UcanInitCanEx() and structure\ + tUcanInitCanParam)."); + + case USBCAN_WARN_FW_TXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or\ + UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in\ + the CAN driver status. However, the transmit CAN message could be\ + stored to the DLL transmit buffer. This warning indicates that at least\ + one transmit CAN message got lost in the device firmware layer. This\ + warning does not indicate the position of the lost CAN message."); + + case USBCAN_WARN_FW_RXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or\ + UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag\ + USBCAN_CANERR_OVERRUN are set in the CAN driver status. The\ + function has returned with a valid CAN message. This warning indicates\ + that at least one received CAN message got lost in the firmware layer.\ + This warning does not indicate the position of the lost CAN message."); + + case USBCAN_WARN_NULL_PTR: return("This warning is returned by functions UcanInitHwConnectControl() or\ + UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."); + + case USBCAN_WARN_TXLIMIT: return("This warning is returned by the function UcanWriteCanMsgEx() if it was\ + called to transmit more than one CAN message, but a part of them\ + could not be stored to the transmit buffer within the DLL (because the\ + buffer is full). The returned variable addressed by the parameter\ + pdwCount_p indicates the number of CAN messages which are stored\ + successfully to the transmit buffer."); + + default: return("unknown error code"); + } +} \ No newline at end of file From 6440de564702bffac04dfcfd71bd6a6dc9493fe7 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 13:36:48 +0100 Subject: [PATCH 03/63] use switch case to handle systec read responses --- src/main/CanVendorSystec.cpp | 81 +++++++++++++++--------------------- 1 file changed, 34 insertions(+), 47 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 94fcd178..c3626c17 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -123,55 +123,42 @@ DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec) // TODO was previously vendorPointer, can this thread see CanLogIt::h()? idk LOG(Log::DBG, CanLogIt::h()) << "CanScanControlThread Started. m_CanScanThreadShutdownFlag = [" << vendorPointer->m_CanScanThreadShutdownFlag <<"]"; while (vendorPointer->m_CanScanThreadShutdownFlag) { - status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, 0); - // seems to always be USBCAN_WARN_NODATA... - if (status != USBCAN_WARN_NODATA) LOG(Log::DBG, CanLogIt::h()) << STcanGetErrorText(status); - LOG(Log::TRC, CanLogIt::h()) << STcanGetErrorText(status); - if (status == USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Got flags" << std::hex << (long) readCanMessage.m_bFF; - if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) { - LOG(Log::ERR, CanLogIt::h()) << "TODO REMOVE ME USBCA_MSG_FF_RTR"; - continue; + status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, NULL); + switch (status) { + case USBCAN_WARN_SYS_RXOVERRUN: // fallthrough intended for warnings + case USBCAN_WARN_DLL_RXOVERRUN: + case USBCAN_WARN_FW_RXOVERRUN: + LOG(Log::WRN, CanLogIt::h()) << STcanGetErrorText(status); + case USBCAN_SUCCESSFUL: { + if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) break; + // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); + std::vector data(8); + for (int i = 0; i < 8; i++) + data[i] = readCanMessage.m_bData[i]; + // id, data, flags + CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); + // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" + vendorPointer->received(canMsgCopy); + // vendorPointer->m_statistics.onReceive( readCanMessage.m_bDLC ); + // vendorPointer->m_statistics.setTimeSinceReceived(); + + // we can reset the reconnectionTimeout here, since we have received a message + // vendorPointer->resetTimeoutOnReception(); + break; } - - // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); - - std::vector data(8); - for (int i = 0; i < 8; i++) - data[i] = readCanMessage.m_bData[i]; - // id, data, flags - CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); - // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" - - LOG(Log::ERR, CanLogIt::h()) << "Received response yay with message " << std::string(canMsgCopy.message().data()); - vendorPointer->received(canMsgCopy); - // vendorPointer->m_statistics.onReceive( readCanMessage.m_bDLC ); - // vendorPointer->m_statistics.setTimeSinceReceived(); - - // we can reset the reconnectionTimeout here, since we have received a message - // vendorPointer->resetTimeoutOnReception(); + case USBCAN_WARN_NODATA: + LOG(Log::TRC, CanLogIt::h()) << STcanGetErrorText(status); + // TODO is it correct to sleep here? + // Sleep(100); // ms + break; + default: // errors + // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL + // TODO should we raise some error state here? + LOG(Log::ERR, CanLogIt::h()) << STcanGetErrorText(status); + break; } - else if (status == USBCAN_WARN_NODATA) { - // pass for now - // // TODO for now, we always connect, TODO reimplement the full logic. yucky... - // LOG(Log::INF, CanLogIt::h()) << "Reconnecting the handle and channel etc"; - - // // deinit single bus and reopen - // UcanDeinitCanEx ( vendorPointer->m_UcanHandle, (BYTE )vendorPointer->m_channelNumber ); - // // setCanHandleInUse( vendorPointer->m_moduleNumber, false); - // // TODO is it right to comment the line below? it's not exactly the same as "port is in use" - // // vendorPointer->m_handleMap.erase(vendorPointer->m_moduleNumber); - // // TODO the UcanDeinitCanEx should be encapulsated in init_can_port, not here. - // auto returnCode = vendorPointer->init_can_port(); // TODO snake_case... - - // LOG(Log::INF, CanLogIt::h()) << "reinit return code" << std::hex << long(returnCode); - // Sleep( 100 ); // ms - } - else { - // TODO - // vendorPointer->sendErrorCode(status); - } - } + } + ExitThread(0); return 0; } From df040c4e397f868ff704741d3a62e08c60b35cd1 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 14:20:42 +0100 Subject: [PATCH 04/63] reconnect systec on a failed send use actual systec error code names for diagnostics.state --- src/main/CanVendorSystec.cpp | 116 +++++++++++++++++------------------ 1 file changed, 56 insertions(+), 60 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index c3626c17..1bd7ff0b 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -170,14 +170,7 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { // TODO set time since opened equivalent... // m_statistics.setTimeSinceOpened(); - // TODO we need to start a thread which somehow makes sure received frames are passed to the receiver callback(s)... - // that thread needs to call received(CanFrame(...)) - // see anagate_receive and see how it registered as a callback in the CAN api... - // basically, we should copy CanScanControlThread - // After the canboard is configured and started, we start the scan control thread - - // TODO reenable thread... m_hReceiveThread = CreateThread(NULL, 0, CanScanControlThread, this, 0, &m_idReceiveThread); if (NULL == m_hReceiveThread) { @@ -193,7 +186,7 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { m_CanScanThreadShutdownFlag = false; DWORD result = WaitForSingleObject(m_hReceiveThread, INFINITE); //Shut down can scan thread UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channelNumber); - LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ <<" closed successfully"; + LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; return CanReturnCode::success; }; @@ -233,56 +226,15 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { memcpy(canMsgToBeSent.m_bData, message, messageLengthToBeProcessed); // MLOG(TRC,this) << "Channel Number: [" << m_channelNumber << "], cobID: [" << canMsgToBeSent.m_dwID << "], Message Length: [" << static_cast(canMsgToBeSent.m_bDLC) << "]"; Status = UcanWriteCanMsgEx(m_UcanHandle, m_channelNumber, &canMsgToBeSent, NULL); - if ( Status != USBCAN_SUCCESSFUL ) { + if (Status != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message."; - // switch( m_reconnectCondition ){ - // case CanModule::ReconnectAutoCondition::sendFail: { - // LOG(Log::WRN, CanLogIt::h()) << " detected a sendFail, triggerCounter= " << m_triggerCounter - // << " failedSendCounter= " << m_failedSendCounter; - // m_triggerCounter--; - // break; - // } - // case CanModule::ReconnectAutoCondition::never: - // default:{ - // m_triggerCounter = m_failedSendCounter; - // break; - // } - // } - - // switch ( m_reconnectAction ){ - // case CanModule::ReconnectAction::allBusesOnBridge: { - // if ( m_triggerCounter <= 0 ){ - // LOG(Log::INF, CanLogIt::h()) << " reconnect condition " << (int) m_reconnectCondition - // << reconnectConditionString(m_reconnectCondition) - // << " triggered action " << (int) m_reconnectAction - // << reconnectActionString(m_reconnectAction); - - // CanVendorSystec::reconnectAllPorts( m_UcanHandle ); - // m_triggerCounter = m_failedSendCounter; - // } - // break; - // } - // case CanModule::ReconnectAction::singleBus: { - // if ( m_triggerCounter <= 0 ){ - // LOG(Log::INF, CanLogIt::h()) << " reconnect condition " << (int) m_reconnectCondition - // << reconnectConditionString(m_reconnectCondition) - // << " triggered action " << (int) m_reconnectAction - // << reconnectActionString(m_reconnectAction); - // openCanPort( createInitializationParameters( m_baudRate )); - // LOG(Log::INF, CanLogIt::h())<< "reconnect one CAN port m_UcanHandle= " << (int) m_UcanHandle; - // m_triggerCounter = m_failedSendCounter; - // } - // break; - // } - // default: { - // break; - // } - // } + // for now, just always reconnect on a failed send. + vendor_close(); + vendor_open(); + return CanReturnCode::unknown_send_error; } else { - // why is it requesting so few sends... hmmm - LOG(Log::ERR, CanLogIt::h()) << "Sent message on systec successfully!!!"; // m_statistics.onTransmit( canMsgToBeSent.m_bDLC ); // m_statistics.setTimeSinceTransmitted(); return CanReturnCode::success; @@ -290,19 +242,48 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { // return sendErrorCode(Status); }; CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { - // TODO, look at getPortStatus, use UcanGetStatus + + // TODO we can read the operating mode, either kUcanModeNormal, ListenOnly or TxEcho CanDiagnostics diagnostics{}; tStatusStruct status; // TODO check return code of these functions... UcanGetStatusEx(m_UcanHandle, m_channelNumber, &status); WORD can_status = status.m_wCanStatus; - diagnostics.state = (can_status == 0) ? "ERROR_ACTIVE" : "ERROR_TODO"; // TODO look at page 101 of the manual - // and use more useful states... - // the function has a return code separate from the status... - // or Ex version for specific channel? + switch (can_status) { + case USBCAN_CANERR_OK: + diagnostics.state = "USBCAN_CANERR_OK"; + break; + case USBCAN_CANERR_XMTFULL: + diagnostics.state = "USBCAN_CANERR_XMTFULL"; + break; + case USBCAN_CANERR_OVERRUN: + diagnostics.state = "USBCAN_CANERR_OVERRUN"; + break; + case USBCAN_CANERR_BUSLIGHT: + diagnostics.state = "USBCAN_CANERR_BUSLIGHT"; + break; + case USBCAN_CANERR_BUSHEAVY: + diagnostics.state = "USBCAN_CANERR_BUSHEAVY"; + break; + case USBCAN_CANERR_BUSOFF: + diagnostics.state = "USBCAN_CANERR_BUSOFF"; + break; + case USBCAN_CANERR_QOVERRUN: + diagnostics.state = "USBCAN_CANERR_QOVERRUN"; + break; + case USBCAN_CANERR_QXMTFULL: + diagnostics.state = "USBCAN_CANERR_QXMTFULL"; + break; + case USBCAN_CANERR_REGTEST: + diagnostics.state = "USBCAN_CANERR_REGTEST"; + break; + case USBCAN_CANERR_TXMSGLOST: + diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; + break; + } + tUcanMsgCountInfo msg_count_info; UcanGetMsgCountInfoEx(m_UcanHandle, m_channelNumber, &msg_count_info); - diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; @@ -311,6 +292,21 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; + tUcanHardwareInfo hw_info; + if (UcanGetHardwareInfo(m_UcanHandle, &hw_info) != USBCAN_SUCCESSFUL) + diagnostics.mode = "OFFLINE"; + else switch (hw_info.m_bMode) { + case kUcanModeNormal: + diagnostics.mode = "NORMAL"; + break; + case kUcanModeListenOnly: + diagnostics.mode = "LISTEN_ONLY"; + break; + case kUcanModeTxEcho: + diagnostics.mode = "LOOPBACK"; + break; + } + // DWORD module_time; // UcanGetModuleTime(m_UcanHandle, &module_time); From e89d90830edf0a96f96c5725eec10e898b1f7bdf Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 15:40:45 +0100 Subject: [PATCH 05/63] fix header guard for CanVendorSystec --- src/include/CanVendorSystec.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 94d8c323..6b997d8a 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -1,5 +1,5 @@ -#ifndef SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ -#define SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ +#ifndef SRC_INCLUDE_CANVENDORSYSTEC_H_ +#define SRC_INCLUDE_CANVENDORSYSTEC_H_ #include #include "tchar.h" @@ -54,4 +54,4 @@ struct CanVendorSystec : CanDevice { // std::unique_ptr m_can_vendor_socketcan; }; -#endif // SRC_INCLUDE_CANVENDORSOCKETCANSYSTEC_H_ +#endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ From 9eba564b05c18dec35be11fcb01a0ea6b1645a73 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 15:41:09 +0100 Subject: [PATCH 06/63] use std::copy to copy Systec can message data to CanFrame --- src/main/CanVendorSystec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 1bd7ff0b..f2628059 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -29,6 +29,7 @@ #include #include #include +#include CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args) { @@ -133,8 +134,7 @@ DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec) if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) break; // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); std::vector data(8); - for (int i = 0; i < 8; i++) - data[i] = readCanMessage.m_bData[i]; + std::copy(readCanMessage.m_bData, readCanMessage.m_bData + 8, data.begin()); // id, data, flags CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" From 87ddd7dbf0536d32a761eb60cb6e6e338903c161 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 16:05:15 +0100 Subject: [PATCH 07/63] small cleanups --- src/main/CanVendorSystec.cpp | 45 +++++++++++++----------------------- 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index f2628059..898b66f8 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -121,7 +121,6 @@ DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec) BYTE status; tCanMsgStruct readCanMessage; CanVendorSystec *vendorPointer = reinterpret_cast(pCanVendorSystec); - // TODO was previously vendorPointer, can this thread see CanLogIt::h()? idk LOG(Log::DBG, CanLogIt::h()) << "CanScanControlThread Started. m_CanScanThreadShutdownFlag = [" << vendorPointer->m_CanScanThreadShutdownFlag <<"]"; while (vendorPointer->m_CanScanThreadShutdownFlag) { status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, NULL); @@ -152,7 +151,8 @@ DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec) // Sleep(100); // ms break; default: // errors - // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL + // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, + // USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL // TODO should we raise some error state here? LOG(Log::ERR, CanLogIt::h()) << STcanGetErrorText(status); break; @@ -174,7 +174,7 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { m_hReceiveThread = CreateThread(NULL, 0, CanScanControlThread, this, 0, &m_idReceiveThread); if (NULL == m_hReceiveThread) { - LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; + LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; return CanReturnCode::unknown_open_error; } @@ -251,35 +251,25 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { WORD can_status = status.m_wCanStatus; switch (can_status) { case USBCAN_CANERR_OK: - diagnostics.state = "USBCAN_CANERR_OK"; - break; + diagnostics.state = "USBCAN_CANERR_OK"; break; case USBCAN_CANERR_XMTFULL: - diagnostics.state = "USBCAN_CANERR_XMTFULL"; - break; + diagnostics.state = "USBCAN_CANERR_XMTFULL"; break; case USBCAN_CANERR_OVERRUN: - diagnostics.state = "USBCAN_CANERR_OVERRUN"; - break; + diagnostics.state = "USBCAN_CANERR_OVERRUN"; break; case USBCAN_CANERR_BUSLIGHT: - diagnostics.state = "USBCAN_CANERR_BUSLIGHT"; - break; + diagnostics.state = "USBCAN_CANERR_BUSLIGHT"; break; case USBCAN_CANERR_BUSHEAVY: - diagnostics.state = "USBCAN_CANERR_BUSHEAVY"; - break; + diagnostics.state = "USBCAN_CANERR_BUSHEAVY"; break; case USBCAN_CANERR_BUSOFF: - diagnostics.state = "USBCAN_CANERR_BUSOFF"; - break; + diagnostics.state = "USBCAN_CANERR_BUSOFF"; break; case USBCAN_CANERR_QOVERRUN: - diagnostics.state = "USBCAN_CANERR_QOVERRUN"; - break; + diagnostics.state = "USBCAN_CANERR_QOVERRUN"; break; case USBCAN_CANERR_QXMTFULL: - diagnostics.state = "USBCAN_CANERR_QXMTFULL"; - break; + diagnostics.state = "USBCAN_CANERR_QXMTFULL"; break; case USBCAN_CANERR_REGTEST: - diagnostics.state = "USBCAN_CANERR_REGTEST"; - break; + diagnostics.state = "USBCAN_CANERR_REGTEST"; break; case USBCAN_CANERR_TXMSGLOST: - diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; - break; + diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; break; } tUcanMsgCountInfo msg_count_info; @@ -297,14 +287,11 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { diagnostics.mode = "OFFLINE"; else switch (hw_info.m_bMode) { case kUcanModeNormal: - diagnostics.mode = "NORMAL"; - break; + diagnostics.mode = "NORMAL"; break; case kUcanModeListenOnly: - diagnostics.mode = "LISTEN_ONLY"; - break; + diagnostics.mode = "LISTEN_ONLY"; break; case kUcanModeTxEcho: - diagnostics.mode = "LOOPBACK"; - break; + diagnostics.mode = "LOOPBACK"; break; } // DWORD module_time; From 3a8298cd234a13264c23c95494196567ec9974d7 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 16:19:31 +0100 Subject: [PATCH 08/63] renames, cleanups, reorderings --- src/include/CanVendorSystec.h | 6 +- src/main/CanVendorSystec.cpp | 113 +++++++++++++++++----------------- 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 6b997d8a..21576c2d 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -29,8 +29,7 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } - // should it be a friend or static? STCanScan.h uses a static... - friend DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec); + static DWORD WINAPI SystecRxThread(LPVOID pCanVendorSystec); private: bool m_CanScanThreadShutdownFlag = true; @@ -45,13 +44,10 @@ struct CanVendorSystec : CanDevice { CanDiagnostics vendor_diagnostics() noexcept override; CanReturnCode init_can_port(); - // CanReturnCode close_can_port(); // TODO i don't like this too much inline static std::unordered_map m_handleMap = {}; - - // std::unique_ptr m_can_vendor_socketcan; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 898b66f8..b595f859 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -43,7 +43,6 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) m_channelNumber = handleNumber % 2; } - /** * We create and fill initializationParameters, to pass it to openCanPort */ @@ -108,61 +107,6 @@ CanReturnCode CanVendorSystec::init_can_port() { return CanReturnCode::success; } - - -// forward declaration: TODO delete me -std::string STcanGetErrorText( long errCode ); - -/** - * thread to supervise port activity - */ -DWORD WINAPI CanScanControlThread(LPVOID pCanVendorSystec) -{ - BYTE status; - tCanMsgStruct readCanMessage; - CanVendorSystec *vendorPointer = reinterpret_cast(pCanVendorSystec); - LOG(Log::DBG, CanLogIt::h()) << "CanScanControlThread Started. m_CanScanThreadShutdownFlag = [" << vendorPointer->m_CanScanThreadShutdownFlag <<"]"; - while (vendorPointer->m_CanScanThreadShutdownFlag) { - status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, NULL); - switch (status) { - case USBCAN_WARN_SYS_RXOVERRUN: // fallthrough intended for warnings - case USBCAN_WARN_DLL_RXOVERRUN: - case USBCAN_WARN_FW_RXOVERRUN: - LOG(Log::WRN, CanLogIt::h()) << STcanGetErrorText(status); - case USBCAN_SUCCESSFUL: { - if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) break; - // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); - std::vector data(8); - std::copy(readCanMessage.m_bData, readCanMessage.m_bData + 8, data.begin()); - // id, data, flags - CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); - // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" - vendorPointer->received(canMsgCopy); - // vendorPointer->m_statistics.onReceive( readCanMessage.m_bDLC ); - // vendorPointer->m_statistics.setTimeSinceReceived(); - - // we can reset the reconnectionTimeout here, since we have received a message - // vendorPointer->resetTimeoutOnReception(); - break; - } - case USBCAN_WARN_NODATA: - LOG(Log::TRC, CanLogIt::h()) << STcanGetErrorText(status); - // TODO is it correct to sleep here? - // Sleep(100); // ms - break; - default: // errors - // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, - // USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL - // TODO should we raise some error state here? - LOG(Log::ERR, CanLogIt::h()) << STcanGetErrorText(status); - break; - } - } - - ExitThread(0); - return 0; -} - CanReturnCode CanVendorSystec::vendor_open() noexcept { auto returnCode = init_can_port(); @@ -171,7 +115,7 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { // m_statistics.setTimeSinceOpened(); // After the canboard is configured and started, we start the scan control thread - m_hReceiveThread = CreateThread(NULL, 0, CanScanControlThread, this, 0, &m_idReceiveThread); + m_hReceiveThread = CreateThread(NULL, 0, SystecRxThread, this, 0, &m_idReceiveThread); if (NULL == m_hReceiveThread) { LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; @@ -241,6 +185,7 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { } // return sendErrorCode(Status); }; + CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { // TODO we can read the operating mode, either kUcanModeNormal, ListenOnly or TxEcho @@ -300,12 +245,64 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { return diagnostics; }; +std::string UsbCanGetErrorText( long errCode ); // forward declaration + +/** + * thread to handle reception of Can messages from the systec device + */ +DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) +{ + BYTE status; + tCanMsgStruct readCanMessage; + CanVendorSystec *vendorPointer = reinterpret_cast(pCanVendorSystec); + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_CanScanThreadShutdownFlag = [" << vendorPointer->m_CanScanThreadShutdownFlag <<"]"; + while (vendorPointer->m_CanScanThreadShutdownFlag) { + status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, NULL); + switch (status) { + case USBCAN_WARN_SYS_RXOVERRUN: // fallthrough intended for warnings + case USBCAN_WARN_DLL_RXOVERRUN: + case USBCAN_WARN_FW_RXOVERRUN: + LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); + case USBCAN_SUCCESSFUL: { + if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) break; + // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); + std::vector data(8); + std::copy(readCanMessage.m_bData, readCanMessage.m_bData + 8, data.begin()); + // id, data, flags + CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); + // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" + vendorPointer->received(canMsgCopy); + // vendorPointer->m_statistics.onReceive( readCanMessage.m_bDLC ); + // vendorPointer->m_statistics.setTimeSinceReceived(); + + // we can reset the reconnectionTimeout here, since we have received a message + // vendorPointer->resetTimeoutOnReception(); + break; + } + case USBCAN_WARN_NODATA: + LOG(Log::TRC, CanLogIt::h()) << UsbCanGetErrorText(status); + // TODO is it correct to sleep here? + // Sleep(100); // ms + break; + default: // errors + // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, + // USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL + // TODO should we raise some error state here? + LOG(Log::ERR, CanLogIt::h()) << UsbCanGetErrorText(status); + break; + } + } + + ExitThread(0); + return 0; +} + /** * error text specific to STcan according to table24 * I am just copying the whole descriptions from the doc, verbatim, wtf. * you get some shakespeare from it. */ -std::string STcanGetErrorText( long errCode ){ +std::string UsbCanGetErrorText( long errCode ){ switch( errCode ){ case USBCAN_SUCCESSFUL: return("success"); From dbc8a1e4b7f239b63930a511b606c961e9f4fe90 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Feb 2026 16:27:40 +0100 Subject: [PATCH 09/63] fix whitespace issue with warning messages for Systec vendor --- src/main/CanVendorSystec.cpp | 296 +++++++++++++++++------------------ 1 file changed, 148 insertions(+), 148 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index b595f859..560ed768 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -306,154 +306,154 @@ std::string UsbCanGetErrorText( long errCode ){ switch( errCode ){ case USBCAN_SUCCESSFUL: return("success"); - case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this\ - case the term resource means memory and handles provided by the Windows OS"); - - case USBCAN_ERR_MAXMODULES: return("An application has tried to open more than 64 USB-CANmodul devices.\ - The standard version of the DLL only supports up to 64 USB-CANmodul\ - devices at the same time. This error also appears if several applications\ - try to access more than 64 USB-CANmodul devices. For example,\ - application 1 has opened 60 modules, application 2 has opened 4\ - modules and application 3 wants to open a module. Application 3\ - receives this error code."); - - case USBCAN_ERR_HWINUSE: return("An application tries to initialize an USB-CANmodul with the given device\ - number. If this module has already been initialized by its own or by\ - another application, this error code is returned."); - - case USBCAN_ERR_ILLVERSION: return("This error code returns if the firmware version of the USB-CANmodul is\ - not compatible to the software version of the DLL. In this case, install\ - the latest driver for the USB-CANmodul. Furthermore make sure that\ - the latest firmware version is programmed to the USB-CANmodul."); - - case USBCAN_ERR_ILLHW: return("This error code returns if an USB-CANmodul with the given device\ - number is not found. If the function UcanInitHardware() or\ - UcanInitHardwareEx() has been called with the device number\ - USBCAN_ANY_MODULE, and the error code appears, it indicates that\ - no module is connected to the PC or all connected modules are already\ - in use."); - - case USBCAN_ERR_ILLHANDLE: return("This error code returns if a function received an incorrect USBCAN\ - handle. The function first checks which USB-CANmodul is related to this\ - handle. This error occurs if no device belongs this handle."); - - case USBCAN_ERR_ILLPARAM: return("This error code returns if a wrong parameter is passed to the function.\ - For example, the value NULL has been passed to a pointer variable\ - instead of a valid address."); - - case USBCAN_ERR_BUSY: return("This error code occurs if several threads are accessing an\ - USB-CANmodul within a single application. After the other threads have\ - finished their tasks, the function may be called again."); - - case USBCAN_ERR_TIMEOUT: return("This error code occurs if the function transmits a command to the\ - USB-CANmodul but no reply is returned. To solve this problem, close\ - the application, disconnect the USB-CANmodul, and connect it again."); - - case USBCAN_ERR_IOFAILED: return("This error code occurs if the communication to the kernel driver was\ - interrupted. This happens, for example, if the USB-CANmodul is\ - disconnected during transferring data or commands to the\ - USB-CANmodul."); - - case USBCAN_ERR_DLL_TXFULL: return("The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks\ - if the transmit buffer within the DLL has enough capacity to store new\ - CAN messages. If the buffer is full, this error code returns. The CAN\ - message passed to these functions will not be written into the transmit\ - buffer in order to protect other CAN messages against overwriting. The\ - size of the transmit buffer is configurable (refer to function\ - UcanInitCanEx() and structure tUcanInitCanParam)."); - - case USBCAN_ERR_MAXINSTANCES: return("A maximum amount of 64 applications are able to have access to the\ - DLL. If more applications attempting to access to the DLL, this error\ - code is returned. In this case, it is not possible to use an\ - USB-CANmodul by this application."); - - case USBCAN_ERR_CANNOTINIT: return("This error code returns if an application tries to call an API function\ - which only can be called in software state CAN_INIT but the current\ - software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for\ - detailed information."); - - case USBCAN_ERR_DISCONNECT: return("This error code occurs if an API function was called for an\ - USB-CANmodul that was plugged-off from the computer recently."); - - case USBCAN_ERR_ILLCHANNEL: return("This error code is returned if an extended function of the DLL is called\ - with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."); - - case USBCAN_ERR_ILLHWTYPE: return("This error code occurs if an extended function of the DLL was called for\ - a hardware which does not support the feature."); - - case USBCAN_ERRCMD_NOTEQU: return("This error code occurs during communication between the PC and an\ - USB-CANmodul. The PC sends a command to the USB-CANmodul,\ - then the module executes the command and returns a response to the\ - PC. This error code returns if the reply does not correspond to the command."); - - case USBCAN_ERRCMD_REGTST: return("The software tests the CAN controller on the USB-CANmodul when the\ - CAN interface is initialized. Several registers of the CAN controller are\ - checked. This error code returns if an error appears during this register test."); - - case USBCAN_ERRCMD_ILLCMD: return("This error code returns if the USB-CANmodul receives a non-defined\ - command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."); - - case USBCAN_ERRCMD_EEPROM: return("The USB-CANmodul has a built-in EEPROM. This EEPROM contains\ - several configurations, e.g. the device number and the serial number. If\ - an error occurs while reading these values, this error code is returned."); - - case USBCAN_ERRCMD_ILLBDR: return("The USB-CANmodul has been initialized with an invalid baud rate (refer\ - to section 4.3.4)."); - - case USBCAN_ERRCMD_NOTINIT: return("It was tried to access a CAN-channel of a multi-channel\ - USB-CANmodul that was not initialized."); - - case USBCAN_ERRCMD_ALREADYINIT: return("The accessed CAN-channel of a multi-channel USB-CANmodul was\ - already initialized"); - - case USBCAN_ERRCMD_ILLSUBCMD: return("An internal error occurred within the DLL. In this case an unknown sub-\ - command was called instead of a main command (e.g. for the cyclic CAN message-feature)."); - - case USBCAN_ERRCMD_ILLIDX: return("An internal error occurred within the DLL. In this case an invalid index\ - for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."); - - case USBCAN_ERRCMD_RUNNING: return("The caller tries to define a new list of cyclic CAN messages but this\ - feature was already started. For defining a new list, it is necessary to stop the feature beforehand."); - - case USBCAN_WARN_NODATA: return("If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns\ - with this warning, it is an indication that the receive buffer contains no CAN messages."); - - case USBCAN_WARN_SYS_RXOVERRUN: return("This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the\ - receive buffer within the kernel driver runs over. The function\ - nevertheless returns a valid CAN message. It also indicates that at least\ - one CAN message are lost. However, it does not indicate the position of the lost CAN messages."); - - case USBCAN_WARN_DLL_RXOVERRUN: return("The DLL automatically requests CAN messages from the\ - USB-CANmodul and stores the messages into a buffer of the DLL. If\ - more CAN messages are received than the DLL buffer size allows, this\ - error code returns and CAN messages are lost. However, it does not\ - indicate the position of the lost CAN messages. The size of the receive\ - buffer is configurable (refer to function UcanInitCanEx() and structure\ - tUcanInitCanParam)."); - - case USBCAN_WARN_FW_TXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or\ - UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in\ - the CAN driver status. However, the transmit CAN message could be\ - stored to the DLL transmit buffer. This warning indicates that at least\ - one transmit CAN message got lost in the device firmware layer. This\ - warning does not indicate the position of the lost CAN message."); - - case USBCAN_WARN_FW_RXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or\ - UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag\ - USBCAN_CANERR_OVERRUN are set in the CAN driver status. The\ - function has returned with a valid CAN message. This warning indicates\ - that at least one received CAN message got lost in the firmware layer.\ - This warning does not indicate the position of the lost CAN message."); - - case USBCAN_WARN_NULL_PTR: return("This warning is returned by functions UcanInitHwConnectControl() or\ - UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."); - - case USBCAN_WARN_TXLIMIT: return("This warning is returned by the function UcanWriteCanMsgEx() if it was\ - called to transmit more than one CAN message, but a part of them\ - could not be stored to the transmit buffer within the DLL (because the\ - buffer is full). The returned variable addressed by the parameter\ - pdwCount_p indicates the number of CAN messages which are stored\ - successfully to the transmit buffer."); + case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this " + "case the term resource means memory and handles provided by the Windows OS"); + + case USBCAN_ERR_MAXMODULES: return("An application has tried to open more than 64 USB-CANmodul devices. " + "The standard version of the DLL only supports up to 64 USB-CANmodul " + "devices at the same time. This error also appears if several applications " + "try to access more than 64 USB-CANmodul devices. For example, " + "application 1 has opened 60 modules, application 2 has opened 4 " + "modules and application 3 wants to open a module. Application 3 " + "receives this error code."); + + case USBCAN_ERR_HWINUSE: return("An application tries to initialize an USB-CANmodul with the given device " + "number. If this module has already been initialized by its own or by " + "another application, this error code is returned."); + + case USBCAN_ERR_ILLVERSION: return("This error code returns if the firmware version of the USB-CANmodul is " + "not compatible to the software version of the DLL. In this case, install " + "the latest driver for the USB-CANmodul. Furthermore make sure that " + "the latest firmware version is programmed to the USB-CANmodul."); + + case USBCAN_ERR_ILLHW: return("This error code returns if an USB-CANmodul with the given device " + "number is not found. If the function UcanInitHardware() or " + "UcanInitHardwareEx() has been called with the device number " + "USBCAN_ANY_MODULE, and the error code appears, it indicates that " + "no module is connected to the PC or all connected modules are already " + "in use."); + + case USBCAN_ERR_ILLHANDLE: return("This error code returns if a function received an incorrect USBCAN " + "handle. The function first checks which USB-CANmodul is related to this " + "handle. This error occurs if no device belongs this handle."); + + case USBCAN_ERR_ILLPARAM: return("This error code returns if a wrong parameter is passed to the function. " + "For example, the value NULL has been passed to a pointer variable " + "instead of a valid address."); + + case USBCAN_ERR_BUSY: return("This error code occurs if several threads are accessing an " + "USB-CANmodul within a single application. After the other threads have " + "finished their tasks, the function may be called again."); + + case USBCAN_ERR_TIMEOUT: return("This error code occurs if the function transmits a command to the " + "USB-CANmodul but no reply is returned. To solve this problem, close " + "the application, disconnect the USB-CANmodul, and connect it again."); + + case USBCAN_ERR_IOFAILED: return("This error code occurs if the communication to the kernel driver was " + "interrupted. This happens, for example, if the USB-CANmodul is " + "disconnected during transferring data or commands to the " + "USB-CANmodul."); + + case USBCAN_ERR_DLL_TXFULL: return("The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks " + "if the transmit buffer within the DLL has enough capacity to store new " + "CAN messages. If the buffer is full, this error code returns. The CAN " + "message passed to these functions will not be written into the transmit " + "buffer in order to protect other CAN messages against overwriting. The " + "size of the transmit buffer is configurable (refer to function " + "UcanInitCanEx() and structure tUcanInitCanParam)."); + + case USBCAN_ERR_MAXINSTANCES: return("A maximum amount of 64 applications are able to have access to the " + "DLL. If more applications attempting to access to the DLL, this error " + "code is returned. In this case, it is not possible to use an " + "USB-CANmodul by this application."); + + case USBCAN_ERR_CANNOTINIT: return("This error code returns if an application tries to call an API function " + "which only can be called in software state CAN_INIT but the current " + "software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for " + "detailed information."); + + case USBCAN_ERR_DISCONNECT: return("This error code occurs if an API function was called for an " + "USB-CANmodul that was plugged-off from the computer recently."); + + case USBCAN_ERR_ILLCHANNEL: return("This error code is returned if an extended function of the DLL is called " + "with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."); + + case USBCAN_ERR_ILLHWTYPE: return("This error code occurs if an extended function of the DLL was called for " + "a hardware which does not support the feature."); + + case USBCAN_ERRCMD_NOTEQU: return("This error code occurs during communication between the PC and an " + "USB-CANmodul. The PC sends a command to the USB-CANmodul, " + "then the module executes the command and returns a response to the " + "PC. This error code returns if the reply does not correspond to the command."); + + case USBCAN_ERRCMD_REGTST: return("The software tests the CAN controller on the USB-CANmodul when the " + "CAN interface is initialized. Several registers of the CAN controller are " + "checked. This error code returns if an error appears during this register test."); + + case USBCAN_ERRCMD_ILLCMD: return("This error code returns if the USB-CANmodul receives a non-defined " + "command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."); + + case USBCAN_ERRCMD_EEPROM: return("The USB-CANmodul has a built-in EEPROM. This EEPROM contains " + "several configurations, e.g. the device number and the serial number. If " + "an error occurs while reading these values, this error code is returned."); + + case USBCAN_ERRCMD_ILLBDR: return("The USB-CANmodul has been initialized with an invalid baud rate (refer " + "to section 4.3.4)."); + + case USBCAN_ERRCMD_NOTINIT: return("It was tried to access a CAN-channel of a multi-channel " + "USB-CANmodul that was not initialized."); + + case USBCAN_ERRCMD_ALREADYINIT: return("The accessed CAN-channel of a multi-channel USB-CANmodul was " + "already initialized"); + + case USBCAN_ERRCMD_ILLSUBCMD: return("An internal error occurred within the DLL. In this case an unknown sub- " + "command was called instead of a main command (e.g. for the cyclic CAN message-feature)."); + + case USBCAN_ERRCMD_ILLIDX: return("An internal error occurred within the DLL. In this case an invalid index " + "for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."); + + case USBCAN_ERRCMD_RUNNING: return("The caller tries to define a new list of cyclic CAN messages but this " + "feature was already started. For defining a new list, it is necessary to stop the feature beforehand."); + + case USBCAN_WARN_NODATA: return("If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " + "with this warning, it is an indication that the receive buffer contains no CAN messages."); + + case USBCAN_WARN_SYS_RXOVERRUN: return("This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the " + "receive buffer within the kernel driver runs over. The function " + "nevertheless returns a valid CAN message. It also indicates that at least " + "one CAN message are lost. However, it does not indicate the position of the lost CAN messages."); + + case USBCAN_WARN_DLL_RXOVERRUN: return("The DLL automatically requests CAN messages from the " + "USB-CANmodul and stores the messages into a buffer of the DLL. If " + "more CAN messages are received than the DLL buffer size allows, this " + "error code returns and CAN messages are lost. However, it does not " + "indicate the position of the lost CAN messages. The size of the receive " + "buffer is configurable (refer to function UcanInitCanEx() and structure " + "tUcanInitCanParam)."); + + case USBCAN_WARN_FW_TXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " + "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in " + "the CAN driver status. However, the transmit CAN message could be " + "stored to the DLL transmit buffer. This warning indicates that at least " + "one transmit CAN message got lost in the device firmware layer. This " + "warning does not indicate the position of the lost CAN message."); + + case USBCAN_WARN_FW_RXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " + "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag " + "USBCAN_CANERR_OVERRUN are set in the CAN driver status. The " + "function has returned with a valid CAN message. This warning indicates " + "that at least one received CAN message got lost in the firmware layer. " + "This warning does not indicate the position of the lost CAN message."); + + case USBCAN_WARN_NULL_PTR: return("This warning is returned by functions UcanInitHwConnectControl() or " + "UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."); + + case USBCAN_WARN_TXLIMIT: return("This warning is returned by the function UcanWriteCanMsgEx() if it was " + "called to transmit more than one CAN message, but a part of them " + "could not be stored to the transmit buffer within the DLL (because the " + "buffer is full). The returned variable addressed by the parameter " + "pdwCount_p indicates the number of CAN messages which are stored " + "successfully to the transmit buffer."); default: return("unknown error code"); } From 8517d00764fddeb3ca73047e4e2f6dfdf7d27a05 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Feb 2026 08:45:20 +0100 Subject: [PATCH 10/63] avoid std::copy by initializing vector with data directly --- src/main/CanVendorSystec.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 560ed768..8aa99564 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -29,7 +29,6 @@ #include #include #include -#include CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args) { @@ -266,8 +265,7 @@ DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) case USBCAN_SUCCESSFUL: { if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) break; // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); - std::vector data(8); - std::copy(readCanMessage.m_bData, readCanMessage.m_bData + 8, data.begin()); + std::vector data(readCanMessage.m_bData, readCanMessage.m_bData + 8); // id, data, flags CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" From b9179c7c75071312f6a5210931258a7a0dff5694 Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 26 Feb 2026 09:05:16 +0100 Subject: [PATCH 11/63] use lock guards on Systec handle map access small cleanups --- src/include/CanVendorSystec.h | 5 +- src/main/CanVendorSystec.cpp | 98 +++++++++++++++++++---------------- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 21576c2d..36a3ed21 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -44,7 +44,10 @@ struct CanVendorSystec : CanDevice { CanDiagnostics vendor_diagnostics() noexcept override; CanReturnCode init_can_port(); - + static std::mutex m_handles_lock; + + inline void map_module_to_handle(int module, tUcanHandle handle) { m_handleMap[module] = handle; } + inline int erase_module_handle(int module) { return m_handleMap.erase(module); } // TODO i don't like this too much inline static std::unordered_map m_handleMap = {}; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 8aa99564..855c8935 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -30,6 +30,8 @@ #include #include +std::mutex CanVendorSystec::m_handles_lock; + CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args) { if (!args.config.bus_name.has_value()) { @@ -42,30 +44,26 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) m_channelNumber = handleNumber % 2; } -/** - * We create and fill initializationParameters, to pass it to openCanPort - */ - /* static */ tUcanInitCanParam createInitializationParameters( unsigned int baudRate ){ - tUcanInitCanParam initializationParameters; - initializationParameters.m_dwSize = sizeof(initializationParameters); // size of this struct - initializationParameters.m_bMode = kUcanModeNormal; // normal operation mode - initializationParameters.m_bBTR0 = HIBYTE( baudRate ); // baudrate - initializationParameters.m_bBTR1 = LOBYTE( baudRate ); - initializationParameters.m_bOCR = 0x1A; // standard output - initializationParameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages - initializationParameters.m_dwACR = USBCAN_ACR_ALL; - initializationParameters.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; - initializationParameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - initializationParameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - - return ( initializationParameters ); -} - +// TODO should we make this noexcept? how can we guarantee that? CanReturnCode CanVendorSystec::init_can_port() { BYTE systecCallReturn = USBCAN_SUCCESSFUL; tUcanHandle canModuleHandle; + unsigned int baudRate = USBCAN_BAUD_125kBit; + switch (args().config.bitrate.value_or(0)) { + case 50000: baudRate = USBCAN_BAUD_50kBit; break; + case 100000: baudRate = USBCAN_BAUD_100kBit; break; + case 125000: baudRate = USBCAN_BAUD_125kBit; break; + case 250000: baudRate = USBCAN_BAUD_250kBit; break; + case 500000: baudRate = USBCAN_BAUD_500kBit; break; + case 1000000: baudRate = USBCAN_BAUD_1MBit; break; + default: { + LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baudRate << "]"; + } + } + // check if USB-CANmodul already is initialized + std::lock_guard guard(CanVendorSystec::m_handles_lock); auto pos = m_handleMap.find(m_moduleNumber); if (pos == m_handleMap.end()) { // module not in use systecCallReturn = ::UcanInitHardwareEx(&canModuleHandle, m_moduleNumber, 0, 0); @@ -74,25 +72,22 @@ CanReturnCode CanVendorSystec::init_can_port() { ::UcanDeinitHardware(canModuleHandle); return CanReturnCode::unknown_open_error; } - m_handleMap[m_moduleNumber] = canModuleHandle; + map_module_to_handle(m_moduleNumber, canModuleHandle); } else { // find existing handle of module canModuleHandle = pos->second; LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; } - - unsigned int baudRate = USBCAN_BAUD_125kBit; - switch (args().config.bitrate.value_or(0)) { - case 50000: baudRate = USBCAN_BAUD_50kBit; break; - case 100000: baudRate = USBCAN_BAUD_100kBit; break; - case 125000: baudRate = USBCAN_BAUD_125kBit; break; - case 250000: baudRate = USBCAN_BAUD_250kBit; break; - case 500000: baudRate = USBCAN_BAUD_500kBit; break; - case 1000000: baudRate = USBCAN_BAUD_1MBit; break; - default: { - LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baudRate << "]"; - } - } - auto initializationParameters = createInitializationParameters(baudRate); + tUcanInitCanParam initializationParameters; + initializationParameters.m_dwSize = sizeof(initializationParameters); // size of this struct + initializationParameters.m_bMode = kUcanModeNormal; // normal operation mode + initializationParameters.m_bBTR0 = HIBYTE( baudRate ); // baudrate + initializationParameters.m_bBTR1 = LOBYTE( baudRate ); + initializationParameters.m_bOCR = 0x1A; // standard output + initializationParameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages + initializationParameters.m_dwACR = USBCAN_ACR_ALL; + initializationParameters.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; + initializationParameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + initializationParameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; systecCallReturn = ::UcanInitCanEx2(canModuleHandle, m_channelNumber, &initializationParameters); if ( systecCallReturn != USBCAN_SUCCESSFUL ) { @@ -109,6 +104,7 @@ CanReturnCode CanVendorSystec::init_can_port() { CanReturnCode CanVendorSystec::vendor_open() noexcept { auto returnCode = init_can_port(); + if (returnCode != CanReturnCode::success) return returnCode; // TODO set time since opened equivalent... // m_statistics.setTimeSinceOpened(); @@ -118,7 +114,7 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { if (NULL == m_hReceiveThread) { LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; - return CanReturnCode::unknown_open_error; + return CanReturnCode::internal_api_error; } return returnCode; @@ -126,6 +122,8 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { CanReturnCode CanVendorSystec::vendor_close() noexcept { // TODO what if the return code is not success? + std::lock_guard guard(CanVendorSystec::m_handles_lock); + erase_module_handle(m_moduleNumber); m_CanScanThreadShutdownFlag = false; DWORD result = WaitForSingleObject(m_hReceiveThread, INFINITE); //Shut down can scan thread UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channelNumber); @@ -138,9 +136,7 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { bool rtr = frame.is_remote_request(); uint32_t len = frame.length(); char *message = frame.message().data(); - short cobID = frame.id(); // is this the same as cobid? i don't know... - - // TODO we can't just use message as it's now a vector of chars not a char* + short cobID = frame.id(); LOG(Log::DBG, CanLogIt::h()) << "Sending message: [" << ( message == 0 ? "" : (const char *) message) << "], cobID: [" << cobID << "], Message Length: [" << static_cast(len) << "]"; @@ -172,17 +168,27 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { if (Status != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message."; - // for now, just always reconnect on a failed send. - vendor_close(); - vendor_open(); - - return CanReturnCode::unknown_send_error; - } else { + // for now, just always reconnect on a failed send. + vendor_close(); // TODO maybe we just call close instead of vendor_close + // see how CanVendorSocketCanSystec does reconnects, it intercepts the receiver function and wraps it + vendor_open(); + + switch (Status) { + case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; + case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; + case USBCAN_ERR_CANNOTINIT: return CanReturnCode::unknown_open_error; // maybe disconnected would be better + case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; + case USBCAN_ERR_ILLPARAM: // fallthrough + case USBCAN_ERR_ILLHW: + case USBCAN_ERR_ILLCHANNEL: + case USBCAN_WARN_FW_TXOVERRUN: // TODO should warnings return an error? + case USBCAN_WARN_TXLIMIT: + default: return CanReturnCode::unknown_send_error; + } // m_statistics.onTransmit( canMsgToBeSent.m_bDLC ); // m_statistics.setTimeSinceTransmitted(); - return CanReturnCode::success; } - // return sendErrorCode(Status); + return CanReturnCode::success; }; CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { From e9c11b5eefd54c4dfb496f598d29c7e092e9ae1c Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 26 Feb 2026 09:37:35 +0100 Subject: [PATCH 12/63] use snake_case in CanVendorSystec --- src/include/CanVendorSystec.h | 16 ++-- src/main/CanVendorSystec.cpp | 158 +++++++++++++++++----------------- 2 files changed, 88 insertions(+), 86 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 36a3ed21..4e14e040 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -32,12 +32,12 @@ struct CanVendorSystec : CanDevice { static DWORD WINAPI SystecRxThread(LPVOID pCanVendorSystec); private: - bool m_CanScanThreadShutdownFlag = true; + bool m_receive_thread_flag = true; tUcanHandle m_UcanHandle; - int m_moduleNumber; - int m_channelNumber; - HANDLE m_hReceiveThread; - DWORD m_idReceiveThread; + int m_module_number; + int m_channel_number; + HANDLE m_receive_thread_handle; + DWORD m_receive_thread_id; CanReturnCode vendor_open() noexcept override; CanReturnCode vendor_close() noexcept override; CanReturnCode vendor_send(const CanFrame& frame) noexcept override; @@ -46,11 +46,11 @@ struct CanVendorSystec : CanDevice { CanReturnCode init_can_port(); static std::mutex m_handles_lock; - inline void map_module_to_handle(int module, tUcanHandle handle) { m_handleMap[module] = handle; } - inline int erase_module_handle(int module) { return m_handleMap.erase(module); } + inline void map_module_to_handle(int module, tUcanHandle handle) { m_handle_map[module] = handle; } + inline int erase_module_handle(int module) { return m_handle_map.erase(module); } // TODO i don't like this too much - inline static std::unordered_map m_handleMap = {}; + inline static std::unordered_map m_handle_map = {}; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 855c8935..5a7ffe54 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -39,65 +39,66 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) } // TODO trim possible can prefix use hardcoded value - int handleNumber = std::stoi(args.config.bus_name.value()); - m_moduleNumber = handleNumber / 2; - m_channelNumber = handleNumber % 2; + int handle_number = std::stoi(args.config.bus_name.value()); + m_module_number = handle_number / 2; + m_channel_number = handle_number % 2; } // TODO should we make this noexcept? how can we guarantee that? CanReturnCode CanVendorSystec::init_can_port() { - BYTE systecCallReturn = USBCAN_SUCCESSFUL; - tUcanHandle canModuleHandle; + BYTE systec_call_return = USBCAN_SUCCESSFUL; + tUcanHandle can_module_handle; - unsigned int baudRate = USBCAN_BAUD_125kBit; + unsigned int baud_rate = USBCAN_BAUD_125kBit; switch (args().config.bitrate.value_or(0)) { - case 50000: baudRate = USBCAN_BAUD_50kBit; break; - case 100000: baudRate = USBCAN_BAUD_100kBit; break; - case 125000: baudRate = USBCAN_BAUD_125kBit; break; - case 250000: baudRate = USBCAN_BAUD_250kBit; break; - case 500000: baudRate = USBCAN_BAUD_500kBit; break; - case 1000000: baudRate = USBCAN_BAUD_1MBit; break; + case 50000: baud_rate = USBCAN_BAUD_50kBit; break; + case 100000: baud_rate = USBCAN_BAUD_100kBit; break; + case 125000: baud_rate = USBCAN_BAUD_125kBit; break; + case 250000: baud_rate = USBCAN_BAUD_250kBit; break; + case 500000: baud_rate = USBCAN_BAUD_500kBit; break; + case 1000000: baud_rate = USBCAN_BAUD_1MBit; break; default: { - LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baudRate << "]"; + LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baud_rate << "]"; } } + tUcanInitCanParam initialization_parameters; + initialization_parameters.m_dwSize = sizeof(initialization_parameters); // size of this struct + initialization_parameters.m_bMode = kUcanModeNormal; // normal operation mode + initialization_parameters.m_bBTR0 = HIBYTE( baud_rate ); // baudrate + initialization_parameters.m_bBTR1 = LOBYTE( baud_rate ); + initialization_parameters.m_bOCR = 0x1A; // standard output + initialization_parameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages + initialization_parameters.m_dwACR = USBCAN_ACR_ALL; + initialization_parameters.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; + initialization_parameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + initialization_parameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + // check if USB-CANmodul already is initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); - auto pos = m_handleMap.find(m_moduleNumber); - if (pos == m_handleMap.end()) { // module not in use - systecCallReturn = ::UcanInitHardwareEx(&canModuleHandle, m_moduleNumber, 0, 0); - if (systecCallReturn != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systecCallReturn << std::dec << "]"; - ::UcanDeinitHardware(canModuleHandle); + auto pos = m_handle_map.find(m_module_number); + if (pos == m_handle_map.end()) { // module not in use + systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, 0, 0); + if (systec_call_return != USBCAN_SUCCESSFUL ) { + LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; + ::UcanDeinitHardware(can_module_handle); return CanReturnCode::unknown_open_error; } - map_module_to_handle(m_moduleNumber, canModuleHandle); + map_module_to_handle(m_module_number, can_module_handle); } else { // find existing handle of module - canModuleHandle = pos->second; + can_module_handle = pos->second; LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; } - tUcanInitCanParam initializationParameters; - initializationParameters.m_dwSize = sizeof(initializationParameters); // size of this struct - initializationParameters.m_bMode = kUcanModeNormal; // normal operation mode - initializationParameters.m_bBTR0 = HIBYTE( baudRate ); // baudrate - initializationParameters.m_bBTR1 = LOBYTE( baudRate ); - initializationParameters.m_bOCR = 0x1A; // standard output - initializationParameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages - initializationParameters.m_dwACR = USBCAN_ACR_ALL; - initializationParameters.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; - initializationParameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - initializationParameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - - systecCallReturn = ::UcanInitCanEx2(canModuleHandle, m_channelNumber, &initializationParameters); - if ( systecCallReturn != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systecCallReturn << std::dec << "]"; - ::UcanDeinitCanEx(canModuleHandle, m_channelNumber); + + systec_call_return = ::UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); + if ( systec_call_return != USBCAN_SUCCESSFUL ) { + LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; + ::UcanDeinitCanEx(can_module_handle, m_channel_number); return CanReturnCode::unknown_open_error; } - m_UcanHandle = canModuleHandle; - LOG(Log::INF, CanLogIt::h()) << "Successfully opened CAN port on module " << m_moduleNumber << ", channel " << m_channelNumber; + m_UcanHandle = can_module_handle; + LOG(Log::INF, CanLogIt::h()) << "Successfully opened CAN port on module " << m_module_number << ", channel " << m_channel_number; return CanReturnCode::success; } @@ -110,9 +111,10 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { // m_statistics.setTimeSinceOpened(); // After the canboard is configured and started, we start the scan control thread - m_hReceiveThread = CreateThread(NULL, 0, SystecRxThread, this, 0, &m_idReceiveThread); + m_receive_thread_flag = true; + m_receive_thread_handle = CreateThread(NULL, 0, SystecRxThread, this, 0, &m_receive_thread_id); - if (NULL == m_hReceiveThread) { + if (NULL == m_receive_thread_handle) { LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; return CanReturnCode::internal_api_error; } @@ -123,10 +125,10 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { CanReturnCode CanVendorSystec::vendor_close() noexcept { // TODO what if the return code is not success? std::lock_guard guard(CanVendorSystec::m_handles_lock); - erase_module_handle(m_moduleNumber); - m_CanScanThreadShutdownFlag = false; - DWORD result = WaitForSingleObject(m_hReceiveThread, INFINITE); //Shut down can scan thread - UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channelNumber); + erase_module_handle(m_module_number); + m_receive_thread_flag = false; + DWORD result = WaitForSingleObject(m_receive_thread_handle, INFINITE); //Shut down can scan thread + UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channel_number); LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; return CanReturnCode::success; }; @@ -140,31 +142,31 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { LOG(Log::DBG, CanLogIt::h()) << "Sending message: [" << ( message == 0 ? "" : (const char *) message) << "], cobID: [" << cobID << "], Message Length: [" << static_cast(len) << "]"; - tCanMsgStruct canMsgToBeSent; + tCanMsgStruct can_msg_to_send; BYTE Status; - canMsgToBeSent.m_dwID = cobID; - canMsgToBeSent.m_bDLC = len; - canMsgToBeSent.m_bFF = 0; + can_msg_to_send.m_dwID = cobID; + can_msg_to_send.m_bDLC = len; + can_msg_to_send.m_bFF = 0; if (rtr) { - canMsgToBeSent.m_bFF = USBCAN_MSG_FF_RTR; + can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; } - int messageLengthToBeProcessed; + int message_length_to_process; //If there is more than 8 characters to process, we process 8 of them in this iteration of the loop if (len > 8) { - messageLengthToBeProcessed = 8; + message_length_to_process = 8; LOG(Log::DBG, CanLogIt::h()) << "The length is more then 8 bytes, adjust to 8, ignore >8. len= " << len; } else { //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop - messageLengthToBeProcessed = len; + message_length_to_process = len; if (len < 8) { LOG(Log::DBG, CanLogIt::h())<< "The length is less then 8 bytes, process only. len= " << len; } } - canMsgToBeSent.m_bDLC = messageLengthToBeProcessed; - memcpy(canMsgToBeSent.m_bData, message, messageLengthToBeProcessed); - // MLOG(TRC,this) << "Channel Number: [" << m_channelNumber << "], cobID: [" << canMsgToBeSent.m_dwID << "], Message Length: [" << static_cast(canMsgToBeSent.m_bDLC) << "]"; - Status = UcanWriteCanMsgEx(m_UcanHandle, m_channelNumber, &canMsgToBeSent, NULL); + can_msg_to_send.m_bDLC = message_length_to_process; + memcpy(can_msg_to_send.m_bData, message, message_length_to_process); + // MLOG(TRC,this) << "Channel Number: [" << m_channel_number << "], cobID: [" << can_msg_to_send.m_dwID << "], Message Length: [" << static_cast(can_msg_to_send.m_bDLC) << "]"; + Status = UcanWriteCanMsgEx(m_UcanHandle, m_channel_number, &can_msg_to_send, NULL); if (Status != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message."; @@ -185,7 +187,7 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { case USBCAN_WARN_TXLIMIT: default: return CanReturnCode::unknown_send_error; } - // m_statistics.onTransmit( canMsgToBeSent.m_bDLC ); + // m_statistics.onTransmit( can_msg_to_send.m_bDLC ); // m_statistics.setTimeSinceTransmitted(); } return CanReturnCode::success; @@ -197,7 +199,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { CanDiagnostics diagnostics{}; tStatusStruct status; // TODO check return code of these functions... - UcanGetStatusEx(m_UcanHandle, m_channelNumber, &status); + UcanGetStatusEx(m_UcanHandle, m_channel_number, &status); WORD can_status = status.m_wCanStatus; switch (can_status) { case USBCAN_CANERR_OK: @@ -223,12 +225,12 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { } tUcanMsgCountInfo msg_count_info; - UcanGetMsgCountInfoEx(m_UcanHandle, m_channelNumber, &msg_count_info); + UcanGetMsgCountInfoEx(m_UcanHandle, m_channel_number, &msg_count_info); diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; DWORD tx_error, rx_error; - UcanGetCanErrorCounter(m_UcanHandle, m_channelNumber, &tx_error, &rx_error); + UcanGetCanErrorCounter(m_UcanHandle, m_channel_number, &tx_error, &rx_error); diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; @@ -250,7 +252,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { return diagnostics; }; -std::string UsbCanGetErrorText( long errCode ); // forward declaration +std::string UsbCanGetErrorText( long err_code ); // forward declaration /** * thread to handle reception of Can messages from the systec device @@ -258,29 +260,29 @@ std::string UsbCanGetErrorText( long errCode ); // forward declaration DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) { BYTE status; - tCanMsgStruct readCanMessage; - CanVendorSystec *vendorPointer = reinterpret_cast(pCanVendorSystec); - LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_CanScanThreadShutdownFlag = [" << vendorPointer->m_CanScanThreadShutdownFlag <<"]"; - while (vendorPointer->m_CanScanThreadShutdownFlag) { - status = UcanReadCanMsgEx(vendorPointer->m_UcanHandle, (BYTE *)&vendorPointer->m_channelNumber, &readCanMessage, NULL); + tCanMsgStruct read_can_message; + CanVendorSystec *vendor_pointer = reinterpret_cast(pCanVendorSystec); + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << vendor_pointer->m_receive_thread_flag <<"]"; + while (vendor_pointer->m_receive_thread_flag) { + status = UcanReadCanMsgEx(vendor_pointer->m_UcanHandle, (BYTE *)&vendor_pointer->m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: // fallthrough intended for warnings case USBCAN_WARN_DLL_RXOVERRUN: case USBCAN_WARN_FW_RXOVERRUN: LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); case USBCAN_SUCCESSFUL: { - if (readCanMessage.m_bFF & USBCAN_MSG_FF_RTR) break; - // canMsgCopy.c_time = convertTimepointToTimeval(currentTimeTimeval()); - std::vector data(readCanMessage.m_bData, readCanMessage.m_bData + 8); + if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; + // can_msg_copy.c_time = convertTimepointToTimeval(currentTimeTimeval()); + std::vector data(read_can_message.m_bData, read_can_message.m_bData + 8); // id, data, flags - CanFrame canMsgCopy(readCanMessage.m_dwID, data, readCanMessage.m_bFF); - // TODO the readCanMessage contains a DWORD m_dwTime "receipt time in ms" - vendorPointer->received(canMsgCopy); - // vendorPointer->m_statistics.onReceive( readCanMessage.m_bDLC ); - // vendorPointer->m_statistics.setTimeSinceReceived(); + CanFrame can_msg_copy(read_can_message.m_dwID, data, read_can_message.m_bFF); + // TODO the read_can_message contains a DWORD m_dwTime "receipt time in ms" + vendor_pointer->received(can_msg_copy); + // vendor_pointer->m_statistics.onReceive( read_can_message.m_bDLC ); + // vendor_pointer->m_statistics.setTimeSinceReceived(); // we can reset the reconnectionTimeout here, since we have received a message - // vendorPointer->resetTimeoutOnReception(); + // vendor_pointer->resetTimeoutOnReception(); break; } case USBCAN_WARN_NODATA: @@ -306,8 +308,8 @@ DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) * I am just copying the whole descriptions from the doc, verbatim, wtf. * you get some shakespeare from it. */ -std::string UsbCanGetErrorText( long errCode ){ - switch( errCode ){ +std::string UsbCanGetErrorText( long err_code ){ + switch( err_code ){ case USBCAN_SUCCESSFUL: return("success"); case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this " From a9cf432a40198911bcb5b781e3b295a90ddeec2f Mon Sep 17 00:00:00 2001 From: James Souter Date: Fri, 27 Feb 2026 17:02:00 +0100 Subject: [PATCH 13/63] replace Windows specific thread calls with std library calls --- src/include/CanVendorSystec.h | 6 +++--- src/main/CanVendorSystec.cpp | 36 +++++++++++++++++------------------ 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 4e14e040..d3f16820 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -14,6 +14,7 @@ #include "CanVendorLoopback.h" #include "CanDevice.h" #include +#include /** * @struct CanVendorSystec @@ -29,15 +30,14 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } - static DWORD WINAPI SystecRxThread(LPVOID pCanVendorSystec); + int SystecRxThread(); private: bool m_receive_thread_flag = true; tUcanHandle m_UcanHandle; int m_module_number; int m_channel_number; - HANDLE m_receive_thread_handle; - DWORD m_receive_thread_id; + std::thread m_SystecRxThread; CanReturnCode vendor_open() noexcept override; CanReturnCode vendor_close() noexcept override; CanReturnCode vendor_send(const CanFrame& frame) noexcept override; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 5a7ffe54..2b295112 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -112,12 +112,13 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { // After the canboard is configured and started, we start the scan control thread m_receive_thread_flag = true; - m_receive_thread_handle = CreateThread(NULL, 0, SystecRxThread, this, 0, &m_receive_thread_id); + m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); - if (NULL == m_receive_thread_handle) { - LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; - return CanReturnCode::internal_api_error; - } + // todo reintroduce check here... + // if (NULL == m_receive_thread_handle) { + // LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; + // return CanReturnCode::internal_api_error; + // } return returnCode; } @@ -127,14 +128,13 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { std::lock_guard guard(CanVendorSystec::m_handles_lock); erase_module_handle(m_module_number); m_receive_thread_flag = false; - DWORD result = WaitForSingleObject(m_receive_thread_handle, INFINITE); //Shut down can scan thread - UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channel_number); - LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; + m_SystecRxThread.join(); + UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channel_number); + LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; return CanReturnCode::success; }; CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { - // bool CanVendorSystec::sendMessage(short cobID, unsigned char len, unsigned char *message, bool rtr) bool rtr = frame.is_remote_request(); uint32_t len = frame.length(); char *message = frame.message().data(); @@ -257,14 +257,13 @@ std::string UsbCanGetErrorText( long err_code ); // forward declaration /** * thread to handle reception of Can messages from the systec device */ -DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) +int CanVendorSystec::SystecRxThread() { BYTE status; tCanMsgStruct read_can_message; - CanVendorSystec *vendor_pointer = reinterpret_cast(pCanVendorSystec); - LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << vendor_pointer->m_receive_thread_flag <<"]"; - while (vendor_pointer->m_receive_thread_flag) { - status = UcanReadCanMsgEx(vendor_pointer->m_UcanHandle, (BYTE *)&vendor_pointer->m_channel_number, &read_can_message, NULL); + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << this->m_receive_thread_flag <<"]"; + while (this->m_receive_thread_flag) { + status = UcanReadCanMsgEx(this->m_UcanHandle, (BYTE *)&this->m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: // fallthrough intended for warnings case USBCAN_WARN_DLL_RXOVERRUN: @@ -277,12 +276,12 @@ DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) // id, data, flags CanFrame can_msg_copy(read_can_message.m_dwID, data, read_can_message.m_bFF); // TODO the read_can_message contains a DWORD m_dwTime "receipt time in ms" - vendor_pointer->received(can_msg_copy); - // vendor_pointer->m_statistics.onReceive( read_can_message.m_bDLC ); - // vendor_pointer->m_statistics.setTimeSinceReceived(); + this->received(can_msg_copy); + // this->m_statistics.onReceive( read_can_message.m_bDLC ); + // this->m_statistics.setTimeSinceReceived(); // we can reset the reconnectionTimeout here, since we have received a message - // vendor_pointer->resetTimeoutOnReception(); + // this->resetTimeoutOnReception(); break; } case USBCAN_WARN_NODATA: @@ -299,7 +298,6 @@ DWORD WINAPI CanVendorSystec::SystecRxThread(LPVOID pCanVendorSystec) } } - ExitThread(0); return 0; } From 93fd7c5f29c7364c5cf75f477de7b87ebe12efff Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 2 Mar 2026 09:43:59 +0100 Subject: [PATCH 14/63] don't copy beyond m_bDLC into Systec data vector --- src/main/CanVendorSystec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 2b295112..1ac846f7 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -272,7 +272,7 @@ int CanVendorSystec::SystecRxThread() case USBCAN_SUCCESSFUL: { if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; // can_msg_copy.c_time = convertTimepointToTimeval(currentTimeTimeval()); - std::vector data(read_can_message.m_bData, read_can_message.m_bData + 8); + std::vector data(read_can_message.m_bData, read_can_message.m_bData + read_can_message.m_bDLC); // id, data, flags CanFrame can_msg_copy(read_can_message.m_dwID, data, read_can_message.m_bFF); // TODO the read_can_message contains a DWORD m_dwTime "receipt time in ms" From ebab3d2609f3396dc85f015144e301d525780a47 Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 2 Mar 2026 09:44:31 +0100 Subject: [PATCH 15/63] remove comments copies from STCanScan.cpp --- src/main/CanVendorSystec.cpp | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 1ac846f7..56d3533c 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -1,27 +1,3 @@ -/** © Copyright CERN, 2015. All rights not expressly granted are reserved. - * - * STCanScap.cpp - * - * Created on: Jul 21, 2011 - * Based on work by vfilimon - * Rework and logging done by Piotr Nikiel - * mludwig at cern dot ch - * - * This file is part of Quasar. - * - * Quasar is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public Licence as published by - * the Free Software Foundation, either version 3 of the Licence. - * - * Quasar is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public Licence for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Quasar. If not, see . - */ - #include "CanVendorSystec.h" #include @@ -301,11 +277,6 @@ int CanVendorSystec::SystecRxThread() return 0; } -/** - * error text specific to STcan according to table24 - * I am just copying the whole descriptions from the doc, verbatim, wtf. - * you get some shakespeare from it. - */ std::string UsbCanGetErrorText( long err_code ){ switch( err_code ){ case USBCAN_SUCCESSFUL: return("success"); From 540c0266dc2523d4f80c3638a061f7d3d60d78a4 Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 2 Mar 2026 09:46:19 +0100 Subject: [PATCH 16/63] use fallthrough attributes --- src/main/CanVendorSystec.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 56d3533c..fad26135 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -241,9 +241,9 @@ int CanVendorSystec::SystecRxThread() while (this->m_receive_thread_flag) { status = UcanReadCanMsgEx(this->m_UcanHandle, (BYTE *)&this->m_channel_number, &read_can_message, NULL); switch (status) { - case USBCAN_WARN_SYS_RXOVERRUN: // fallthrough intended for warnings - case USBCAN_WARN_DLL_RXOVERRUN: - case USBCAN_WARN_FW_RXOVERRUN: + case USBCAN_WARN_SYS_RXOVERRUN: [[ fallthrough ]]; + case USBCAN_WARN_DLL_RXOVERRUN: [[ fallthrough ]]; + case USBCAN_WARN_FW_RXOVERRUN: [[ fallthrough ]]; LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); case USBCAN_SUCCESSFUL: { if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; From 4850aff1666ee6b73710588bd017f1860250cc08 Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 2 Mar 2026 13:06:11 +0100 Subject: [PATCH 17/63] Log send errors in systec and use fallthrough attributes --- src/main/CanVendorSystec.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index fad26135..94a3fc1e 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -7,6 +7,7 @@ #include std::mutex CanVendorSystec::m_handles_lock; +std::string UsbCanGetErrorText( long err_code ); // forward declaration CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args) { @@ -144,7 +145,8 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { // MLOG(TRC,this) << "Channel Number: [" << m_channel_number << "], cobID: [" << can_msg_to_send.m_dwID << "], Message Length: [" << static_cast(can_msg_to_send.m_bDLC) << "]"; Status = UcanWriteCanMsgEx(m_UcanHandle, m_channel_number, &can_msg_to_send, NULL); if (Status != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message."; + LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " + << UsbCanGetErrorText(Status); // for now, just always reconnect on a failed send. vendor_close(); // TODO maybe we just call close instead of vendor_close @@ -153,14 +155,14 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { switch (Status) { case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; + case USBCAN_ERR_CANNOTINIT: [[ fallthrough ]]; case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; - case USBCAN_ERR_CANNOTINIT: return CanReturnCode::unknown_open_error; // maybe disconnected would be better case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; - case USBCAN_ERR_ILLPARAM: // fallthrough - case USBCAN_ERR_ILLHW: - case USBCAN_ERR_ILLCHANNEL: - case USBCAN_WARN_FW_TXOVERRUN: // TODO should warnings return an error? - case USBCAN_WARN_TXLIMIT: + case USBCAN_ERR_ILLPARAM: [[ fallthrough ]]; + case USBCAN_ERR_ILLHW: [[ fallthrough ]]; + case USBCAN_ERR_ILLCHANNEL: [[ fallthrough ]]; + case USBCAN_WARN_TXLIMIT: [[ fallthrough ]]; + case USBCAN_WARN_FW_TXOVERRUN: [[ fallthrough ]]; default: return CanReturnCode::unknown_send_error; } // m_statistics.onTransmit( can_msg_to_send.m_bDLC ); @@ -228,7 +230,6 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { return diagnostics; }; -std::string UsbCanGetErrorText( long err_code ); // forward declaration /** * thread to handle reception of Can messages from the systec device From 70f52a8f0d922c9db8b6b9fe71ba62bc149d1dee Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 09:55:24 +0100 Subject: [PATCH 18/63] only join systec thread if joinable --- src/main/CanVendorSystec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 94a3fc1e..58ed883f 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -105,7 +105,7 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { std::lock_guard guard(CanVendorSystec::m_handles_lock); erase_module_handle(m_module_number); m_receive_thread_flag = false; - m_SystecRxThread.join(); + if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channel_number); LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; return CanReturnCode::success; From 88c95610ff444d6e6d5eb34115baa002dad028b8 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 10:07:45 +0100 Subject: [PATCH 19/63] switch case formatting --- src/main/CanVendorSystec.cpp | 42 ++++++++++++------------------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 58ed883f..9535731d 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -15,7 +15,7 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) throw std::invalid_argument("Missing required configuration parameters"); } - // TODO trim possible can prefix use hardcoded value + // TODO trim possible can prefix int handle_number = std::stoi(args.config.bus_name.value()); m_module_number = handle_number / 2; m_channel_number = handle_number % 2; @@ -180,28 +180,17 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { UcanGetStatusEx(m_UcanHandle, m_channel_number, &status); WORD can_status = status.m_wCanStatus; switch (can_status) { - case USBCAN_CANERR_OK: - diagnostics.state = "USBCAN_CANERR_OK"; break; - case USBCAN_CANERR_XMTFULL: - diagnostics.state = "USBCAN_CANERR_XMTFULL"; break; - case USBCAN_CANERR_OVERRUN: - diagnostics.state = "USBCAN_CANERR_OVERRUN"; break; - case USBCAN_CANERR_BUSLIGHT: - diagnostics.state = "USBCAN_CANERR_BUSLIGHT"; break; - case USBCAN_CANERR_BUSHEAVY: - diagnostics.state = "USBCAN_CANERR_BUSHEAVY"; break; - case USBCAN_CANERR_BUSOFF: - diagnostics.state = "USBCAN_CANERR_BUSOFF"; break; - case USBCAN_CANERR_QOVERRUN: - diagnostics.state = "USBCAN_CANERR_QOVERRUN"; break; - case USBCAN_CANERR_QXMTFULL: - diagnostics.state = "USBCAN_CANERR_QXMTFULL"; break; - case USBCAN_CANERR_REGTEST: - diagnostics.state = "USBCAN_CANERR_REGTEST"; break; - case USBCAN_CANERR_TXMSGLOST: - diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; break; + case USBCAN_CANERR_OK: diagnostics.state = "USBCAN_CANERR_OK"; break; + case USBCAN_CANERR_XMTFULL: diagnostics.state = "USBCAN_CANERR_XMTFULL"; break; + case USBCAN_CANERR_OVERRUN: diagnostics.state = "USBCAN_CANERR_OVERRUN"; break; + case USBCAN_CANERR_BUSLIGHT: diagnostics.state = "USBCAN_CANERR_BUSLIGHT"; break; + case USBCAN_CANERR_BUSHEAVY: diagnostics.state = "USBCAN_CANERR_BUSHEAVY"; break; + case USBCAN_CANERR_BUSOFF: diagnostics.state = "USBCAN_CANERR_BUSOFF"; break; + case USBCAN_CANERR_QOVERRUN: diagnostics.state = "USBCAN_CANERR_QOVERRUN"; break; + case USBCAN_CANERR_QXMTFULL: diagnostics.state = "USBCAN_CANERR_QXMTFULL"; break; + case USBCAN_CANERR_REGTEST: diagnostics.state = "USBCAN_CANERR_REGTEST"; break; + case USBCAN_CANERR_TXMSGLOST: diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; break; } - tUcanMsgCountInfo msg_count_info; UcanGetMsgCountInfoEx(m_UcanHandle, m_channel_number, &msg_count_info); diagnostics.tx = msg_count_info.m_wSentMsgCount; @@ -216,12 +205,9 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { if (UcanGetHardwareInfo(m_UcanHandle, &hw_info) != USBCAN_SUCCESSFUL) diagnostics.mode = "OFFLINE"; else switch (hw_info.m_bMode) { - case kUcanModeNormal: - diagnostics.mode = "NORMAL"; break; - case kUcanModeListenOnly: - diagnostics.mode = "LISTEN_ONLY"; break; - case kUcanModeTxEcho: - diagnostics.mode = "LOOPBACK"; break; + case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; + case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; + case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; } // DWORD module_time; From a94c57657f159387e8e2d116dd2534c6bd282c66 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 10:08:26 +0100 Subject: [PATCH 20/63] convert indentation to spaces --- src/main/CanVendorSystec.cpp | 444 +++++++++++++++++------------------ 1 file changed, 222 insertions(+), 222 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 9535731d..1035abc1 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -23,8 +23,8 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) // TODO should we make this noexcept? how can we guarantee that? CanReturnCode CanVendorSystec::init_can_port() { - BYTE systec_call_return = USBCAN_SUCCESSFUL; - tUcanHandle can_module_handle; + BYTE systec_call_return = USBCAN_SUCCESSFUL; + tUcanHandle can_module_handle; unsigned int baud_rate = USBCAN_BAUD_125kBit; switch (args().config.bitrate.value_or(0)) { @@ -51,20 +51,20 @@ CanReturnCode CanVendorSystec::init_can_port() { initialization_parameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; initialization_parameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - // check if USB-CANmodul already is initialized + // check if USB-CANmodul already is initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); auto pos = m_handle_map.find(m_module_number); if (pos == m_handle_map.end()) { // module not in use - systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, 0, 0); - if (systec_call_return != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; - ::UcanDeinitHardware(can_module_handle); - return CanReturnCode::unknown_open_error; + systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, 0, 0); + if (systec_call_return != USBCAN_SUCCESSFUL ) { + LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; + ::UcanDeinitHardware(can_module_handle); + return CanReturnCode::unknown_open_error; } map_module_to_handle(m_module_number, can_module_handle); } else { // find existing handle of module can_module_handle = pos->second; - LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; + LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; } systec_call_return = ::UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); @@ -87,15 +87,15 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { // TODO set time since opened equivalent... // m_statistics.setTimeSinceOpened(); - // After the canboard is configured and started, we start the scan control thread + // After the canboard is configured and started, we start the scan control thread m_receive_thread_flag = true; m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); - // todo reintroduce check here... - // if (NULL == m_receive_thread_handle) { - // LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; - // return CanReturnCode::internal_api_error; - // } + // todo reintroduce check here... + // if (NULL == m_receive_thread_handle) { + // LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; + // return CanReturnCode::internal_api_error; + // } return returnCode; } @@ -119,55 +119,55 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { LOG(Log::DBG, CanLogIt::h()) << "Sending message: [" << ( message == 0 ? "" : (const char *) message) << "], cobID: [" << cobID << "], Message Length: [" << static_cast(len) << "]"; - tCanMsgStruct can_msg_to_send; - BYTE Status; - - can_msg_to_send.m_dwID = cobID; - can_msg_to_send.m_bDLC = len; - can_msg_to_send.m_bFF = 0; - if (rtr) { - can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; - } - int message_length_to_process; - //If there is more than 8 characters to process, we process 8 of them in this iteration of the loop - if (len > 8) { - message_length_to_process = 8; - LOG(Log::DBG, CanLogIt::h()) << "The length is more then 8 bytes, adjust to 8, ignore >8. len= " << len; - } else { - //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop - message_length_to_process = len; - if (len < 8) { - LOG(Log::DBG, CanLogIt::h())<< "The length is less then 8 bytes, process only. len= " << len; - } - } - can_msg_to_send.m_bDLC = message_length_to_process; - memcpy(can_msg_to_send.m_bData, message, message_length_to_process); - // MLOG(TRC,this) << "Channel Number: [" << m_channel_number << "], cobID: [" << can_msg_to_send.m_dwID << "], Message Length: [" << static_cast(can_msg_to_send.m_bDLC) << "]"; - Status = UcanWriteCanMsgEx(m_UcanHandle, m_channel_number, &can_msg_to_send, NULL); - if (Status != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " + tCanMsgStruct can_msg_to_send; + BYTE Status; + + can_msg_to_send.m_dwID = cobID; + can_msg_to_send.m_bDLC = len; + can_msg_to_send.m_bFF = 0; + if (rtr) { + can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; + } + int message_length_to_process; + //If there is more than 8 characters to process, we process 8 of them in this iteration of the loop + if (len > 8) { + message_length_to_process = 8; + LOG(Log::DBG, CanLogIt::h()) << "The length is more then 8 bytes, adjust to 8, ignore >8. len= " << len; + } else { + //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop + message_length_to_process = len; + if (len < 8) { + LOG(Log::DBG, CanLogIt::h())<< "The length is less then 8 bytes, process only. len= " << len; + } + } + can_msg_to_send.m_bDLC = message_length_to_process; + memcpy(can_msg_to_send.m_bData, message, message_length_to_process); + // MLOG(TRC,this) << "Channel Number: [" << m_channel_number << "], cobID: [" << can_msg_to_send.m_dwID << "], Message Length: [" << static_cast(can_msg_to_send.m_bDLC) << "]"; + Status = UcanWriteCanMsgEx(m_UcanHandle, m_channel_number, &can_msg_to_send, NULL); + if (Status != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " << UsbCanGetErrorText(Status); - // for now, just always reconnect on a failed send. + // for now, just always reconnect on a failed send. vendor_close(); // TODO maybe we just call close instead of vendor_close - // see how CanVendorSocketCanSystec does reconnects, it intercepts the receiver function and wraps it - vendor_open(); - - switch (Status) { - case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; - case USBCAN_ERR_CANNOTINIT: [[ fallthrough ]]; - case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; - case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; - case USBCAN_ERR_ILLPARAM: [[ fallthrough ]]; - case USBCAN_ERR_ILLHW: [[ fallthrough ]]; - case USBCAN_ERR_ILLCHANNEL: [[ fallthrough ]]; - case USBCAN_WARN_TXLIMIT: [[ fallthrough ]]; - case USBCAN_WARN_FW_TXOVERRUN: [[ fallthrough ]]; - default: return CanReturnCode::unknown_send_error; - } - // m_statistics.onTransmit( can_msg_to_send.m_bDLC ); - // m_statistics.setTimeSinceTransmitted(); - } + // see how CanVendorSocketCanSystec does reconnects, it intercepts the receiver function and wraps it + vendor_open(); + + switch (Status) { + case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; + case USBCAN_ERR_CANNOTINIT: [[ fallthrough ]]; + case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; + case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; + case USBCAN_ERR_ILLPARAM: [[ fallthrough ]]; + case USBCAN_ERR_ILLHW: [[ fallthrough ]]; + case USBCAN_ERR_ILLCHANNEL: [[ fallthrough ]]; + case USBCAN_WARN_TXLIMIT: [[ fallthrough ]]; + case USBCAN_WARN_FW_TXOVERRUN: [[ fallthrough ]]; + default: return CanReturnCode::unknown_send_error; + } + // m_statistics.onTransmit( can_msg_to_send.m_bDLC ); + // m_statistics.setTimeSinceTransmitted(); + } return CanReturnCode::success; }; @@ -222,11 +222,11 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { */ int CanVendorSystec::SystecRxThread() { - BYTE status; - tCanMsgStruct read_can_message; - LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << this->m_receive_thread_flag <<"]"; - while (this->m_receive_thread_flag) { - status = UcanReadCanMsgEx(this->m_UcanHandle, (BYTE *)&this->m_channel_number, &read_can_message, NULL); + BYTE status; + tCanMsgStruct read_can_message; + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << this->m_receive_thread_flag <<"]"; + while (this->m_receive_thread_flag) { + status = UcanReadCanMsgEx(this->m_UcanHandle, (BYTE *)&this->m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: [[ fallthrough ]]; case USBCAN_WARN_DLL_RXOVERRUN: [[ fallthrough ]]; @@ -258,165 +258,165 @@ int CanVendorSystec::SystecRxThread() // TODO should we raise some error state here? LOG(Log::ERR, CanLogIt::h()) << UsbCanGetErrorText(status); break; - } + } } - return 0; + return 0; } std::string UsbCanGetErrorText( long err_code ){ - switch( err_code ){ - case USBCAN_SUCCESSFUL: return("success"); - - case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this " - "case the term resource means memory and handles provided by the Windows OS"); - - case USBCAN_ERR_MAXMODULES: return("An application has tried to open more than 64 USB-CANmodul devices. " - "The standard version of the DLL only supports up to 64 USB-CANmodul " - "devices at the same time. This error also appears if several applications " - "try to access more than 64 USB-CANmodul devices. For example, " - "application 1 has opened 60 modules, application 2 has opened 4 " - "modules and application 3 wants to open a module. Application 3 " - "receives this error code."); - - case USBCAN_ERR_HWINUSE: return("An application tries to initialize an USB-CANmodul with the given device " - "number. If this module has already been initialized by its own or by " - "another application, this error code is returned."); - - case USBCAN_ERR_ILLVERSION: return("This error code returns if the firmware version of the USB-CANmodul is " - "not compatible to the software version of the DLL. In this case, install " - "the latest driver for the USB-CANmodul. Furthermore make sure that " - "the latest firmware version is programmed to the USB-CANmodul."); - - case USBCAN_ERR_ILLHW: return("This error code returns if an USB-CANmodul with the given device " - "number is not found. If the function UcanInitHardware() or " - "UcanInitHardwareEx() has been called with the device number " - "USBCAN_ANY_MODULE, and the error code appears, it indicates that " - "no module is connected to the PC or all connected modules are already " - "in use."); - - case USBCAN_ERR_ILLHANDLE: return("This error code returns if a function received an incorrect USBCAN " - "handle. The function first checks which USB-CANmodul is related to this " - "handle. This error occurs if no device belongs this handle."); - - case USBCAN_ERR_ILLPARAM: return("This error code returns if a wrong parameter is passed to the function. " - "For example, the value NULL has been passed to a pointer variable " - "instead of a valid address."); - - case USBCAN_ERR_BUSY: return("This error code occurs if several threads are accessing an " - "USB-CANmodul within a single application. After the other threads have " - "finished their tasks, the function may be called again."); - - case USBCAN_ERR_TIMEOUT: return("This error code occurs if the function transmits a command to the " - "USB-CANmodul but no reply is returned. To solve this problem, close " - "the application, disconnect the USB-CANmodul, and connect it again."); - - case USBCAN_ERR_IOFAILED: return("This error code occurs if the communication to the kernel driver was " - "interrupted. This happens, for example, if the USB-CANmodul is " - "disconnected during transferring data or commands to the " - "USB-CANmodul."); - - case USBCAN_ERR_DLL_TXFULL: return("The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks " - "if the transmit buffer within the DLL has enough capacity to store new " - "CAN messages. If the buffer is full, this error code returns. The CAN " - "message passed to these functions will not be written into the transmit " - "buffer in order to protect other CAN messages against overwriting. The " - "size of the transmit buffer is configurable (refer to function " - "UcanInitCanEx() and structure tUcanInitCanParam)."); - - case USBCAN_ERR_MAXINSTANCES: return("A maximum amount of 64 applications are able to have access to the " - "DLL. If more applications attempting to access to the DLL, this error " - "code is returned. In this case, it is not possible to use an " - "USB-CANmodul by this application."); - - case USBCAN_ERR_CANNOTINIT: return("This error code returns if an application tries to call an API function " - "which only can be called in software state CAN_INIT but the current " - "software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for " - "detailed information."); - - case USBCAN_ERR_DISCONNECT: return("This error code occurs if an API function was called for an " - "USB-CANmodul that was plugged-off from the computer recently."); - - case USBCAN_ERR_ILLCHANNEL: return("This error code is returned if an extended function of the DLL is called " - "with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."); - - case USBCAN_ERR_ILLHWTYPE: return("This error code occurs if an extended function of the DLL was called for " - "a hardware which does not support the feature."); - - case USBCAN_ERRCMD_NOTEQU: return("This error code occurs during communication between the PC and an " - "USB-CANmodul. The PC sends a command to the USB-CANmodul, " - "then the module executes the command and returns a response to the " - "PC. This error code returns if the reply does not correspond to the command."); - - case USBCAN_ERRCMD_REGTST: return("The software tests the CAN controller on the USB-CANmodul when the " - "CAN interface is initialized. Several registers of the CAN controller are " - "checked. This error code returns if an error appears during this register test."); - - case USBCAN_ERRCMD_ILLCMD: return("This error code returns if the USB-CANmodul receives a non-defined " - "command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."); - - case USBCAN_ERRCMD_EEPROM: return("The USB-CANmodul has a built-in EEPROM. This EEPROM contains " - "several configurations, e.g. the device number and the serial number. If " - "an error occurs while reading these values, this error code is returned."); - - case USBCAN_ERRCMD_ILLBDR: return("The USB-CANmodul has been initialized with an invalid baud rate (refer " - "to section 4.3.4)."); - - case USBCAN_ERRCMD_NOTINIT: return("It was tried to access a CAN-channel of a multi-channel " - "USB-CANmodul that was not initialized."); - - case USBCAN_ERRCMD_ALREADYINIT: return("The accessed CAN-channel of a multi-channel USB-CANmodul was " - "already initialized"); - - case USBCAN_ERRCMD_ILLSUBCMD: return("An internal error occurred within the DLL. In this case an unknown sub- " - "command was called instead of a main command (e.g. for the cyclic CAN message-feature)."); - - case USBCAN_ERRCMD_ILLIDX: return("An internal error occurred within the DLL. In this case an invalid index " - "for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."); - - case USBCAN_ERRCMD_RUNNING: return("The caller tries to define a new list of cyclic CAN messages but this " - "feature was already started. For defining a new list, it is necessary to stop the feature beforehand."); - - case USBCAN_WARN_NODATA: return("If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " - "with this warning, it is an indication that the receive buffer contains no CAN messages."); - - case USBCAN_WARN_SYS_RXOVERRUN: return("This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the " - "receive buffer within the kernel driver runs over. The function " - "nevertheless returns a valid CAN message. It also indicates that at least " - "one CAN message are lost. However, it does not indicate the position of the lost CAN messages."); - - case USBCAN_WARN_DLL_RXOVERRUN: return("The DLL automatically requests CAN messages from the " - "USB-CANmodul and stores the messages into a buffer of the DLL. If " - "more CAN messages are received than the DLL buffer size allows, this " - "error code returns and CAN messages are lost. However, it does not " - "indicate the position of the lost CAN messages. The size of the receive " - "buffer is configurable (refer to function UcanInitCanEx() and structure " - "tUcanInitCanParam)."); - - case USBCAN_WARN_FW_TXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " - "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in " - "the CAN driver status. However, the transmit CAN message could be " - "stored to the DLL transmit buffer. This warning indicates that at least " - "one transmit CAN message got lost in the device firmware layer. This " - "warning does not indicate the position of the lost CAN message."); - - case USBCAN_WARN_FW_RXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " - "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag " - "USBCAN_CANERR_OVERRUN are set in the CAN driver status. The " - "function has returned with a valid CAN message. This warning indicates " - "that at least one received CAN message got lost in the firmware layer. " - "This warning does not indicate the position of the lost CAN message."); - - case USBCAN_WARN_NULL_PTR: return("This warning is returned by functions UcanInitHwConnectControl() or " - "UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."); - - case USBCAN_WARN_TXLIMIT: return("This warning is returned by the function UcanWriteCanMsgEx() if it was " - "called to transmit more than one CAN message, but a part of them " - "could not be stored to the transmit buffer within the DLL (because the " - "buffer is full). The returned variable addressed by the parameter " - "pdwCount_p indicates the number of CAN messages which are stored " - "successfully to the transmit buffer."); - - default: return("unknown error code"); - } + switch( err_code ){ + case USBCAN_SUCCESSFUL: return("success"); + + case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this " + "case the term resource means memory and handles provided by the Windows OS"); + + case USBCAN_ERR_MAXMODULES: return("An application has tried to open more than 64 USB-CANmodul devices. " + "The standard version of the DLL only supports up to 64 USB-CANmodul " + "devices at the same time. This error also appears if several applications " + "try to access more than 64 USB-CANmodul devices. For example, " + "application 1 has opened 60 modules, application 2 has opened 4 " + "modules and application 3 wants to open a module. Application 3 " + "receives this error code."); + + case USBCAN_ERR_HWINUSE: return("An application tries to initialize an USB-CANmodul with the given device " + "number. If this module has already been initialized by its own or by " + "another application, this error code is returned."); + + case USBCAN_ERR_ILLVERSION: return("This error code returns if the firmware version of the USB-CANmodul is " + "not compatible to the software version of the DLL. In this case, install " + "the latest driver for the USB-CANmodul. Furthermore make sure that " + "the latest firmware version is programmed to the USB-CANmodul."); + + case USBCAN_ERR_ILLHW: return("This error code returns if an USB-CANmodul with the given device " + "number is not found. If the function UcanInitHardware() or " + "UcanInitHardwareEx() has been called with the device number " + "USBCAN_ANY_MODULE, and the error code appears, it indicates that " + "no module is connected to the PC or all connected modules are already " + "in use."); + + case USBCAN_ERR_ILLHANDLE: return("This error code returns if a function received an incorrect USBCAN " + "handle. The function first checks which USB-CANmodul is related to this " + "handle. This error occurs if no device belongs this handle."); + + case USBCAN_ERR_ILLPARAM: return("This error code returns if a wrong parameter is passed to the function. " + "For example, the value NULL has been passed to a pointer variable " + "instead of a valid address."); + + case USBCAN_ERR_BUSY: return("This error code occurs if several threads are accessing an " + "USB-CANmodul within a single application. After the other threads have " + "finished their tasks, the function may be called again."); + + case USBCAN_ERR_TIMEOUT: return("This error code occurs if the function transmits a command to the " + "USB-CANmodul but no reply is returned. To solve this problem, close " + "the application, disconnect the USB-CANmodul, and connect it again."); + + case USBCAN_ERR_IOFAILED: return("This error code occurs if the communication to the kernel driver was " + "interrupted. This happens, for example, if the USB-CANmodul is " + "disconnected during transferring data or commands to the " + "USB-CANmodul."); + + case USBCAN_ERR_DLL_TXFULL: return("The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks " + "if the transmit buffer within the DLL has enough capacity to store new " + "CAN messages. If the buffer is full, this error code returns. The CAN " + "message passed to these functions will not be written into the transmit " + "buffer in order to protect other CAN messages against overwriting. The " + "size of the transmit buffer is configurable (refer to function " + "UcanInitCanEx() and structure tUcanInitCanParam)."); + + case USBCAN_ERR_MAXINSTANCES: return("A maximum amount of 64 applications are able to have access to the " + "DLL. If more applications attempting to access to the DLL, this error " + "code is returned. In this case, it is not possible to use an " + "USB-CANmodul by this application."); + + case USBCAN_ERR_CANNOTINIT: return("This error code returns if an application tries to call an API function " + "which only can be called in software state CAN_INIT but the current " + "software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for " + "detailed information."); + + case USBCAN_ERR_DISCONNECT: return("This error code occurs if an API function was called for an " + "USB-CANmodul that was plugged-off from the computer recently."); + + case USBCAN_ERR_ILLCHANNEL: return("This error code is returned if an extended function of the DLL is called " + "with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."); + + case USBCAN_ERR_ILLHWTYPE: return("This error code occurs if an extended function of the DLL was called for " + "a hardware which does not support the feature."); + + case USBCAN_ERRCMD_NOTEQU: return("This error code occurs during communication between the PC and an " + "USB-CANmodul. The PC sends a command to the USB-CANmodul, " + "then the module executes the command and returns a response to the " + "PC. This error code returns if the reply does not correspond to the command."); + + case USBCAN_ERRCMD_REGTST: return("The software tests the CAN controller on the USB-CANmodul when the " + "CAN interface is initialized. Several registers of the CAN controller are " + "checked. This error code returns if an error appears during this register test."); + + case USBCAN_ERRCMD_ILLCMD: return("This error code returns if the USB-CANmodul receives a non-defined " + "command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."); + + case USBCAN_ERRCMD_EEPROM: return("The USB-CANmodul has a built-in EEPROM. This EEPROM contains " + "several configurations, e.g. the device number and the serial number. If " + "an error occurs while reading these values, this error code is returned."); + + case USBCAN_ERRCMD_ILLBDR: return("The USB-CANmodul has been initialized with an invalid baud rate (refer " + "to section 4.3.4)."); + + case USBCAN_ERRCMD_NOTINIT: return("It was tried to access a CAN-channel of a multi-channel " + "USB-CANmodul that was not initialized."); + + case USBCAN_ERRCMD_ALREADYINIT: return("The accessed CAN-channel of a multi-channel USB-CANmodul was " + "already initialized"); + + case USBCAN_ERRCMD_ILLSUBCMD: return("An internal error occurred within the DLL. In this case an unknown sub- " + "command was called instead of a main command (e.g. for the cyclic CAN message-feature)."); + + case USBCAN_ERRCMD_ILLIDX: return("An internal error occurred within the DLL. In this case an invalid index " + "for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."); + + case USBCAN_ERRCMD_RUNNING: return("The caller tries to define a new list of cyclic CAN messages but this " + "feature was already started. For defining a new list, it is necessary to stop the feature beforehand."); + + case USBCAN_WARN_NODATA: return("If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " + "with this warning, it is an indication that the receive buffer contains no CAN messages."); + + case USBCAN_WARN_SYS_RXOVERRUN: return("This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the " + "receive buffer within the kernel driver runs over. The function " + "nevertheless returns a valid CAN message. It also indicates that at least " + "one CAN message are lost. However, it does not indicate the position of the lost CAN messages."); + + case USBCAN_WARN_DLL_RXOVERRUN: return("The DLL automatically requests CAN messages from the " + "USB-CANmodul and stores the messages into a buffer of the DLL. If " + "more CAN messages are received than the DLL buffer size allows, this " + "error code returns and CAN messages are lost. However, it does not " + "indicate the position of the lost CAN messages. The size of the receive " + "buffer is configurable (refer to function UcanInitCanEx() and structure " + "tUcanInitCanParam)."); + + case USBCAN_WARN_FW_TXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " + "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in " + "the CAN driver status. However, the transmit CAN message could be " + "stored to the DLL transmit buffer. This warning indicates that at least " + "one transmit CAN message got lost in the device firmware layer. This " + "warning does not indicate the position of the lost CAN message."); + + case USBCAN_WARN_FW_RXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " + "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag " + "USBCAN_CANERR_OVERRUN are set in the CAN driver status. The " + "function has returned with a valid CAN message. This warning indicates " + "that at least one received CAN message got lost in the firmware layer. " + "This warning does not indicate the position of the lost CAN message."); + + case USBCAN_WARN_NULL_PTR: return("This warning is returned by functions UcanInitHwConnectControl() or " + "UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."); + + case USBCAN_WARN_TXLIMIT: return("This warning is returned by the function UcanWriteCanMsgEx() if it was " + "called to transmit more than one CAN message, but a part of them " + "could not be stored to the transmit buffer within the DLL (because the " + "buffer is full). The returned variable addressed by the parameter " + "pdwCount_p indicates the number of CAN messages which are stored " + "successfully to the transmit buffer."); + + default: return("unknown error code"); + } } \ No newline at end of file From 74ee91145e72859b99af0d1f6c76e973818d8659 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 10:43:05 +0100 Subject: [PATCH 21/63] remove inline from handle map, make error text function a member method --- src/include/CanVendorSystec.h | 4 ++-- src/main/CanVendorSystec.cpp | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index d3f16820..1c98f263 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -45,12 +45,12 @@ struct CanVendorSystec : CanDevice { CanReturnCode init_can_port(); static std::mutex m_handles_lock; + static std::unordered_map m_handle_map; inline void map_module_to_handle(int module, tUcanHandle handle) { m_handle_map[module] = handle; } inline int erase_module_handle(int module) { return m_handle_map.erase(module); } - // TODO i don't like this too much - inline static std::unordered_map m_handle_map = {}; + std::string UsbCanGetErrorText( long err_code ); }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 1035abc1..df00abb6 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -7,7 +7,7 @@ #include std::mutex CanVendorSystec::m_handles_lock; -std::string UsbCanGetErrorText( long err_code ); // forward declaration +std::unordered_map CanVendorSystec::m_handle_map; CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args) { @@ -64,7 +64,7 @@ CanReturnCode CanVendorSystec::init_can_port() { map_module_to_handle(m_module_number, can_module_handle); } else { // find existing handle of module can_module_handle = pos->second; - LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; + LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; } systec_call_return = ::UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); @@ -224,9 +224,9 @@ int CanVendorSystec::SystecRxThread() { BYTE status; tCanMsgStruct read_can_message; - LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << this->m_receive_thread_flag <<"]"; - while (this->m_receive_thread_flag) { - status = UcanReadCanMsgEx(this->m_UcanHandle, (BYTE *)&this->m_channel_number, &read_can_message, NULL); + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << m_receive_thread_flag <<"]"; + while (m_receive_thread_flag) { + status = UcanReadCanMsgEx(m_UcanHandle, (BYTE *)&m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: [[ fallthrough ]]; case USBCAN_WARN_DLL_RXOVERRUN: [[ fallthrough ]]; @@ -239,12 +239,12 @@ int CanVendorSystec::SystecRxThread() // id, data, flags CanFrame can_msg_copy(read_can_message.m_dwID, data, read_can_message.m_bFF); // TODO the read_can_message contains a DWORD m_dwTime "receipt time in ms" - this->received(can_msg_copy); - // this->m_statistics.onReceive( read_can_message.m_bDLC ); - // this->m_statistics.setTimeSinceReceived(); + received(can_msg_copy); + // m_statistics.onReceive( read_can_message.m_bDLC ); + // m_statistics.setTimeSinceReceived(); // we can reset the reconnectionTimeout here, since we have received a message - // this->resetTimeoutOnReception(); + // resetTimeoutOnReception(); break; } case USBCAN_WARN_NODATA: @@ -264,7 +264,7 @@ int CanVendorSystec::SystecRxThread() return 0; } -std::string UsbCanGetErrorText( long err_code ){ +std::string CanVendorSystec::UsbCanGetErrorText( long err_code ) { switch( err_code ){ case USBCAN_SUCCESSFUL: return("success"); From bce764bc233bd64d9a0749838c2a6428779cbecf Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 13:31:43 +0100 Subject: [PATCH 22/63] add uptime to diagnostics for Systec --- src/include/CanDiagnostics.h | 2 +- src/main/CanVendorSystec.cpp | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/include/CanDiagnostics.h b/src/include/CanDiagnostics.h index a0218620..5c45f502 100644 --- a/src/include/CanDiagnostics.h +++ b/src/include/CanDiagnostics.h @@ -47,7 +47,7 @@ struct CanDiagnostics { std::optional temperature; ///< Optional temperature reading for Anagate devices. - std::optional uptime; ///< Optional uptime for Anagate devices. + std::optional uptime; ///< Optional uptime in seconds. std::optional tcp_rx; ///< Optional TCP Received counter for ///< both SocketCAN and Anagate devices. diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index df00abb6..3609beaa 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -1,10 +1,8 @@ #include "CanVendorSystec.h" #include -#include #include #include -#include std::mutex CanVendorSystec::m_handles_lock; std::unordered_map CanVendorSystec::m_handle_map; @@ -210,8 +208,9 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; } - // DWORD module_time; - // UcanGetModuleTime(m_UcanHandle, &module_time); + DWORD module_time; // in ms + UcanGetModuleTime(m_UcanHandle, &module_time); + diagnostics.uptime = (uint32_t) module_time / 1000; return diagnostics; }; From c2b80ad7a884eb0767928645a2f882b510cb6c1f Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 14:18:43 +0100 Subject: [PATCH 23/63] replace broken memcpy with std::copy, some reformatting --- src/main/CanVendorSystec.cpp | 37 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 3609beaa..03effd82 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -1,8 +1,10 @@ #include "CanVendorSystec.h" +#include #include #include #include +#include std::mutex CanVendorSystec::m_handles_lock; std::unordered_map CanVendorSystec::m_handle_map; @@ -104,43 +106,40 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { erase_module_handle(m_module_number); m_receive_thread_flag = false; if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); - UcanDeinitCanEx (m_UcanHandle, (BYTE)m_channel_number); + UcanDeinitCanEx (m_UcanHandle, (BYTE) m_channel_number); LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; return CanReturnCode::success; }; CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { - bool rtr = frame.is_remote_request(); uint32_t len = frame.length(); - char *message = frame.message().data(); - short cobID = frame.id(); - - LOG(Log::DBG, CanLogIt::h()) << "Sending message: [" << ( message == 0 ? "" : (const char *) message) << "], cobID: [" << cobID << "], Message Length: [" << static_cast(len) << "]"; + std::vector message = frame.message(); tCanMsgStruct can_msg_to_send; BYTE Status; - can_msg_to_send.m_dwID = cobID; + can_msg_to_send.m_dwID = frame.id(); can_msg_to_send.m_bDLC = len; can_msg_to_send.m_bFF = 0; - if (rtr) { - can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; + if (frame.is_remote_request()) { + can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; } int message_length_to_process; //If there is more than 8 characters to process, we process 8 of them in this iteration of the loop if (len > 8) { - message_length_to_process = 8; - LOG(Log::DBG, CanLogIt::h()) << "The length is more then 8 bytes, adjust to 8, ignore >8. len= " << len; + message_length_to_process = 8; + LOG(Log::DBG, CanLogIt::h()) << "The length is more than 8 bytes, adjust to 8, ignore > 8. len = " << len; } else { - //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop - message_length_to_process = len; - if (len < 8) { - LOG(Log::DBG, CanLogIt::h())<< "The length is less then 8 bytes, process only. len= " << len; - } + //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop + message_length_to_process = len; + if (len < 8) { + LOG(Log::DBG, CanLogIt::h())<< "The length is less than 8 bytes, process only. len = " << len; + } } can_msg_to_send.m_bDLC = message_length_to_process; - memcpy(can_msg_to_send.m_bData, message, message_length_to_process); - // MLOG(TRC,this) << "Channel Number: [" << m_channel_number << "], cobID: [" << can_msg_to_send.m_dwID << "], Message Length: [" << static_cast(can_msg_to_send.m_bDLC) << "]"; + if (message_length_to_process) + std::copy(message.begin(), message.begin() + message_length_to_process, can_msg_to_send.m_bData); + Status = UcanWriteCanMsgEx(m_UcanHandle, m_channel_number, &can_msg_to_send, NULL); if (Status != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " @@ -225,7 +224,7 @@ int CanVendorSystec::SystecRxThread() tCanMsgStruct read_can_message; LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << m_receive_thread_flag <<"]"; while (m_receive_thread_flag) { - status = UcanReadCanMsgEx(m_UcanHandle, (BYTE *)&m_channel_number, &read_can_message, NULL); + status = UcanReadCanMsgEx(m_UcanHandle, (BYTE *) &m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: [[ fallthrough ]]; case USBCAN_WARN_DLL_RXOVERRUN: [[ fallthrough ]]; From 17889fceb59000a7bc7a08c23d514eccbb9b93a8 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 14:46:16 +0100 Subject: [PATCH 24/63] update CanVendorSystec docstring --- src/include/CanVendorSystec.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 1c98f263..c32cd96c 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -18,14 +18,12 @@ /** * @struct CanVendorSystec - * @brief Represents a SocketCAN Systec specific implementation of a CanDevice. + * @brief Represents a specific implementation of a CanDevice for Systec devices + * on Windows utilising libraries from USB-CANmodul Utility Disk. * - * This class provides the implementation for interacting with a SocketCAN - * Systec device. It extends the CanVendorSocketCan class and overrides the - * necessary methods to open, close, and send CAN frames using the SocketCAN - * interface. It provides a custom mechanishm for handling BUS_OFF errors, - * restarting the interface instead of using the built-in restart mechanism of - * SocketCan due to a kernel-panic bug on Systec linux module. + * This struct provides methods for opening, closing, sending, and receiving CAN + * frames using the Systec CAN-over-USB interface. It also provides diagnostics + * information. */ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); From dbc934ea181c34e8587019469b1ab008e9f32e6b Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 3 Mar 2026 15:33:10 +0100 Subject: [PATCH 25/63] check return codes of open and close on systec reconnect small cleanups --- src/main/CanVendorSystec.cpp | 59 +++++++++++++++++------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 03effd82..a91aa0c3 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -28,12 +28,12 @@ CanReturnCode CanVendorSystec::init_can_port() { unsigned int baud_rate = USBCAN_BAUD_125kBit; switch (args().config.bitrate.value_or(0)) { - case 50000: baud_rate = USBCAN_BAUD_50kBit; break; - case 100000: baud_rate = USBCAN_BAUD_100kBit; break; - case 125000: baud_rate = USBCAN_BAUD_125kBit; break; - case 250000: baud_rate = USBCAN_BAUD_250kBit; break; - case 500000: baud_rate = USBCAN_BAUD_500kBit; break; - case 1000000: baud_rate = USBCAN_BAUD_1MBit; break; + case 50000: baud_rate = USBCAN_BAUD_50kBit; break; + case 100000: baud_rate = USBCAN_BAUD_100kBit; break; + case 125000: baud_rate = USBCAN_BAUD_125kBit; break; + case 250000: baud_rate = USBCAN_BAUD_250kBit; break; + case 500000: baud_rate = USBCAN_BAUD_500kBit; break; + case 1000000: baud_rate = USBCAN_BAUD_1MBit; break; default: { LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baud_rate << "]"; } @@ -51,7 +51,7 @@ CanReturnCode CanVendorSystec::init_can_port() { initialization_parameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; initialization_parameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - // check if USB-CANmodul already is initialized + // check if USB-CANmodul is already initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); auto pos = m_handle_map.find(m_module_number); if (pos == m_handle_map.end()) { // module not in use @@ -84,30 +84,26 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { auto returnCode = init_can_port(); if (returnCode != CanReturnCode::success) return returnCode; - // TODO set time since opened equivalent... - // m_statistics.setTimeSinceOpened(); - - // After the canboard is configured and started, we start the scan control thread - m_receive_thread_flag = true; - m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); - - // todo reintroduce check here... - // if (NULL == m_receive_thread_handle) { - // LOG(Log::ERR, CanLogIt::h()) << "Error creating the canScanControl thread."; - // return CanReturnCode::internal_api_error; - // } + try { + m_receive_thread_flag = true; + m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); + } catch(...) { + returnCode = CanReturnCode::internal_api_error; + } return returnCode; } CanReturnCode CanVendorSystec::vendor_close() noexcept { - // TODO what if the return code is not success? - std::lock_guard guard(CanVendorSystec::m_handles_lock); - erase_module_handle(m_module_number); - m_receive_thread_flag = false; - if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); - UcanDeinitCanEx (m_UcanHandle, (BYTE) m_channel_number); - LOG(Log::DBG, CanLogIt::h()) << __FUNCTION__ << " closed successfully"; + try { + m_receive_thread_flag = false; + std::lock_guard guard(CanVendorSystec::m_handles_lock); + erase_module_handle(m_module_number); + if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); + UcanDeinitCanEx (m_UcanHandle, (BYTE) m_channel_number); + } catch (...) { + return CanReturnCode::unknown_close_error; + } return CanReturnCode::success; }; @@ -146,9 +142,11 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { << UsbCanGetErrorText(Status); // for now, just always reconnect on a failed send. - vendor_close(); // TODO maybe we just call close instead of vendor_close - // see how CanVendorSocketCanSystec does reconnects, it intercepts the receiver function and wraps it - vendor_open(); + auto close_code = close(); + if (close_code != CanReturnCode::success) return close_code; + + auto open_code = open(); + if (open_code != CanReturnCode::success) return open_code; switch (Status) { case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; @@ -162,15 +160,12 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { case USBCAN_WARN_FW_TXOVERRUN: [[ fallthrough ]]; default: return CanReturnCode::unknown_send_error; } - // m_statistics.onTransmit( can_msg_to_send.m_bDLC ); - // m_statistics.setTimeSinceTransmitted(); } return CanReturnCode::success; }; CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { - // TODO we can read the operating mode, either kUcanModeNormal, ListenOnly or TxEcho CanDiagnostics diagnostics{}; tStatusStruct status; // TODO check return code of these functions... From be434c56e73384ab95b8a64ea80b33f9407ef4a0 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 4 Mar 2026 09:06:01 +0100 Subject: [PATCH 26/63] reduce [[ fallthrough ]] use to only where it prevents a warning --- src/main/CanVendorSystec.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index a91aa0c3..7ef3ac4b 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -149,15 +149,15 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { if (open_code != CanReturnCode::success) return open_code; switch (Status) { - case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; - case USBCAN_ERR_CANNOTINIT: [[ fallthrough ]]; + case USBCAN_ERR_CANNOTINIT: case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; - case USBCAN_ERR_ILLPARAM: [[ fallthrough ]]; - case USBCAN_ERR_ILLHW: [[ fallthrough ]]; - case USBCAN_ERR_ILLCHANNEL: [[ fallthrough ]]; - case USBCAN_WARN_TXLIMIT: [[ fallthrough ]]; - case USBCAN_WARN_FW_TXOVERRUN: [[ fallthrough ]]; + case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; + case USBCAN_ERR_ILLPARAM: + case USBCAN_ERR_ILLHW: + case USBCAN_ERR_ILLCHANNEL: + case USBCAN_WARN_TXLIMIT: + case USBCAN_WARN_FW_TXOVERRUN: default: return CanReturnCode::unknown_send_error; } } @@ -221,10 +221,11 @@ int CanVendorSystec::SystecRxThread() while (m_receive_thread_flag) { status = UcanReadCanMsgEx(m_UcanHandle, (BYTE *) &m_channel_number, &read_can_message, NULL); switch (status) { - case USBCAN_WARN_SYS_RXOVERRUN: [[ fallthrough ]]; - case USBCAN_WARN_DLL_RXOVERRUN: [[ fallthrough ]]; - case USBCAN_WARN_FW_RXOVERRUN: [[ fallthrough ]]; + case USBCAN_WARN_SYS_RXOVERRUN: + case USBCAN_WARN_DLL_RXOVERRUN: + case USBCAN_WARN_FW_RXOVERRUN: LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); + [[ fallthrough ]]; case USBCAN_SUCCESSFUL: { if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; // can_msg_copy.c_time = convertTimepointToTimeval(currentTimeTimeval()); From b348b1ba77dc6df001e4e6804a1669a661c722cd Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 4 Mar 2026 09:13:39 +0100 Subject: [PATCH 27/63] improve vendor return codes for systec --- src/main/CanVendorSystec.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 7ef3ac4b..6a5711dc 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -81,17 +81,17 @@ CanReturnCode CanVendorSystec::init_can_port() { CanReturnCode CanVendorSystec::vendor_open() noexcept { - auto returnCode = init_can_port(); - if (returnCode != CanReturnCode::success) return returnCode; + auto return_code = init_can_port(); + if (return_code != CanReturnCode::success) return return_code; try { m_receive_thread_flag = true; m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); } catch(...) { - returnCode = CanReturnCode::internal_api_error; + return_code = CanReturnCode::internal_api_error; } - return returnCode; + return return_code; } CanReturnCode CanVendorSystec::vendor_close() noexcept { @@ -100,9 +100,10 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { std::lock_guard guard(CanVendorSystec::m_handles_lock); erase_module_handle(m_module_number); if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); - UcanDeinitCanEx (m_UcanHandle, (BYTE) m_channel_number); + auto return_code = UcanDeinitCanEx (m_UcanHandle, (BYTE) m_channel_number); + if (return_code != USBCAN_SUCCESSFUL) return CanReturnCode::unknown_close_error; } catch (...) { - return CanReturnCode::unknown_close_error; + return CanReturnCode::internal_api_error; } return CanReturnCode::success; }; From 0784fe21ebe2680628a26c4aceedc93487452275 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 4 Mar 2026 09:17:27 +0100 Subject: [PATCH 28/63] make m_receive_thread_flag atomic --- src/include/CanVendorSystec.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index c32cd96c..d8e7d947 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -1,6 +1,7 @@ #ifndef SRC_INCLUDE_CANVENDORSYSTEC_H_ #define SRC_INCLUDE_CANVENDORSYSTEC_H_ +#include #include #include "tchar.h" #include "Winsock2.h" @@ -31,7 +32,7 @@ struct CanVendorSystec : CanDevice { int SystecRxThread(); private: - bool m_receive_thread_flag = true; + std::atomic m_receive_thread_flag = true; tUcanHandle m_UcanHandle; int m_module_number; int m_channel_number; From 44e2882f5aec313b0678e64527ceac111a3d057c Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 4 Mar 2026 10:12:29 +0100 Subject: [PATCH 29/63] cleanup comments --- src/main/CanVendorSystec.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 6a5711dc..8284ca57 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -229,17 +229,10 @@ int CanVendorSystec::SystecRxThread() [[ fallthrough ]]; case USBCAN_SUCCESSFUL: { if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; - // can_msg_copy.c_time = convertTimepointToTimeval(currentTimeTimeval()); std::vector data(read_can_message.m_bData, read_can_message.m_bData + read_can_message.m_bDLC); // id, data, flags CanFrame can_msg_copy(read_can_message.m_dwID, data, read_can_message.m_bFF); - // TODO the read_can_message contains a DWORD m_dwTime "receipt time in ms" received(can_msg_copy); - // m_statistics.onReceive( read_can_message.m_bDLC ); - // m_statistics.setTimeSinceReceived(); - - // we can reset the reconnectionTimeout here, since we have received a message - // resetTimeoutOnReception(); break; } case USBCAN_WARN_NODATA: From be3557505673a6296167829b81d33a4dcb261c62 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 4 Mar 2026 16:54:44 +0100 Subject: [PATCH 30/63] add get_module_handle() method --- src/include/CanVendorSystec.h | 5 ++++- src/main/CanVendorSystec.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index d8e7d947..b763dc11 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -37,11 +37,14 @@ struct CanVendorSystec : CanDevice { int m_module_number; int m_channel_number; std::thread m_SystecRxThread; + + tUcanHandle get_module_handle() { return m_handle_map[m_module_number]; } + CanReturnCode vendor_open() noexcept override; CanReturnCode vendor_close() noexcept override; CanReturnCode vendor_send(const CanFrame& frame) noexcept override; CanDiagnostics vendor_diagnostics() noexcept override; - + CanReturnCode init_can_port(); static std::mutex m_handles_lock; static std::unordered_map m_handle_map; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 8284ca57..76711a17 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -74,7 +74,6 @@ CanReturnCode CanVendorSystec::init_can_port() { return CanReturnCode::unknown_open_error; } - m_UcanHandle = can_module_handle; LOG(Log::INF, CanLogIt::h()) << "Successfully opened CAN port on module " << m_module_number << ", channel " << m_channel_number; return CanReturnCode::success; } @@ -137,7 +136,7 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { if (message_length_to_process) std::copy(message.begin(), message.begin() + message_length_to_process, can_msg_to_send.m_bData); - Status = UcanWriteCanMsgEx(m_UcanHandle, m_channel_number, &can_msg_to_send, NULL); + Status = UcanWriteCanMsgEx(get_module_handle(), m_channel_number, &can_msg_to_send, NULL); if (Status != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " << UsbCanGetErrorText(Status); @@ -170,7 +169,8 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { CanDiagnostics diagnostics{}; tStatusStruct status; // TODO check return code of these functions... - UcanGetStatusEx(m_UcanHandle, m_channel_number, &status); + auto handle = get_module_handle(); + UcanGetStatusEx(handle, m_channel_number, &status); WORD can_status = status.m_wCanStatus; switch (can_status) { case USBCAN_CANERR_OK: diagnostics.state = "USBCAN_CANERR_OK"; break; @@ -185,17 +185,17 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { case USBCAN_CANERR_TXMSGLOST: diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; break; } tUcanMsgCountInfo msg_count_info; - UcanGetMsgCountInfoEx(m_UcanHandle, m_channel_number, &msg_count_info); + UcanGetMsgCountInfoEx(handle, m_channel_number, &msg_count_info); diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; DWORD tx_error, rx_error; - UcanGetCanErrorCounter(m_UcanHandle, m_channel_number, &tx_error, &rx_error); + UcanGetCanErrorCounter(handle, m_channel_number, &tx_error, &rx_error); diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; tUcanHardwareInfo hw_info; - if (UcanGetHardwareInfo(m_UcanHandle, &hw_info) != USBCAN_SUCCESSFUL) + if (UcanGetHardwareInfo(handle, &hw_info) != USBCAN_SUCCESSFUL) diagnostics.mode = "OFFLINE"; else switch (hw_info.m_bMode) { case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; @@ -204,7 +204,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { } DWORD module_time; // in ms - UcanGetModuleTime(m_UcanHandle, &module_time); + UcanGetModuleTime(handle, &module_time); diagnostics.uptime = (uint32_t) module_time / 1000; return diagnostics; @@ -220,7 +220,7 @@ int CanVendorSystec::SystecRxThread() tCanMsgStruct read_can_message; LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << m_receive_thread_flag <<"]"; while (m_receive_thread_flag) { - status = UcanReadCanMsgEx(m_UcanHandle, (BYTE *) &m_channel_number, &read_can_message, NULL); + status = UcanReadCanMsgEx(get_module_handle(), (BYTE *) &m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: case USBCAN_WARN_DLL_RXOVERRUN: From bb9cc923b2a1b71a002d2f8d1862e5eb8f844c56 Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 5 Mar 2026 09:22:43 +0100 Subject: [PATCH 31/63] Use systec callback to signal when frame ready to read Attempts to fix up open and close logic for multiple channels on same module --- src/include/CanVendorSystec.h | 12 +++--- src/main/CanVendorSystec.cpp | 72 ++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index b763dc11..86144f50 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -33,12 +33,13 @@ struct CanVendorSystec : CanDevice { private: std::atomic m_receive_thread_flag = true; - tUcanHandle m_UcanHandle; + std::atomic m_queued_reads; int m_module_number; int m_channel_number; + int m_port_number; std::thread m_SystecRxThread; - tUcanHandle get_module_handle() { return m_handle_map[m_module_number]; } + tUcanHandle get_module_handle() { return m_module_to_handle_map[m_module_number]; } CanReturnCode vendor_open() noexcept override; CanReturnCode vendor_close() noexcept override; @@ -47,11 +48,10 @@ struct CanVendorSystec : CanDevice { CanReturnCode init_can_port(); static std::mutex m_handles_lock; - static std::unordered_map m_handle_map; + static std::unordered_map m_module_to_handle_map; + static std::unordered_map m_port_to_vendor_map; - inline void map_module_to_handle(int module, tUcanHandle handle) { m_handle_map[module] = handle; } - inline int erase_module_handle(int module) { return m_handle_map.erase(module); } - + friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); std::string UsbCanGetErrorText( long err_code ); }; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 76711a17..f497796a 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -7,18 +7,31 @@ #include std::mutex CanVendorSystec::m_handles_lock; -std::unordered_map CanVendorSystec::m_handle_map; +std::unordered_map CanVendorSystec::m_module_to_handle_map; +std::unordered_map CanVendorSystec::m_port_to_vendor_map; + +// Callback registered per-module to handle receive events +void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p) { + if (bEvent_p == USBCAN_EVENT_RECEIVE) { + int module_number = *(reinterpret_cast(pArg_p)); + int port_number = 2 * module_number + bChannel_p; + CanVendorSystec *vendorPtr = CanVendorSystec::m_port_to_vendor_map[port_number]; + if (!vendorPtr) return; // [] returns nullptr if not found in map + ++(vendorPtr->m_queued_reads); + } +} CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) - : CanDevice("systec", args) { + : CanDevice("systec", args), m_queued_reads{0} { if (!args.config.bus_name.has_value()) { throw std::invalid_argument("Missing required configuration parameters"); } // TODO trim possible can prefix - int handle_number = std::stoi(args.config.bus_name.value()); - m_module_number = handle_number / 2; - m_channel_number = handle_number % 2; + m_port_number = std::stoi(args.config.bus_name.value()); + m_module_number = m_port_number / 2; + m_channel_number = m_port_number % 2; + m_port_to_vendor_map[m_port_number] = this; } // TODO should we make this noexcept? how can we guarantee that? @@ -53,20 +66,26 @@ CanReturnCode CanVendorSystec::init_can_port() { // check if USB-CANmodul is already initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); - auto pos = m_handle_map.find(m_module_number); - if (pos == m_handle_map.end()) { // module not in use - systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, 0, 0); + auto mapping = m_module_to_handle_map.find(m_module_number); + if (mapping == m_module_to_handle_map.end()) { // module not in use + systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); if (systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; ::UcanDeinitHardware(can_module_handle); return CanReturnCode::unknown_open_error; } - map_module_to_handle(m_module_number, can_module_handle); + LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number << " with handle " << (int) can_module_handle; + m_module_to_handle_map[m_module_number] = can_module_handle; } else { // find existing handle of module - can_module_handle = pos->second; - LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanDeinitHardware"; + can_module_handle = mapping->second; + LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanInitHardware"; } + // TODO handle error code for reset... + // also investigate the minimum amount of things to reset to restore good state + UcanResetCanEx(can_module_handle, (BYTE) m_channel_number, (DWORD) 0); + m_port_to_vendor_map[m_port_number] = this; + systec_call_return = ::UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); if ( systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; @@ -97,10 +116,23 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { try { m_receive_thread_flag = false; std::lock_guard guard(CanVendorSystec::m_handles_lock); - erase_module_handle(m_module_number); + m_port_to_vendor_map.erase(m_port_number); if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); - auto return_code = UcanDeinitCanEx (m_UcanHandle, (BYTE) m_channel_number); + + auto handle = get_module_handle(); + + auto return_code = UcanDeinitCanEx(handle, (BYTE) m_channel_number); + m_module_to_handle_map.erase(m_module_number); if (return_code != USBCAN_SUCCESSFUL) return CanReturnCode::unknown_close_error; + + int opposite_channel = 2 * m_module_number + (1 - m_channel_number); + if (!m_port_to_vendor_map[opposite_channel]) { + // de init hardware if neither channel on the module are in use + // e.g. if channel 0, we need to check if channel 1 is in use and vice versa + // TODO how does this work if UcanDeinitCanEx above fails? + auto return_code_hw = UcanDeinitHardware(handle); + if (return_code_hw != USBCAN_SUCCESSFUL) return CanReturnCode::unknown_close_error; + } } catch (...) { return CanReturnCode::internal_api_error; } @@ -219,7 +251,10 @@ int CanVendorSystec::SystecRxThread() BYTE status; tCanMsgStruct read_can_message; LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << m_receive_thread_flag <<"]"; + size_t to_read; while (m_receive_thread_flag) { + to_read = m_queued_reads; + if (to_read < 1) continue; status = UcanReadCanMsgEx(get_module_handle(), (BYTE *) &m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: @@ -228,17 +263,16 @@ int CanVendorSystec::SystecRxThread() LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); [[ fallthrough ]]; case USBCAN_SUCCESSFUL: { + --m_queued_reads; if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; std::vector data(read_can_message.m_bData, read_can_message.m_bData + read_can_message.m_bDLC); - // id, data, flags - CanFrame can_msg_copy(read_can_message.m_dwID, data, read_can_message.m_bFF); - received(can_msg_copy); + CanFrame can_frame(read_can_message.m_dwID, data, read_can_message.m_bFF); + received(can_frame); break; } case USBCAN_WARN_NODATA: - LOG(Log::TRC, CanLogIt::h()) << UsbCanGetErrorText(status); - // TODO is it correct to sleep here? - // Sleep(100); // ms + m_queued_reads -= to_read; + LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); break; default: // errors // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, From 9a1f951fdd2e7cc8e5f2269f8dc90b0a5f1effd2 Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 5 Mar 2026 09:59:00 +0100 Subject: [PATCH 32/63] handle parsing of can and vcan port names for systec --- src/main/CanVendorSystec.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index f497796a..339cc715 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -5,6 +5,7 @@ #include #include #include +#include std::mutex CanVendorSystec::m_handles_lock; std::unordered_map CanVendorSystec::m_module_to_handle_map; @@ -27,8 +28,12 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) throw std::invalid_argument("Missing required configuration parameters"); } - // TODO trim possible can prefix - m_port_number = std::stoi(args.config.bus_name.value()); + std::smatch matches; + // Accept port name of , can or vcan + if (!std::regex_search(args.config.bus_name.value(), matches, std::regex("^(can|vcan)?(\\d+)$"))) + throw std::invalid_argument("Could not parse port name"); + + m_port_number = std::stoi(matches[2]); m_module_number = m_port_number / 2; m_channel_number = m_port_number % 2; m_port_to_vendor_map[m_port_number] = this; From 32f03f739dde8a340ff5235dfbe1ef24e9f867ae Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 5 Mar 2026 12:13:44 +0100 Subject: [PATCH 33/63] fix reconnect logic for multiple channels on same module --- src/main/CanVendorSystec.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 339cc715..bcd58d92 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -75,7 +75,7 @@ CanReturnCode CanVendorSystec::init_can_port() { if (mapping == m_module_to_handle_map.end()) { // module not in use systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); if (systec_call_return != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "UcanInitHardwareEx, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitHardwareEx: " << UsbCanGetErrorText(systec_call_return); ::UcanDeinitHardware(can_module_handle); return CanReturnCode::unknown_open_error; } @@ -127,8 +127,10 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { auto handle = get_module_handle(); auto return_code = UcanDeinitCanEx(handle, (BYTE) m_channel_number); - m_module_to_handle_map.erase(m_module_number); - if (return_code != USBCAN_SUCCESSFUL) return CanReturnCode::unknown_close_error; + if (return_code != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); + return CanReturnCode::unknown_close_error; + } int opposite_channel = 2 * m_module_number + (1 - m_channel_number); if (!m_port_to_vendor_map[opposite_channel]) { @@ -136,7 +138,11 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { // e.g. if channel 0, we need to check if channel 1 is in use and vice versa // TODO how does this work if UcanDeinitCanEx above fails? auto return_code_hw = UcanDeinitHardware(handle); - if (return_code_hw != USBCAN_SUCCESSFUL) return CanReturnCode::unknown_close_error; + m_module_to_handle_map.erase(m_module_number); + if (return_code_hw != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); + return CanReturnCode::unknown_close_error; + } } } catch (...) { return CanReturnCode::internal_api_error; @@ -259,7 +265,7 @@ int CanVendorSystec::SystecRxThread() size_t to_read; while (m_receive_thread_flag) { to_read = m_queued_reads; - if (to_read < 1) continue; + if (to_read < 1) continue; status = UcanReadCanMsgEx(get_module_handle(), (BYTE *) &m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: From d461b91be3f563c7fd18e3d3508a71390a790501 Mon Sep 17 00:00:00 2001 From: James Souter Date: Fri, 6 Mar 2026 13:53:17 +0100 Subject: [PATCH 34/63] fixes for systec build in cmake --- CMakeLists.txt | 4 ++++ cmake/systec.cmake | 47 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fea0ae0..9abd2680 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,9 +71,13 @@ if (UNIX) else() target_link_libraries(CanModuleMain PUBLIC ${anagate_SOURCE_DIR}/Win64/AnaGateCanDll64.lib + ${systec_SOURCE_DIR}/lib/USBCAN64.lib ) + target_include_directories(CanModuleMain PUBLIC ${systec_SOURCE_DIR}/Include) file(COPY "${anagate_SOURCE_DIR}/Win64/AnaGateCan64.dll" DESTINATION "${CMAKE_BINARY_DIR}/Release") + file(COPY "${systec_SOURCE_DIR}/lib/USBCAN64.dll" + DESTINATION "${CMAKE_BINARY_DIR}/Release") endif() if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/cmake/systec.cmake b/cmake/systec.cmake index e60fc3c9..7f9f6846 100644 --- a/cmake/systec.cmake +++ b/cmake/systec.cmake @@ -1,14 +1,37 @@ -#----- -#SYSTEC USB-CANmodul Utility Disk -#----- -if( NOT DEFINED ENV{SYSTEC_PATH_HEADERS} OR NOT DEFINED ENV{SYSTEC_PATH_LIBS} ) - message( FATAL_ERROR "unable to determine SYSTEC USB-CANmodule Utility Disk headers and library paths from environment variables SYSTEC_PATH_HEADERS [$ENV{SYSTEC_PATH_HEADERS}] SYSTEC_PATH_LIBS [$ENV{SYSTEC_PATH_LIBS}]") +include(FetchContent) + +if(NOT DEFINED SYSTEC_LIBRARY) + message(FATAL_ERROR "Could not find systec library locally") + # set(SYTEC_LIBRARY "TODO zip path goes here") +endif() + +set(SYSTEC_FETCHCONTENT_ARGS + URL "${SYSTEC_LIBRARY}" + DOWNLOAD_EXTRACT_TIMESTAMP True +) + +if(EXISTS "${SYSTEC_LIBRARY}") + message(STATUS "Using local Systec archive: ${SYSTEC_LIBRARY}") +elseif(SYSTEC_LIBRARY MATCHES "^https?://") + if(DEFINED ENV{ICS_REPO_DEPS_TOKEN} AND NOT "$ENV{ICS_REPO_DEPS_TOKEN}" STREQUAL "") + message(STATUS "Downloading Systec archive with authentication: ${SYSTEC_LIBRARY}") + list(APPEND SYSTEC_FETCHCONTENT_ARGS + HTTP_HEADER "PRIVATE-TOKEN: $ENV{ICS_REPO_DEPS_TOKEN}" + ) + else() + message(STATUS "Downloading Systec archive without authentication: ${SYSTEC_LIBRARY}") + endif() else() - message( STATUS "using SYSTEC USB-CANmodule Utility Disk headers and library paths from environment variables SYSTEC_PATH_HEADERS [$ENV{SYSTEC_PATH_HEADERS}] SYSTEC_PATH_LIBS [$ENV{SYSTEC_PATH_LIBS}]") + message(FATAL_ERROR "SYSTEC_LIBRARY must be an existing local archive or an http(s) URL. Got: ${SYSTEC_LIBRARY}") +endif() + +FetchContent_Declare( + Systec + ${SYSTEC_FETCHCONTENT_ARGS} +) + +FetchContent_MakeAvailable(Systec) + +if (WIN32) + add_compile_definitions(WIN32) endif() -include_directories($ENV{SYSTEC_PATH_HEADERS}) -set(SYSTEC_LIB_PATH $ENV{SYSTEC_PATH_LIBS}) -if(NOT TARGET libusbcan64) - add_library(libusbcan64 SHARED IMPORTED) - set_property(TARGET libusbcan64 PROPERTY IMPORTED_LOCATION $ENV{SYSTEC_PATH_LIBS}/USBCAN64.lib) -endif() \ No newline at end of file From 5b1cc9d94f1dcd905490cace0ff9467b5eb7ff44 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 17 Mar 2026 16:00:07 +0100 Subject: [PATCH 35/63] remove :: prefix for systec api calls --- src/main/CanVendorSystec.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index bcd58d92..a95aeffd 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -73,10 +73,10 @@ CanReturnCode CanVendorSystec::init_can_port() { std::lock_guard guard(CanVendorSystec::m_handles_lock); auto mapping = m_module_to_handle_map.find(m_module_number); if (mapping == m_module_to_handle_map.end()) { // module not in use - systec_call_return = ::UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); + systec_call_return = UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); if (systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitHardwareEx: " << UsbCanGetErrorText(systec_call_return); - ::UcanDeinitHardware(can_module_handle); + UcanDeinitHardware(can_module_handle); return CanReturnCode::unknown_open_error; } LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number << " with handle " << (int) can_module_handle; @@ -91,10 +91,10 @@ CanReturnCode CanVendorSystec::init_can_port() { UcanResetCanEx(can_module_handle, (BYTE) m_channel_number, (DWORD) 0); m_port_to_vendor_map[m_port_number] = this; - systec_call_return = ::UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); + systec_call_return = UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); if ( systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; - ::UcanDeinitCanEx(can_module_handle, m_channel_number); + UcanDeinitCanEx(can_module_handle, m_channel_number); return CanReturnCode::unknown_open_error; } From ede7de6ef8fa2f9c06c4bd7ebcecb9b5d43280ee Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 17 Mar 2026 16:35:32 +0100 Subject: [PATCH 36/63] split up systec close logic, deinit hardware on failed open --- src/include/CanVendorSystec.h | 2 ++ src/main/CanVendorSystec.cpp | 56 +++++++++++++++++++---------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 86144f50..22fd4a7f 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -53,6 +53,8 @@ struct CanVendorSystec : CanDevice { friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); std::string UsbCanGetErrorText( long err_code ); + + CanReturnCode deinit_channel() noexcept; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index a95aeffd..767d57e5 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -94,7 +94,7 @@ CanReturnCode CanVendorSystec::init_can_port() { systec_call_return = UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); if ( systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; - UcanDeinitCanEx(can_module_handle, m_channel_number); + deinit_channel(); return CanReturnCode::unknown_open_error; } @@ -118,38 +118,42 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { } CanReturnCode CanVendorSystec::vendor_close() noexcept { + auto return_code = CanReturnCode::success; try { m_receive_thread_flag = false; - std::lock_guard guard(CanVendorSystec::m_handles_lock); - m_port_to_vendor_map.erase(m_port_number); if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); - - auto handle = get_module_handle(); - - auto return_code = UcanDeinitCanEx(handle, (BYTE) m_channel_number); - if (return_code != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); - return CanReturnCode::unknown_close_error; - } - - int opposite_channel = 2 * m_module_number + (1 - m_channel_number); - if (!m_port_to_vendor_map[opposite_channel]) { - // de init hardware if neither channel on the module are in use - // e.g. if channel 0, we need to check if channel 1 is in use and vice versa - // TODO how does this work if UcanDeinitCanEx above fails? - auto return_code_hw = UcanDeinitHardware(handle); - m_module_to_handle_map.erase(m_module_number); - if (return_code_hw != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); - return CanReturnCode::unknown_close_error; - } - } + return_code = deinit_channel(); } catch (...) { - return CanReturnCode::internal_api_error; + return_code = CanReturnCode::internal_api_error; } - return CanReturnCode::success; + return return_code; }; +CanReturnCode CanVendorSystec::deinit_channel() noexcept { + auto internal_return_code = CanReturnCode::success; + std::lock_guard guard(CanVendorSystec::m_handles_lock); + m_port_to_vendor_map.erase(m_port_number); + auto handle = get_module_handle(); + auto return_code = UcanDeinitCanEx(handle, m_channel_number); + if (return_code != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); + internal_return_code = CanReturnCode::unknown_close_error; + } // still attempt to deinit hardware if channel deinit fails + + if (!m_port_to_vendor_map[m_port_number ^ 1]) { + // de init hardware if neither channel on the module are in use + // toggle last bit to get the other port on the same module + // e.g. if channel 0, we need to check if channel 1 is in use and vice versa + auto return_code_hw = UcanDeinitHardware(handle); + m_module_to_handle_map.erase(m_module_number); + if (return_code_hw != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); + internal_return_code = CanReturnCode::unknown_close_error; + } + } + return internal_return_code; +} + CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { uint32_t len = frame.length(); std::vector message = frame.message(); From 66f70cc1d127b3a313f2678e1625c12843b33557 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 18 Mar 2026 11:39:43 +0100 Subject: [PATCH 37/63] don't build systec for windows by default --- CMakeLists.txt | 18 +++++++++++------- cmake/systec.cmake | 4 ++-- src/main/CanDevice.cpp | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9abd2680..baf62151 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,12 +36,13 @@ if (UNIX) src/main/CanVendorSocketCan.cpp src/main/CanVendorSocketCanSystec.cpp ) -else() +elseif ("${CANMODULE_BUILD_SYSTEC_WINDOWS}" STREQUAL ON) include(cmake/systec.cmake) include_directories(${SYSTEC_PATH_HEADERS}) list(APPEND VENDOR_SOURCES src/main/CanVendorSystec.cpp ) + add_compile_definitions(CANMODULE_BUILD_SYSTEC_WINDOWS) endif() if (NOT DEFINED CAN_MODULE_MAIN_ONLY) @@ -69,15 +70,18 @@ if (UNIX) libsocketcan ) else() - target_link_libraries(CanModuleMain PUBLIC - ${anagate_SOURCE_DIR}/Win64/AnaGateCanDll64.lib - ${systec_SOURCE_DIR}/lib/USBCAN64.lib - ) target_include_directories(CanModuleMain PUBLIC ${systec_SOURCE_DIR}/Include) + target_link_libraries(CanModuleMain PUBLIC + ${anagate_SOURCE_DIR}/Win64/AnaGateCanDll64.lib) file(COPY "${anagate_SOURCE_DIR}/Win64/AnaGateCan64.dll" DESTINATION "${CMAKE_BINARY_DIR}/Release") - file(COPY "${systec_SOURCE_DIR}/lib/USBCAN64.dll" - DESTINATION "${CMAKE_BINARY_DIR}/Release") + + if ("${CANMODULE_BUILD_SYSTEC_WINDOWS}" STREQUAL ON) + target_link_libraries(CanModuleMain PUBLIC + ${systec_SOURCE_DIR}/lib/USBCAN64.lib) + file(COPY "${systec_SOURCE_DIR}/lib/USBCAN64.dll" + DESTINATION "${CMAKE_BINARY_DIR}/Release") + endif() endif() if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") diff --git a/cmake/systec.cmake b/cmake/systec.cmake index 7f9f6846..cecb817d 100644 --- a/cmake/systec.cmake +++ b/cmake/systec.cmake @@ -1,8 +1,8 @@ include(FetchContent) if(NOT DEFINED SYSTEC_LIBRARY) - message(FATAL_ERROR "Could not find systec library locally") - # set(SYTEC_LIBRARY "TODO zip path goes here") + message(FATAL_ERROR "SYSTEC_LIBRARY not set, trying typical install location") + set(SYSTEC_LIBRARY "C:/Program Files (x86)/SYSTEC-electronic/USB-CANmodul Utility Disk/Examples") endif() set(SYSTEC_FETCHCONTENT_ARGS diff --git a/src/main/CanDevice.cpp b/src/main/CanDevice.cpp index 0f6265bd..34d6d816 100644 --- a/src/main/CanDevice.cpp +++ b/src/main/CanDevice.cpp @@ -13,7 +13,7 @@ #ifndef _WIN32 #include "CanVendorSocketCan.h" #include "CanVendorSocketCanSystec.h" -#else +#elif defined(CANMODULE_BUILD_SYSTEC_WINDOWS) #include "CanVendorSystec.h" #endif @@ -165,7 +165,7 @@ std::unique_ptr CanDevice::create( LOG(Log::DBG, CanLogIt::h()) << "Creating SocketCAN Systec CAN device"; return std::make_unique(configuration); } -#else +#elif defined(CANMODULE_BUILD_SYSTEC_WINDOWS) if (vendor == "systec") { LOG(Log::DBG, CanLogIt::h()) << "Creating Systec CAN device for Windows"; return std::make_unique(configuration); From ae0b19dda04e76884317c7cab0b5a5da2d541f78 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 18 Mar 2026 11:40:14 +0100 Subject: [PATCH 38/63] move reconnect_channel to new method --- src/include/CanVendorSystec.h | 1 + src/main/CanVendorSystec.cpp | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 22fd4a7f..893ef1ac 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -55,6 +55,7 @@ struct CanVendorSystec : CanDevice { std::string UsbCanGetErrorText( long err_code ); CanReturnCode deinit_channel() noexcept; + CanReturnCode reconnect_channel() noexcept; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 767d57e5..9745f1ba 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -17,8 +17,7 @@ void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, v int module_number = *(reinterpret_cast(pArg_p)); int port_number = 2 * module_number + bChannel_p; CanVendorSystec *vendorPtr = CanVendorSystec::m_port_to_vendor_map[port_number]; - if (!vendorPtr) return; // [] returns nullptr if not found in map - ++(vendorPtr->m_queued_reads); + if (vendorPtr) ++(vendorPtr->m_queued_reads); // [] returns nullptr if not found in map; } } @@ -154,6 +153,12 @@ CanReturnCode CanVendorSystec::deinit_channel() noexcept { return internal_return_code; } +CanReturnCode CanVendorSystec::reconnect_channel() noexcept { + auto close_code = close(); + if (close_code != CanReturnCode::success) return close_code; + return open(); +} + CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { uint32_t len = frame.length(); std::vector message = frame.message(); @@ -188,12 +193,10 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " << UsbCanGetErrorText(Status); - // for now, just always reconnect on a failed send. - auto close_code = close(); - if (close_code != CanReturnCode::success) return close_code; - - auto open_code = open(); - if (open_code != CanReturnCode::success) return open_code; + // for now, just reconnect, could implement configuration option + // for how to handle responding to a failed send + auto reconnect_code = reconnect_channel(); + if (reconnect_code != CanReturnCode::success) return reconnect_code; switch (Status) { case USBCAN_ERR_CANNOTINIT: From 18d14d793b95cc26ba45207d1f6cfe5fabac447b Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 18 Mar 2026 14:14:33 +0100 Subject: [PATCH 39/63] remove erroneous FATAL_ERROR from systec.cmake message --- cmake/systec.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/systec.cmake b/cmake/systec.cmake index cecb817d..394c2b0c 100644 --- a/cmake/systec.cmake +++ b/cmake/systec.cmake @@ -1,7 +1,7 @@ include(FetchContent) if(NOT DEFINED SYSTEC_LIBRARY) - message(FATAL_ERROR "SYSTEC_LIBRARY not set, trying typical install location") + message("SYSTEC_LIBRARY not set, trying typical install location") set(SYSTEC_LIBRARY "C:/Program Files (x86)/SYSTEC-electronic/USB-CANmodul Utility Disk/Examples") endif() From 4af4598795e81aacbe2b35ab5b826984fee761fc Mon Sep 17 00:00:00 2001 From: James Souter Date: Fri, 20 Mar 2026 10:53:49 +0100 Subject: [PATCH 40/63] remove check on message length in systec vendor_send --- src/main/CanVendorSystec.cpp | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 9745f1ba..7522002a 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -160,33 +160,19 @@ CanReturnCode CanVendorSystec::reconnect_channel() noexcept { } CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { - uint32_t len = frame.length(); std::vector message = frame.message(); tCanMsgStruct can_msg_to_send; BYTE Status; can_msg_to_send.m_dwID = frame.id(); - can_msg_to_send.m_bDLC = len; + can_msg_to_send.m_bDLC = frame.length(); can_msg_to_send.m_bFF = 0; if (frame.is_remote_request()) { can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; } - int message_length_to_process; - //If there is more than 8 characters to process, we process 8 of them in this iteration of the loop - if (len > 8) { - message_length_to_process = 8; - LOG(Log::DBG, CanLogIt::h()) << "The length is more than 8 bytes, adjust to 8, ignore > 8. len = " << len; - } else { - //Otherwise if there is less than 8 characters to process, we process all of them in this iteration of the loop - message_length_to_process = len; - if (len < 8) { - LOG(Log::DBG, CanLogIt::h())<< "The length is less than 8 bytes, process only. len = " << len; - } - } - can_msg_to_send.m_bDLC = message_length_to_process; - if (message_length_to_process) - std::copy(message.begin(), message.begin() + message_length_to_process, can_msg_to_send.m_bData); + + std::copy(message.begin(), message.begin() + can_msg_to_send.m_bDLC, can_msg_to_send.m_bData); Status = UcanWriteCanMsgEx(get_module_handle(), m_channel_number, &can_msg_to_send, NULL); if (Status != USBCAN_SUCCESSFUL) { From 17f4cc75d04ed2b1b8cea4f33856516c258d800c Mon Sep 17 00:00:00 2001 From: James Souter Date: Fri, 20 Mar 2026 11:12:54 +0100 Subject: [PATCH 41/63] Add WIP systec test for pcaticswin11 --- test/python/test_systec.py | 79 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 test/python/test_systec.py diff --git a/test/python/test_systec.py b/test/python/test_systec.py new file mode 100644 index 00000000..440c4a7c --- /dev/null +++ b/test/python/test_systec.py @@ -0,0 +1,79 @@ +from time import sleep, time +import pytest +from common import * +import os +import subprocess +import struct +import socket + +# pytestmark = pytest.mark.skipif( +# not sys.platform.startswith("win"), +# reason="This test module is only for Windows environments.", +# ) + +pytestmark = pytest.mark.skipif( + socket.gethostname() != "pcaticswin11", + reason="Tests currently only work when run on the pcaticswin11 development server" +) + +@pytest.fixture +def device_and_frames(): + config = CanDeviceConfiguration() + config.host = "localhost" + config.bitrate = 125_000 + config.enable_termination = True + config.high_speed = True + config.bus_name = "can0" + received = [] + device = CanDevice.create( + "systec", CanDeviceArguments(config, received.append) + ) + o1 = device.open() + assert o1 == CanReturnCode.success + received.clear() + yield device, received + c1 = device.close() + assert c1 == CanReturnCode.success + + +def test_sync_messages_elmb(device_and_frames): + device, received = device_and_frames + # on new connect, all statistics reset (for now) + diag = device.diagnostics() + assert diag.mode == "NORMAL" + assert diag.state == "USBCAN_CANERR_OK" + assert isinstance(diag.uptime, int) + rx = diag.rx + tx = diag.tx + assert diag.rx_error == 0 + assert diag.tx_error == 0 + + sleep(1) + received.clear() # hopefully clear any previously buffered frames + r = device.send(CanFrame(0x80)) # send sync message + assert r == CanReturnCode.success + + start = time() + while (time() - start < 15): + if len(received) == 65: + break + else: + raise RuntimeError(f"Did not receive expected frames before timeout: {len(received)}/65") + diff = time() - start + + diag = device.diagnostics() + assert diag.state == "USBCAN_CANERR_OK" + assert diag.rx - rx == 65 + assert diag.tx - tx == 1 + assert diag.rx_per_second == pytest.approx(65/diff, rel=0.3) + assert diag.tx_per_second == pytest.approx(1/diff, rel=0.3) + + sync_frame = received[0] + assert sync_frame.id() == 399 + assert ord(sync_frame.message()[0]) == 255 + for idx, frame in enumerate(received[1:]): + assert frame.id() == 911 + assert ord(frame.message()[0]) == idx + + +# def test_manufacturer_stuff(device_and_frames): From 78178bd699ad96f831fc733ec1aa1dac987742fb Mon Sep 17 00:00:00 2001 From: James Souter Date: Fri, 20 Mar 2026 11:53:23 +0100 Subject: [PATCH 42/63] update test_systec for ELMB id 15 --- test/python/test_systec.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/python/test_systec.py b/test/python/test_systec.py index 440c4a7c..a153c536 100644 --- a/test/python/test_systec.py +++ b/test/python/test_systec.py @@ -16,6 +16,8 @@ reason="Tests currently only work when run on the pcaticswin11 development server" ) +ELMB_ID = 15 + @pytest.fixture def device_and_frames(): config = CanDeviceConfiguration() @@ -68,9 +70,10 @@ def test_sync_messages_elmb(device_and_frames): assert diag.rx_per_second == pytest.approx(65/diff, rel=0.3) assert diag.tx_per_second == pytest.approx(1/diff, rel=0.3) - sync_frame = received[0] - assert sync_frame.id() == 399 - assert ord(sync_frame.message()[0]) == 255 + digital_input_frame = received[0] + # see page 16 https://www.nikhef.nl/pub/departments/ct/po/html/ELMB128/ELMB24.pdf + assert digital_input_frame.id() == 0x180 + ELMB_ID + assert ord(digital_input_frame.message()[0]) == 255 for idx, frame in enumerate(received[1:]): assert frame.id() == 911 assert ord(frame.message()[0]) == idx From 77556627d0115541b1156eb7a1c8968ad984cdf7 Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 23 Mar 2026 09:35:36 +0100 Subject: [PATCH 43/63] implement suggestions from review * parse bus_number in systec * update uptime comment * use string_view for systec error message --- src/include/CanDiagnostics.h | 3 ++- src/include/CanVendorSystec.h | 4 ++-- src/main/CanVendorSystec.cpp | 12 +++--------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/include/CanDiagnostics.h b/src/include/CanDiagnostics.h index 5c45f502..4b44fc18 100644 --- a/src/include/CanDiagnostics.h +++ b/src/include/CanDiagnostics.h @@ -47,7 +47,8 @@ struct CanDiagnostics { std::optional temperature; ///< Optional temperature reading for Anagate devices. - std::optional uptime; ///< Optional uptime in seconds. + std::optional uptime; ///< Optional uptime in seconds for Anagate + ///< and Systec for Windows. std::optional tcp_rx; ///< Optional TCP Received counter for ///< both SocketCAN and Anagate devices. diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 893ef1ac..91e0ee73 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -32,7 +32,7 @@ struct CanVendorSystec : CanDevice { int SystecRxThread(); private: - std::atomic m_receive_thread_flag = true; + std::atomic m_receive_thread_flag {true}; std::atomic m_queued_reads; int m_module_number; int m_channel_number; @@ -52,7 +52,7 @@ struct CanVendorSystec : CanDevice { static std::unordered_map m_port_to_vendor_map; friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); - std::string UsbCanGetErrorText( long err_code ); + std::string_view UsbCanGetErrorText( long err_code ); CanReturnCode deinit_channel() noexcept; CanReturnCode reconnect_channel() noexcept; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 7522002a..71ac9d84 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -5,7 +5,6 @@ #include #include #include -#include std::mutex CanVendorSystec::m_handles_lock; std::unordered_map CanVendorSystec::m_module_to_handle_map; @@ -23,16 +22,11 @@ void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, v CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args), m_queued_reads{0} { - if (!args.config.bus_name.has_value()) { + if (!args.config.bus_number.has_value()) { throw std::invalid_argument("Missing required configuration parameters"); } - std::smatch matches; - // Accept port name of , can or vcan - if (!std::regex_search(args.config.bus_name.value(), matches, std::regex("^(can|vcan)?(\\d+)$"))) - throw std::invalid_argument("Could not parse port name"); - - m_port_number = std::stoi(matches[2]); + m_port_number = args.config.bus_number.value(); m_module_number = m_port_number / 2; m_channel_number = m_port_number % 2; m_port_to_vendor_map[m_port_number] = this; @@ -290,7 +284,7 @@ int CanVendorSystec::SystecRxThread() return 0; } -std::string CanVendorSystec::UsbCanGetErrorText( long err_code ) { +std::string_view CanVendorSystec::UsbCanGetErrorText( long err_code ) { switch( err_code ){ case USBCAN_SUCCESSFUL: return("success"); From 5fc4c1e10895b101dc0602cea8ec0c4d2c36f70e Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 23 Mar 2026 11:14:54 +0100 Subject: [PATCH 44/63] Use descriptive error messages for systec status in diagnostics.state --- src/include/CanVendorSystec.h | 1 + src/main/CanVendorSystec.cpp | 169 ++++++++++++++++++---------------- 2 files changed, 90 insertions(+), 80 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 91e0ee73..7295a73f 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -53,6 +53,7 @@ struct CanVendorSystec : CanDevice { friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); std::string_view UsbCanGetErrorText( long err_code ); + std::string UsbCanGetStatusText( long err_code ); CanReturnCode deinit_channel() noexcept; CanReturnCode reconnect_channel() noexcept; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 71ac9d84..32447d56 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -202,18 +202,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { auto handle = get_module_handle(); UcanGetStatusEx(handle, m_channel_number, &status); WORD can_status = status.m_wCanStatus; - switch (can_status) { - case USBCAN_CANERR_OK: diagnostics.state = "USBCAN_CANERR_OK"; break; - case USBCAN_CANERR_XMTFULL: diagnostics.state = "USBCAN_CANERR_XMTFULL"; break; - case USBCAN_CANERR_OVERRUN: diagnostics.state = "USBCAN_CANERR_OVERRUN"; break; - case USBCAN_CANERR_BUSLIGHT: diagnostics.state = "USBCAN_CANERR_BUSLIGHT"; break; - case USBCAN_CANERR_BUSHEAVY: diagnostics.state = "USBCAN_CANERR_BUSHEAVY"; break; - case USBCAN_CANERR_BUSOFF: diagnostics.state = "USBCAN_CANERR_BUSOFF"; break; - case USBCAN_CANERR_QOVERRUN: diagnostics.state = "USBCAN_CANERR_QOVERRUN"; break; - case USBCAN_CANERR_QXMTFULL: diagnostics.state = "USBCAN_CANERR_QXMTFULL"; break; - case USBCAN_CANERR_REGTEST: diagnostics.state = "USBCAN_CANERR_REGTEST"; break; - case USBCAN_CANERR_TXMSGLOST: diagnostics.state = "USBCAN_CANERR_TXMSGLOST"; break; - } + diagnostics.state = UsbCanGetStatusText(can_status); tUcanMsgCountInfo msg_count_info; UcanGetMsgCountInfoEx(handle, m_channel_number, &msg_count_info); diagnostics.tx = msg_count_info.m_wSentMsgCount; @@ -286,157 +275,177 @@ int CanVendorSystec::SystecRxThread() std::string_view CanVendorSystec::UsbCanGetErrorText( long err_code ) { switch( err_code ){ - case USBCAN_SUCCESSFUL: return("success"); + case USBCAN_SUCCESSFUL: return "success"; - case USBCAN_ERR_RESOURCE: return ("This error code returns if one resource could not be generated. In this " - "case the term resource means memory and handles provided by the Windows OS"); + case USBCAN_ERR_RESOURCE: return "This error code returns if one resource could not be generated. In this " + "case the term resource means memory and handles provided by the Windows OS"; - case USBCAN_ERR_MAXMODULES: return("An application has tried to open more than 64 USB-CANmodul devices. " + case USBCAN_ERR_MAXMODULES: return "An application has tried to open more than 64 USB-CANmodul devices. " "The standard version of the DLL only supports up to 64 USB-CANmodul " "devices at the same time. This error also appears if several applications " "try to access more than 64 USB-CANmodul devices. For example, " "application 1 has opened 60 modules, application 2 has opened 4 " "modules and application 3 wants to open a module. Application 3 " - "receives this error code."); + "receives this error code."; - case USBCAN_ERR_HWINUSE: return("An application tries to initialize an USB-CANmodul with the given device " + case USBCAN_ERR_HWINUSE: return "An application tries to initialize an USB-CANmodul with the given device " "number. If this module has already been initialized by its own or by " - "another application, this error code is returned."); + "another application, this error code is returned."; - case USBCAN_ERR_ILLVERSION: return("This error code returns if the firmware version of the USB-CANmodul is " + case USBCAN_ERR_ILLVERSION: return "This error code returns if the firmware version of the USB-CANmodul is " "not compatible to the software version of the DLL. In this case, install " "the latest driver for the USB-CANmodul. Furthermore make sure that " - "the latest firmware version is programmed to the USB-CANmodul."); + "the latest firmware version is programmed to the USB-CANmodul."; - case USBCAN_ERR_ILLHW: return("This error code returns if an USB-CANmodul with the given device " + case USBCAN_ERR_ILLHW: return "This error code returns if an USB-CANmodul with the given device " "number is not found. If the function UcanInitHardware() or " "UcanInitHardwareEx() has been called with the device number " "USBCAN_ANY_MODULE, and the error code appears, it indicates that " "no module is connected to the PC or all connected modules are already " - "in use."); + "in use."; - case USBCAN_ERR_ILLHANDLE: return("This error code returns if a function received an incorrect USBCAN " + case USBCAN_ERR_ILLHANDLE: return "This error code returns if a function received an incorrect USBCAN " "handle. The function first checks which USB-CANmodul is related to this " - "handle. This error occurs if no device belongs this handle."); + "handle. This error occurs if no device belongs this handle."; - case USBCAN_ERR_ILLPARAM: return("This error code returns if a wrong parameter is passed to the function. " + case USBCAN_ERR_ILLPARAM: return "This error code returns if a wrong parameter is passed to the function. " "For example, the value NULL has been passed to a pointer variable " - "instead of a valid address."); + "instead of a valid address."; - case USBCAN_ERR_BUSY: return("This error code occurs if several threads are accessing an " + case USBCAN_ERR_BUSY: return "This error code occurs if several threads are accessing an " "USB-CANmodul within a single application. After the other threads have " - "finished their tasks, the function may be called again."); + "finished their tasks, the function may be called again."; - case USBCAN_ERR_TIMEOUT: return("This error code occurs if the function transmits a command to the " + case USBCAN_ERR_TIMEOUT: return "This error code occurs if the function transmits a command to the " "USB-CANmodul but no reply is returned. To solve this problem, close " - "the application, disconnect the USB-CANmodul, and connect it again."); + "the application, disconnect the USB-CANmodul, and connect it again."; - case USBCAN_ERR_IOFAILED: return("This error code occurs if the communication to the kernel driver was " + case USBCAN_ERR_IOFAILED: return "This error code occurs if the communication to the kernel driver was " "interrupted. This happens, for example, if the USB-CANmodul is " "disconnected during transferring data or commands to the " - "USB-CANmodul."); + "USB-CANmodul."; - case USBCAN_ERR_DLL_TXFULL: return("The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks " + case USBCAN_ERR_DLL_TXFULL: return "The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks " "if the transmit buffer within the DLL has enough capacity to store new " "CAN messages. If the buffer is full, this error code returns. The CAN " "message passed to these functions will not be written into the transmit " "buffer in order to protect other CAN messages against overwriting. The " "size of the transmit buffer is configurable (refer to function " - "UcanInitCanEx() and structure tUcanInitCanParam)."); + "UcanInitCanEx() and structure tUcanInitCanParam)."; - case USBCAN_ERR_MAXINSTANCES: return("A maximum amount of 64 applications are able to have access to the " + case USBCAN_ERR_MAXINSTANCES: return "A maximum amount of 64 applications are able to have access to the " "DLL. If more applications attempting to access to the DLL, this error " "code is returned. In this case, it is not possible to use an " - "USB-CANmodul by this application."); + "USB-CANmodul by this application."; - case USBCAN_ERR_CANNOTINIT: return("This error code returns if an application tries to call an API function " + case USBCAN_ERR_CANNOTINIT: return "This error code returns if an application tries to call an API function " "which only can be called in software state CAN_INIT but the current " "software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for " - "detailed information."); + "detailed information."; - case USBCAN_ERR_DISCONNECT: return("This error code occurs if an API function was called for an " - "USB-CANmodul that was plugged-off from the computer recently."); + case USBCAN_ERR_DISCONNECT: return "This error code occurs if an API function was called for an " + "USB-CANmodul that was plugged-off from the computer recently."; - case USBCAN_ERR_ILLCHANNEL: return("This error code is returned if an extended function of the DLL is called " - "with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."); + case USBCAN_ERR_ILLCHANNEL: return "This error code is returned if an extended function of the DLL is called " + "with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."; - case USBCAN_ERR_ILLHWTYPE: return("This error code occurs if an extended function of the DLL was called for " - "a hardware which does not support the feature."); + case USBCAN_ERR_ILLHWTYPE: return "This error code occurs if an extended function of the DLL was called for " + "a hardware which does not support the feature."; - case USBCAN_ERRCMD_NOTEQU: return("This error code occurs during communication between the PC and an " + case USBCAN_ERRCMD_NOTEQU: return "This error code occurs during communication between the PC and an " "USB-CANmodul. The PC sends a command to the USB-CANmodul, " "then the module executes the command and returns a response to the " - "PC. This error code returns if the reply does not correspond to the command."); + "PC. This error code returns if the reply does not correspond to the command."; - case USBCAN_ERRCMD_REGTST: return("The software tests the CAN controller on the USB-CANmodul when the " + case USBCAN_ERRCMD_REGTST: return "The software tests the CAN controller on the USB-CANmodul when the " "CAN interface is initialized. Several registers of the CAN controller are " - "checked. This error code returns if an error appears during this register test."); + "checked. This error code returns if an error appears during this register test."; - case USBCAN_ERRCMD_ILLCMD: return("This error code returns if the USB-CANmodul receives a non-defined " - "command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."); + case USBCAN_ERRCMD_ILLCMD: return "This error code returns if the USB-CANmodul receives a non-defined " + "command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."; - case USBCAN_ERRCMD_EEPROM: return("The USB-CANmodul has a built-in EEPROM. This EEPROM contains " + case USBCAN_ERRCMD_EEPROM: return "The USB-CANmodul has a built-in EEPROM. This EEPROM contains " "several configurations, e.g. the device number and the serial number. If " - "an error occurs while reading these values, this error code is returned."); + "an error occurs while reading these values, this error code is returned."; - case USBCAN_ERRCMD_ILLBDR: return("The USB-CANmodul has been initialized with an invalid baud rate (refer " - "to section 4.3.4)."); + case USBCAN_ERRCMD_ILLBDR: return "The USB-CANmodul has been initialized with an invalid baud rate (refer " + "to section 4.3.4)."; - case USBCAN_ERRCMD_NOTINIT: return("It was tried to access a CAN-channel of a multi-channel " - "USB-CANmodul that was not initialized."); + case USBCAN_ERRCMD_NOTINIT: return "It was tried to access a CAN-channel of a multi-channel " + "USB-CANmodul that was not initialized."; - case USBCAN_ERRCMD_ALREADYINIT: return("The accessed CAN-channel of a multi-channel USB-CANmodul was " - "already initialized"); + case USBCAN_ERRCMD_ALREADYINIT: return "The accessed CAN-channel of a multi-channel USB-CANmodul was " + "already initialized"; - case USBCAN_ERRCMD_ILLSUBCMD: return("An internal error occurred within the DLL. In this case an unknown sub- " - "command was called instead of a main command (e.g. for the cyclic CAN message-feature)."); + case USBCAN_ERRCMD_ILLSUBCMD: return "An internal error occurred within the DLL. In this case an unknown sub- " + "command was called instead of a main command (e.g. for the cyclic CAN message-feature)."; - case USBCAN_ERRCMD_ILLIDX: return("An internal error occurred within the DLL. In this case an invalid index " - "for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."); + case USBCAN_ERRCMD_ILLIDX: return "An internal error occurred within the DLL. In this case an invalid index " + "for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."; - case USBCAN_ERRCMD_RUNNING: return("The caller tries to define a new list of cyclic CAN messages but this " - "feature was already started. For defining a new list, it is necessary to stop the feature beforehand."); + case USBCAN_ERRCMD_RUNNING: return "The caller tries to define a new list of cyclic CAN messages but this " + "feature was already started. For defining a new list, it is necessary to stop the feature beforehand."; - case USBCAN_WARN_NODATA: return("If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " - "with this warning, it is an indication that the receive buffer contains no CAN messages."); + case USBCAN_WARN_NODATA: return "If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " + "with this warning, it is an indication that the receive buffer contains no CAN messages."; - case USBCAN_WARN_SYS_RXOVERRUN: return("This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the " + case USBCAN_WARN_SYS_RXOVERRUN: return "This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the " "receive buffer within the kernel driver runs over. The function " "nevertheless returns a valid CAN message. It also indicates that at least " - "one CAN message are lost. However, it does not indicate the position of the lost CAN messages."); + "one CAN message are lost. However, it does not indicate the position of the lost CAN messages."; - case USBCAN_WARN_DLL_RXOVERRUN: return("The DLL automatically requests CAN messages from the " + case USBCAN_WARN_DLL_RXOVERRUN: return "The DLL automatically requests CAN messages from the " "USB-CANmodul and stores the messages into a buffer of the DLL. If " "more CAN messages are received than the DLL buffer size allows, this " "error code returns and CAN messages are lost. However, it does not " "indicate the position of the lost CAN messages. The size of the receive " "buffer is configurable (refer to function UcanInitCanEx() and structure " - "tUcanInitCanParam)."); + "tUcanInitCanParam)."; - case USBCAN_WARN_FW_TXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " + case USBCAN_WARN_FW_TXOVERRUN: return "This warning is returned by function UcanWriteCanMsg() or " "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in " "the CAN driver status. However, the transmit CAN message could be " "stored to the DLL transmit buffer. This warning indicates that at least " "one transmit CAN message got lost in the device firmware layer. This " - "warning does not indicate the position of the lost CAN message."); + "warning does not indicate the position of the lost CAN message."; - case USBCAN_WARN_FW_RXOVERRUN: return("This warning is returned by function UcanWriteCanMsg() or " + case USBCAN_WARN_FW_RXOVERRUN: return "This warning is returned by function UcanWriteCanMsg() or " "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag " "USBCAN_CANERR_OVERRUN are set in the CAN driver status. The " "function has returned with a valid CAN message. This warning indicates " "that at least one received CAN message got lost in the firmware layer. " - "This warning does not indicate the position of the lost CAN message."); + "This warning does not indicate the position of the lost CAN message."; - case USBCAN_WARN_NULL_PTR: return("This warning is returned by functions UcanInitHwConnectControl() or " - "UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."); + case USBCAN_WARN_NULL_PTR: return "This warning is returned by functions UcanInitHwConnectControl() or " + "UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."; - case USBCAN_WARN_TXLIMIT: return("This warning is returned by the function UcanWriteCanMsgEx() if it was " + case USBCAN_WARN_TXLIMIT: return "This warning is returned by the function UcanWriteCanMsgEx() if it was " "called to transmit more than one CAN message, but a part of them " "could not be stored to the transmit buffer within the DLL (because the " "buffer is full). The returned variable addressed by the parameter " "pdwCount_p indicates the number of CAN messages which are stored " - "successfully to the transmit buffer."); + "successfully to the transmit buffer."; + + default: return "unknown error code"; + } +} - default: return("unknown error code"); +std::string CanVendorSystec::UsbCanGetStatusText(long err_code) { + switch(err_code) { + case USBCAN_CANERR_OK: return "No error."; + case USBCAN_CANERR_XMTFULL: return "Transmit buffer in CAN controller is overrun."; + case USBCAN_CANERR_OVERRUN: return "Receive buffer in CAN controller is overrun."; + case USBCAN_CANERR_BUSLIGHT: return " Error limit 1 in CAN controller exceeded, CAN controller " + "is in state “Warning limit” now."; + case USBCAN_CANERR_BUSHEAVY: return "Error limit 2 in CAN controller exceeded, CAN controller " + "is in state “Error Passive” now"; + case USBCAN_CANERR_BUSOFF: return "CAN controller is in BUSOFF state."; + case USBCAN_CANERR_QOVERRUN: return "Receive buffer in module is overrun."; + case USBCAN_CANERR_QXMTFULL: return "Transmit buffer in module is overrun."; + case USBCAN_CANERR_REGTEST: return "CAN controller not found (hardware error)."; + case USBCAN_CANERR_TXMSGLOST: return "A transmit CAN message was deleted automatically by the " + "firmware because transmission timeout run over (refer to " + "function UcanSetTxTimeout() )."; + default: return "unknown error code"; } } \ No newline at end of file From eba8463e1cec2597bcb9bc6fab86f3375edaa7aa Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 23 Mar 2026 14:19:44 +0100 Subject: [PATCH 45/63] fixes for test_systec --- test/python/test_systec.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/python/test_systec.py b/test/python/test_systec.py index a153c536..5cc24e97 100644 --- a/test/python/test_systec.py +++ b/test/python/test_systec.py @@ -21,11 +21,10 @@ @pytest.fixture def device_and_frames(): config = CanDeviceConfiguration() - config.host = "localhost" config.bitrate = 125_000 config.enable_termination = True config.high_speed = True - config.bus_name = "can0" + config.bus_number = 0 received = [] device = CanDevice.create( "systec", CanDeviceArguments(config, received.append) @@ -43,7 +42,7 @@ def test_sync_messages_elmb(device_and_frames): # on new connect, all statistics reset (for now) diag = device.diagnostics() assert diag.mode == "NORMAL" - assert diag.state == "USBCAN_CANERR_OK" + assert diag.state == "No error." assert isinstance(diag.uptime, int) rx = diag.rx tx = diag.tx @@ -64,7 +63,7 @@ def test_sync_messages_elmb(device_and_frames): diff = time() - start diag = device.diagnostics() - assert diag.state == "USBCAN_CANERR_OK" + assert diag.state == "No error." assert diag.rx - rx == 65 assert diag.tx - tx == 1 assert diag.rx_per_second == pytest.approx(65/diff, rel=0.3) @@ -79,4 +78,3 @@ def test_sync_messages_elmb(device_and_frames): assert ord(frame.message()[0]) == idx -# def test_manufacturer_stuff(device_and_frames): From 24cf1c9cbce9dc70974c53139c392a1e2d1a418f Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 23 Mar 2026 15:08:43 +0100 Subject: [PATCH 46/63] throw in systec if invalid bitrate specified --- src/include/CanVendorSystec.h | 1 + src/main/CanVendorSystec.cpp | 31 +++++++++++++++---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 7295a73f..df6d9c13 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -37,6 +37,7 @@ struct CanVendorSystec : CanDevice { int m_module_number; int m_channel_number; int m_port_number; + DWORD m_baud_rate; std::thread m_SystecRxThread; tUcanHandle get_module_handle() { return m_module_to_handle_map[m_module_number]; } diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 32447d56..9053affc 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -22,10 +22,22 @@ void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, v CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) : CanDevice("systec", args), m_queued_reads{0} { - if (!args.config.bus_number.has_value()) { + if (!args.config.bus_number.has_value() || !args.config.bitrate.has_value()) { throw std::invalid_argument("Missing required configuration parameters"); } + switch (args.config.bitrate.value()) { + case 50000: m_baud_rate = USBCAN_BAUD_50kBit; break; + case 100000: m_baud_rate = USBCAN_BAUD_100kBit; break; + case 125000: m_baud_rate = USBCAN_BAUD_125kBit; break; + case 250000: m_baud_rate = USBCAN_BAUD_250kBit; break; + case 500000: m_baud_rate = USBCAN_BAUD_500kBit; break; + case 1000000: m_baud_rate = USBCAN_BAUD_1MBit; break; + default: { + throw std::invalid_argument("Invalid bitrate provided"); + } + } + m_port_number = args.config.bus_number.value(); m_module_number = m_port_number / 2; m_channel_number = m_port_number % 2; @@ -37,24 +49,11 @@ CanReturnCode CanVendorSystec::init_can_port() { BYTE systec_call_return = USBCAN_SUCCESSFUL; tUcanHandle can_module_handle; - unsigned int baud_rate = USBCAN_BAUD_125kBit; - switch (args().config.bitrate.value_or(0)) { - case 50000: baud_rate = USBCAN_BAUD_50kBit; break; - case 100000: baud_rate = USBCAN_BAUD_100kBit; break; - case 125000: baud_rate = USBCAN_BAUD_125kBit; break; - case 250000: baud_rate = USBCAN_BAUD_250kBit; break; - case 500000: baud_rate = USBCAN_BAUD_500kBit; break; - case 1000000: baud_rate = USBCAN_BAUD_1MBit; break; - default: { - LOG(Log::WRN, CanLogIt::h()) << "baud rate illegal, taking default 125000 [" << baud_rate << "]"; - } - } - tUcanInitCanParam initialization_parameters; initialization_parameters.m_dwSize = sizeof(initialization_parameters); // size of this struct initialization_parameters.m_bMode = kUcanModeNormal; // normal operation mode - initialization_parameters.m_bBTR0 = HIBYTE( baud_rate ); // baudrate - initialization_parameters.m_bBTR1 = LOBYTE( baud_rate ); + initialization_parameters.m_bBTR0 = HIBYTE( m_baud_rate ); // baudrate + initialization_parameters.m_bBTR1 = LOBYTE( m_baud_rate ); initialization_parameters.m_bOCR = 0x1A; // standard output initialization_parameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages initialization_parameters.m_dwACR = USBCAN_ACR_ALL; From 8b0e7754479ba71c861d8badabe698ce93aeb142 Mon Sep 17 00:00:00 2001 From: James Souter Date: Mon, 23 Mar 2026 17:03:55 +0100 Subject: [PATCH 47/63] wrap init_can_port in try block --- src/main/CanVendorSystec.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 9053affc..66a281f5 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -44,7 +44,6 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) m_port_to_vendor_map[m_port_number] = this; } -// TODO should we make this noexcept? how can we guarantee that? CanReturnCode CanVendorSystec::init_can_port() { BYTE systec_call_return = USBCAN_SUCCESSFUL; tUcanHandle can_module_handle; @@ -95,11 +94,11 @@ CanReturnCode CanVendorSystec::init_can_port() { } CanReturnCode CanVendorSystec::vendor_open() noexcept { - - auto return_code = init_can_port(); - if (return_code != CanReturnCode::success) return return_code; + CanReturnCode return_code = CanReturnCode::unknown_open_error; try { + return_code = init_can_port(); + if (return_code != CanReturnCode::success) return return_code; m_receive_thread_flag = true; m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); } catch(...) { From f2b84aa336a4c9cc4ab64a973116a5191b4b70a9 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Mar 2026 09:52:33 +0100 Subject: [PATCH 48/63] delay adding CanVendorSystec to port to vendor map --- src/main/CanVendorSystec.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 66a281f5..cdcc6278 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -41,7 +41,6 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) m_port_number = args.config.bus_number.value(); m_module_number = m_port_number / 2; m_channel_number = m_port_number % 2; - m_port_to_vendor_map[m_port_number] = this; } CanReturnCode CanVendorSystec::init_can_port() { From 9fc556da51a67c49917dc8d691ed8548210abd49 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Mar 2026 10:45:34 +0100 Subject: [PATCH 49/63] Only call deinit on nonzero handles, clean up some logging messages --- src/main/CanVendorSystec.cpp | 41 +++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index cdcc6278..d665c6fe 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -61,19 +61,18 @@ CanReturnCode CanVendorSystec::init_can_port() { // check if USB-CANmodul is already initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); - auto mapping = m_module_to_handle_map.find(m_module_number); - if (mapping == m_module_to_handle_map.end()) { // module not in use + if (auto mapping = m_module_to_handle_map.find(m_module_number); mapping == m_module_to_handle_map.end()) { // module not in use systec_call_return = UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); - if (systec_call_return != USBCAN_SUCCESSFUL ) { + if (systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitHardwareEx: " << UsbCanGetErrorText(systec_call_return); UcanDeinitHardware(can_module_handle); return CanReturnCode::unknown_open_error; } - LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number << " with handle " << (int) can_module_handle; + LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number; m_module_to_handle_map[m_module_number] = can_module_handle; } else { // find existing handle of module can_module_handle = mapping->second; - LOG(Log::WRN, CanLogIt::h()) << "trying to open a can port which is in use, reuse handle, skipping UCanInitHardware"; + LOG(Log::INF, CanLogIt::h()) << "Reuing handle for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; } // TODO handle error code for reset... @@ -124,22 +123,26 @@ CanReturnCode CanVendorSystec::deinit_channel() noexcept { std::lock_guard guard(CanVendorSystec::m_handles_lock); m_port_to_vendor_map.erase(m_port_number); auto handle = get_module_handle(); - auto return_code = UcanDeinitCanEx(handle, m_channel_number); - if (return_code != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); - internal_return_code = CanReturnCode::unknown_close_error; - } // still attempt to deinit hardware if channel deinit fails - - if (!m_port_to_vendor_map[m_port_number ^ 1]) { - // de init hardware if neither channel on the module are in use - // toggle last bit to get the other port on the same module - // e.g. if channel 0, we need to check if channel 1 is in use and vice versa - auto return_code_hw = UcanDeinitHardware(handle); - m_module_to_handle_map.erase(m_module_number); - if (return_code_hw != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); + if (handle) { + auto return_code = UcanDeinitCanEx(handle, m_channel_number); + if (return_code != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); internal_return_code = CanReturnCode::unknown_close_error; + } // still attempt to deinit hardware if channel deinit fails + + if (!m_port_to_vendor_map[m_port_number ^ 1]) { + // de init hardware if neither channel on the module are in use + // toggle last bit to get the other port on the same module + // e.g. if channel 0, we need to check if channel 1 is in use and vice versa + auto return_code_hw = UcanDeinitHardware(handle); + m_module_to_handle_map.erase(m_module_number); + if (return_code_hw != USBCAN_SUCCESSFUL) { + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); + internal_return_code = CanReturnCode::unknown_close_error; + } } + } else { + LOG(Log::WRN, CanLogIt::h()) << "No handle found for module, close() may have already been called"; } return internal_return_code; } From 38d93987989b18f09ab34a112db01986c04bf835 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Mar 2026 14:26:41 +0100 Subject: [PATCH 50/63] Improve logging for error messages --- src/main/CanVendorSystec.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index d665c6fe..26946cc6 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -72,7 +72,7 @@ CanReturnCode CanVendorSystec::init_can_port() { m_module_to_handle_map[m_module_number] = can_module_handle; } else { // find existing handle of module can_module_handle = mapping->second; - LOG(Log::INF, CanLogIt::h()) << "Reuing handle for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; + LOG(Log::INF, CanLogIt::h()) << "Reusing handle for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; } // TODO handle error code for reset... @@ -82,7 +82,7 @@ CanReturnCode CanVendorSystec::init_can_port() { systec_call_return = UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); if ( systec_call_return != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "UcanInitCanEx2, return code = [ 0x" << std::hex << (int) systec_call_return << std::dec << "]"; + LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitCanEx2: " << UsbCanGetErrorText(systec_call_return); deinit_channel(); return CanReturnCode::unknown_open_error; } @@ -426,7 +426,10 @@ std::string_view CanVendorSystec::UsbCanGetErrorText( long err_code ) { "pdwCount_p indicates the number of CAN messages which are stored " "successfully to the transmit buffer."; - default: return "unknown error code"; + default: + std::stringstream ss; + ss << "Unknown error code: 0x" << std::hex << err_code; + return ss.str(); } } @@ -446,6 +449,9 @@ std::string CanVendorSystec::UsbCanGetStatusText(long err_code) { case USBCAN_CANERR_TXMSGLOST: return "A transmit CAN message was deleted automatically by the " "firmware because transmission timeout run over (refer to " "function UcanSetTxTimeout() )."; - default: return "unknown error code"; + default: + std::stringstream ss; + ss << "Unknown error code: 0x" << std::hex << err_code; + return ss.str(); } -} \ No newline at end of file +} From af2f3f00d8dff6f131d3df238cae4cf3cb99cd1a Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 24 Mar 2026 16:04:29 +0100 Subject: [PATCH 51/63] Use std::optional for systec handles, avoid use of [] accessor --- src/include/CanVendorSystec.h | 10 +++++++-- src/main/CanVendorSystec.cpp | 39 +++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index df6d9c13..def2af81 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -15,6 +15,7 @@ #include "CanVendorLoopback.h" #include "CanDevice.h" #include +#include #include /** @@ -30,7 +31,7 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } int SystecRxThread(); - + private: std::atomic m_receive_thread_flag {true}; std::atomic m_queued_reads; @@ -40,7 +41,12 @@ struct CanVendorSystec : CanDevice { DWORD m_baud_rate; std::thread m_SystecRxThread; - tUcanHandle get_module_handle() { return m_module_to_handle_map[m_module_number]; } + std::optional get_module_handle() { + if (auto mapping = m_module_to_handle_map.find(m_module_number); mapping != m_module_to_handle_map.end()) { + return mapping->second; + } + return std::nullopt; + } CanReturnCode vendor_open() noexcept override; CanReturnCode vendor_close() noexcept override; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 26946cc6..3712d61d 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -61,7 +61,8 @@ CanReturnCode CanVendorSystec::init_can_port() { // check if USB-CANmodul is already initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); - if (auto mapping = m_module_to_handle_map.find(m_module_number); mapping == m_module_to_handle_map.end()) { // module not in use + auto handle = get_module_handle(); + if (!handle.has_value()) { // module not in use systec_call_return = UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); if (systec_call_return != USBCAN_SUCCESSFUL ) { LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitHardwareEx: " << UsbCanGetErrorText(systec_call_return); @@ -123,8 +124,8 @@ CanReturnCode CanVendorSystec::deinit_channel() noexcept { std::lock_guard guard(CanVendorSystec::m_handles_lock); m_port_to_vendor_map.erase(m_port_number); auto handle = get_module_handle(); - if (handle) { - auto return_code = UcanDeinitCanEx(handle, m_channel_number); + if (handle.has_value()) { + auto return_code = UcanDeinitCanEx(handle.value(), m_channel_number); if (return_code != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); internal_return_code = CanReturnCode::unknown_close_error; @@ -134,7 +135,7 @@ CanReturnCode CanVendorSystec::deinit_channel() noexcept { // de init hardware if neither channel on the module are in use // toggle last bit to get the other port on the same module // e.g. if channel 0, we need to check if channel 1 is in use and vice versa - auto return_code_hw = UcanDeinitHardware(handle); + auto return_code_hw = UcanDeinitHardware(handle.value()); m_module_to_handle_map.erase(m_module_number); if (return_code_hw != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); @@ -168,7 +169,12 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { std::copy(message.begin(), message.begin() + can_msg_to_send.m_bDLC, can_msg_to_send.m_bData); - Status = UcanWriteCanMsgEx(get_module_handle(), m_channel_number, &can_msg_to_send, NULL); + auto handle = get_module_handle(); + if (!handle.has_value()) { + LOG(Log::ERR, CanLogIt::h()) << "Could not send message, no handle found for module " << m_module_number; + return CanReturnCode::disconnected; + } + Status = UcanWriteCanMsgEx(handle.value(), m_channel_number, &can_msg_to_send, NULL); if (Status != USBCAN_SUCCESSFUL) { LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " << UsbCanGetErrorText(Status); @@ -200,21 +206,26 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { tStatusStruct status; // TODO check return code of these functions... auto handle = get_module_handle(); - UcanGetStatusEx(handle, m_channel_number, &status); + if (!handle.has_value()) { + LOG(Log::ERR, CanLogIt::h()) << "TODO figure out what to do here, no handle found"; + return CanReturnCode::disconnected; + } + auto handle_value = handle.value(); + UcanGetStatusEx(handle_value, m_channel_number, &status); WORD can_status = status.m_wCanStatus; diagnostics.state = UsbCanGetStatusText(can_status); tUcanMsgCountInfo msg_count_info; - UcanGetMsgCountInfoEx(handle, m_channel_number, &msg_count_info); + UcanGetMsgCountInfoEx(handle_value, m_channel_number, &msg_count_info); diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; DWORD tx_error, rx_error; - UcanGetCanErrorCounter(handle, m_channel_number, &tx_error, &rx_error); + UcanGetCanErrorCounter(handle_value, m_channel_number, &tx_error, &rx_error); diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; tUcanHardwareInfo hw_info; - if (UcanGetHardwareInfo(handle, &hw_info) != USBCAN_SUCCESSFUL) + if (UcanGetHardwareInfo(handle_value, &hw_info) != USBCAN_SUCCESSFUL) diagnostics.mode = "OFFLINE"; else switch (hw_info.m_bMode) { case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; @@ -223,7 +234,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { } DWORD module_time; // in ms - UcanGetModuleTime(handle, &module_time); + UcanGetModuleTime(handle_value, &module_time); diagnostics.uptime = (uint32_t) module_time / 1000; return diagnostics; @@ -239,10 +250,16 @@ int CanVendorSystec::SystecRxThread() tCanMsgStruct read_can_message; LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << m_receive_thread_flag <<"]"; size_t to_read; + + auto handle = get_module_handle(); + if (!handle.has_value()) { + LOG(Log::ERR, CanLogIt::h()) << "Could not start rx thread without valid handle for module"; + return -1; // TODO more useful error code + } while (m_receive_thread_flag) { to_read = m_queued_reads; if (to_read < 1) continue; - status = UcanReadCanMsgEx(get_module_handle(), (BYTE *) &m_channel_number, &read_can_message, NULL); + status = UcanReadCanMsgEx(handle.value(), (BYTE *) &m_channel_number, &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: case USBCAN_WARN_DLL_RXOVERRUN: From 389eb419c95af133105e9db23f73d6eb2261d21f Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Mar 2026 14:12:53 +0100 Subject: [PATCH 52/63] use convenience template to log error messages for all ucan calls --- src/include/CanVendorSystec.h | 4 +- src/main/CanVendorSystec.cpp | 132 ++++++++++++++++------------------ 2 files changed, 64 insertions(+), 72 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index def2af81..88045b8a 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -31,6 +31,8 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } int SystecRxThread(); + static std::string_view UsbCanGetErrorText( long err_code ); + static std::string UsbCanGetStatusText( long err_code ); private: std::atomic m_receive_thread_flag {true}; @@ -59,8 +61,6 @@ struct CanVendorSystec : CanDevice { static std::unordered_map m_port_to_vendor_map; friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); - std::string_view UsbCanGetErrorText( long err_code ); - std::string UsbCanGetStatusText( long err_code ); CanReturnCode deinit_channel() noexcept; CanReturnCode reconnect_channel() noexcept; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 3712d61d..1db197ea 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -10,6 +10,15 @@ std::mutex CanVendorSystec::m_handles_lock; std::unordered_map CanVendorSystec::m_module_to_handle_map; std::unordered_map CanVendorSystec::m_port_to_vendor_map; +template +long CallAndLog(T f, const char *name, Args... args) { + long code = f(args...); + if (code != USBCAN_SUCCESSFUL) + LOG(Log::ERR, CanLogIt::h()) << "Got error code calling " << name << ": " << CanVendorSystec::UsbCanGetErrorText(code); + return code; +} + + // Callback registered per-module to handle receive events void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p) { if (bEvent_p == USBCAN_EVENT_RECEIVE) { @@ -63,27 +72,23 @@ CanReturnCode CanVendorSystec::init_can_port() { std::lock_guard guard(CanVendorSystec::m_handles_lock); auto handle = get_module_handle(); if (!handle.has_value()) { // module not in use - systec_call_return = UcanInitHardwareEx(&can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); - if (systec_call_return != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitHardwareEx: " << UsbCanGetErrorText(systec_call_return); - UcanDeinitHardware(can_module_handle); + if (CallAndLog(UcanInitHardwareEx, "init hardware", &can_module_handle, m_module_number, systec_receive, (void*) &m_module_number)) { + CallAndLog(UcanDeinitHardware, "deinit channel", can_module_handle); return CanReturnCode::unknown_open_error; } LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number; m_module_to_handle_map[m_module_number] = can_module_handle; } else { // find existing handle of module - can_module_handle = mapping->second; - LOG(Log::INF, CanLogIt::h()) << "Reusing handle for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; + can_module_handle = handle.value(); + LOG(Log::INF, CanLogIt::h()) << "Reusing handle (" << (size_t) can_module_handle << ") for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; } // TODO handle error code for reset... // also investigate the minimum amount of things to reset to restore good state - UcanResetCanEx(can_module_handle, (BYTE) m_channel_number, (DWORD) 0); + CallAndLog(UcanResetCanEx, "reset channel", can_module_handle, (BYTE) m_channel_number, (DWORD) 0); m_port_to_vendor_map[m_port_number] = this; - systec_call_return = UcanInitCanEx2(can_module_handle, m_channel_number, &initialization_parameters); - if ( systec_call_return != USBCAN_SUCCESSFUL ) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanInitCanEx2: " << UsbCanGetErrorText(systec_call_return); + if (CallAndLog(UcanInitCanEx2, "init channel", can_module_handle, m_channel_number, &initialization_parameters)) { deinit_channel(); return CanReturnCode::unknown_open_error; } @@ -125,22 +130,16 @@ CanReturnCode CanVendorSystec::deinit_channel() noexcept { m_port_to_vendor_map.erase(m_port_number); auto handle = get_module_handle(); if (handle.has_value()) { - auto return_code = UcanDeinitCanEx(handle.value(), m_channel_number); - if (return_code != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitCanEx: " << UsbCanGetErrorText(return_code); + if (CallAndLog(UcanDeinitCanEx, "deinit channel", handle.value(), m_channel_number)) internal_return_code = CanReturnCode::unknown_close_error; - } // still attempt to deinit hardware if channel deinit fails if (!m_port_to_vendor_map[m_port_number ^ 1]) { // de init hardware if neither channel on the module are in use // toggle last bit to get the other port on the same module // e.g. if channel 0, we need to check if channel 1 is in use and vice versa - auto return_code_hw = UcanDeinitHardware(handle.value()); - m_module_to_handle_map.erase(m_module_number); - if (return_code_hw != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "Error calling UcanDeinitHardware: " << UsbCanGetErrorText(return_code_hw); + if (CallAndLog(UcanDeinitHardware, "deinit hw", handle.value())) internal_return_code = CanReturnCode::unknown_close_error; - } + m_module_to_handle_map.erase(m_module_number); } } else { LOG(Log::WRN, CanLogIt::h()) << "No handle found for module, close() may have already been called"; @@ -158,7 +157,6 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { std::vector message = frame.message(); tCanMsgStruct can_msg_to_send; - BYTE Status; can_msg_to_send.m_dwID = frame.id(); can_msg_to_send.m_bDLC = frame.length(); @@ -174,28 +172,19 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { LOG(Log::ERR, CanLogIt::h()) << "Could not send message, no handle found for module " << m_module_number; return CanReturnCode::disconnected; } - Status = UcanWriteCanMsgEx(handle.value(), m_channel_number, &can_msg_to_send, NULL); - if (Status != USBCAN_SUCCESSFUL) { - LOG(Log::ERR, CanLogIt::h()) << "There was a problem when sending a message: " - << UsbCanGetErrorText(Status); - - // for now, just reconnect, could implement configuration option - // for how to handle responding to a failed send - auto reconnect_code = reconnect_channel(); - if (reconnect_code != CanReturnCode::success) return reconnect_code; - - switch (Status) { - case USBCAN_ERR_CANNOTINIT: - case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; - case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; - case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; - case USBCAN_ERR_ILLPARAM: - case USBCAN_ERR_ILLHW: - case USBCAN_ERR_ILLCHANNEL: - case USBCAN_WARN_TXLIMIT: - case USBCAN_WARN_FW_TXOVERRUN: - default: return CanReturnCode::unknown_send_error; - } + auto write_status = CallAndLog(UcanWriteCanMsgEx, "write", handle.value(), m_channel_number, &can_msg_to_send, nullptr); + switch (write_status) { + case USBCAN_SUCCESSFUL: break; + case USBCAN_ERR_CANNOTINIT: + case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; + case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; + case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; + case USBCAN_ERR_ILLPARAM: + case USBCAN_ERR_ILLHW: + case USBCAN_ERR_ILLCHANNEL: + case USBCAN_WARN_TXLIMIT: + case USBCAN_WARN_FW_TXOVERRUN: + default: return CanReturnCode::unknown_send_error; } return CanReturnCode::success; }; @@ -204,39 +193,42 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { CanDiagnostics diagnostics{}; tStatusStruct status; - // TODO check return code of these functions... auto handle = get_module_handle(); if (!handle.has_value()) { - LOG(Log::ERR, CanLogIt::h()) << "TODO figure out what to do here, no handle found"; - return CanReturnCode::disconnected; - } - auto handle_value = handle.value(); - UcanGetStatusEx(handle_value, m_channel_number, &status); - WORD can_status = status.m_wCanStatus; - diagnostics.state = UsbCanGetStatusText(can_status); - tUcanMsgCountInfo msg_count_info; - UcanGetMsgCountInfoEx(handle_value, m_channel_number, &msg_count_info); - diagnostics.tx = msg_count_info.m_wSentMsgCount; - diagnostics.rx = msg_count_info.m_wRecvdMsgCount; - - DWORD tx_error, rx_error; - UcanGetCanErrorCounter(handle_value, m_channel_number, &tx_error, &rx_error); - diagnostics.tx_error = tx_error; - diagnostics.rx_error = rx_error; - - tUcanHardwareInfo hw_info; - if (UcanGetHardwareInfo(handle_value, &hw_info) != USBCAN_SUCCESSFUL) + LOG(Log::ERR, CanLogIt::h()) << "Could not get diagnostics as no handle found for channel " << m_channel_number; diagnostics.mode = "OFFLINE"; - else switch (hw_info.m_bMode) { - case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; - case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; - case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; - } + } else { + auto handle_value = handle.value(); + CallAndLog(UcanGetStatusEx, "get status", handle_value, m_channel_number, &status); + WORD can_status = status.m_wCanStatus; + diagnostics.state = UsbCanGetStatusText(can_status); - DWORD module_time; // in ms - UcanGetModuleTime(handle_value, &module_time); - diagnostics.uptime = (uint32_t) module_time / 1000; + tUcanMsgCountInfo msg_count_info; + if (!CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", handle_value, m_channel_number, &msg_count_info)) { + diagnostics.tx = msg_count_info.m_wSentMsgCount; + diagnostics.rx = msg_count_info.m_wRecvdMsgCount; + } + + DWORD tx_error, rx_error; + if (!CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, m_channel_number, &tx_error, &rx_error)) { + diagnostics.tx_error = tx_error; + diagnostics.rx_error = rx_error; + } + + tUcanHardwareInfo hw_info; + if (CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, &hw_info)) + diagnostics.mode = "OFFLINE"; + else switch (hw_info.m_bMode) { + case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; + case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; + case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; + } + + DWORD module_time; // in ms + if (!CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time)); + diagnostics.uptime = (uint32_t) module_time / 1000; + } return diagnostics; }; From 7a7b34999dc5de7f0697a9a548f0cfcde85a199f Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Mar 2026 14:27:23 +0100 Subject: [PATCH 53/63] remove reconnect_channel --- src/include/CanVendorSystec.h | 1 - src/main/CanVendorSystec.cpp | 11 ++--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 88045b8a..25912a17 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -63,7 +63,6 @@ struct CanVendorSystec : CanDevice { friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); CanReturnCode deinit_channel() noexcept; - CanReturnCode reconnect_channel() noexcept; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 1db197ea..cb49d30e 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -147,12 +147,6 @@ CanReturnCode CanVendorSystec::deinit_channel() noexcept { return internal_return_code; } -CanReturnCode CanVendorSystec::reconnect_channel() noexcept { - auto close_code = close(); - if (close_code != CanReturnCode::success) return close_code; - return open(); -} - CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { std::vector message = frame.message(); @@ -172,8 +166,7 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { LOG(Log::ERR, CanLogIt::h()) << "Could not send message, no handle found for module " << m_module_number; return CanReturnCode::disconnected; } - auto write_status = CallAndLog(UcanWriteCanMsgEx, "write", handle.value(), m_channel_number, &can_msg_to_send, nullptr); - switch (write_status) { + switch(CallAndLog(UcanWriteCanMsgEx, "write", handle.value(), m_channel_number, &can_msg_to_send, nullptr)) { case USBCAN_SUCCESSFUL: break; case USBCAN_ERR_CANNOTINIT: case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; @@ -195,7 +188,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { tStatusStruct status; auto handle = get_module_handle(); if (!handle.has_value()) { - LOG(Log::ERR, CanLogIt::h()) << "Could not get diagnostics as no handle found for channel " << m_channel_number; + LOG(Log::ERR, CanLogIt::h()) << "Could not get diagnostics as no handle found for module " << m_module_number; diagnostics.mode = "OFFLINE"; } else { auto handle_value = handle.value(); From 826cde55264117089763650bb06aa009fbfb6777 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Mar 2026 14:31:08 +0100 Subject: [PATCH 54/63] Log status error messages and add to log_entries --- src/main/CanVendorSystec.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index cb49d30e..6dad1f92 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -195,6 +195,10 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { CallAndLog(UcanGetStatusEx, "get status", handle_value, m_channel_number, &status); WORD can_status = status.m_wCanStatus; diagnostics.state = UsbCanGetStatusText(can_status); + if (can_status) { + LOG(Log::ERR, CanLogIt::h()) << "Got error calling get status: " << diagnostics.state.value(); + diagnostics.log_entries = std::vector{diagnostics.state.value()}; + } tUcanMsgCountInfo msg_count_info; From f055593ac1efcf17a0a7e3b6ac4abac2f1b10843 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Mar 2026 16:53:17 +0100 Subject: [PATCH 55/63] fix broken if --- src/main/CanVendorSystec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 6dad1f92..0a5fb6b2 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -223,7 +223,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { } DWORD module_time; // in ms - if (!CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time)); + if (!CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time)) diagnostics.uptime = (uint32_t) module_time / 1000; } return diagnostics; From 3b2079e0a58fde3f8ce24675fa8a3c4d73d3a8cf Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Mar 2026 17:08:20 +0100 Subject: [PATCH 56/63] move lock_guard out of deinit_channel to prevent double lock acquisition --- src/main/CanVendorSystec.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 0a5fb6b2..391b5fc8 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -117,6 +117,7 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { try { m_receive_thread_flag = false; if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); + std::lock_guard guard(CanVendorSystec::m_handles_lock); return_code = deinit_channel(); } catch (...) { return_code = CanReturnCode::internal_api_error; @@ -126,7 +127,6 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { CanReturnCode CanVendorSystec::deinit_channel() noexcept { auto internal_return_code = CanReturnCode::success; - std::lock_guard guard(CanVendorSystec::m_handles_lock); m_port_to_vendor_map.erase(m_port_number); auto handle = get_module_handle(); if (handle.has_value()) { From d903c0d22aa22b1bcd67a9b03bad6d9ec118a8d7 Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 25 Mar 2026 17:21:57 +0100 Subject: [PATCH 57/63] return early from vendor_diagnostics on error --- src/main/CanVendorSystec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 391b5fc8..8be3081d 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -205,13 +205,13 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { if (!CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", handle_value, m_channel_number, &msg_count_info)) { diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; - } + } else return diagnostics; DWORD tx_error, rx_error; if (!CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, m_channel_number, &tx_error, &rx_error)) { diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; - } + } else return diagnostics; tUcanHardwareInfo hw_info; if (CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, &hw_info)) From 23d085367bcf42e2eba7b4b090e71026bda9344d Mon Sep 17 00:00:00 2001 From: James Souter Date: Thu, 26 Mar 2026 09:57:52 +0100 Subject: [PATCH 58/63] add config option to deinit both channels when vendor_close called on systec register callback for connect control add error messages to log_entries in diagnostics --- src/include/CanDeviceConfiguration.h | 10 +++ src/include/CanVendorSystec.h | 5 +- src/main/CanVendorSystec.cpp | 123 ++++++++++++++++++--------- 3 files changed, 95 insertions(+), 43 deletions(-) diff --git a/src/include/CanDeviceConfiguration.h b/src/include/CanDeviceConfiguration.h index 93731a99..a727d6ec 100644 --- a/src/include/CanDeviceConfiguration.h +++ b/src/include/CanDeviceConfiguration.h @@ -95,6 +95,16 @@ struct CanDeviceConfiguration { */ std::optional sent_acknowledgement; + /** + * @brief Enable or disable closing both channels on systec module when one is closed. + * + * This parameter is optional for Systec on Windows and defaults to false. + * If turned on, calling close on a Systec CanDevice will deintialise both channels + * of a module when one is closed. + * + */ + std::optional close_both_channels; + std::string to_string() const noexcept; }; diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 25912a17..b1e5ae90 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -35,7 +35,7 @@ struct CanVendorSystec : CanDevice { static std::string UsbCanGetStatusText( long err_code ); private: - std::atomic m_receive_thread_flag {true}; + std::atomic m_module_in_use {false}; std::atomic m_queued_reads; int m_module_number; int m_channel_number; @@ -62,7 +62,8 @@ struct CanVendorSystec : CanDevice { friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); - CanReturnCode deinit_channel() noexcept; + CanReturnCode deinit_channel(tUcanHandle handle) noexcept; + CanReturnCode deinit_other_channel(tUcanHandle handle, CanVendorSystec *other) noexcept; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index 8be3081d..d047dd69 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -9,6 +9,7 @@ std::mutex CanVendorSystec::m_handles_lock; std::unordered_map CanVendorSystec::m_module_to_handle_map; std::unordered_map CanVendorSystec::m_port_to_vendor_map; +static bool module_control_callback_registered = false; template long CallAndLog(T f, const char *name, Args... args) { @@ -18,6 +19,16 @@ long CallAndLog(T f, const char *name, Args... args) { return code; } +void connect_control_callback(BYTE bEvent_p, DWORD dwParam_p) { + switch(bEvent_p) { + case USBCAN_EVENT_CONNECT: + LOG(Log::DBG) << "USB CAN module connected"; break; + case USBCAN_EVENT_DISCONNECT: + LOG(Log::WRN) << "USB CAN module disconnected"; break; + case USBCAN_EVENT_FATALDISCON: + LOG(Log::ERR) << "USB CAN module with handle " << (int) dwParam_p << "fatally disconnected"; break; + } +} // Callback registered per-module to handle receive events void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p) { @@ -50,6 +61,7 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) m_port_number = args.config.bus_number.value(); m_module_number = m_port_number / 2; m_channel_number = m_port_number % 2; + m_port_to_vendor_map[m_port_number] = this; } CanReturnCode CanVendorSystec::init_can_port() { @@ -72,8 +84,12 @@ CanReturnCode CanVendorSystec::init_can_port() { std::lock_guard guard(CanVendorSystec::m_handles_lock); auto handle = get_module_handle(); if (!handle.has_value()) { // module not in use - if (CallAndLog(UcanInitHardwareEx, "init hardware", &can_module_handle, m_module_number, systec_receive, (void*) &m_module_number)) { - CallAndLog(UcanDeinitHardware, "deinit channel", can_module_handle); + if (!module_control_callback_registered) { + if (!CallAndLog(UcanInitHwConnectControl, "hw connect control callback", connect_control_callback)) + module_control_callback_registered = true; + } + if (auto systec_code = CallAndLog(UcanInitHardwareEx, "init hardware", &can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); systec_code != 0) { + CallAndLog(UcanDeinitHardware, "deinit hardware", can_module_handle); return CanReturnCode::unknown_open_error; } LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number; @@ -83,16 +99,14 @@ CanReturnCode CanVendorSystec::init_can_port() { LOG(Log::INF, CanLogIt::h()) << "Reusing handle (" << (size_t) can_module_handle << ") for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; } - // TODO handle error code for reset... - // also investigate the minimum amount of things to reset to restore good state - CallAndLog(UcanResetCanEx, "reset channel", can_module_handle, (BYTE) m_channel_number, (DWORD) 0); - m_port_to_vendor_map[m_port_number] = this; - if (CallAndLog(UcanInitCanEx2, "init channel", can_module_handle, m_channel_number, &initialization_parameters)) { - deinit_channel(); + deinit_channel(can_module_handle); return CanReturnCode::unknown_open_error; } + // investigate the minimum amount of things to reset to restore good state + CallAndLog(UcanResetCanEx, "reset channel", can_module_handle, (BYTE) m_channel_number, (DWORD) 0); + LOG(Log::INF, CanLogIt::h()) << "Successfully opened CAN port on module " << m_module_number << ", channel " << m_channel_number; return CanReturnCode::success; } @@ -103,7 +117,7 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { try { return_code = init_can_port(); if (return_code != CanReturnCode::success) return return_code; - m_receive_thread_flag = true; + m_module_in_use = true; m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); } catch(...) { return_code = CanReturnCode::internal_api_error; @@ -114,36 +128,60 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { CanReturnCode CanVendorSystec::vendor_close() noexcept { auto return_code = CanReturnCode::success; + std::lock_guard guard(CanVendorSystec::m_handles_lock); + + bool other_in_use = false; + CanVendorSystec *other = nullptr; + // toggle last bit to get the other port on the same module + // e.g. if channel 0, we need to check if channel 1 is in use and vice versa + if (auto mapping = m_port_to_vendor_map.find(m_port_number ^ 1); mapping != m_port_to_vendor_map.end()) { + other = mapping->second; + other_in_use = other->m_module_in_use; + } + + bool close_both_channels = args().config.close_both_channels.value_or(false); + try { - m_receive_thread_flag = false; + m_module_in_use = false; if (m_SystecRxThread.joinable()) m_SystecRxThread.join(); - std::lock_guard guard(CanVendorSystec::m_handles_lock); - return_code = deinit_channel(); + + auto handle = get_module_handle(); + if (!handle.has_value()) { + LOG(Log::WRN, CanLogIt::h()) << "No handle found for module, close() may have already been called"; + return CanReturnCode::success; // is success correct? + } + + if (close_both_channels && other_in_use) { + LOG(Log::WRN, CanLogIt::h()) << "Deinitialising other channel " << other->m_channel_number << " on module."; + return_code = deinit_other_channel(handle.value(), other); + } + return_code = deinit_channel(handle.value()); + + // if there are no channels still using the handle, deinit hardware + // and erase handle from map + if (!other_in_use || close_both_channels) { + if (auto systec_code = CallAndLog(UcanDeinitHardware, "deinit hw", handle.value()); systec_code != 0) + return_code = CanReturnCode::unknown_close_error; + m_module_to_handle_map.erase(m_module_number); + } } catch (...) { return_code = CanReturnCode::internal_api_error; } return return_code; }; -CanReturnCode CanVendorSystec::deinit_channel() noexcept { +CanReturnCode CanVendorSystec::deinit_channel(tUcanHandle handle) noexcept { auto internal_return_code = CanReturnCode::success; - m_port_to_vendor_map.erase(m_port_number); - auto handle = get_module_handle(); - if (handle.has_value()) { - if (CallAndLog(UcanDeinitCanEx, "deinit channel", handle.value(), m_channel_number)) - internal_return_code = CanReturnCode::unknown_close_error; - - if (!m_port_to_vendor_map[m_port_number ^ 1]) { - // de init hardware if neither channel on the module are in use - // toggle last bit to get the other port on the same module - // e.g. if channel 0, we need to check if channel 1 is in use and vice versa - if (CallAndLog(UcanDeinitHardware, "deinit hw", handle.value())) - internal_return_code = CanReturnCode::unknown_close_error; - m_module_to_handle_map.erase(m_module_number); - } - } else { - LOG(Log::WRN, CanLogIt::h()) << "No handle found for module, close() may have already been called"; - } + if (CallAndLog(UcanDeinitCanEx, "deinit channel", handle, m_channel_number)) + internal_return_code = CanReturnCode::unknown_close_error; + return internal_return_code; +} + +CanReturnCode CanVendorSystec::deinit_other_channel(tUcanHandle handle, CanVendorSystec *other) noexcept { + auto internal_return_code = CanReturnCode::success; + if (CallAndLog(UcanDeinitCanEx, "deinit channel", handle, other->m_channel_number)) + internal_return_code = CanReturnCode::unknown_close_error; + other->m_module_in_use = false; return internal_return_code; } @@ -187,6 +225,7 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { CanDiagnostics diagnostics{}; tStatusStruct status; auto handle = get_module_handle(); + diagnostics.log_entries = std::vector(); if (!handle.has_value()) { LOG(Log::ERR, CanLogIt::h()) << "Could not get diagnostics as no handle found for module " << m_module_number; diagnostics.mode = "OFFLINE"; @@ -196,35 +235,37 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { WORD can_status = status.m_wCanStatus; diagnostics.state = UsbCanGetStatusText(can_status); if (can_status) { - LOG(Log::ERR, CanLogIt::h()) << "Got error calling get status: " << diagnostics.state.value(); - diagnostics.log_entries = std::vector{diagnostics.state.value()}; + diagnostics.log_entries.value().push_back(diagnostics.state.value()); } tUcanMsgCountInfo msg_count_info; - if (!CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", handle_value, m_channel_number, &msg_count_info)) { + long err_code; + if (err_code = CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", handle_value, m_channel_number, &msg_count_info); err_code == 0) { diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; - } else return diagnostics; + } else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); DWORD tx_error, rx_error; - if (!CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, m_channel_number, &tx_error, &rx_error)) { + if (err_code = CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, m_channel_number, &tx_error, &rx_error); err_code == 0) { diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; - } else return diagnostics; + } else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); tUcanHardwareInfo hw_info; - if (CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, &hw_info)) + if (err_code = CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, &hw_info); err_code != 0) { diagnostics.mode = "OFFLINE"; - else switch (hw_info.m_bMode) { + diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + } else switch (hw_info.m_bMode) { case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; } DWORD module_time; // in ms - if (!CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time)) + if (err_code = CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time); err_code == 0) diagnostics.uptime = (uint32_t) module_time / 1000; + else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); } return diagnostics; }; @@ -237,7 +278,7 @@ int CanVendorSystec::SystecRxThread() { BYTE status; tCanMsgStruct read_can_message; - LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_receive_thread_flag = [" << m_receive_thread_flag <<"]"; + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_module_in_use = [" << m_module_in_use <<"]"; size_t to_read; auto handle = get_module_handle(); @@ -245,7 +286,7 @@ int CanVendorSystec::SystecRxThread() LOG(Log::ERR, CanLogIt::h()) << "Could not start rx thread without valid handle for module"; return -1; // TODO more useful error code } - while (m_receive_thread_flag) { + while (m_module_in_use) { to_read = m_queued_reads; if (to_read < 1) continue; status = UcanReadCanMsgEx(handle.value(), (BYTE *) &m_channel_number, &read_can_message, NULL); From 61aa60a4406cb688bc287f7b5b6a9c4357de33c4 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 7 Apr 2026 10:57:29 +0100 Subject: [PATCH 59/63] add systec to canmodule-utils.py on windows --- docs/CANMODULE-UTILS.md | 12 ++++-- python/canmodule-utils.py | 82 ++++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/docs/CANMODULE-UTILS.md b/docs/CANMODULE-UTILS.md index ca36c066..8ce7db1f 100644 --- a/docs/CANMODULE-UTILS.md +++ b/docs/CANMODULE-UTILS.md @@ -6,7 +6,9 @@ The tool is loosely based on `can-utils` for SocketCAN on Linux. ## Installation -To use this tool, ensure you have a recent version of Python installed (tested with version 3.9.18). Additionally, you need to have `libsocketcan` installed on your Linux system. +To use this tool, ensure you have a recent version of Python installed (tested with version 3.9.18). +Additionally, you need to have `libsocketcan` installed on your Linux system, or `USB-CANmodul Utility Disk` +on Windows to communicate with Systec devices. You will also require the `canmodule.cpython*` file and all Anagate-related libraries (such as `.so` files on Linux or `.dll` files on Windows). These files can be found in the build artifacts, located in the `/build` directory on Linux and the `/build/Release` directory on Windows. @@ -19,6 +21,7 @@ It opens a connection to the device and print all received frames. The syntax is ```bash python canmodule-utils.py anagate [host] [port number] dump python canmodule-utils.py socketcan [device] dump +python canmodule-utils.py systec [device] dump ``` ### send @@ -28,6 +31,7 @@ It sends a single CAN frame. The syntax is: ```bash python canmodule-utils.py anagate [host] [port number] send [can_frame] python canmodule-utils.py socketcan [device] send [can_frame] +python canmodule-utils.py systec [device] send [can_frame] ``` Examples of CAN frames are: @@ -47,6 +51,7 @@ It opens a connection and send random frames. The syntax is: ```bash python canmodule-utils.py anagate [host] [port number] gen python canmodule-utils.py socketcan [device] gen +python canmodule-utils.py systec [device] gen ``` ### diag @@ -54,6 +59,7 @@ python canmodule-utils.py socketcan [device] gen It opens a connection and print the diagnostics. The syntax is: ```bash -python python canmodule-utils.py.py anagate [host] [port number] diag -python python canmodule-utils.py.py socketcan [device] diag +python canmodule-utils.py anagate [host] [port number] diag +python canmodule-utils.py socketcan [device] diag +python canmodule-utils.py systec [device] diag ``` diff --git a/python/canmodule-utils.py b/python/canmodule-utils.py index 7a8d9f37..fd4a3ed2 100644 --- a/python/canmodule-utils.py +++ b/python/canmodule-utils.py @@ -10,7 +10,7 @@ def parse_arguments(): Parses command-line arguments for the CAN Module Utilities. The function uses argparse to define and parse command-line arguments for different CAN module operations. - It supports two CAN modules: Anagate and SocketCAN. Each module has its own subcommands for different actions, + It supports Anagate, SocketCAN on Linux and Systec on Windows. Each module has its own subcommands for different actions, such as dumping frames, generating random frames, sending specified frames, and printing diagnostics. Parameters: @@ -35,15 +35,6 @@ def parse_arguments(): dest="action", required=True, help="Anagate actions" ) - anagate_subparsers.add_parser("dump", help="Dump frames") - anagate_subparsers.add_parser("diag", help="Print diagnostics") - anagate_subparsers.add_parser("gen", help="Generate and send random frames") - - anagate_send_parser = anagate_subparsers.add_parser( - "send", help="Send a specified frame" - ) - anagate_send_parser.add_argument("can_frame", type=str, help="CAN frame to send") - # socketcan subparser socketcan_parser = subparsers.add_parser( "socketcan", help="Use the SocketCAN module" @@ -54,14 +45,22 @@ def parse_arguments(): dest="action", required=True, help="SocketCAN actions" ) - socketcan_subparsers.add_parser("dump", help="Dump frames") - socketcan_subparsers.add_parser("diag", help="Print diagnostics") - socketcan_subparsers.add_parser("gen", help="Generate and send random frames") + # systec subparser + systec_parser = subparsers.add_parser("systec", help="Use the Systec module") + systec_parser.add_argument("device", type=int, help="Bus number for Systec") + systec_parser.add_argument( + "baudrate", type=int, default=125000, help="Baud rate for Systec CAN channel", nargs='?') - socketcan_send_parser = socketcan_subparsers.add_parser( - "send", help="Send a specified frame" + systec_subparsers = systec_parser.add_subparsers( + dest="action", required=True, help="systec actions" ) - socketcan_send_parser.add_argument("can_frame", type=str, help="CAN frame to send") + + for subparsers in [anagate_subparsers, socketcan_subparsers, systec_subparsers]: + subparsers.add_parser("dump", help="Dump frames") + subparsers.add_parser("diag", help="Print diagnostics") + subparsers.add_parser("gen", help="Generate and send random frames") + send_parser = subparsers.add_parser("send", help="Send a specified frame") + send_parser.add_argument("can_frame", type=str, help="CAN frame to send") return parser.parse_args() @@ -96,6 +95,13 @@ def main(): "device": args.device, } ) + elif args.command == "systec": + device.update( + { + "device": args.device, + "bitrate": args.baudrate, + } + ) if args.action == "dump": dump(device) @@ -173,17 +179,22 @@ def process_device(device): Processes the device dictionary to create a CanDeviceConfiguration object. This function takes a dictionary representing a CAN device and creates a CanDeviceConfiguration object - based on the provided device information. The function supports two types of CAN devices: Anagate and SocketCAN. + based on the provided device information. The function supports several CAN devices: Anagate, SocketCAN + on Linux and Systec on Windows. For Anagate devices, the function sets the host and bus number in the configuration object. For SocketCAN devices, - the function sets the bus name in the configuration object. If an unsupported vendor is provided, the function + the function sets the bus name in the configuration object. For Systec devices, the function sets the + bus number in the configuration object. If an unsupported vendor is provided, the function prints an error message and exits with a status code of 1. Parameters: device (dict): A dictionary representing the CAN device. The dictionary should contain the following keys: - - "vendor" (str): The vendor of the CAN device. It can be either "anagate" or "socketcan". + - "vendor" (str): The vendor of the CAN device. It can be either "anagate", + "socketcan" on Linux or "systec" on Windows. - "host" (str): The host address for Anagate devices. - "port" (int): The port number for Anagate devices. - - "device" (str): The device name for SocketCAN devices. + - "device" (str): The device name for SocketCAN devices + or device number of Systec devices. + - "baudrate" (int): The baud rate for Systec devices. Returns: CanDeviceConfiguration: A CanDeviceConfiguration object configured based on the provided device information. @@ -194,6 +205,9 @@ def process_device(device): configuration.bus_number = device["port"] elif device["vendor"] == "socketcan": configuration.bus_name = device["device"] + elif device["vendor"] == "systec": + configuration.bus_number = device["device"] + configuration.bitrate = device["bitrate"] else: print(f"Unsupported vendor: {device['vendor']}") exit(1) @@ -206,10 +220,13 @@ def dump(device): Parameters: device (dict): A dictionary representing the CAN device. The dictionary should contain the following keys: - - "vendor" (str): The vendor of the CAN device. It can be either "anagate" or "socketcan". + - "vendor" (str): The vendor of the CAN device. It can be either "anagate", + "socketcan" on Linux or "systec" on Windows. - "host" (str): The host address for Anagate devices. - "port" (int): The port number for Anagate devices. - - "device" (str): The device name for SocketCAN devices. + - "device" (str): The device name for SocketCAN devices + or device number of Systec devices. + - "baudrate" (int): The baud rate for Systec devices. Returns: None @@ -277,10 +294,13 @@ def send(device, frame): Parameters: device (dict): A dictionary representing the CAN device. The dictionary should contain the following keys: - - "vendor" (str): The vendor of the CAN device. It can be either "anagate" or "socketcan". + - "vendor" (str): The vendor of the CAN device. It can be either "anagate", + "socketcan" on Linux or "systec" on Windows. - "host" (str): The host address for Anagate devices. - "port" (int): The port number for Anagate devices. - - "device" (str): The device name for SocketCAN devices. + - "device" (str): The device name for SocketCAN devices + or device number of Systec devices. + - "baudrate" (int): The baud rate for Systec devices. frame (dict): A dictionary representing the CAN frame to be sent. The dictionary should contain the following keys: - "can_id" (str): The hexadecimal representation of the CAN identifier. - "len" (int): The number of data bytes for a remote request frame. @@ -347,10 +367,13 @@ def gen(device): Parameters: device (dict): A dictionary representing the CAN device. The dictionary should contain the following keys: - - "vendor" (str): The vendor of the CAN device. It can be either "anagate" or "socketcan". + - "vendor" (str): The vendor of the CAN device. It can be either "anagate", + "socketcan" on Linux or "systec" on Windows. - "host" (str): The host address for Anagate devices. - "port" (int): The port number for Anagate devices. - - "device" (str): The device name for SocketCAN devices. + - "device" (str): The device name for SocketCAN devices + or device number of Systec devices. + - "baudrate" (int): The baud rate for Systec devices. Returns: None @@ -385,10 +408,13 @@ def diag(device): Parameters: device (dict): A dictionary representing the CAN device. The dictionary should contain the following keys: - - "vendor" (str): The vendor of the CAN device. It can be either "anagate" or "socketcan". + - "vendor" (str): The vendor of the CAN device. It can be either "anagate", + "socketcan" on Linux or "systec" on Windows. - "host" (str): The host address for Anagate devices. - "port" (int): The port number for Anagate devices. - - "device" (str): The device name for SocketCAN devices. + - "device" (str): The device name for SocketCAN devices + or device number of Systec devices. + - "baudrate" (int): The baud rate for Systec devices. Returns: None From 205159f0294918293a1e3236f1fafa3248e5269b Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 7 Apr 2026 15:07:28 +0100 Subject: [PATCH 60/63] build systec dependency from zip file installer\ --- CMakeLists.txt | 6 +++--- cmake/systec.cmake | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index baf62151..51a92c43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ if (UNIX) libsocketcan ) else() - target_include_directories(CanModuleMain PUBLIC ${systec_SOURCE_DIR}/Include) + target_include_directories(CanModuleMain PUBLIC ${systec_BINARY_DIR}/Examples/Include) target_link_libraries(CanModuleMain PUBLIC ${anagate_SOURCE_DIR}/Win64/AnaGateCanDll64.lib) file(COPY "${anagate_SOURCE_DIR}/Win64/AnaGateCan64.dll" @@ -78,8 +78,8 @@ else() if ("${CANMODULE_BUILD_SYSTEC_WINDOWS}" STREQUAL ON) target_link_libraries(CanModuleMain PUBLIC - ${systec_SOURCE_DIR}/lib/USBCAN64.lib) - file(COPY "${systec_SOURCE_DIR}/lib/USBCAN64.dll" + ${systec_BINARY_DIR}/Examples/lib/USBCAN64.lib) + file(COPY "${systec_BINARY_DIR}/Examples/lib/USBCAN64.dll" DESTINATION "${CMAKE_BINARY_DIR}/Release") endif() endif() diff --git a/cmake/systec.cmake b/cmake/systec.cmake index 394c2b0c..451d83a5 100644 --- a/cmake/systec.cmake +++ b/cmake/systec.cmake @@ -1,8 +1,7 @@ include(FetchContent) if(NOT DEFINED SYSTEC_LIBRARY) - message("SYSTEC_LIBRARY not set, trying typical install location") - set(SYSTEC_LIBRARY "C:/Program Files (x86)/SYSTEC-electronic/USB-CANmodul Utility Disk/Examples") + set(SYSTEC_LIBRARY "https://www.systec-electronic.com/media/default/Redakteur/produkte/Interfaces_Gateways/sysWORXX_USB_CANmodul_Series/Downloads/SO-387.zip") endif() set(SYSTEC_FETCHCONTENT_ARGS @@ -13,14 +12,7 @@ set(SYSTEC_FETCHCONTENT_ARGS if(EXISTS "${SYSTEC_LIBRARY}") message(STATUS "Using local Systec archive: ${SYSTEC_LIBRARY}") elseif(SYSTEC_LIBRARY MATCHES "^https?://") - if(DEFINED ENV{ICS_REPO_DEPS_TOKEN} AND NOT "$ENV{ICS_REPO_DEPS_TOKEN}" STREQUAL "") - message(STATUS "Downloading Systec archive with authentication: ${SYSTEC_LIBRARY}") - list(APPEND SYSTEC_FETCHCONTENT_ARGS - HTTP_HEADER "PRIVATE-TOKEN: $ENV{ICS_REPO_DEPS_TOKEN}" - ) - else() - message(STATUS "Downloading Systec archive without authentication: ${SYSTEC_LIBRARY}") - endif() + message(STATUS "Downloading Systec archive: ${SYSTEC_LIBRARY}") else() message(FATAL_ERROR "SYSTEC_LIBRARY must be an existing local archive or an http(s) URL. Got: ${SYSTEC_LIBRARY}") endif() @@ -32,6 +24,17 @@ FetchContent_Declare( FetchContent_MakeAvailable(Systec) +execute_process( + COMMAND ${systec_SOURCE_DIR}/SO-387.exe /SP- /VERYSILENT /DIR=${systec_BINARY_DIR} /LOG=${systec_SOURCE_DIR}/build.log + WORKING_DIRECTORY ${systec_SOURCE_DIR} + RESULT_VARIABLE systec_build_result + OUTPUT_VARIABLE systec_build_output +) + +if(NOT systec_build_result EQUAL 0) + message(FATAL_ERROR "Error installing USB-CANmodul Utility Disk: ${systec_build_output}") +endif() + if (WIN32) add_compile_definitions(WIN32) endif() From f55351a4ed40050f15562f2814689bf8e2e84d82 Mon Sep 17 00:00:00 2001 From: James Souter Date: Tue, 7 Apr 2026 15:42:26 +0100 Subject: [PATCH 61/63] black format python files --- python/canmodule-utils.py | 7 ++++++- test/python/test_systec.py | 21 ++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/python/canmodule-utils.py b/python/canmodule-utils.py index fd4a3ed2..0debf638 100644 --- a/python/canmodule-utils.py +++ b/python/canmodule-utils.py @@ -49,7 +49,12 @@ def parse_arguments(): systec_parser = subparsers.add_parser("systec", help="Use the Systec module") systec_parser.add_argument("device", type=int, help="Bus number for Systec") systec_parser.add_argument( - "baudrate", type=int, default=125000, help="Baud rate for Systec CAN channel", nargs='?') + "baudrate", + type=int, + default=125000, + help="Baud rate for Systec CAN channel", + nargs="?", + ) systec_subparsers = systec_parser.add_subparsers( dest="action", required=True, help="systec actions" diff --git a/test/python/test_systec.py b/test/python/test_systec.py index 5cc24e97..bdc2b7ed 100644 --- a/test/python/test_systec.py +++ b/test/python/test_systec.py @@ -13,11 +13,12 @@ pytestmark = pytest.mark.skipif( socket.gethostname() != "pcaticswin11", - reason="Tests currently only work when run on the pcaticswin11 development server" + reason="Tests currently only work when run on the pcaticswin11 development server", ) ELMB_ID = 15 + @pytest.fixture def device_and_frames(): config = CanDeviceConfiguration() @@ -26,9 +27,7 @@ def device_and_frames(): config.high_speed = True config.bus_number = 0 received = [] - device = CanDevice.create( - "systec", CanDeviceArguments(config, received.append) - ) + device = CanDevice.create("systec", CanDeviceArguments(config, received.append)) o1 = device.open() assert o1 == CanReturnCode.success received.clear() @@ -50,24 +49,26 @@ def test_sync_messages_elmb(device_and_frames): assert diag.tx_error == 0 sleep(1) - received.clear() # hopefully clear any previously buffered frames + received.clear() # hopefully clear any previously buffered frames r = device.send(CanFrame(0x80)) # send sync message assert r == CanReturnCode.success start = time() - while (time() - start < 15): + while time() - start < 15: if len(received) == 65: break else: - raise RuntimeError(f"Did not receive expected frames before timeout: {len(received)}/65") + raise RuntimeError( + f"Did not receive expected frames before timeout: {len(received)}/65" + ) diff = time() - start diag = device.diagnostics() assert diag.state == "No error." assert diag.rx - rx == 65 assert diag.tx - tx == 1 - assert diag.rx_per_second == pytest.approx(65/diff, rel=0.3) - assert diag.tx_per_second == pytest.approx(1/diff, rel=0.3) + assert diag.rx_per_second == pytest.approx(65 / diff, rel=0.3) + assert diag.tx_per_second == pytest.approx(1 / diff, rel=0.3) digital_input_frame = received[0] # see page 16 https://www.nikhef.nl/pub/departments/ct/po/html/ELMB128/ELMB24.pdf @@ -76,5 +77,3 @@ def test_sync_messages_elmb(device_and_frames): for idx, frame in enumerate(received[1:]): assert frame.id() == 911 assert ord(frame.message()[0]) == idx - - From f07c9cd1cd8e2071c119f939af327a45539e86bf Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 8 Apr 2026 08:25:36 +0100 Subject: [PATCH 62/63] cpplint fixes --- src/include/CanVendorSystec.h | 4 ++-- src/main/CanVendorSystec.cpp | 39 +++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index b1e5ae90..107d5e21 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -31,8 +31,8 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } int SystecRxThread(); - static std::string_view UsbCanGetErrorText( long err_code ); - static std::string UsbCanGetStatusText( long err_code ); + static std::string_view UsbCanGetErrorText( uint16_t err_code ); + static std::string UsbCanGetStatusText( uint16_t err_code ); private: std::atomic m_module_in_use {false}; diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index d047dd69..d52e7d2b 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include std::mutex CanVendorSystec::m_handles_lock; @@ -12,8 +14,8 @@ std::unordered_map CanVendorSystec::m_port_to_vendor_map; static bool module_control_callback_registered = false; template -long CallAndLog(T f, const char *name, Args... args) { - long code = f(args...); +uint16_t CallAndLog(T f, const char *name, Args... args) { + uint16_t code = f(args...); if (code != USBCAN_SUCCESSFUL) LOG(Log::ERR, CanLogIt::h()) << "Got error code calling " << name << ": " << CanVendorSystec::UsbCanGetErrorText(code); return code; @@ -26,7 +28,7 @@ void connect_control_callback(BYTE bEvent_p, DWORD dwParam_p) { case USBCAN_EVENT_DISCONNECT: LOG(Log::WRN) << "USB CAN module disconnected"; break; case USBCAN_EVENT_FATALDISCON: - LOG(Log::ERR) << "USB CAN module with handle " << (int) dwParam_p << "fatally disconnected"; break; + LOG(Log::ERR) << "USB CAN module with handle " << static_cast(dwParam_p) << "fatally disconnected"; break; } } @@ -96,7 +98,7 @@ CanReturnCode CanVendorSystec::init_can_port() { m_module_to_handle_map[m_module_number] = can_module_handle; } else { // find existing handle of module can_module_handle = handle.value(); - LOG(Log::INF, CanLogIt::h()) << "Reusing handle (" << (size_t) can_module_handle << ") for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; + LOG(Log::INF, CanLogIt::h()) << "Reusing handle (" << static_cast(can_module_handle) << ") for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; } if (CallAndLog(UcanInitCanEx2, "init channel", can_module_handle, m_channel_number, &initialization_parameters)) { @@ -240,31 +242,37 @@ CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { tUcanMsgCountInfo msg_count_info; - long err_code; + uint16_t err_code; if (err_code = CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", handle_value, m_channel_number, &msg_count_info); err_code == 0) { diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; - } else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + } else { + diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + } DWORD tx_error, rx_error; if (err_code = CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, m_channel_number, &tx_error, &rx_error); err_code == 0) { diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; - } else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + } else { + diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + } tUcanHardwareInfo hw_info; if (err_code = CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, &hw_info); err_code != 0) { diagnostics.mode = "OFFLINE"; diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); - } else switch (hw_info.m_bMode) { - case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; - case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; - case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; + } else { + switch (hw_info.m_bMode) { + case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; + case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; + case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; + } } DWORD module_time; // in ms if (err_code = CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time); err_code == 0) - diagnostics.uptime = (uint32_t) module_time / 1000; + diagnostics.uptime = static_cast(module_time) / 1000; else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); } return diagnostics; @@ -284,7 +292,7 @@ int CanVendorSystec::SystecRxThread() auto handle = get_module_handle(); if (!handle.has_value()) { LOG(Log::ERR, CanLogIt::h()) << "Could not start rx thread without valid handle for module"; - return -1; // TODO more useful error code + return -1; // TODO(jsouter): more useful error code } while (m_module_in_use) { to_read = m_queued_reads; @@ -311,7 +319,6 @@ int CanVendorSystec::SystecRxThread() default: // errors // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, // USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL - // TODO should we raise some error state here? LOG(Log::ERR, CanLogIt::h()) << UsbCanGetErrorText(status); break; } @@ -320,7 +327,7 @@ int CanVendorSystec::SystecRxThread() return 0; } -std::string_view CanVendorSystec::UsbCanGetErrorText( long err_code ) { +std::string_view CanVendorSystec::UsbCanGetErrorText( uint16_t err_code ) { switch( err_code ){ case USBCAN_SUCCESSFUL: return "success"; @@ -480,7 +487,7 @@ std::string_view CanVendorSystec::UsbCanGetErrorText( long err_code ) { } } -std::string CanVendorSystec::UsbCanGetStatusText(long err_code) { +std::string CanVendorSystec::UsbCanGetStatusText(uint16_t err_code) { switch(err_code) { case USBCAN_CANERR_OK: return "No error."; case USBCAN_CANERR_XMTFULL: return "Transmit buffer in CAN controller is overrun."; From b53784c5bd2179529af5d9c455bb1f5b5f1503ba Mon Sep 17 00:00:00 2001 From: James Souter Date: Wed, 8 Apr 2026 09:50:52 +0200 Subject: [PATCH 63/63] clang-format on CanVendorSystec --- src/include/CanDeviceConfiguration.h | 15 +- src/include/CanVendorSystec.h | 39 +- src/main/CanVendorSystec.cpp | 656 ++++++++++++++++----------- 3 files changed, 429 insertions(+), 281 deletions(-) diff --git a/src/include/CanDeviceConfiguration.h b/src/include/CanDeviceConfiguration.h index a727d6ec..3fb60acf 100644 --- a/src/include/CanDeviceConfiguration.h +++ b/src/include/CanDeviceConfiguration.h @@ -96,13 +96,14 @@ struct CanDeviceConfiguration { std::optional sent_acknowledgement; /** - * @brief Enable or disable closing both channels on systec module when one is closed. - * - * This parameter is optional for Systec on Windows and defaults to false. - * If turned on, calling close on a Systec CanDevice will deintialise both channels - * of a module when one is closed. - * - */ + * @brief Enable or disable closing both channels on systec module when one is + * closed. + * + * This parameter is optional for Systec on Windows and defaults to false. + * If turned on, calling close on a Systec CanDevice will deintialise both + * channels of a module when one is closed. + * + */ std::optional close_both_channels; std::string to_string() const noexcept; diff --git a/src/include/CanVendorSystec.h b/src/include/CanVendorSystec.h index 107d5e21..b8e9b22b 100644 --- a/src/include/CanVendorSystec.h +++ b/src/include/CanVendorSystec.h @@ -1,22 +1,24 @@ #ifndef SRC_INCLUDE_CANVENDORSYSTEC_H_ #define SRC_INCLUDE_CANVENDORSYSTEC_H_ +#include +#include +#include +#include + #include -#include -#include "tchar.h" -#include "Winsock2.h" -#include "windows.h" -#include -#include "usbcan32.h" #include #include +#include #include //NOLINT -#include "CanDiagnostics.h" -#include "CanVendorLoopback.h" -#include "CanDevice.h" -#include #include +#include #include +#include + +#include "CanDevice.h" +#include "CanDiagnostics.h" +#include "CanVendorLoopback.h" /** * @struct CanVendorSystec @@ -31,11 +33,11 @@ struct CanVendorSystec : CanDevice { explicit CanVendorSystec(const CanDeviceArguments& args); ~CanVendorSystec() { vendor_close(); } int SystecRxThread(); - static std::string_view UsbCanGetErrorText( uint16_t err_code ); - static std::string UsbCanGetStatusText( uint16_t err_code ); + static std::string_view UsbCanGetErrorText(uint16_t err_code); + static std::string UsbCanGetStatusText(uint16_t err_code); - private: - std::atomic m_module_in_use {false}; + private: + std::atomic m_module_in_use{false}; std::atomic m_queued_reads; int m_module_number; int m_channel_number; @@ -44,7 +46,8 @@ struct CanVendorSystec : CanDevice { std::thread m_SystecRxThread; std::optional get_module_handle() { - if (auto mapping = m_module_to_handle_map.find(m_module_number); mapping != m_module_to_handle_map.end()) { + if (auto mapping = m_module_to_handle_map.find(m_module_number); + mapping != m_module_to_handle_map.end()) { return mapping->second; } return std::nullopt; @@ -60,10 +63,12 @@ struct CanVendorSystec : CanDevice { static std::unordered_map m_module_to_handle_map; static std::unordered_map m_port_to_vendor_map; - friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p); + friend void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, + BYTE bChannel_p, void* pArg_p); CanReturnCode deinit_channel(tUcanHandle handle) noexcept; - CanReturnCode deinit_other_channel(tUcanHandle handle, CanVendorSystec *other) noexcept; + CanReturnCode deinit_other_channel(tUcanHandle handle, + CanVendorSystec* other) noexcept; }; #endif // SRC_INCLUDE_CANVENDORSYSTEC_H_ diff --git a/src/main/CanVendorSystec.cpp b/src/main/CanVendorSystec.cpp index d52e7d2b..2129f6ff 100644 --- a/src/main/CanVendorSystec.cpp +++ b/src/main/CanVendorSystec.cpp @@ -1,11 +1,12 @@ #include "CanVendorSystec.h" -#include -#include #include +#include + +#include #include -#include #include +#include #include std::mutex CanVendorSystec::m_handles_lock; @@ -14,31 +15,39 @@ std::unordered_map CanVendorSystec::m_port_to_vendor_map; static bool module_control_callback_registered = false; template -uint16_t CallAndLog(T f, const char *name, Args... args) { +uint16_t CallAndLog(T f, const char* name, Args... args) { uint16_t code = f(args...); if (code != USBCAN_SUCCESSFUL) - LOG(Log::ERR, CanLogIt::h()) << "Got error code calling " << name << ": " << CanVendorSystec::UsbCanGetErrorText(code); + LOG(Log::ERR, CanLogIt::h()) << "Got error code calling " << name << ": " + << CanVendorSystec::UsbCanGetErrorText(code); return code; } void connect_control_callback(BYTE bEvent_p, DWORD dwParam_p) { - switch(bEvent_p) { + switch (bEvent_p) { case USBCAN_EVENT_CONNECT: - LOG(Log::DBG) << "USB CAN module connected"; break; + LOG(Log::DBG) << "USB CAN module connected"; + break; case USBCAN_EVENT_DISCONNECT: - LOG(Log::WRN) << "USB CAN module disconnected"; break; + LOG(Log::WRN) << "USB CAN module disconnected"; + break; case USBCAN_EVENT_FATALDISCON: - LOG(Log::ERR) << "USB CAN module with handle " << static_cast(dwParam_p) << "fatally disconnected"; break; + LOG(Log::ERR) << "USB CAN module with handle " + << static_cast(dwParam_p) << "fatally disconnected"; + break; } } // Callback registered per-module to handle receive events -void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, void* pArg_p) { +void systec_receive(tUcanHandle UcanHandle_p, DWORD bEvent_p, BYTE bChannel_p, + void* pArg_p) { if (bEvent_p == USBCAN_EVENT_RECEIVE) { int module_number = *(reinterpret_cast(pArg_p)); int port_number = 2 * module_number + bChannel_p; - CanVendorSystec *vendorPtr = CanVendorSystec::m_port_to_vendor_map[port_number]; - if (vendorPtr) ++(vendorPtr->m_queued_reads); // [] returns nullptr if not found in map; + CanVendorSystec* vendorPtr = + CanVendorSystec::m_port_to_vendor_map[port_number]; + if (vendorPtr) + ++(vendorPtr->m_queued_reads); // [] returns nullptr if not found in map; } } @@ -49,12 +58,24 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) } switch (args.config.bitrate.value()) { - case 50000: m_baud_rate = USBCAN_BAUD_50kBit; break; - case 100000: m_baud_rate = USBCAN_BAUD_100kBit; break; - case 125000: m_baud_rate = USBCAN_BAUD_125kBit; break; - case 250000: m_baud_rate = USBCAN_BAUD_250kBit; break; - case 500000: m_baud_rate = USBCAN_BAUD_500kBit; break; - case 1000000: m_baud_rate = USBCAN_BAUD_1MBit; break; + case 50000: + m_baud_rate = USBCAN_BAUD_50kBit; + break; + case 100000: + m_baud_rate = USBCAN_BAUD_100kBit; + break; + case 125000: + m_baud_rate = USBCAN_BAUD_125kBit; + break; + case 250000: + m_baud_rate = USBCAN_BAUD_250kBit; + break; + case 500000: + m_baud_rate = USBCAN_BAUD_500kBit; + break; + case 1000000: + m_baud_rate = USBCAN_BAUD_1MBit; + break; default: { throw std::invalid_argument("Invalid bitrate provided"); } @@ -68,48 +89,61 @@ CanVendorSystec::CanVendorSystec(const CanDeviceArguments& args) CanReturnCode CanVendorSystec::init_can_port() { BYTE systec_call_return = USBCAN_SUCCESSFUL; - tUcanHandle can_module_handle; - - tUcanInitCanParam initialization_parameters; - initialization_parameters.m_dwSize = sizeof(initialization_parameters); // size of this struct - initialization_parameters.m_bMode = kUcanModeNormal; // normal operation mode - initialization_parameters.m_bBTR0 = HIBYTE( m_baud_rate ); // baudrate - initialization_parameters.m_bBTR1 = LOBYTE( m_baud_rate ); - initialization_parameters.m_bOCR = 0x1A; // standard output - initialization_parameters.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages - initialization_parameters.m_dwACR = USBCAN_ACR_ALL; - initialization_parameters.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; - initialization_parameters.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; - initialization_parameters.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + tUcanHandle can_module_handle; + + tUcanInitCanParam init_params; + init_params.m_dwSize = sizeof(init_params); // size of this struct + init_params.m_bMode = kUcanModeNormal; // normal operation mode + init_params.m_bBTR0 = HIBYTE(m_baud_rate); // baudrate + init_params.m_bBTR1 = LOBYTE(m_baud_rate); + init_params.m_bOCR = 0x1A; // standard output + init_params.m_dwAMR = USBCAN_AMR_ALL; // receive all CAN messages + init_params.m_dwACR = USBCAN_ACR_ALL; + init_params.m_dwBaudrate = USBCAN_BAUDEX_USE_BTR01; + init_params.m_wNrOfRxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; + init_params.m_wNrOfTxBufferEntries = USBCAN_DEFAULT_BUFFER_ENTRIES; // check if USB-CANmodul is already initialized std::lock_guard guard(CanVendorSystec::m_handles_lock); auto handle = get_module_handle(); - if (!handle.has_value()) { // module not in use + if (!handle.has_value()) { // module not in use if (!module_control_callback_registered) { - if (!CallAndLog(UcanInitHwConnectControl, "hw connect control callback", connect_control_callback)) + if (!CallAndLog(UcanInitHwConnectControl, "hw connect control callback", + connect_control_callback)) module_control_callback_registered = true; } - if (auto systec_code = CallAndLog(UcanInitHardwareEx, "init hardware", &can_module_handle, m_module_number, systec_receive, (void*) &m_module_number); systec_code != 0) { + if (auto systec_code = + CallAndLog(UcanInitHardwareEx, "init hardware", &can_module_handle, + m_module_number, systec_receive, + reinterpret_cast(&m_module_number)); + systec_code != 0) { CallAndLog(UcanDeinitHardware, "deinit hardware", can_module_handle); return CanReturnCode::unknown_open_error; } - LOG(Log::INF, CanLogIt::h()) << "Initialised hardware for Systec module " << m_module_number; + LOG(Log::INF, CanLogIt::h()) + << "Initialised hardware for Systec module " << m_module_number; m_module_to_handle_map[m_module_number] = can_module_handle; - } else { // find existing handle of module + } else { // find existing handle of module can_module_handle = handle.value(); - LOG(Log::INF, CanLogIt::h()) << "Reusing handle (" << static_cast(can_module_handle) << ") for module " << m_module_number << " already in use, skipping UCanInitHardwareEx"; + LOG(Log::INF, CanLogIt::h()) + << "Reusing handle (" << static_cast(can_module_handle) + << ") for module " << m_module_number + << " already in use, skipping UCanInitHardwareEx"; } - if (CallAndLog(UcanInitCanEx2, "init channel", can_module_handle, m_channel_number, &initialization_parameters)) { + if (CallAndLog(UcanInitCanEx2, "init channel", can_module_handle, + m_channel_number, &init_params)) { deinit_channel(can_module_handle); return CanReturnCode::unknown_open_error; } // investigate the minimum amount of things to reset to restore good state - CallAndLog(UcanResetCanEx, "reset channel", can_module_handle, (BYTE) m_channel_number, (DWORD) 0); + CallAndLog(UcanResetCanEx, "reset channel", can_module_handle, + (BYTE)m_channel_number, (DWORD)0); - LOG(Log::INF, CanLogIt::h()) << "Successfully opened CAN port on module " << m_module_number << ", channel " << m_channel_number; + LOG(Log::INF, CanLogIt::h()) + << "Successfully opened CAN port on module " << m_module_number + << ", channel " << m_channel_number; return CanReturnCode::success; } @@ -121,7 +155,7 @@ CanReturnCode CanVendorSystec::vendor_open() noexcept { if (return_code != CanReturnCode::success) return return_code; m_module_in_use = true; m_SystecRxThread = std::thread(&CanVendorSystec::SystecRxThread, this); - } catch(...) { + } catch (...) { return_code = CanReturnCode::internal_api_error; } @@ -133,10 +167,11 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { std::lock_guard guard(CanVendorSystec::m_handles_lock); bool other_in_use = false; - CanVendorSystec *other = nullptr; + CanVendorSystec* other = nullptr; // toggle last bit to get the other port on the same module // e.g. if channel 0, we need to check if channel 1 is in use and vice versa - if (auto mapping = m_port_to_vendor_map.find(m_port_number ^ 1); mapping != m_port_to_vendor_map.end()) { + if (auto mapping = m_port_to_vendor_map.find(m_port_number ^ 1); + mapping != m_port_to_vendor_map.end()) { other = mapping->second; other_in_use = other->m_module_in_use; } @@ -149,12 +184,14 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { auto handle = get_module_handle(); if (!handle.has_value()) { - LOG(Log::WRN, CanLogIt::h()) << "No handle found for module, close() may have already been called"; - return CanReturnCode::success; // is success correct? + LOG(Log::WRN, CanLogIt::h()) + << "No handle found for module, close() may have already been called"; + return CanReturnCode::success; // is success correct? } if (close_both_channels && other_in_use) { - LOG(Log::WRN, CanLogIt::h()) << "Deinitialising other channel " << other->m_channel_number << " on module."; + LOG(Log::WRN, CanLogIt::h()) << "Deinitialising other channel " + << other->m_channel_number << " on module."; return_code = deinit_other_channel(handle.value(), other); } return_code = deinit_channel(handle.value()); @@ -162,7 +199,9 @@ CanReturnCode CanVendorSystec::vendor_close() noexcept { // if there are no channels still using the handle, deinit hardware // and erase handle from map if (!other_in_use || close_both_channels) { - if (auto systec_code = CallAndLog(UcanDeinitHardware, "deinit hw", handle.value()); systec_code != 0) + if (auto systec_code = + CallAndLog(UcanDeinitHardware, "deinit hw", handle.value()); + systec_code != 0) return_code = CanReturnCode::unknown_close_error; m_module_to_handle_map.erase(m_module_number); } @@ -179,9 +218,11 @@ CanReturnCode CanVendorSystec::deinit_channel(tUcanHandle handle) noexcept { return internal_return_code; } -CanReturnCode CanVendorSystec::deinit_other_channel(tUcanHandle handle, CanVendorSystec *other) noexcept { +CanReturnCode CanVendorSystec::deinit_other_channel( + tUcanHandle handle, CanVendorSystec* other) noexcept { auto internal_return_code = CanReturnCode::success; - if (CallAndLog(UcanDeinitCanEx, "deinit channel", handle, other->m_channel_number)) + if (CallAndLog(UcanDeinitCanEx, "deinit channel", handle, + other->m_channel_number)) internal_return_code = CanReturnCode::unknown_close_error; other->m_module_in_use = false; return internal_return_code; @@ -196,119 +237,154 @@ CanReturnCode CanVendorSystec::vendor_send(const CanFrame& frame) noexcept { can_msg_to_send.m_bDLC = frame.length(); can_msg_to_send.m_bFF = 0; if (frame.is_remote_request()) { - can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; + can_msg_to_send.m_bFF = USBCAN_MSG_FF_RTR; } - std::copy(message.begin(), message.begin() + can_msg_to_send.m_bDLC, can_msg_to_send.m_bData); + std::copy(message.begin(), message.begin() + can_msg_to_send.m_bDLC, + can_msg_to_send.m_bData); auto handle = get_module_handle(); if (!handle.has_value()) { - LOG(Log::ERR, CanLogIt::h()) << "Could not send message, no handle found for module " << m_module_number; + LOG(Log::ERR, CanLogIt::h()) + << "Could not send message, no handle found for module " + << m_module_number; return CanReturnCode::disconnected; } - switch(CallAndLog(UcanWriteCanMsgEx, "write", handle.value(), m_channel_number, &can_msg_to_send, nullptr)) { - case USBCAN_SUCCESSFUL: break; + switch (CallAndLog(UcanWriteCanMsgEx, "write", handle.value(), + m_channel_number, &can_msg_to_send, nullptr)) { + case USBCAN_SUCCESSFUL: + break; case USBCAN_ERR_CANNOTINIT: - case USBCAN_ERR_ILLHANDLE: return CanReturnCode::disconnected; - case USBCAN_ERR_DLL_TXFULL: return CanReturnCode::tx_buffer_overflow; - case USBCAN_ERR_MAXINSTANCES: return CanReturnCode::too_many_connections; + case USBCAN_ERR_ILLHANDLE: + return CanReturnCode::disconnected; + case USBCAN_ERR_DLL_TXFULL: + return CanReturnCode::tx_buffer_overflow; + case USBCAN_ERR_MAXINSTANCES: + return CanReturnCode::too_many_connections; case USBCAN_ERR_ILLPARAM: case USBCAN_ERR_ILLHW: case USBCAN_ERR_ILLCHANNEL: case USBCAN_WARN_TXLIMIT: case USBCAN_WARN_FW_TXOVERRUN: - default: return CanReturnCode::unknown_send_error; + default: + return CanReturnCode::unknown_send_error; } return CanReturnCode::success; }; CanDiagnostics CanVendorSystec::vendor_diagnostics() noexcept { - CanDiagnostics diagnostics{}; tStatusStruct status; auto handle = get_module_handle(); diagnostics.log_entries = std::vector(); if (!handle.has_value()) { - LOG(Log::ERR, CanLogIt::h()) << "Could not get diagnostics as no handle found for module " << m_module_number; + LOG(Log::ERR, CanLogIt::h()) + << "Could not get diagnostics as no handle found for module " + << m_module_number; diagnostics.mode = "OFFLINE"; } else { auto handle_value = handle.value(); - CallAndLog(UcanGetStatusEx, "get status", handle_value, m_channel_number, &status); + CallAndLog(UcanGetStatusEx, "get status", handle_value, m_channel_number, + &status); WORD can_status = status.m_wCanStatus; diagnostics.state = UsbCanGetStatusText(can_status); if (can_status) { diagnostics.log_entries.value().push_back(diagnostics.state.value()); } - tUcanMsgCountInfo msg_count_info; uint16_t err_code; - if (err_code = CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", handle_value, m_channel_number, &msg_count_info); err_code == 0) { + if (err_code = CallAndLog(UcanGetMsgCountInfoEx, "get msg counts", + handle_value, m_channel_number, &msg_count_info); + err_code == 0) { diagnostics.tx = msg_count_info.m_wSentMsgCount; diagnostics.rx = msg_count_info.m_wRecvdMsgCount; } else { - diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + diagnostics.log_entries.value().push_back( + std::string(UsbCanGetErrorText(err_code))); } DWORD tx_error, rx_error; - if (err_code = CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, m_channel_number, &tx_error, &rx_error); err_code == 0) { + if (err_code = + CallAndLog(UcanGetCanErrorCounter, "get errors", handle_value, + m_channel_number, &tx_error, &rx_error); + err_code == 0) { diagnostics.tx_error = tx_error; diagnostics.rx_error = rx_error; } else { - diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + diagnostics.log_entries.value().push_back( + std::string(UsbCanGetErrorText(err_code))); } tUcanHardwareInfo hw_info; - if (err_code = CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, &hw_info); err_code != 0) { + if (err_code = CallAndLog(UcanGetHardwareInfo, "get hw info", handle_value, + &hw_info); + err_code != 0) { diagnostics.mode = "OFFLINE"; - diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + diagnostics.log_entries.value().push_back( + std::string(UsbCanGetErrorText(err_code))); } else { switch (hw_info.m_bMode) { - case kUcanModeNormal: diagnostics.mode = "NORMAL"; break; - case kUcanModeListenOnly: diagnostics.mode = "LISTEN_ONLY"; break; - case kUcanModeTxEcho: diagnostics.mode = "LOOPBACK"; break; + case kUcanModeNormal: + diagnostics.mode = "NORMAL"; + break; + case kUcanModeListenOnly: + diagnostics.mode = "LISTEN_ONLY"; + break; + case kUcanModeTxEcho: + diagnostics.mode = "LOOPBACK"; + break; } } - DWORD module_time; // in ms - if (err_code = CallAndLog(UcanGetModuleTime, "get module time", handle_value, &module_time); err_code == 0) + DWORD module_time; // in ms + if (err_code = CallAndLog(UcanGetModuleTime, "get module time", + handle_value, &module_time); + err_code == 0) diagnostics.uptime = static_cast(module_time) / 1000; - else diagnostics.log_entries.value().push_back(std::string(UsbCanGetErrorText(err_code))); + else + diagnostics.log_entries.value().push_back( + std::string(UsbCanGetErrorText(err_code))); } return diagnostics; }; - /** * thread to handle reception of Can messages from the systec device */ -int CanVendorSystec::SystecRxThread() -{ +int CanVendorSystec::SystecRxThread() { BYTE status; tCanMsgStruct read_can_message; - LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_module_in_use = [" << m_module_in_use <<"]"; + LOG(Log::DBG, CanLogIt::h()) << "SystecRxThread Started. m_module_in_use = [" + << m_module_in_use << "]"; size_t to_read; auto handle = get_module_handle(); if (!handle.has_value()) { - LOG(Log::ERR, CanLogIt::h()) << "Could not start rx thread without valid handle for module"; - return -1; // TODO(jsouter): more useful error code + LOG(Log::ERR, CanLogIt::h()) + << "Could not start rx thread without valid handle for module"; + return -1; // TODO(jsouter): more useful error code } while (m_module_in_use) { to_read = m_queued_reads; if (to_read < 1) continue; - status = UcanReadCanMsgEx(handle.value(), (BYTE *) &m_channel_number, &read_can_message, NULL); + status = UcanReadCanMsgEx(handle.value(), + reinterpret_cast(&m_channel_number), + &read_can_message, NULL); switch (status) { case USBCAN_WARN_SYS_RXOVERRUN: case USBCAN_WARN_DLL_RXOVERRUN: case USBCAN_WARN_FW_RXOVERRUN: LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); - [[ fallthrough ]]; + [[fallthrough]]; case USBCAN_SUCCESSFUL: { --m_queued_reads; if (read_can_message.m_bFF & USBCAN_MSG_FF_RTR) break; - std::vector data(read_can_message.m_bData, read_can_message.m_bData + read_can_message.m_bDLC); - CanFrame can_frame(read_can_message.m_dwID, data, read_can_message.m_bFF); + std::vector data( + read_can_message.m_bData, + read_can_message.m_bData + read_can_message.m_bDLC); + CanFrame can_frame(read_can_message.m_dwID, data, + read_can_message.m_bFF); received(can_frame); break; } @@ -316,7 +392,7 @@ int CanVendorSystec::SystecRxThread() m_queued_reads -= to_read; LOG(Log::WRN, CanLogIt::h()) << UsbCanGetErrorText(status); break; - default: // errors + default: // errors // USBCAN_ERR_MAXINSTANCES, USBCAN_ERR_ILLHANDLE, USBCAN_ERR_CANNOTINIT, // USBCAN_ERR_ILLPARAM, USBCAN_ERR_ILLHW, USBCAN_ERR_ILLCHANNEL LOG(Log::ERR, CanLogIt::h()) << UsbCanGetErrorText(status); @@ -327,182 +403,248 @@ int CanVendorSystec::SystecRxThread() return 0; } -std::string_view CanVendorSystec::UsbCanGetErrorText( uint16_t err_code ) { - switch( err_code ){ - case USBCAN_SUCCESSFUL: return "success"; - - case USBCAN_ERR_RESOURCE: return "This error code returns if one resource could not be generated. In this " - "case the term resource means memory and handles provided by the Windows OS"; - - case USBCAN_ERR_MAXMODULES: return "An application has tried to open more than 64 USB-CANmodul devices. " - "The standard version of the DLL only supports up to 64 USB-CANmodul " - "devices at the same time. This error also appears if several applications " - "try to access more than 64 USB-CANmodul devices. For example, " - "application 1 has opened 60 modules, application 2 has opened 4 " - "modules and application 3 wants to open a module. Application 3 " - "receives this error code."; - - case USBCAN_ERR_HWINUSE: return "An application tries to initialize an USB-CANmodul with the given device " - "number. If this module has already been initialized by its own or by " - "another application, this error code is returned."; - - case USBCAN_ERR_ILLVERSION: return "This error code returns if the firmware version of the USB-CANmodul is " - "not compatible to the software version of the DLL. In this case, install " - "the latest driver for the USB-CANmodul. Furthermore make sure that " - "the latest firmware version is programmed to the USB-CANmodul."; - - case USBCAN_ERR_ILLHW: return "This error code returns if an USB-CANmodul with the given device " - "number is not found. If the function UcanInitHardware() or " - "UcanInitHardwareEx() has been called with the device number " - "USBCAN_ANY_MODULE, and the error code appears, it indicates that " - "no module is connected to the PC or all connected modules are already " - "in use."; - - case USBCAN_ERR_ILLHANDLE: return "This error code returns if a function received an incorrect USBCAN " - "handle. The function first checks which USB-CANmodul is related to this " - "handle. This error occurs if no device belongs this handle."; - - case USBCAN_ERR_ILLPARAM: return "This error code returns if a wrong parameter is passed to the function. " - "For example, the value NULL has been passed to a pointer variable " - "instead of a valid address."; - - case USBCAN_ERR_BUSY: return "This error code occurs if several threads are accessing an " - "USB-CANmodul within a single application. After the other threads have " - "finished their tasks, the function may be called again."; - - case USBCAN_ERR_TIMEOUT: return "This error code occurs if the function transmits a command to the " - "USB-CANmodul but no reply is returned. To solve this problem, close " - "the application, disconnect the USB-CANmodul, and connect it again."; - - case USBCAN_ERR_IOFAILED: return "This error code occurs if the communication to the kernel driver was " - "interrupted. This happens, for example, if the USB-CANmodul is " - "disconnected during transferring data or commands to the " - "USB-CANmodul."; - - case USBCAN_ERR_DLL_TXFULL: return "The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first checks " - "if the transmit buffer within the DLL has enough capacity to store new " - "CAN messages. If the buffer is full, this error code returns. The CAN " - "message passed to these functions will not be written into the transmit " - "buffer in order to protect other CAN messages against overwriting. The " - "size of the transmit buffer is configurable (refer to function " - "UcanInitCanEx() and structure tUcanInitCanParam)."; - - case USBCAN_ERR_MAXINSTANCES: return "A maximum amount of 64 applications are able to have access to the " - "DLL. If more applications attempting to access to the DLL, this error " - "code is returned. In this case, it is not possible to use an " - "USB-CANmodul by this application."; - - case USBCAN_ERR_CANNOTINIT: return "This error code returns if an application tries to call an API function " - "which only can be called in software state CAN_INIT but the current " - "software is still in state HW_INIT. Refer to section 4.3.1 and Table 11 for " - "detailed information."; - - case USBCAN_ERR_DISCONNECT: return "This error code occurs if an API function was called for an " - "USB-CANmodul that was plugged-off from the computer recently."; - - case USBCAN_ERR_ILLCHANNEL: return "This error code is returned if an extended function of the DLL is called " - "with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a single-channel USB-CANmodul was used."; - - case USBCAN_ERR_ILLHWTYPE: return "This error code occurs if an extended function of the DLL was called for " - "a hardware which does not support the feature."; - - case USBCAN_ERRCMD_NOTEQU: return "This error code occurs during communication between the PC and an " - "USB-CANmodul. The PC sends a command to the USB-CANmodul, " - "then the module executes the command and returns a response to the " - "PC. This error code returns if the reply does not correspond to the command."; - - case USBCAN_ERRCMD_REGTST: return "The software tests the CAN controller on the USB-CANmodul when the " - "CAN interface is initialized. Several registers of the CAN controller are " - "checked. This error code returns if an error appears during this register test."; - - case USBCAN_ERRCMD_ILLCMD: return "This error code returns if the USB-CANmodul receives a non-defined " - "command. This error represents a version conflict between the firmware in the USB-CANmodul and the DLL."; - - case USBCAN_ERRCMD_EEPROM: return "The USB-CANmodul has a built-in EEPROM. This EEPROM contains " - "several configurations, e.g. the device number and the serial number. If " - "an error occurs while reading these values, this error code is returned."; - - case USBCAN_ERRCMD_ILLBDR: return "The USB-CANmodul has been initialized with an invalid baud rate (refer " - "to section 4.3.4)."; - - case USBCAN_ERRCMD_NOTINIT: return "It was tried to access a CAN-channel of a multi-channel " - "USB-CANmodul that was not initialized."; - - case USBCAN_ERRCMD_ALREADYINIT: return "The accessed CAN-channel of a multi-channel USB-CANmodul was " - "already initialized"; - - case USBCAN_ERRCMD_ILLSUBCMD: return "An internal error occurred within the DLL. In this case an unknown sub- " - "command was called instead of a main command (e.g. for the cyclic CAN message-feature)."; - - case USBCAN_ERRCMD_ILLIDX: return "An internal error occurred within the DLL. In this case an invalid index " - "for a list was delivered to the firmware (e.g. for the cyclic CAN message-feature)."; - - case USBCAN_ERRCMD_RUNNING: return "The caller tries to define a new list of cyclic CAN messages but this " - "feature was already started. For defining a new list, it is necessary to stop the feature beforehand."; - - case USBCAN_WARN_NODATA: return "If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " - "with this warning, it is an indication that the receive buffer contains no CAN messages."; - - case USBCAN_WARN_SYS_RXOVERRUN: return "This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if the " - "receive buffer within the kernel driver runs over. The function " - "nevertheless returns a valid CAN message. It also indicates that at least " - "one CAN message are lost. However, it does not indicate the position of the lost CAN messages."; - - case USBCAN_WARN_DLL_RXOVERRUN: return "The DLL automatically requests CAN messages from the " - "USB-CANmodul and stores the messages into a buffer of the DLL. If " - "more CAN messages are received than the DLL buffer size allows, this " - "error code returns and CAN messages are lost. However, it does not " - "indicate the position of the lost CAN messages. The size of the receive " - "buffer is configurable (refer to function UcanInitCanEx() and structure " - "tUcanInitCanParam)."; - - case USBCAN_WARN_FW_TXOVERRUN: return "This warning is returned by function UcanWriteCanMsg() or " - "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in " - "the CAN driver status. However, the transmit CAN message could be " - "stored to the DLL transmit buffer. This warning indicates that at least " - "one transmit CAN message got lost in the device firmware layer. This " - "warning does not indicate the position of the lost CAN message."; - - case USBCAN_WARN_FW_RXOVERRUN: return "This warning is returned by function UcanWriteCanMsg() or " - "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag " - "USBCAN_CANERR_OVERRUN are set in the CAN driver status. The " - "function has returned with a valid CAN message. This warning indicates " - "that at least one received CAN message got lost in the firmware layer. " - "This warning does not indicate the position of the lost CAN message."; - - case USBCAN_WARN_NULL_PTR: return "This warning is returned by functions UcanInitHwConnectControl() or " - "UcanInitHwConnectControlEx() if a NULL pointer was passed as callback function address."; - - case USBCAN_WARN_TXLIMIT: return "This warning is returned by the function UcanWriteCanMsgEx() if it was " - "called to transmit more than one CAN message, but a part of them " - "could not be stored to the transmit buffer within the DLL (because the " - "buffer is full). The returned variable addressed by the parameter " - "pdwCount_p indicates the number of CAN messages which are stored " - "successfully to the transmit buffer."; - - default: - std::stringstream ss; - ss << "Unknown error code: 0x" << std::hex << err_code; - return ss.str(); +std::string_view CanVendorSystec::UsbCanGetErrorText(uint16_t err_code) { + switch (err_code) { + case USBCAN_SUCCESSFUL: + return "success"; + + case USBCAN_ERR_RESOURCE: + return "This error code returns if one resource could not be generated. " + "In this case the term resource means memory and handles provided " + "by the Windows OS"; + + case USBCAN_ERR_MAXMODULES: + return "An application has tried to open more than 64 USB-CANmodul " + "devices. The standard version of the DLL only supports up to 64 " + "USB-CANmodul devices at the same time. This error also appears " + "if several applications try to access more than 64 USB-CANmodul " + "devices. For example, application 1 has opened 60 modules, " + "application 2 has opened 4 modules and application 3 wants to " + "open a module. Application 3 receives this error code."; + + case USBCAN_ERR_HWINUSE: + return "An application tries to initialize an USB-CANmodul with the " + "given device number. If this module has already been initialized " + "by its own or by another application, this error code is " + "returned."; + + case USBCAN_ERR_ILLVERSION: + return "This error code returns if the firmware version of the " + "USB-CANmodul is not compatible to the software version of the " + "DLL. " + "In this case, install the latest driver for the USB-CANmodul. " + "Furthermore make sure that the latest firmware version is " + "programmed to the USB-CANmodul."; + + case USBCAN_ERR_ILLHW: + return "This error code returns if an USB-CANmodul with the given device " + "number is not found. If the function UcanInitHardware() or " + "UcanInitHardwareEx() has been called with the device number " + "USBCAN_ANY_MODULE, and the error code appears, it indicates that " + "no module is connected to the PC or all connected modules are " + "already in use."; + + case USBCAN_ERR_ILLHANDLE: + return "This error code returns if a function received an incorrect " + "USBCAN handle. The function first checks which USB-CANmodul is " + "related to this handle. This error occurs if no device belongs " + "this handle."; + + case USBCAN_ERR_ILLPARAM: + return "This error code returns if a wrong parameter is passed to the " + "function. For example, the value NULL has been passed to a " + "pointer variable instead of a valid address."; + + case USBCAN_ERR_BUSY: + return "This error code occurs if several threads are accessing an " + "USB-CANmodul within a single application. After the other " + "threads have finished their tasks, the function may be called " + "again."; + + case USBCAN_ERR_TIMEOUT: + return "This error code occurs if the function transmits a command to " + "the USB-CANmodul but no reply is returned. To solve this " + "problem, close the application, disconnect the USB-CANmodul, " + "and connect it again."; + + case USBCAN_ERR_IOFAILED: + return "This error code occurs if the communication to the kernel driver " + "was interrupted. This happens, for example, if the USB-CANmodul " + "is disconnected during transferring data or commands to the " + "USB-CANmodul."; + + case USBCAN_ERR_DLL_TXFULL: + return "The function UcanWriteCanMsg() or UcanWriteCanMsgEx() first " + "checks if the transmit buffer within the DLL has enough capacity " + "to store new CAN messages. If the buffer is full, this error " + "code returns. " + "The CAN message passed to these functions will not be written " + "into the transmit buffer in order to protect other CAN messages " + "against overwriting. The size of the transmit buffer is " + "configurable (refer to function UcanInitCanEx() and structure " + "tUcanInitCanParam)."; + + case USBCAN_ERR_MAXINSTANCES: + return "A maximum amount of 64 applications are able to have access to " + "the DLL. If more applications attempting to access to the DLL, " + "this error code is returned. In this case, it is not possible to " + "use an USB-CANmodul by this application."; + + case USBCAN_ERR_CANNOTINIT: + return "This error code returns if an application tries to call an API " + "function which only can be called in software state CAN_INIT but " + "the current software is still in state HW_INIT. Refer to section " + "4.3.1 and Table 11 for detailed information."; + + case USBCAN_ERR_DISCONNECT: + return "This error code occurs if an API function was called for an " + "USB-CANmodul that was plugged-off from the computer recently."; + + case USBCAN_ERR_ILLCHANNEL: + return "This error code is returned if an extended function of the DLL " + "is called with parameter bChannel_p = USBCAN_CHANNEL_CH1, but a " + "single-channel USB-CANmodul was used."; + + case USBCAN_ERR_ILLHWTYPE: + return "This error code occurs if an extended function of the DLL was " + "called for a hardware which does not support the feature."; + + case USBCAN_ERRCMD_NOTEQU: + return "This error code occurs during communication between the PC and " + "an USB-CANmodul. The PC sends a command to the USB-CANmodul, " + "then the module executes the command and returns a response to " + "the PC. This error code returns if the reply does not correspond " + "to the command."; + + case USBCAN_ERRCMD_REGTST: + return "The software tests the CAN controller on the USB-CANmodul when " + "the CAN interface is initialized. Several registers of the CAN " + "controller are checked. This error code returns if an error " + "appears during this register test."; + + case USBCAN_ERRCMD_ILLCMD: + return "This error code returns if the USB-CANmodul receives a " + "non-defined command. This error represents a version conflict " + "between the firmware in the USB-CANmodul and the DLL."; + + case USBCAN_ERRCMD_EEPROM: + return "The USB-CANmodul has a built-in EEPROM. This EEPROM contains " + "several configurations, e.g. the device number and the serial " + "number. If an error occurs while reading these values, this " + "error code is returned."; + + case USBCAN_ERRCMD_ILLBDR: + return "The USB-CANmodul has been initialized with an invalid baud rate " + "(refer to section 4.3.4)."; + + case USBCAN_ERRCMD_NOTINIT: + return "It was tried to access a CAN-channel of a multi-channel " + "USB-CANmodul that was not initialized."; + + case USBCAN_ERRCMD_ALREADYINIT: + return "The accessed CAN-channel of a multi-channel USB-CANmodul was " + "already initialized"; + + case USBCAN_ERRCMD_ILLSUBCMD: + return "An internal error occurred within the DLL. In this case an " + "unknown sub-command was called instead of a main command (e.g. " + "for the cyclic CAN message-feature)."; + + case USBCAN_ERRCMD_ILLIDX: + return "An internal error occurred within the DLL. In this case an " + "invalid index for a list was delivered to the firmware (e.g. for " + "the cyclic CAN message-feature)."; + + case USBCAN_ERRCMD_RUNNING: + return "The caller tries to define a new list of cyclic CAN messages but " + "this feature was already started. For defining a new list, it is " + "necessary to stop the feature beforehand."; + + case USBCAN_WARN_NODATA: + return "If the function UcanReadCanMsg() or UcanReadCanMsgEx() returns " + "with this warning, it is an indication that the receive buffer " + "contains no CAN messages."; + + case USBCAN_WARN_SYS_RXOVERRUN: + return "This is returned by UcanReadCanMsg() or UcanReadCanMsgEx() if " + "the receive buffer within the kernel driver runs over. The " + "function nevertheless returns a valid CAN message. It also " + "indicates that at least one CAN message are lost. However, it " + "does not indicate the position of the lost CAN messages."; + + case USBCAN_WARN_DLL_RXOVERRUN: + return "The DLL automatically requests CAN messages from the " + "USB-CANmodul and stores the messages into a buffer of the DLL. " + "If more CAN messages are received than the DLL buffer size " + "allows, this error code returns and CAN messages are lost. " + "However, it does not indicate the position of the lost CAN " + "messages. The size of the receive buffer is configurable (refer " + "to function UcanInitCanEx() and structure tUcanInitCanParam)."; + + case USBCAN_WARN_FW_TXOVERRUN: + return "This warning is returned by function UcanWriteCanMsg() or " + "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QXMTFULL is set in " + "the CAN driver status. However, the transmit CAN message could " + "be stored to the DLL transmit buffer. This warning indicates " + "that at least one transmit CAN message got lost in the device " + "firmware layer. This warning does not indicate the position of " + "the lost CAN message."; + + case USBCAN_WARN_FW_RXOVERRUN: + return "This warning is returned by function UcanWriteCanMsg() or " + "UcanWriteCanMsgEx() if flag USBCAN_CANERR_QOVERRUN or flag " + "USBCAN_CANERR_OVERRUN are set in the CAN driver status. The " + "function has returned with a valid CAN message. This warning " + "indicates that at least one received CAN message got lost in the " + "firmware layer. This warning does not indicate the position of " + "the lost CAN message."; + + case USBCAN_WARN_NULL_PTR: + return "This warning is returned by functions UcanInitHwConnectControl() " + "or UcanInitHwConnectControlEx() if a NULL pointer was passed as " + "callback function address."; + + case USBCAN_WARN_TXLIMIT: + return "This warning is returned by the function UcanWriteCanMsgEx() if " + "it was called to transmit more than one CAN message, but a part " + "of them could not be stored to the transmit buffer within the " + "DLL because the buffer is full). The returned variable addressed " + "by the parameter pdwCount_p indicates the number of CAN messages " + "which are stored successfully to the transmit buffer."; + + default: + std::stringstream ss; + ss << "Unknown error code: 0x" << std::hex << err_code; + return ss.str(); } } std::string CanVendorSystec::UsbCanGetStatusText(uint16_t err_code) { - switch(err_code) { - case USBCAN_CANERR_OK: return "No error."; - case USBCAN_CANERR_XMTFULL: return "Transmit buffer in CAN controller is overrun."; - case USBCAN_CANERR_OVERRUN: return "Receive buffer in CAN controller is overrun."; - case USBCAN_CANERR_BUSLIGHT: return " Error limit 1 in CAN controller exceeded, CAN controller " - "is in state “Warning limit” now."; - case USBCAN_CANERR_BUSHEAVY: return "Error limit 2 in CAN controller exceeded, CAN controller " - "is in state “Error Passive” now"; - case USBCAN_CANERR_BUSOFF: return "CAN controller is in BUSOFF state."; - case USBCAN_CANERR_QOVERRUN: return "Receive buffer in module is overrun."; - case USBCAN_CANERR_QXMTFULL: return "Transmit buffer in module is overrun."; - case USBCAN_CANERR_REGTEST: return "CAN controller not found (hardware error)."; - case USBCAN_CANERR_TXMSGLOST: return "A transmit CAN message was deleted automatically by the " - "firmware because transmission timeout run over (refer to " - "function UcanSetTxTimeout() )."; + switch (err_code) { + case USBCAN_CANERR_OK: + return "No error."; + case USBCAN_CANERR_XMTFULL: + return "Transmit buffer in CAN controller is overrun."; + case USBCAN_CANERR_OVERRUN: + return "Receive buffer in CAN controller is overrun."; + case USBCAN_CANERR_BUSLIGHT: + return " Error limit 1 in CAN controller exceeded, CAN controller " + "is in state “Warning limit” now."; + case USBCAN_CANERR_BUSHEAVY: + return "Error limit 2 in CAN controller exceeded, CAN controller " + "is in state “Error Passive” now"; + case USBCAN_CANERR_BUSOFF: + return "CAN controller is in BUSOFF state."; + case USBCAN_CANERR_QOVERRUN: + return "Receive buffer in module is overrun."; + case USBCAN_CANERR_QXMTFULL: + return "Transmit buffer in module is overrun."; + case USBCAN_CANERR_REGTEST: + return "CAN controller not found (hardware error)."; + case USBCAN_CANERR_TXMSGLOST: + return "A transmit CAN message was deleted automatically by the " + "firmware because transmission timeout run over (refer to " + "function UcanSetTxTimeout() )."; default: std::stringstream ss; ss << "Unknown error code: 0x" << std::hex << err_code;