py_canoe 3.0.3__py3-none-any.whl → 26.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_canoe/__init__.py +2 -1
- py_canoe/canoe.py +910 -0
- py_canoe/core/__init__.py +0 -0
- py_canoe/core/application.py +170 -0
- py_canoe/core/bus.py +301 -0
- py_canoe/core/capl.py +59 -0
- py_canoe/core/child_elements/__init__.py +0 -0
- py_canoe/core/child_elements/application_model.py +24 -0
- py_canoe/core/child_elements/application_model_file.py +21 -0
- py_canoe/core/child_elements/application_model_files.py +22 -0
- py_canoe/core/child_elements/application_model_setup.py +15 -0
- py_canoe/core/child_elements/application_models.py +22 -0
- py_canoe/core/child_elements/application_socket.py +11 -0
- py_canoe/core/child_elements/application_specific_module.py +24 -0
- py_canoe/core/child_elements/application_specific_modules.py +16 -0
- py_canoe/core/child_elements/audio_interface.py +28 -0
- py_canoe/core/child_elements/available_modules.py +22 -0
- py_canoe/core/child_elements/basic_module.py +19 -0
- py_canoe/core/child_elements/basic_modules.py +16 -0
- py_canoe/core/child_elements/c_libraries.py +28 -0
- py_canoe/core/child_elements/c_library.py +33 -0
- py_canoe/core/child_elements/can_controller.py +74 -0
- py_canoe/core/child_elements/capl_function.py +17 -0
- py_canoe/core/child_elements/ccp_setup.py +15 -0
- py_canoe/core/child_elements/channel.py +20 -0
- py_canoe/core/child_elements/channels.py +19 -0
- py_canoe/core/child_elements/communication_setup.py +23 -0
- py_canoe/core/child_elements/compile_result.py +22 -0
- py_canoe/core/child_elements/configured_channel.py +48 -0
- py_canoe/core/child_elements/configured_channels.py +21 -0
- py_canoe/core/child_elements/configured_module.py +82 -0
- py_canoe/core/child_elements/configured_modules.py +61 -0
- py_canoe/core/child_elements/connected_modules.py +14 -0
- py_canoe/core/child_elements/data_source.py +42 -0
- py_canoe/core/child_elements/data_source_file.py +21 -0
- py_canoe/core/child_elements/data_source_files.py +22 -0
- py_canoe/core/child_elements/data_source_issue.py +22 -0
- py_canoe/core/child_elements/data_source_issues.py +16 -0
- py_canoe/core/child_elements/data_source_setup.py +17 -0
- py_canoe/core/child_elements/data_sources.py +27 -0
- py_canoe/core/child_elements/database_setup.py +62 -0
- py_canoe/core/child_elements/device.py +34 -0
- py_canoe/core/child_elements/devices.py +13 -0
- py_canoe/core/child_elements/diagnostic.py +22 -0
- py_canoe/core/child_elements/diagnostic_request.py +59 -0
- py_canoe/core/child_elements/diagnostic_response.py +34 -0
- py_canoe/core/child_elements/diagnostic_responses.py +13 -0
- py_canoe/core/child_elements/diagnostics_setup.py +254 -0
- py_canoe/core/child_elements/distributed_mode.py +74 -0
- py_canoe/core/child_elements/encoding.py +27 -0
- py_canoe/core/child_elements/encodings.py +13 -0
- py_canoe/core/child_elements/environment_array.py +13 -0
- py_canoe/core/child_elements/environment_group.py +22 -0
- py_canoe/core/child_elements/environment_info.py +14 -0
- py_canoe/core/child_elements/environment_variable.py +55 -0
- py_canoe/core/child_elements/fdx_files.py +50 -0
- py_canoe/core/child_elements/file_group_data_source.py +17 -0
- py_canoe/core/child_elements/general_setup.py +66 -0
- py_canoe/core/child_elements/macros_setup.py +52 -0
- py_canoe/core/child_elements/mc_ecus.py +428 -0
- py_canoe/core/child_elements/measurement_setup.py +269 -0
- py_canoe/core/child_elements/modules.py +87 -0
- py_canoe/core/child_elements/most_disassembler.py +21 -0
- py_canoe/core/child_elements/most_network_interface.py +4 -0
- py_canoe/core/child_elements/namespace.py +21 -0
- py_canoe/core/child_elements/namespaces.py +19 -0
- py_canoe/core/child_elements/network.py +18 -0
- py_canoe/core/child_elements/network_adapters.py +13 -0
- py_canoe/core/child_elements/nodes.py +119 -0
- py_canoe/core/child_elements/open_configuration_result.py +0 -0
- py_canoe/core/child_elements/panel_setup.py +97 -0
- py_canoe/core/child_elements/participant.py +17 -0
- py_canoe/core/child_elements/participants.py +22 -0
- py_canoe/core/child_elements/ports.py +81 -0
- py_canoe/core/child_elements/replay_collection.py +56 -0
- py_canoe/core/child_elements/security_configuration.py +20 -0
- py_canoe/core/child_elements/security_setup.py +31 -0
- py_canoe/core/child_elements/signals.py +39 -0
- py_canoe/core/child_elements/simulation_setup.py +0 -0
- py_canoe/core/child_elements/single_file_data_source.py +13 -0
- py_canoe/core/child_elements/snippet_setup.py +68 -0
- py_canoe/core/child_elements/standalone_mode.py +0 -0
- py_canoe/core/child_elements/start_value_list.py +0 -0
- py_canoe/core/child_elements/symbol_mappings.py +0 -0
- py_canoe/core/child_elements/tcp_ip_stack_setting.py +0 -0
- py_canoe/core/child_elements/test_configurations.py +0 -0
- py_canoe/core/child_elements/test_environment.py +64 -0
- py_canoe/core/child_elements/test_environments.py +26 -0
- py_canoe/core/child_elements/test_module.py +213 -0
- py_canoe/core/child_elements/test_modules.py +23 -0
- py_canoe/core/child_elements/test_setup.py +16 -0
- py_canoe/core/child_elements/test_setup_folder_ext.py +36 -0
- py_canoe/core/child_elements/test_setup_folders.py +25 -0
- py_canoe/core/child_elements/user_files.py +0 -0
- py_canoe/core/child_elements/variable.py +144 -0
- py_canoe/core/child_elements/variable_events.py +14 -0
- py_canoe/core/child_elements/variables.py +29 -0
- py_canoe/core/child_elements/variables_file.py +15 -0
- py_canoe/core/child_elements/variables_files.py +19 -0
- py_canoe/core/child_elements/visual_sequence_setup.py +46 -0
- py_canoe/core/child_elements/vt_system.py +83 -0
- py_canoe/core/child_elements/vtt_sut_import_result.py +21 -0
- py_canoe/core/child_elements/write.py +71 -0
- py_canoe/core/child_elements/xcp_setup.py +12 -0
- py_canoe/core/configuration.py +509 -0
- py_canoe/core/environment.py +59 -0
- py_canoe/core/measurement.py +149 -0
- py_canoe/core/networks.py +103 -0
- py_canoe/core/performance.py +21 -0
- py_canoe/core/simulation.py +53 -0
- py_canoe/core/system.py +164 -0
- py_canoe/core/ui.py +53 -0
- py_canoe/core/version.py +54 -0
- py_canoe/helpers/__init__.py +0 -0
- py_canoe/helpers/common.py +78 -0
- {py_canoe-3.0.3.dist-info → py_canoe-26.0.0.dist-info}/METADATA +331 -327
- py_canoe-26.0.0.dist-info/RECORD +118 -0
- py_canoe-26.0.0.dist-info/WHEEL +4 -0
- py_canoe/logging_collection.py +0 -345
- py_canoe/py_canoe.py +0 -2586
- py_canoe/py_canoe_logger.py +0 -29
- py_canoe-3.0.3.dist-info/LICENSE +0 -21
- py_canoe-3.0.3.dist-info/RECORD +0 -8
- py_canoe-3.0.3.dist-info/WHEEL +0 -4
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import win32com.client
|
|
3
|
+
|
|
4
|
+
from py_canoe.core.capl import CaplFunction
|
|
5
|
+
from py_canoe.helpers.common import DoEventsUntil
|
|
6
|
+
from py_canoe.helpers.common import logger, wait
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MeasurementEvents:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.APP_COM_OBJ = object
|
|
12
|
+
self.INIT: bool = False
|
|
13
|
+
self.START: bool = False
|
|
14
|
+
self.STOP: bool = False
|
|
15
|
+
self.EXIT: bool = False
|
|
16
|
+
self.CAPL_FUNCTION_OBJECTS = dict()
|
|
17
|
+
self.CAPL_FUNCTION_NAMES = tuple()
|
|
18
|
+
|
|
19
|
+
def OnInit(self):
|
|
20
|
+
"""measurement is initialized"""
|
|
21
|
+
for fun in self.CAPL_FUNCTION_NAMES:
|
|
22
|
+
self.CAPL_FUNCTION_OBJECTS[fun] = CaplFunction(self.APP_COM_OBJ.CAPL.GetFunction(fun))
|
|
23
|
+
self.INIT = True
|
|
24
|
+
|
|
25
|
+
def OnStart(self):
|
|
26
|
+
"""measurement is started"""
|
|
27
|
+
self.START = True
|
|
28
|
+
|
|
29
|
+
def OnStop(self):
|
|
30
|
+
"""measurement is stopped"""
|
|
31
|
+
self.STOP = True
|
|
32
|
+
|
|
33
|
+
def OnExit(self):
|
|
34
|
+
"""measurement is exited"""
|
|
35
|
+
self.CAPL_FUNCTION_OBJECTS.clear()
|
|
36
|
+
self.EXIT = True
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Measurement:
|
|
40
|
+
def __init__(self, app):
|
|
41
|
+
self.com_object = win32com.client.Dispatch(app.com_object.Measurement)
|
|
42
|
+
self.measurement_events: MeasurementEvents = win32com.client.WithEvents(self.com_object, MeasurementEvents)
|
|
43
|
+
self.measurement_events.APP_COM_OBJ = app.com_object
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def animation_delay(self) -> int:
|
|
47
|
+
return self.com_object.AnimationDelay
|
|
48
|
+
|
|
49
|
+
@animation_delay.setter
|
|
50
|
+
def animation_delay(self, delay: int):
|
|
51
|
+
self.com_object.AnimationDelay = delay
|
|
52
|
+
logger.info(f"📢 Animation Delay ⏲️ set to: {delay} ms")
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def measurement_index(self) -> int:
|
|
56
|
+
index = self.com_object.MeasurementIndex
|
|
57
|
+
logger.info(f"📢 Measurement Index value: {index}")
|
|
58
|
+
return index
|
|
59
|
+
|
|
60
|
+
@measurement_index.setter
|
|
61
|
+
def measurement_index(self, index: int):
|
|
62
|
+
self.com_object.MeasurementIndex = index
|
|
63
|
+
logger.info(f"📢 Measurement Index set to: {index}")
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def running(self) -> bool:
|
|
67
|
+
return self.com_object.Running
|
|
68
|
+
|
|
69
|
+
def start(self, timeout=30) -> bool:
|
|
70
|
+
try:
|
|
71
|
+
if self.running:
|
|
72
|
+
logger.warning("⚠️ Measurement is already running")
|
|
73
|
+
return True
|
|
74
|
+
self.measurement_events.START = False
|
|
75
|
+
self.com_object.Start()
|
|
76
|
+
status = DoEventsUntil(lambda: self.measurement_events.START, timeout, "CANoe Measurement Start")
|
|
77
|
+
if status:
|
|
78
|
+
logger.info('📢 Measurement Started 🏃➡️')
|
|
79
|
+
return status
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"❌ Error starting CANoe measurement: {e}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def stop(self, timeout=30) -> bool:
|
|
85
|
+
return self.stop_ex(timeout)
|
|
86
|
+
|
|
87
|
+
def stop_ex(self, timeout=30) -> bool:
|
|
88
|
+
try:
|
|
89
|
+
if not self.running:
|
|
90
|
+
logger.warning("⚠️ Measurement is already stopped")
|
|
91
|
+
return True
|
|
92
|
+
self.measurement_events.STOP = False
|
|
93
|
+
self.com_object.Stop()
|
|
94
|
+
status = DoEventsUntil(lambda: self.measurement_events.STOP, timeout, "CANoe Measurement Stop")
|
|
95
|
+
if status:
|
|
96
|
+
logger.info('📢 Measurement Stopped 🧍')
|
|
97
|
+
wait(1) # Allow 1 second buffer
|
|
98
|
+
return status
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.error(f"❌ Error stopping CANoe measurement: {e}")
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def start_measurement_in_animation_mode(self, animation_delay=100, timeout=30) -> bool:
|
|
104
|
+
try:
|
|
105
|
+
if self.running:
|
|
106
|
+
logger.warning("⚠️ Measurement is already running, cannot animate")
|
|
107
|
+
return False
|
|
108
|
+
self.measurement_events.START = False
|
|
109
|
+
self.animation_delay = animation_delay
|
|
110
|
+
self.com_object.Animate()
|
|
111
|
+
status = DoEventsUntil(lambda: self.measurement_events.START, timeout, "CANoe Measurement Animation Initialization")
|
|
112
|
+
if status:
|
|
113
|
+
logger.info(f'📢 Measurement started 🏃➡️ in Animation mode with animation delay ⏲️ {animation_delay} ms')
|
|
114
|
+
else:
|
|
115
|
+
logger.error(f"❌ Measurement did not start in Animation mode within {timeout} seconds")
|
|
116
|
+
return status
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logger.error(f"❌ Error starting CANoe measurement in animation mode: {e}")
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
def break_measurement_in_offline_mode(self) -> bool:
|
|
122
|
+
try:
|
|
123
|
+
if not self.running:
|
|
124
|
+
logger.warning("⚠️ Measurement is not running, cannot break")
|
|
125
|
+
return False
|
|
126
|
+
self.com_object.Break()
|
|
127
|
+
logger.info('📢 Measurement break applied 🫷 in Offline mode')
|
|
128
|
+
return True
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"❌ Error breaking CANoe measurement in offline mode: {e}")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def reset_measurement_in_offline_mode(self) -> bool:
|
|
134
|
+
try:
|
|
135
|
+
self.com_object.Reset()
|
|
136
|
+
logger.info('📢 Measurement reset applied 🔁 in Offline mode')
|
|
137
|
+
return True
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logger.error(f"❌ Error resetting CANoe measurement in offline mode: {e}")
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def process_measurement_event_in_single_step(self) -> bool:
|
|
143
|
+
try:
|
|
144
|
+
self.com_object.Step()
|
|
145
|
+
logger.info('📢 Processed a measurement event in single step 👣')
|
|
146
|
+
return True
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"❌ Error processing CANoe measurement event in single step: {e}")
|
|
149
|
+
return False
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from py_canoe.helpers.common import logger
|
|
4
|
+
from py_canoe.helpers.common import wait
|
|
5
|
+
from py_canoe.core.child_elements.diagnostic import Diagnostic
|
|
6
|
+
from py_canoe.core.child_elements.network import Network
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Networks:
|
|
10
|
+
"""
|
|
11
|
+
The Networks object represents the networks of CANoe.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self, app):
|
|
14
|
+
self.com_object = app.com_object.Networks
|
|
15
|
+
self.diagnostic_devices: dict[str, Diagnostic] = dict()
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def count(self) -> int:
|
|
19
|
+
return self.com_object.Count
|
|
20
|
+
|
|
21
|
+
def item(self, index: int) -> Network:
|
|
22
|
+
return Network(self.com_object.Item(index))
|
|
23
|
+
|
|
24
|
+
def fetch_diagnostic_devices(self):
|
|
25
|
+
try:
|
|
26
|
+
for i in range(1, self.count + 1):
|
|
27
|
+
network = self.item(i)
|
|
28
|
+
for j in range(1, network.devices.count + 1):
|
|
29
|
+
device = network.devices.item(j)
|
|
30
|
+
try:
|
|
31
|
+
diagnostic = getattr(device.com_object, 'Diagnostic', None)
|
|
32
|
+
if diagnostic:
|
|
33
|
+
self.diagnostic_devices[device.name] = Diagnostic(diagnostic)
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"❌ Error fetching Diagnostic Devices: {e}")
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def send_diag_request(self, diag_ecu_qualifier_name: str, request: str, request_in_bytes=True, return_sender_name=False, response_in_bytearray=False) -> Union[str, dict]:
|
|
41
|
+
try:
|
|
42
|
+
diag_device: Diagnostic = self.diagnostic_devices.get(diag_ecu_qualifier_name)
|
|
43
|
+
if diag_device:
|
|
44
|
+
if request_in_bytes:
|
|
45
|
+
diag_req_in_bytes = bytearray()
|
|
46
|
+
byte_stream = ''.join(request.split(' '))
|
|
47
|
+
for i in range(0, len(byte_stream), 2):
|
|
48
|
+
diag_req_in_bytes.append(int(byte_stream[i:i + 2], 16))
|
|
49
|
+
diag_request = diag_device.create_request_from_stream(diag_req_in_bytes)
|
|
50
|
+
else:
|
|
51
|
+
diag_request = diag_device.create_request(request)
|
|
52
|
+
diag_request.send()
|
|
53
|
+
logger.info(f'💉 {diag_ecu_qualifier_name}: Diagnostic Request = {request}')
|
|
54
|
+
while diag_request.pending:
|
|
55
|
+
wait(0.01)
|
|
56
|
+
diag_responses_dict = {}
|
|
57
|
+
diag_response_including_sender_name = {}
|
|
58
|
+
for i in range(1, diag_request.responses.count + 1):
|
|
59
|
+
diag_response = diag_request.responses.item(i)
|
|
60
|
+
diag_response_positive = diag_response.positive
|
|
61
|
+
response_code = diag_response.response_code
|
|
62
|
+
response_sender = diag_response.sender
|
|
63
|
+
response_stream = diag_response.stream
|
|
64
|
+
response_stream_in_str = " ".join(f"{d:02X}" for d in response_stream).upper()
|
|
65
|
+
diag_responses_dict[response_sender] = {
|
|
66
|
+
"positive": diag_response_positive,
|
|
67
|
+
"response_code": response_code,
|
|
68
|
+
"stream": response_stream,
|
|
69
|
+
"stream_in_str": response_stream_in_str
|
|
70
|
+
}
|
|
71
|
+
if response_in_bytearray:
|
|
72
|
+
diag_response_including_sender_name[response_sender] = response_stream
|
|
73
|
+
else:
|
|
74
|
+
diag_response_including_sender_name[response_sender] = response_stream_in_str
|
|
75
|
+
if diag_response_positive:
|
|
76
|
+
logger.info(f'🟢 {response_sender}: Diagnostic Response = {response_stream_in_str}')
|
|
77
|
+
else:
|
|
78
|
+
logger.info(f'🔴 {response_sender}: Diagnostic Response = {response_stream_in_str}')
|
|
79
|
+
return diag_response_including_sender_name if return_sender_name else diag_response_including_sender_name[diag_ecu_qualifier_name]
|
|
80
|
+
else:
|
|
81
|
+
logger.warning(f'⚠️ No responses received for request: {request}')
|
|
82
|
+
return {"error": "No responses received"}
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(f"❌ Error sending diagnostic request: {e}")
|
|
85
|
+
return {"error": str(e)}
|
|
86
|
+
|
|
87
|
+
def control_tester_present(self, diag_ecu_qualifier_name: str, value: bool) -> bool:
|
|
88
|
+
try:
|
|
89
|
+
diag_device: Diagnostic = self.diagnostic_devices.get(diag_ecu_qualifier_name)
|
|
90
|
+
if diag_device:
|
|
91
|
+
if value:
|
|
92
|
+
diag_device.diag_start_tester_present()
|
|
93
|
+
logger.info(f'✔️ {diag_ecu_qualifier_name}: Tester Present started 🏃➡️')
|
|
94
|
+
else:
|
|
95
|
+
diag_device.diag_stop_tester_present()
|
|
96
|
+
logger.info(f'⏹️ {diag_ecu_qualifier_name}: Tester Present stopped 🧍')
|
|
97
|
+
return True
|
|
98
|
+
else:
|
|
99
|
+
logger.warning(f'⚠️ No diagnostic device found for: {diag_ecu_qualifier_name}')
|
|
100
|
+
return False
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"❌ Error controlling tester present: {e}")
|
|
103
|
+
return False
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from py_canoe.helpers.common import logger
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Performance:
|
|
5
|
+
"""
|
|
6
|
+
The Performance object allows setting or returning parameters that influence the performance on multicore processors.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, app):
|
|
9
|
+
self.app = app
|
|
10
|
+
self.com_object = self.app.com_object.Performance
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def max_num_measurement_setup_threads(self) -> int:
|
|
14
|
+
return self.com_object.MaxNumMeasurementSetupThreads
|
|
15
|
+
|
|
16
|
+
@max_num_measurement_setup_threads.setter
|
|
17
|
+
def max_num_measurement_setup_threads(self, num: int):
|
|
18
|
+
if not self.app.get_measurement_running_status():
|
|
19
|
+
self.com_object.MaxNumMeasurementSetupThreads = num
|
|
20
|
+
else:
|
|
21
|
+
logger.warning("⚠️ Cannot set MaxNumMeasurementSetupThreads while measurement is running.")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import win32com.client
|
|
2
|
+
from typing import Union
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SimulationEvents:
|
|
6
|
+
EVENTS_INFORMATION = {}
|
|
7
|
+
|
|
8
|
+
@staticmethod
|
|
9
|
+
def OnIdle(timeHigh, time):
|
|
10
|
+
SimulationEvents.EVENTS_INFORMATION['timeHigh'] = timeHigh
|
|
11
|
+
SimulationEvents.EVENTS_INFORMATION['time'] = time
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Simulation:
|
|
15
|
+
"""
|
|
16
|
+
The Simulation object represents CANoe's measurement functions in the Simulation mode.
|
|
17
|
+
With the help of the Simulation object you can control the system time from an external source during the measurement.
|
|
18
|
+
CANoe automatically goes into Slave mode at the measurement start if you access the Simulation object.
|
|
19
|
+
"""
|
|
20
|
+
def __init__(self, app, enable_events: bool = False):
|
|
21
|
+
self.com_object = app.com_object.Simulation
|
|
22
|
+
if enable_events:
|
|
23
|
+
win32com.client.WithEvents(self.com_object, SimulationEvents)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def animation(self) -> Union[int, float]:
|
|
27
|
+
return self.com_object.Animation
|
|
28
|
+
|
|
29
|
+
@animation.setter
|
|
30
|
+
def animation(self, value: Union[int, float]):
|
|
31
|
+
self.com_object.Animation = value
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def current_time(self) ->int:
|
|
35
|
+
return self.com_object.CurrentTime
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def current_time_high(self) -> int:
|
|
39
|
+
return self.com_object.CurrentTimeHigh
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def notification_type(self) -> int:
|
|
43
|
+
return self.com_object.NotificationType
|
|
44
|
+
|
|
45
|
+
@notification_type.setter
|
|
46
|
+
def notification_type(self, value: int):
|
|
47
|
+
self.com_object.NotificationType = value
|
|
48
|
+
|
|
49
|
+
def increment_time(self, ticks: int):
|
|
50
|
+
self.com_object.IncrementTime(ticks)
|
|
51
|
+
|
|
52
|
+
def increment_time_and_wait(self, ticks: int):
|
|
53
|
+
self.com_object.IncrementTimeAndWait(ticks)
|
py_canoe/core/system.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
from py_canoe.helpers.common import logger
|
|
4
|
+
from py_canoe.core.child_elements.namespaces import Namespaces
|
|
5
|
+
from py_canoe.core.child_elements.variables_files import VariablesFiles
|
|
6
|
+
from py_canoe.core.child_elements.variable import Variable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class System:
|
|
10
|
+
"""
|
|
11
|
+
The System object represents the system of the CANoe application.
|
|
12
|
+
The System object offers access to the namespaces for data exchange with external applications.
|
|
13
|
+
"""
|
|
14
|
+
def __init__(self, app):
|
|
15
|
+
self.com_object = app.com_object.System
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def namespaces(self) -> Namespaces:
|
|
19
|
+
return Namespaces(self.com_object.Namespaces)
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def variables_files(self) -> VariablesFiles:
|
|
23
|
+
return VariablesFiles(self.com_object.VariablesFiles)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def add_variable(self, sys_var_name: str, value: Union[int, float, str], read_only: bool = False) -> Union[object, None]:
|
|
27
|
+
new_var_com_obj = None
|
|
28
|
+
try:
|
|
29
|
+
parts = sys_var_name.split('::')
|
|
30
|
+
if len(parts) < 2:
|
|
31
|
+
logger.error(f"❌ Invalid system variable name '{sys_var_name}'. Must be in 'namespace::variable' format.")
|
|
32
|
+
return None
|
|
33
|
+
namespace = '::'.join(parts[:-1])
|
|
34
|
+
variable_name = parts[-1]
|
|
35
|
+
try:
|
|
36
|
+
namespace_obj = self.com_object.Namespaces(namespace)
|
|
37
|
+
except Exception:
|
|
38
|
+
logger.info(f"namespace '{namespace}' not present. Creating namespace...")
|
|
39
|
+
namespaces_obj = self.com_object.Namespaces
|
|
40
|
+
namespace_obj = namespaces_obj.Add(namespace)
|
|
41
|
+
logger.info(f"Created new namespace: {namespace}")
|
|
42
|
+
variables_obj = namespace_obj.Variables
|
|
43
|
+
if read_only:
|
|
44
|
+
new_var_com_obj = variables_obj.Add(variable_name, value)
|
|
45
|
+
else:
|
|
46
|
+
new_var_com_obj = variables_obj.AddWriteable(variable_name, value)
|
|
47
|
+
logger.info(f"System Variable '{sys_var_name}' defined successfully with value: {value}")
|
|
48
|
+
return new_var_com_obj
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"❌ Error defining System Variable '{sys_var_name}': {e}")
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def remove_variable(self, sys_var_name: str) -> bool:
|
|
54
|
+
try:
|
|
55
|
+
parts = sys_var_name.split('::')
|
|
56
|
+
if len(parts) < 2:
|
|
57
|
+
logger.error(f"❌ Invalid system variable name '{sys_var_name}'. Must be in 'namespace::variable' format.")
|
|
58
|
+
return None
|
|
59
|
+
namespace = '::'.join(parts[:-1])
|
|
60
|
+
variable_name = parts[-1]
|
|
61
|
+
namespace_obj = self.com_object.Namespaces(namespace)
|
|
62
|
+
variables_obj = namespace_obj.Variables
|
|
63
|
+
for i in range(1, variables_obj.Count + 1):
|
|
64
|
+
variable_obj = variables_obj.Item(i)
|
|
65
|
+
if variable_obj.Name == variable_name:
|
|
66
|
+
variables_obj.Remove(i)
|
|
67
|
+
logger.info(f"System Variable '{sys_var_name}' removed successfully.")
|
|
68
|
+
return True
|
|
69
|
+
logger.info(f"System Variable '{sys_var_name}' not found.")
|
|
70
|
+
return False
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"❌ Error removing System Variable '{sys_var_name}': {e}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def get_variable_value(self, sys_var_name: str, return_symbolic_name=False) -> Union[int, float, str, None]:
|
|
76
|
+
try:
|
|
77
|
+
parts = sys_var_name.split('::')
|
|
78
|
+
if len(parts) < 2:
|
|
79
|
+
logger.error(f"❌ Invalid system variable name '{sys_var_name}'. Must be in 'namespace::variable' format.")
|
|
80
|
+
return None
|
|
81
|
+
namespace = '::'.join(parts[:-1])
|
|
82
|
+
variable_name = parts[-1]
|
|
83
|
+
namespace_obj = self.com_object.Namespaces(namespace)
|
|
84
|
+
variable_obj = Variable(namespace_obj.Variables(variable_name))
|
|
85
|
+
value = variable_obj.get_value()
|
|
86
|
+
if return_symbolic_name:
|
|
87
|
+
symbolic_value = variable_obj.get_symbolic_value_name(value)
|
|
88
|
+
logger.info(f"System Variable '{sys_var_name}' symbolic value: {symbolic_value}")
|
|
89
|
+
return symbolic_value
|
|
90
|
+
logger.info(f"System Variable '{sys_var_name}' value: {value}")
|
|
91
|
+
return value
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"❌ Error retrieving System Variable '{sys_var_name}': {e}")
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def set_variable_value(self, sys_var_name: str, value: Union[int, float, str], timeout: Union[int, float] = 1) -> bool:
|
|
97
|
+
try:
|
|
98
|
+
parts = sys_var_name.split('::')
|
|
99
|
+
if len(parts) < 2:
|
|
100
|
+
logger.error(f"❌ Invalid system variable name '{sys_var_name}'. Must be in 'namespace::variable' format.")
|
|
101
|
+
return False
|
|
102
|
+
namespace = '::'.join(parts[:-1])
|
|
103
|
+
variable_name = parts[-1]
|
|
104
|
+
namespace_obj = self.com_object.Namespaces(namespace)
|
|
105
|
+
variable_obj = Variable(namespace_obj.Variables(variable_name))
|
|
106
|
+
var_type = type(variable_obj.get_value())
|
|
107
|
+
try:
|
|
108
|
+
converted_value = var_type(value)
|
|
109
|
+
except Exception:
|
|
110
|
+
logger.error(f"❌ Could not convert value '{value}' to type {var_type.__name__} for '{sys_var_name}'")
|
|
111
|
+
return False
|
|
112
|
+
status = variable_obj.set_value(converted_value, timeout)
|
|
113
|
+
return status
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.error(f"❌ Error setting System Variable '{sys_var_name}': {e}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def set_variable_array_values(self, sys_var_name: str, value: tuple, index: int = 0, timeout: Union[int, float] = 1) -> bool:
|
|
119
|
+
try:
|
|
120
|
+
parts = sys_var_name.split('::')
|
|
121
|
+
if len(parts) < 2:
|
|
122
|
+
logger.error(f"❌ Invalid system variable name '{sys_var_name}'. Must be in 'namespace::variable' format.")
|
|
123
|
+
return False
|
|
124
|
+
namespace = '::'.join(parts[:-1])
|
|
125
|
+
variable_name = parts[-1]
|
|
126
|
+
namespace_obj = self.com_object.Namespaces(namespace)
|
|
127
|
+
variable_obj = Variable(namespace_obj.Variables(variable_name))
|
|
128
|
+
arr = list(variable_obj.get_value())
|
|
129
|
+
if index < 0 or index + len(value) > len(arr):
|
|
130
|
+
logger.error(f"❌ Not enough space in System Variable Array '{sys_var_name}' to set values.")
|
|
131
|
+
return False
|
|
132
|
+
value_type = type(arr[0]) if arr else type(value[0])
|
|
133
|
+
arr[index:index + len(value)] = [value_type(v) for v in value]
|
|
134
|
+
status = variable_obj.set_value(tuple(arr), timeout)
|
|
135
|
+
return status
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"❌ Error setting System Variable Array '{sys_var_name}': {e}")
|
|
138
|
+
return False
|
|
139
|
+
|
|
140
|
+
def get_namespaces(self) -> Union[dict['str': 'Namespace'], None]:
|
|
141
|
+
try:
|
|
142
|
+
namespaces_dict = {}
|
|
143
|
+
namespaces = self.namespaces
|
|
144
|
+
for index in range(1, namespaces.count + 1):
|
|
145
|
+
namespace = namespaces.item(index)
|
|
146
|
+
namespaces_dict[namespace.name] = namespace
|
|
147
|
+
logger.info(f"📢 total {namespaces.count} system root namespaces found.")
|
|
148
|
+
return namespaces_dict
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.error(f"❌ Error getting system namespaces: {e}")
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
def get_variables_files(self) -> Union[dict['str': 'VariablesFile'], None]:
|
|
154
|
+
try:
|
|
155
|
+
variables_files_dict = {}
|
|
156
|
+
variables_files = self.variables_files
|
|
157
|
+
for index in range(1, variables_files.count + 1):
|
|
158
|
+
variables_file = variables_files.item(index)
|
|
159
|
+
variables_files_dict[variables_file.full_name] = variables_file
|
|
160
|
+
logger.info(f"📢 total {variables_files.count} system variables files found.")
|
|
161
|
+
return variables_files_dict
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"❌ Error getting system variables files: {e}")
|
|
164
|
+
return None
|
py_canoe/core/ui.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from py_canoe.helpers.common import logger
|
|
2
|
+
|
|
3
|
+
from py_canoe.core.child_elements.write import Write
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Ui:
|
|
7
|
+
"""
|
|
8
|
+
The UI object represents the user interface in CANoe.
|
|
9
|
+
"""
|
|
10
|
+
def __init__(self, app):
|
|
11
|
+
self.app = app
|
|
12
|
+
self.com_object = self.app.com_object.UI
|
|
13
|
+
|
|
14
|
+
def get_command_enabled(self, command: str) -> bool:
|
|
15
|
+
return self.com_object.GetCommandEnabled(command)
|
|
16
|
+
|
|
17
|
+
def set_command_enabled(self, command: str, enabled: bool) -> None:
|
|
18
|
+
self.com_object.SetCommandEnabled(command, enabled)
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def write(self) -> Write:
|
|
22
|
+
return Write(self.com_object.Write)
|
|
23
|
+
|
|
24
|
+
def activate_desktop(self, desktop_name: str) -> bool:
|
|
25
|
+
try:
|
|
26
|
+
self.com_object.ActivateDesktop(desktop_name)
|
|
27
|
+
logger.info(f"📢 UI Desktop '{desktop_name}' activated successfully")
|
|
28
|
+
return True
|
|
29
|
+
except Exception as e:
|
|
30
|
+
logger.error(f"❌ Error activating UI Desktop '{desktop_name}': {e}")
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
def create_desktop(self, desktop_name: str) -> bool:
|
|
34
|
+
try:
|
|
35
|
+
if float(f"{self.app.version.major}.{self.app.version.minor}") >= 15.3:
|
|
36
|
+
self.com_object.CreateDesktop(desktop_name)
|
|
37
|
+
logger.info(f"📢 UI Desktop '{desktop_name}' created successfully")
|
|
38
|
+
return True
|
|
39
|
+
else:
|
|
40
|
+
logger.warning(f"❌ Cannot create desktop '{desktop_name}': Requires CANoe version 15.3 or higher.")
|
|
41
|
+
return False
|
|
42
|
+
except Exception as e:
|
|
43
|
+
logger.error(f"❌ Error creating UI Desktop '{desktop_name}': {e}")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def open_baudrate_dialog(self) -> bool:
|
|
47
|
+
try:
|
|
48
|
+
self.com_object.OpenBaudrateDialog()
|
|
49
|
+
logger.info("📢 UI Baudrate Dialog opened successfully")
|
|
50
|
+
return True
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logger.error(f"❌ Error opening UI Baudrate Dialog: {e}")
|
|
53
|
+
return False
|
py_canoe/core/version.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from py_canoe.helpers.common import logger
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Version:
|
|
5
|
+
"""
|
|
6
|
+
The Version object represents the version of the CANoe application.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self, app):
|
|
9
|
+
self.com_object = app.com_object.Version
|
|
10
|
+
|
|
11
|
+
def __str__(self):
|
|
12
|
+
return f"{self.full_name}"
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def build(self):
|
|
16
|
+
return self.com_object.Build
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def full_name(self):
|
|
20
|
+
return self.com_object.FullName
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def major(self):
|
|
24
|
+
return self.com_object.major
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def minor(self):
|
|
28
|
+
return self.com_object.minor
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def name(self):
|
|
32
|
+
return self.com_object.Name
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def patch(self):
|
|
36
|
+
return self.com_object.Patch
|
|
37
|
+
|
|
38
|
+
def get_canoe_version_info(self) -> dict[str, str | int]:
|
|
39
|
+
try:
|
|
40
|
+
version_info = {
|
|
41
|
+
'full_name': self.full_name,
|
|
42
|
+
'name': self.name,
|
|
43
|
+
'major': self.major,
|
|
44
|
+
'minor': self.minor,
|
|
45
|
+
'build': self.build,
|
|
46
|
+
'patch': self.patch
|
|
47
|
+
}
|
|
48
|
+
logger.info('📜 CANoe Version Information:')
|
|
49
|
+
for key, value in version_info.items():
|
|
50
|
+
logger.info(f" {key}: {value}")
|
|
51
|
+
return version_info
|
|
52
|
+
except Exception as e:
|
|
53
|
+
logger.error(f"❌ Error retrieving CANoe version information: {e}")
|
|
54
|
+
return {}
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
import pythoncom
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
def check_if_path_exists(path: str, create_if_not_exist: bool=False) -> bool:
|
|
10
|
+
"""Check if a given path exists. Optionally create it if it doesn't."""
|
|
11
|
+
if os.path.exists(path):
|
|
12
|
+
return True
|
|
13
|
+
else:
|
|
14
|
+
if create_if_not_exist:
|
|
15
|
+
try:
|
|
16
|
+
os.makedirs(path, exist_ok=True)
|
|
17
|
+
return True
|
|
18
|
+
except Exception as e:
|
|
19
|
+
logger.error(f"❌ Error creating directory {path}: {e}")
|
|
20
|
+
return False
|
|
21
|
+
else:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
def setup_logger(name='py_canoe', filename='py_canoe.log'):
|
|
25
|
+
"""Set up and return a logger with console and file handlers."""
|
|
26
|
+
# os.makedirs(os.path.dirname(filename), exist_ok=True)
|
|
27
|
+
logger = logging.getLogger(name)
|
|
28
|
+
logger.setLevel(logging.INFO)
|
|
29
|
+
fmt = "%(asctime)s [PY_CANOE] [%(levelname)-4.8s] %(message)s"
|
|
30
|
+
# Add console handler if not already present
|
|
31
|
+
if not any(isinstance(h, logging.StreamHandler) for h in logger.handlers):
|
|
32
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
33
|
+
console_handler.setLevel(logging.INFO)
|
|
34
|
+
console_handler.setFormatter(logging.Formatter(fmt))
|
|
35
|
+
logger.addHandler(console_handler)
|
|
36
|
+
logger.propagate = False
|
|
37
|
+
return logger
|
|
38
|
+
|
|
39
|
+
def update_logger_file_path(logger: logging.Logger, log_dir_path: str | Path) -> None:
|
|
40
|
+
"""Update the file handler of an existing logger to a new file path."""
|
|
41
|
+
new_filename = os.path.join(log_dir_path, 'py_canoe.log')
|
|
42
|
+
if check_if_path_exists(os.path.dirname(new_filename), create_if_not_exist=True):
|
|
43
|
+
# Remove existing FileHandlers
|
|
44
|
+
for handler in logger.handlers[:]:
|
|
45
|
+
if isinstance(handler, logging.FileHandler):
|
|
46
|
+
logger.removeHandler(handler)
|
|
47
|
+
handler.close()
|
|
48
|
+
# Add new FileHandler
|
|
49
|
+
fmt = "%(asctime)s [PY_CANOE] [%(levelname)-4.8s] %(message)s"
|
|
50
|
+
file_handler = logging.FileHandler(new_filename, mode='w', encoding='utf-8')
|
|
51
|
+
file_handler.setLevel(logging.INFO)
|
|
52
|
+
file_handler.setFormatter(logging.Formatter(fmt))
|
|
53
|
+
logger.addHandler(file_handler)
|
|
54
|
+
else:
|
|
55
|
+
logger.error(f"❌ Cannot update logger file path. Directory does not exist and could not be created: {os.path.dirname(new_filename)}")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
logger = setup_logger()
|
|
59
|
+
|
|
60
|
+
def wait(timeout_seconds=0.1):
|
|
61
|
+
"""Pump waiting COM messages and sleep for the given timeout."""
|
|
62
|
+
pythoncom.PumpWaitingMessages()
|
|
63
|
+
time.sleep(timeout_seconds)
|
|
64
|
+
|
|
65
|
+
def DoEvents() -> None:
|
|
66
|
+
wait(0.01)
|
|
67
|
+
|
|
68
|
+
def DoEventsUntil(cond, timeout, title) -> bool:
|
|
69
|
+
base_time = datetime.now()
|
|
70
|
+
while not cond():
|
|
71
|
+
DoEvents()
|
|
72
|
+
now = datetime.now()
|
|
73
|
+
difference = now - base_time
|
|
74
|
+
seconds = difference.seconds
|
|
75
|
+
if seconds > timeout:
|
|
76
|
+
logger.warning(f'⚠️ {title} timeout({timeout} s)')
|
|
77
|
+
return False
|
|
78
|
+
return True
|