Python π Package for accessing Vector CANoe πΆ Tool via COM Interface
Note: Looking for volunteers to maintain and contribute to this project. If interested, please reach out to me on LinkedIn.
- EN documentation
- π¨π³ δΈζζζ‘£ / Chinese Documentation
- pypi package
- github releases
- create issue/request feature here
- fork repo and create pull request to contribute back to this project. or message me your GitHub username in LinkedIn to add you as collaborator.
- vector canoe documentation
- python(>=3.10)
- vector canoe software(>=v11)
- visual studio code
- Windows PC(recommended windows 11 OS along with 16GB RAM)
# install py-canoe package
pip install py-canoe
# upgrade py-canoe package
pip install py-canoe --upgrade
# install py-canoe package with all optional dependencies
pip install py-canoe[all]# install py-canoe package
uv pip install py-canoe
# upgrade py-canoe package
uv pip install py-canoe --upgrade
# install py-canoe package with all optional dependencies
uv pip install py-canoe[all]
# add py-canoe as dependency to your pyproject.toml
uv add py-canoe
# add py-canoe package with all optional dependencies in your pyproject.toml
uv add py-canoe[all]
# upgrade py-canoe package in your pyproject.toml
uv update py-canoefrom py_canoe import CANoe, wait
canoe_inst = CANoe()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo.cfg')
canoe_inst.start_measurement()
canoe_version_info = canoe_inst.get_canoe_version_info()
canoe_inst.stop_measurement()
canoe_inst.quit()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo.cfg')
canoe_inst.start_measurement()
canoe_inst.reset_measurement()
canoe_inst.stop_ex_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(r'tests\demo_cfg\demo_offline.cfg')
canoe_inst.add_offline_source_log_file(r'tests\demo_cfg\Logs\demo_log.blf')
canoe_inst.start_measurement_in_animation_mode(animation_delay=200)
canoe_inst.break_measurement_in_offline_mode()
canoe_inst.step_measurement_event_in_single_step()
canoe_inst.reset_measurement_in_offline_mode()
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
meas_index_value = canoe_inst.get_measurement_index()
canoe_inst.start_measurement()
canoe_inst.stop_measurement()
meas_index_value = canoe_inst.get_measurement_index()
canoe_inst.set_measurement_index(meas_index_value + 1)
meas_index_new = canoe_inst.get_measurement_index()
canoe_inst.reset_measurement()
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.save_configuration_as(path=r'tests\demo_cfg\demo_v10.cfg', major=10, minor=0, create_dir=True)from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
canoe_inst.get_can_bus_statistics(channel=1)
canoe_inst.stop_measurement()from py_canoe import CANoe, BusType, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
sig_full_name = canoe_inst.get_signal_full_name(bus=BusType.CAN, channel=1, message='LightState', signal='FlashLight')
sig_value = canoe_inst.get_signal_value(bus=BusType.CAN, channel=1, message='LightState', signal='FlashLight', raw_value=False)
canoe_inst.set_signal_value(bus=BusType.CAN, channel=1, message='LightState', signal='FlashLight', value=1, raw_value=False)
sig_online_state = canoe_inst.check_signal_online(bus=BusType.CAN, channel=1, message='LightState', signal='FlashLight')
sig_state = canoe_inst.check_signal_state(bus=BusType.CAN, channel=1, message='LightState', signal='FlashLight')
sig_val = canoe_inst.get_signal_value(bus=BusType.CAN, channel=1, message='LightState', signal='FlashLight', raw_value=True)
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.enable_write_window_output_file(r'tests\demo_cfg\Logs\write_win.txt')
canoe_inst.start_measurement()
canoe_inst.clear_write_window_content()
canoe_inst.write_text_in_write_window("hello from py_canoe!")
text = canoe_inst.read_text_from_write_window()
canoe_inst.stop_measurement()
canoe_inst.disable_write_window_output_file()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.ui_activate_desktop('Configuration')from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
canoe_inst.set_system_variable_value('demo::level_two_1::sys_var2', 20)
canoe_inst.set_system_variable_value('demo::string_var', 'hey hello this is string variable')
canoe_inst.set_system_variable_value('demo::data_var', 'hey hello this is data variable')
canoe_inst.set_system_variable_array_values('demo::int_array_var', (00, 11, 22, 33, 44, 55, 66, 77, 88, 99))
sys_var_val = canoe_inst.get_system_variable_value('demo::level_two_1::sys_var2')
sys_var_val = canoe_inst.get_system_variable_value('demo::data_var')
canoe_inst.stop_measurement()
canoe_inst.define_system_variable('sys_demo::demo', 1)
canoe_inst.save_configuration()
canoe_inst.start_measurement()
sys_var_val = canoe_inst.get_system_variable_value('sys_demo::demo')
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
namespace_names = canoe_inst.application.system.get_all_namespace_names()
variables = canoe_inst.application.system.get_all_variables_in_namespace('demo')from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(r'tests\demo_cfg\demo_diag.cfg')
canoe_inst.start_measurement()
resp = canoe_inst.send_diag_request('Door', 'DefaultSession_Start', False)
canoe_inst.control_tester_present('Door', False)
wait(2)
canoe_inst.control_tester_present('Door', True)
wait(5)
resp = canoe_inst.send_diag_request('Door', '10 02')
canoe_inst.control_tester_present('Door', False)
wait(2)
resp = canoe_inst.send_diag_request('Door', '10 03', return_sender_name=True)
wait(2)
resp = canoe_inst.send_diag_request('Door', 'Variant_Coding_Write', False, CountryType="Europe")
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
canoe_inst.set_replay_block_file(block_name='DemoReplayBlock', recording_file_path=r'tests\demo_cfg\Logs\demo_log.blf')
canoe_inst.control_replay_block(block_name='DemoReplayBlock', start_stop=True)
wait(2)
canoe_inst.control_replay_block(block_name='DemoReplayBlock', start_stop=False)
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
# Simple bool check
if canoe_inst.application.configuration.run_compilation():
print("Compilation OK")
# Get detailed error information
result = canoe_inst.application.configuration.get_compilation_result()
if not result["success"]:
print(f"Compilation failed: {result['error']}")from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.compile_all_capl_nodes()
canoe_inst.start_measurement()
canoe_inst.call_capl_function('addition_function', 100, 200)
canoe_inst.call_capl_function('hello_world')
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'CAN\Diagnostics\UDSSystem\UDSSystem.cfg')
canoe_inst.start_measurement()
canoe_inst.execute_all_test_configurations(wait_for_completion=True)
canoe_inst.execute_test_configuration('DiagTestConfiguration', wait_for_completion=False)
wait(5)
canoe_inst.stop_test_configuration()
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
canoe_inst.execute_all_test_modules_in_test_env(demo_test_environment)
canoe_inst.execute_test_module('demo_test_node_002')
canoe_inst.stop_measurement()add_testEnvironments creates (or loads from a file) a test environment and returns a TestEnvironment object that supports adding test modules and folders directly, without fetching the items collection first.
from py_canoe import CANoe
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_test_setup.cfg')
# Create a new test environment (by name)
test_env = canoe_inst.add_testEnvironments('NewTestEnv')
# Load an existing test environment from a file (by path)
# test_env = canoe_inst.add_testEnvironments(r'path\to\test_environment.stenv')
# Add a test module: pass the file path of a CAPL program (.can)
# or an XML test description (.tse/.stse/.vxt)
test_module = test_env.add_test_module(r'path\to\TestCapl.can')
# Optional `name` argument: rename immediately after adding
test_module = test_env.add_test_module(r'path\to\TestCapl.can', name='My Test Module')
print(test_module.name) # My Test Module
# Add a folder
test_folder = test_env.add_folder('TestFolder')
# Rename after adding (the COM Name property is writable in practice)
test_module.name = 'Renamed Module'
test_folder.name = 'Renamed Folder'
# Recursively fetch all test modules in the environment (including those in folders)
all_modules = test_env.get_all_test_modules()
# Access report settings
report = test_env.report
print(report.last_written_full_name) # path of the last generated test reportA TestModule provides two collections to manage its dependencies:
modulesβ theModulesobject: add node layer modules (DLL), assemblies (DLL), .NET source files (CS) or CAPL source files (CAN)librariesβ theTestLibrariesobject: test case files used in XML or .NET test modules
from py_canoe import CANoe
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_test_setup.cfg')
test_env = canoe_inst.add_testEnvironments('NewTestEnv')
test_module = test_env.add_test_module(r'path\to\TestCapl.can')
# --- Modules (add DLL / CS / CAN files) ---
test_module.modules.add(r'path\to\node_layer.dll') # node layer module (DLL)
test_module.modules.add(r'path\to\assembly.dll') # assembly (DLL, .NET test nodes)
test_module.modules.add(r'path\to\source.cs') # .NET source file
test_module.modules.add(r'path\to\source.can') # CAPL source file
print(test_module.modules.count) # number of modules
first_module = test_module.modules.item(1)
print(first_module.name, first_module.full_name)
# remove a module by index / filename / full path
test_module.modules.remove(1)
# --- TestLibraries (test case files for XML / .NET test modules) ---
test_module.libraries.add(r'path\to\test_cases.tsf')
print(test_module.libraries.count) # number of test case files
lib = test_module.libraries.item(1)
print(lib.name, lib.full_name)
test_module.libraries.remove(1)CANoe has two independent concepts that are easy to confuse:
- CANoe application process (
CANoe64.exe/CANoe32.exe): the GUI process. It can be 64-bit while the RT kernel is 32-bit, and vice versa. - RT Kernel (real-time kernel) architecture: the execution environment that runs CAPL programs and loads external DLLs (node layer modules / test modules). Its bitness decides which DLLs can be loaded:
| RT Kernel | Loadable DLL |
|---|---|
| 32-bit | 32-bit DLL only |
| 64-bit | 64-bit DLL only |
A 64-bit program cannot load a 32-bit DLL, and vice versa. So if modules.add() fails while the DLL looks correct, check the RT kernel architecture of the currently open configuration:
app = CANoe()
app.open(r'path\to\your.cfg')
# 0 = cWin32 (32-bit), 1 = cWin64 (64-bit), 2 = cLinux64 ...
arch = app.get_rt_kernel_architecture()
print('RT Kernel architecture:', arch)
# force 64-bit RT kernel for the current configuration
app.set_rt_kernel_architecture(1)
app.save_configuration() # persist the change- By default the 32-bit RT kernel is used; existing configurations may have been created with either variant.
- The variant is stored in the configuration file, and can also be overridden via
CAN.iniβ[SYSTEM]βEnforceRtkTargetArchitecture(0= use config,1= force 32-bit,2= force 64-bit). - Practical rule: match the DLL bitness to the RT kernel bitness of the configuration you open. If you always use 64-bit DLLs, ensure the configuration's RT kernel is 64-bit.
The execute_test_module method supports selectively enabling or disabling test cases before execution using wildcard or regex patterns.
Pattern matching rules:
- Wildcard (default): uses fnmatch-style patterns (
*matches everything,?matches a single character,[seq]matches any character in seq) - Regex: patterns starting with
(?or containing regex metacharacters (^,$,[,],+,{,}) are treated as regular expressions - Priority:
disable_test_casestakes precedence overenable_test_cases
from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
# enable only smoke test cases (wildcard)
canoe_inst.execute_test_module('demo_test_node_002', enable_test_cases=["SmokeTest_*"])
# disable slow/stress tests, enable everything else (wildcard)
canoe_inst.execute_test_module('demo_test_node_002', enable_test_cases=["*"], disable_test_cases=["*slow*", "*stress*"])
# use regex to enable specific test cases by number
canoe_inst.execute_test_module('demo_test_node_002', enable_test_cases=["(?i)^tc_(001|002|003)$"])
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
canoe_inst.execute_test_module('demo_test_node_002')
# get result: report path + all test case verdicts
result = canoe_inst.get_test_module_result('demo_test_node_002')
print(f"Verdict: {result['verdict_name']}")
print(f"Pass rate: {result['pass_rate']:.1f}% ({result['passed']}/{result['total']})")
print(f"Report: {result['report']['generated_full_name']}")
for tc in result['test_cases']:
print(f" {tc['name']}: {tc['verdict_name']} (enabled={tc['enabled']})")
canoe_inst.stop_measurement()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
canoe_inst.start_measurement()
canoe_inst.set_environment_variable_value('int_var', 123.12)
canoe_inst.set_environment_variable_value('float_var', 111.123)
canoe_inst.set_environment_variable_value('string_var', 'this is string variable')
canoe_inst.set_environment_variable_value('data_var', (1, 2, 3, 4, 5, 6, 7))
var_value = canoe_inst.get_environment_variable_value('int_var')
var_value = canoe_inst.get_environment_variable_value('float_var')
var_value = canoe_inst.get_environment_variable_value('string_var')
var_value = canoe_inst.get_environment_variable_value('data_var')
canoe_inst.stop_measurement()from py_canoe import CANoe, BusType
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
# Add a CAN network with software channel 2
canoe_inst.add_netWork('MyCAN', BusType.CAN, sw_channel=2)
# Get or set channel count per bus type
usage = canoe_inst.get_channelUsage(BusType.CAN)
canoe_inst.set_channelUsage(BusType.CAN, 3)
# Remove network by name (the last network cannot be removed)
canoe_inst.remove_netWork('MyCAN')from py_canoe import CANoe, BusType
# Hardware channel enumeration (before opening CANoe)
can_channels = CANoe().get_hardware_channels(BusType.CAN)
for ch in can_channels:
print(ch.label) # "Ch00 idx=0 [VN1630 CAN/Piggy]"
# Map CANoe app-channel 0 to this physical channel
ch.apply_to("CANoe", 0, BusType.CAN)
# Open CANoe and add network with hardware assignment
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
# One-liner: add network + software channel + hardware channel
canoe_inst.add_netWork_with_hardware(
'MyCAN2', BusType.CAN, sw_channel=1, hw_channel=can_channels[0])
# Query current hardware-channel mapping
hw = canoe_inst.get_hardware_config(0, BusType.CAN)
print(hw.label if hw else "not mapped")
# Clear all CANoe hardware mappings
canoe_inst.clear_hardware_channels()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r"tests\demo_cfg\demo_conf_gen_db_setup.cfg")
canoe_inst.start_measurement()
# add database
canoe_inst.add_database(fr"{file_path}\demo_cfg\DBs\sample_databases\XCP.dbc", 'CAN1', 1)
# remove database
canoe_inst.remove_database(fr"{file_path}\demo_cfg\DBs\sample_databases\XCP.dbc", 1)from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
network_names = canoe_inst.application.networks.get_all_network_names()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r'tests\demo_cfg\demo_dev.cfg')
bus_names = canoe_inst.get_simulation_bus_names()
db_paths = canoe_inst.get_simulation_database_paths()from py_canoe import CANoe, wait
canoe_inst = CANoe()
canoe_inst.open(canoe_cfg=r"tests\demo_cfg\demo_online_setup.cfg")
canoe_inst.start_measurement()
# stop logging block
canoe_inst.start_stop_online_logging_block(fr'{demo_cfg_dir}\Logs\demo_online_setup_log.blf', start_stop=False)
wait(2)
# start logging block
canoe_inst.start_stop_online_logging_block(fr'{demo_cfg_dir}\Logs\demo_online_setup_log.blf', start_stop=True)from py_canoe import CANoe, wait
canoe_inst = CANoe()
# remove current logging blocks
for i in range(canoe_inst.logging_collection.count):
canoe_inst.remove_logging_block(1) # iteration start from 1 and shifts after each delete
# add a new block
# define dest path with file format as asc, blf or other
# may include field functions like {IncMeasurement}
full_path = "C:/sample_log_{IncMeasurement}.blf"
canoe_inst.add_logging_block(full_path)
canoe_inst.start_measurement()
# ...
canoe_inst.stop_measurement()
# log should be fully generated at this point for you to analyze
canoe_inst.set_configuration_modified(False) # to avoid popup asking to save changes
canoe_inst.quit()By default, py-canoe uses COM event sinks (WithEvents) to receive notifications from CANoe
(e.g., measurement started, measurement stopped). This works well for interactive desktop applications,
but can cause issues in server environments:
The Problem:
- CANoe may reject COM calls with
RPC_E_CALL_REJECTEDwhen it's busy (e.g., during report generation) - Long-running server processes need robust error handling for these transient states
- In some scenarios, Windows may show a "program is busy" dialog (reduced in recent versions)
The Solution:
Use the low-level Application class with enable_events=False to disable COM event sinks.
py-canoe will use polling instead, which is more reliable for server/headless operation:
from py_canoe.core.application import Application
# Create instance without COM event sinks (uses polling instead)
app = Application(enable_events=False)
app.open(r'tests\demo_cfg\demo.cfg', visible=True, auto_save=True, prompt_user=False)
app.measurement.start()
# ... run your test ...
app.measurement.stop()
app.quit()Parameters explained:
enable_events=False: Disables COM event sinks, uses polling to detect state changestimeout: Available onstart()andstop()methods (default: 30s) - maximum time to wait
Benefits:
- Reduced "program is busy" dialogs (internal COM proxy sharing)
- Automatic retry when CANoe is temporarily busy (e.g., during report generation)
- Safe for long-running server processes (REST APIs, MCP servers, scheduled tasks)
- Configurable timeouts for all operations
Switching configurations without restarting CANoe:
Server applications often need to run tests with different CANoe configurations.
Use open_config() to switch configurations while keeping CANoe running:
# CANoe is already running with a configuration
canoe_inst.open_config(r'tests\demo_cfg\another_config.cfg', timeout=60)
# Now running with the new configurationCustom COM message pumping:
For advanced use cases where you need to pump COM messages in your own wait loops:
import time
# Custom wait loop with COM message pumping
while not my_condition():
canoe_inst.pump_messages() # Process pending COM messages
time.sleep(0.1)The project includes a small generator that creates the Robot Framework Python
library wrapper at src/py_canoe/canoe_robot_lib.py. Do not edit that file
manually β it is auto-generated. To regenerate it run:
python -m py_canoe.helpers.gen_canoe_robot_libThe generated file includes a timestamp and generator metadata in its header.