diff --git a/README.md b/README.md index ea1e82e..0fea48a 100755 --- a/README.md +++ b/README.md @@ -71,6 +71,28 @@ In order to install this application, Please see the file winbuild.bat in this directory, or use the MSI file build by github actions on any commit +### USB2CAN (8devices Korlan) Support on Windows + +The DroneCAN GUI Tool now includes support for 8devices USB2CAN (Korlan) adapters on Windows. + +**Setup:** +1. Connect your 8devices USB2CAN adapter +2. Run the setup script to install the required DLL: + ``` + python setup_usb2can.py + ``` +3. The USB2CAN adapter will now appear in the interface selection dropdown as "8devices USB2CAN (channel_id)" + +**Features:** +- Automatic detection of USB2CAN adapters +- Native integration with DroneCAN protocol +- Full support for all GUI tool features + +**Requirements:** +- 8devices USB2CAN adapter (Korlan) +- Windows 10/11 (x64 or x86) +- DLL files included in `bin/usb2can_canal_v2.0.0/` + ## Installing on macOS OSX support is a bit lacking in the way that installation doesn't create an entry in the applications menu, diff --git a/bin/usb2can_canal_v2.0.0/x64/Release/usb2can.dll b/bin/usb2can_canal_v2.0.0/x64/Release/usb2can.dll new file mode 100644 index 0000000..6d756b3 Binary files /dev/null and b/bin/usb2can_canal_v2.0.0/x64/Release/usb2can.dll differ diff --git a/bin/usb2can_canal_v2.0.0/x64/Release/usb2can.lib b/bin/usb2can_canal_v2.0.0/x64/Release/usb2can.lib new file mode 100644 index 0000000..ffeb392 Binary files /dev/null and b/bin/usb2can_canal_v2.0.0/x64/Release/usb2can.lib differ diff --git a/bin/usb2can_canal_v2.0.0/x86/Release/usb2can.dll b/bin/usb2can_canal_v2.0.0/x86/Release/usb2can.dll new file mode 100644 index 0000000..d58f036 Binary files /dev/null and b/bin/usb2can_canal_v2.0.0/x86/Release/usb2can.dll differ diff --git a/bin/usb2can_canal_v2.0.0/x86/Release/usb2can.lib b/bin/usb2can_canal_v2.0.0/x86/Release/usb2can.lib new file mode 100644 index 0000000..7d8c1da Binary files /dev/null and b/bin/usb2can_canal_v2.0.0/x86/Release/usb2can.lib differ diff --git a/dronecan_gui_tool/main.py b/dronecan_gui_tool/main.py index 611ffc2..f4d8b31 100644 --- a/dronecan_gui_tool/main.py +++ b/dronecan_gui_tool/main.py @@ -629,7 +629,36 @@ def main(): node_info.software_version.major = __version__[0] node_info.software_version.minor = __version__[1] - node = dronecan.make_node(iface, + # Handle USB2CAN interface specification (format: "usb2can:channel") + actual_iface = iface + if iface and ':' in iface: + bustype, channel = iface.split(':', 1) + if bustype == 'usb2can': + # For USB2CAN interfaces, specify the bustype and DLL path + import platform + import os + + actual_iface = channel + iface_kwargs['bustype'] = 'usb2can' + + # Determine the correct DLL path based on architecture + current_dir = os.path.dirname(os.path.abspath(__file__)) + gui_tool_dir = os.path.dirname(current_dir) + + if platform.machine().lower() in ['amd64', 'x86_64', 'x64']: + dll_path = os.path.join(gui_tool_dir, 'bin', 'usb2can_canal_v2.0.0', 'x64', 'Release', 'usb2can.dll') + else: + dll_path = os.path.join(gui_tool_dir, 'bin', 'usb2can_canal_v2.0.0', 'x86', 'Release', 'usb2can.dll') + + iface_kwargs['dll'] = dll_path + logger.info('Using USB2CAN interface: channel=%s, dll=%s', channel, dll_path) + elif bustype == 'pcan': + # PCAN interfaces should also specify bustype + actual_iface = channel + iface_kwargs['bustype'] = 'pcan' + logger.info('Using PCAN interface: channel=%s', channel) + + node = dronecan.make_node(actual_iface, node_info=node_info, mode=dronecan.uavcan.protocol.NodeStatus().MODE_OPERATIONAL, **iface_kwargs) diff --git a/dronecan_gui_tool/setup_window.py b/dronecan_gui_tool/setup_window.py index 227eedf..f9708fb 100644 --- a/dronecan_gui_tool/setup_window.py +++ b/dronecan_gui_tool/setup_window.py @@ -96,18 +96,34 @@ def list_ifaces(): # Windows, Mac, whatever from PyQt5 import QtSerialPort - out = OrderedDict() + # Collect ports with priority handling for USB2CAN adapters + priority_ports = [] # USB2CAN adapters go first + regular_ports = [] + for port in QtSerialPort.QSerialPortInfo.availablePorts(): if sys.platform == 'darwin': if 'tty' in port.systemLocation(): if port.systemLocation() not in MACOS_SERIAL_PORTS_FILTER: - out[port.systemLocation()] = port.systemLocation() + regular_ports.append((port.systemLocation(), port.systemLocation())) else: sys_name = port.systemLocation() sys_alpha = re.sub(r'[^a-zA-Z0-9]', '', sys_name) description = port.description() - # show the COM port in parentheses to make it clearer which port it is - out["%s (%s)" % (description, sys_alpha)] = sys_name + + # Special handling for 8devices Korlan USB2CAN adapter + # The Korlan appears in Windows as "USB2CAN converter" + if "USB2CAN converter" in description: + display_name = "8devices Korlan USB2CAN (%s)" % sys_alpha + priority_ports.append((display_name, sys_name)) + else: + # show the COM port in parentheses to make it clearer which port it is + display_name = "%s (%s)" % (description, sys_alpha) + regular_ports.append((display_name, sys_name)) + + # Build output with priority ports first + out = OrderedDict() + for display_name, sys_name in priority_ports + regular_ports: + out[display_name] = sys_name mifaces = _mavcan_interfaces() mifaces += ["mcast:0", "mcast:1"] @@ -125,6 +141,13 @@ def list_ifaces(): for interface in detect_available_configs(): if interface['interface'] == "pcan": out[interface['channel']] = interface['channel'] + elif interface['interface'] == "usb2can": + # Add USB2CAN (8devices Korlan) interfaces with descriptive name + # Store as "usb2can:channel" to specify the bustype + display_name = "8devices USB2CAN (%s)" % interface['channel'] + interface_spec = "usb2can:%s" % interface['channel'] + out[display_name] = interface_spec + logger.info('Added USB2CAN interface: %s -> %s', display_name, interface_spec) except Exception as ex: logger.warning('Could not load can interfaces: %s', ex, exc_info=True) diff --git a/setup.py b/setup.py index dea9620..f8bb95a 100755 --- a/setup.py +++ b/setup.py @@ -111,7 +111,7 @@ # Windows-specific options and hacks # if os.name == 'nt': - args.setdefault('install_requires', []).append('cx_Freeze ~= 6.1') + args.setdefault('install_requires', []).append('cx_Freeze >= 6.1') if ('bdist_msi' in sys.argv) or ('build_exe' in sys.argv): import cx_Freeze @@ -122,6 +122,7 @@ 'qtpy', 'qtconsole', 'easywebdav', + 'python-can', # Add python-can to unpacked eggs (package name is python-can, import name is can) ] unpacked_eggs_dir = os.path.join('build', 'hatched_eggs') sys.path.insert(0, unpacked_eggs_dir) @@ -144,6 +145,7 @@ import jupyter_client import traitlets import numpy + import can # Add python-can import # Oh, Windows, never change. missing_dlls = glob.glob(os.path.join(os.path.dirname(numpy.core.__file__), '*.dll')) @@ -157,6 +159,41 @@ 'zmq', 'pygments', 'jupyter_client', + # Add python-can and related packages + 'can', + 'can.interfaces', + 'can.interfaces.usb2can', + # Add only the most critical missing modules + 'traceback', + 'token', + 'threading', + 'queue', + 'logging', + 'json', + 'urllib', + 'socket', + 'ssl', + 'datetime', + 'multiprocessing', + 'subprocess', + 'tempfile', + 'shutil', + 'glob', + 'pathlib', + 'io', + 'platform', + 'importlib', + 'encodings', + 'enum', + 'contextlib', + 'configparser', + 'winreg', + 'msvcrt', + 'email', + 'csv', + 'xml', + 'zlib', + 'zipfile', ], 'include_msvcr': True, 'include_files': [ @@ -172,6 +209,7 @@ os.path.join(unpacked_eggs_dir, os.path.dirname(jupyter_client.__file__)), os.path.join(unpacked_eggs_dir, os.path.dirname(traitlets.__file__)), os.path.join(unpacked_eggs_dir, os.path.dirname(numpy.__file__)), + os.path.join(unpacked_eggs_dir, os.path.dirname(can.__file__)), # Include python-can package files ] + missing_dlls, }, 'bdist_msi': { diff --git a/setup_usb2can.py b/setup_usb2can.py new file mode 100644 index 0000000..a44b26e --- /dev/null +++ b/setup_usb2can.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +""" +USB2CAN setup script for DroneCAN GUI Tool + +This script sets up the USB2CAN DLL for use with the DroneCAN GUI Tool. +It copies the appropriate DLL to a location where it can be found by python-can. +""" + +import os +import shutil +import platform +import sys + +def setup_usb2can_dll(): + """Copy the USB2CAN DLL to an accessible location""" + print("Setting up USB2CAN DLL for DroneCAN GUI Tool...") + + # Get the current directory (should be the gui_tool root) + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # Determine the correct DLL based on architecture + if platform.machine().lower() in ['amd64', 'x86_64', 'x64']: + dll_source = os.path.join(current_dir, 'bin', 'usb2can_canal_v2.0.0', 'x64', 'Release', 'usb2can.dll') + arch = 'x64' + else: + dll_source = os.path.join(current_dir, 'bin', 'usb2can_canal_v2.0.0', 'x86', 'Release', 'usb2can.dll') + arch = 'x86' + + if not os.path.exists(dll_source): + print(f"Error: USB2CAN DLL not found at {dll_source}") + return False + + # Copy to the current directory (where the script is run from) + dll_dest = os.path.join(current_dir, 'usb2can.dll') + + try: + shutil.copy2(dll_source, dll_dest) + print(f"Successfully copied {arch} USB2CAN DLL to {dll_dest}") + return True + except Exception as e: + print(f"Error copying DLL: {e}") + return False + +if __name__ == "__main__": + success = setup_usb2can_dll() + if success: + print("\n✅ USB2CAN setup completed successfully!") + print("You can now use 8devices USB2CAN adapters with the DroneCAN GUI Tool.") + else: + print("\n❌ USB2CAN setup failed!") + sys.exit(1) \ No newline at end of file