Skip to main content

py-canoe

about package

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.

🔗 useful links

prerequisites

installation

standard way

# 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]

using astral uv

# 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-canoe

example use cases

import CANoe module and create CANoe class object

from py_canoe import CANoe, wait

canoe_inst = CANoe()

open CANoe, start measurement, get version info, stop measurement and close canoe configuration

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()

restart/reset running measurement

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()

open CANoe offline config and start/break/step/reset/stop measurement in offline mode

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()

get/set CANoe measurement index

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()

save CANoe config to a different version with different name

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)

get CAN bus statistics of CAN channel 1

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()

get/set bus signal value, check signal state and get signal full name

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()

clear write window / read text from write window / control 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.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()

switch between CANoe desktops

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')

get/set system variable or define system variable

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()

list system variable namespaces and variables

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')

send diagnostic request, control tester present

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()

set replay block source file / control replay block start stop

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()

compile CAPL nodes with success check

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']}")

compile CAPL nodes and call capl function

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()

execute test configuration test units

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()

execute test setup test module / test environment

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()

create test environment and add test modules / folders

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 report

manage test libraries and modules (DLL) of a test module

A TestModule provides two collections to manage its dependencies:

  • modules — the Modules object: add node layer modules (DLL), assemblies (DLL), .NET source files (CS) or CAPL source files (CAN)
  • libraries — the TestLibraries object: 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)

execute test module with selective test case enable/disable

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_cases takes precedence over enable_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()

get test module result (report path + test case verdicts)

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()

get/set environment variable value

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()

add/remove network with channel control

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')

query and map hardware channels

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()

add/remove database

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)

get configured network names

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()

get configured simulation buses and database paths

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()

start/stop online logging block

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)

working with logging blocks

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()

server/headless mode (no GUI interaction)

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_REJECTED when 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 changes
  • timeout: Available on start() and stop() 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 configuration

Custom 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)

Regenerating Generated Robot Library

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_lib

The generated file includes a timestamp and generator metadata in its header.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_canoe-26.4.0.tar.gz (1.5 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_canoe-26.4.0-py3-none-any.whl (1.1 MB view details)

Uploaded Python 3

File details

Details for the file py_canoe-26.4.0.tar.gz.

File metadata

  • Download URL: py_canoe-26.4.0.tar.gz
  • Upload date:
  • Size: 1.5 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for py_canoe-26.4.0.tar.gz
Algorithm Hash digest
SHA256 bd921c0fc64c66577c303cceb1aa66b2f09670bc6b998d9c6f2574981ebb8d1f
MD5 dcd258ea63d5a7dd5e495f7decb04aa6
BLAKE2b-256 a2480f090cb4f70f1a461468921c2d14159eb9b11da0f2350f563884e69fd3d7

See more details on using hashes here.

File details

Details for the file py_canoe-26.4.0-py3-none-any.whl.

File metadata

  • Download URL: py_canoe-26.4.0-py3-none-any.whl
  • Upload date:
  • Size: 1.1 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for py_canoe-26.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 18177718c11372c127189d388eb9a7ff919d688154651fa73f2f4b98b5ffa8ac
MD5 8f5f5d3d40e2d65ab36cd46c6b4e1da3
BLAKE2b-256 3676380715c042abb72d3bded8c6f082c87badfbca0bdf3c9e2b37a724dbe4b1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page