Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,24 @@ set(VENDOR_SOURCES
src/main/CanVendorAnagate.cpp
)

if (NOT DEFINED CANMODULE_BUILD_SYSTEC_WINDOWS)
set(CANMODULE_BUILD_SYSTEC_WINDOWS ON)
endif()

if (UNIX)
include(cmake/libsocketcan.cmake)
include_directories(${LIBSOCKETCAN_INCLUDE_DIR})
list(APPEND VENDOR_SOURCES
src/main/CanVendorSocketCan.cpp
src/main/CanVendorSocketCanSystec.cpp
)
elseif ("${CANMODULE_BUILD_SYSTEC_WINDOWS}" STREQUAL ON)
include(cmake/systec.cmake)
include_directories(${SYSTEC_PATH_HEADERS})
list(APPEND VENDOR_SOURCES
src/main/CanVendorSystec.cpp
)
Comment thread
jsouter marked this conversation as resolved.
add_compile_definitions(CANMODULE_BUILD_SYSTEC_WINDOWS)
endif()

if (NOT DEFINED CAN_MODULE_MAIN_ONLY)
Expand Down Expand Up @@ -63,11 +74,18 @@ if (UNIX)
libsocketcan
)
else()
target_include_directories(CanModuleMain PUBLIC ${systec_BINARY_DIR}/Examples/Include)
target_link_libraries(CanModuleMain PUBLIC
${anagate_SOURCE_DIR}/Win64/AnaGateCanDll64.lib
)
${anagate_SOURCE_DIR}/Win64/AnaGateCanDll64.lib)
file(COPY "${anagate_SOURCE_DIR}/Win64/AnaGateCan64.dll"
DESTINATION "${CMAKE_BINARY_DIR}/Release")

if ("${CANMODULE_BUILD_SYSTEC_WINDOWS}" STREQUAL ON)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is systec planned to be supported also in the power supplies and/or several OPC Servers? or is it only a single-case? The answer will tell us if having a variable to activate that part of the build is good or bad idea.

target_link_libraries(CanModuleMain PUBLIC
${systec_BINARY_DIR}/Examples/lib/USBCAN64.lib)
file(COPY "${systec_BINARY_DIR}/Examples/lib/USBCAN64.dll"
DESTINATION "${CMAKE_BINARY_DIR}/Release")
endif()
endif()

if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
Expand Down
40 changes: 40 additions & 0 deletions cmake/systec.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
include(FetchContent)

if(NOT DEFINED SYSTEC_LIBRARY)
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
URL "${SYSTEC_LIBRARY}"
DOWNLOAD_EXTRACT_TIMESTAMP True
)

if(EXISTS "${SYSTEC_LIBRARY}")
message(STATUS "Using local Systec archive: ${SYSTEC_LIBRARY}")
elseif(SYSTEC_LIBRARY MATCHES "^https?://")
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()

FetchContent_Declare(
Systec
${SYSTEC_FETCHCONTENT_ARGS}
)

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()
12 changes: 9 additions & 3 deletions docs/CANMODULE-UTILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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
Expand All @@ -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:
Expand All @@ -47,13 +51,15 @@ 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

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
```
87 changes: 59 additions & 28 deletions python/canmodule-utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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"
Expand All @@ -54,14 +45,27 @@ 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()

Expand Down Expand Up @@ -96,6 +100,13 @@ def main():
"device": args.device,
}
)
elif args.command == "systec":
device.update(
{
"device": args.device,
"bitrate": args.baudrate,
}
)

if args.action == "dump":
dump(device)
Expand Down Expand Up @@ -173,17 +184,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.
Expand All @@ -194,6 +210,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)
Expand All @@ -206,10 +225,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
Expand Down Expand Up @@ -277,10 +299,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.
Expand Down Expand Up @@ -347,10 +372,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
Expand Down Expand Up @@ -385,10 +413,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
Expand Down
3 changes: 2 additions & 1 deletion src/include/CanDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ struct CanDiagnostics {

std::optional<float>
temperature; ///< Optional temperature reading for Anagate devices.
std::optional<uint32_t> uptime; ///< Optional uptime for Anagate devices.
std::optional<uint32_t> uptime; ///< Optional uptime in seconds for Anagate
///< and Systec for Windows.

std::optional<uint32_t> tcp_rx; ///< Optional TCP Received counter for
///< both SocketCAN and Anagate devices.
Expand Down
74 changes: 74 additions & 0 deletions src/include/CanVendorSystec.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#ifndef SRC_INCLUDE_CANVENDORSYSTEC_H_
#define SRC_INCLUDE_CANVENDORSYSTEC_H_

#include <Winsock2.h>
#include <tchar.h>
#include <usbcan32.h>
#include <windows.h>

#include <atomic>
#include <cstdint>
#include <map>
#include <memory>
#include <mutex> //NOLINT
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>

#include "CanDevice.h"
#include "CanDiagnostics.h"
#include "CanVendorLoopback.h"

/**
* @struct CanVendorSystec
* @brief Represents a specific implementation of a CanDevice for Systec devices
* on Windows utilising libraries from USB-CANmodul Utility Disk.
*
* 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);
~CanVendorSystec() { vendor_close(); }
int SystecRxThread();
static std::string_view UsbCanGetErrorText(uint16_t err_code);
static std::string UsbCanGetStatusText(uint16_t err_code);

private:
std::atomic<bool> m_module_in_use{false};
std::atomic<size_t> m_queued_reads;
int m_module_number;
int m_channel_number;
int m_port_number;
DWORD m_baud_rate;
std::thread m_SystecRxThread;

std::optional<tUcanHandle> 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;
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<int, tUcanHandle> m_module_to_handle_map;
static std::unordered_map<int, CanVendorSystec*> m_port_to_vendor_map;

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;
};

#endif // SRC_INCLUDE_CANVENDORSYSTEC_H_
Loading
Loading