py_canoe 3.0.2__tar.gz → 3.0.4__tar.gz
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-3.0.2 → py_canoe-3.0.4}/PKG-INFO +53 -14
- {py_canoe-3.0.2 → py_canoe-3.0.4}/README.md +47 -2
- py_canoe-3.0.4/py_canoe/__init__.py +1 -0
- py_canoe-3.0.2/py_canoe/__init__.py → py_canoe-3.0.4/py_canoe/py_canoe.py +101 -18
- py_canoe-3.0.4/py_canoe/py_canoe_utils/logging_collection.py +345 -0
- {py_canoe-3.0.2/py_canoe → py_canoe-3.0.4/py_canoe/py_canoe_utils}/py_canoe_logger.py +7 -10
- {py_canoe-3.0.2 → py_canoe-3.0.4}/pyproject.toml +18 -13
- {py_canoe-3.0.2 → py_canoe-3.0.4}/LICENSE +0 -0
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: py_canoe
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.4
|
|
4
4
|
Summary: Python CANoe Package
|
|
5
|
-
Home-page: https://github.com/chaitu-ycr/py_canoe
|
|
6
5
|
License: LICENSE
|
|
7
6
|
Keywords: Vector,CANoe,py_canoe
|
|
8
7
|
Author: chaitu-ycr
|
|
9
8
|
Author-email: chaitu.ycr@gmail.com
|
|
10
|
-
Requires-Python: >=3.9
|
|
9
|
+
Requires-Python: >=3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: License :: Other/Proprietary License
|
|
13
12
|
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
20
13
|
Classifier: Topic :: Software Development :: Embedded Systems
|
|
21
|
-
Requires-Dist: pywin32
|
|
14
|
+
Requires-Dist: pywin32
|
|
22
15
|
Project-URL: Documentation, https://chaitu-ycr.github.io/py_canoe/
|
|
16
|
+
Project-URL: Homepage, https://github.com/chaitu-ycr/py_canoe
|
|
23
17
|
Project-URL: Repository, https://github.com/chaitu-ycr/py_canoe
|
|
24
18
|
Description-Content-Type: text/markdown
|
|
25
19
|
|
|
@@ -78,8 +72,7 @@ pip install py_canoe --upgrade
|
|
|
78
72
|
### import CANoe module and create CANoe object instance
|
|
79
73
|
|
|
80
74
|
```python
|
|
81
|
-
from py_canoe import CANoe
|
|
82
|
-
from time import sleep as wait
|
|
75
|
+
from py_canoe import CANoe, wait
|
|
83
76
|
|
|
84
77
|
canoe_inst = CANoe()
|
|
85
78
|
```
|
|
@@ -289,3 +282,49 @@ wait(1)
|
|
|
289
282
|
canoe_inst.stop_measurement()
|
|
290
283
|
```
|
|
291
284
|
|
|
285
|
+
### add/remove database
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
self.canoe_inst.open(canoe_cfg=self.canoe_cfg_gen_db_setup, visible=True, auto_save=True, prompt_user=False, auto_stop=True)
|
|
289
|
+
self.canoe_inst.start_measurement()
|
|
290
|
+
wait(1)
|
|
291
|
+
# add database
|
|
292
|
+
self.canoe_inst.add_database(fr"{self.file_path}\demo_cfg\DBs\sample_databases\XCP.dbc", 'CAN1', 1)
|
|
293
|
+
# remove database
|
|
294
|
+
self.canoe_inst.remove_database(fr"{self.file_path}\demo_cfg\DBs\sample_databases\XCP.dbc", 1)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### start/stop online measurement setup logging block
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
self.canoe_inst.open(canoe_cfg=self.canoe_cfg_online_setup)
|
|
301
|
+
self.canoe_inst.start_measurement()
|
|
302
|
+
wait(1)
|
|
303
|
+
# stop logging block
|
|
304
|
+
self.canoe_inst.start_stop_online_measurement_setup_logging_block(fr'{self.demo_cfg_dir}\Logs\demo_online_setup_log.blf', start=False)
|
|
305
|
+
wait(2)
|
|
306
|
+
# start logging block
|
|
307
|
+
self.canoe_inst.start_stop_online_measurement_setup_logging_block(fr'{self.demo_cfg_dir}\Logs\demo_online_setup_log.blf', start=True)
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### working with logging blocks
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
# remove current logging blocks
|
|
314
|
+
for i in range(canoe_inst.logging_collection.count):
|
|
315
|
+
canoe_inst.remove_logging_block(1) # iteration start from 1 and shifts after each delete
|
|
316
|
+
|
|
317
|
+
# add a new block
|
|
318
|
+
# define dest path with file format as asc, blf or other
|
|
319
|
+
# may include field functions like {IncMeasurement}
|
|
320
|
+
full_path = "C:/sample_log_{IncMeasurement}.blf"
|
|
321
|
+
canoe_inst.add_logging_block(full_path)
|
|
322
|
+
canoe_inst.start_measurement()
|
|
323
|
+
# ...
|
|
324
|
+
canoe_inst.stop_measurement()
|
|
325
|
+
# log should be fully generated at this point for you to analyze
|
|
326
|
+
canoe_inst.set_configuration_modified(False) # to avoid popup asking to save changes
|
|
327
|
+
canoe_inst.quit()
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
|
|
@@ -53,8 +53,7 @@ pip install py_canoe --upgrade
|
|
|
53
53
|
### import CANoe module and create CANoe object instance
|
|
54
54
|
|
|
55
55
|
```python
|
|
56
|
-
from py_canoe import CANoe
|
|
57
|
-
from time import sleep as wait
|
|
56
|
+
from py_canoe import CANoe, wait
|
|
58
57
|
|
|
59
58
|
canoe_inst = CANoe()
|
|
60
59
|
```
|
|
@@ -263,3 +262,49 @@ var_value = canoe_inst.get_environment_variable_value('data_var')
|
|
|
263
262
|
wait(1)
|
|
264
263
|
canoe_inst.stop_measurement()
|
|
265
264
|
```
|
|
265
|
+
|
|
266
|
+
### add/remove database
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
self.canoe_inst.open(canoe_cfg=self.canoe_cfg_gen_db_setup, visible=True, auto_save=True, prompt_user=False, auto_stop=True)
|
|
270
|
+
self.canoe_inst.start_measurement()
|
|
271
|
+
wait(1)
|
|
272
|
+
# add database
|
|
273
|
+
self.canoe_inst.add_database(fr"{self.file_path}\demo_cfg\DBs\sample_databases\XCP.dbc", 'CAN1', 1)
|
|
274
|
+
# remove database
|
|
275
|
+
self.canoe_inst.remove_database(fr"{self.file_path}\demo_cfg\DBs\sample_databases\XCP.dbc", 1)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### start/stop online measurement setup logging block
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
self.canoe_inst.open(canoe_cfg=self.canoe_cfg_online_setup)
|
|
282
|
+
self.canoe_inst.start_measurement()
|
|
283
|
+
wait(1)
|
|
284
|
+
# stop logging block
|
|
285
|
+
self.canoe_inst.start_stop_online_measurement_setup_logging_block(fr'{self.demo_cfg_dir}\Logs\demo_online_setup_log.blf', start=False)
|
|
286
|
+
wait(2)
|
|
287
|
+
# start logging block
|
|
288
|
+
self.canoe_inst.start_stop_online_measurement_setup_logging_block(fr'{self.demo_cfg_dir}\Logs\demo_online_setup_log.blf', start=True)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### working with logging blocks
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# remove current logging blocks
|
|
295
|
+
for i in range(canoe_inst.logging_collection.count):
|
|
296
|
+
canoe_inst.remove_logging_block(1) # iteration start from 1 and shifts after each delete
|
|
297
|
+
|
|
298
|
+
# add a new block
|
|
299
|
+
# define dest path with file format as asc, blf or other
|
|
300
|
+
# may include field functions like {IncMeasurement}
|
|
301
|
+
full_path = "C:/sample_log_{IncMeasurement}.blf"
|
|
302
|
+
canoe_inst.add_logging_block(full_path)
|
|
303
|
+
canoe_inst.start_measurement()
|
|
304
|
+
# ...
|
|
305
|
+
canoe_inst.stop_measurement()
|
|
306
|
+
# log should be fully generated at this point for you to analyze
|
|
307
|
+
canoe_inst.set_configuration_modified(False) # to avoid popup asking to save changes
|
|
308
|
+
canoe_inst.quit()
|
|
309
|
+
```
|
|
310
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .py_canoe import CANoe, wait
|
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
import os
|
|
3
3
|
import time
|
|
4
4
|
import logging
|
|
5
|
+
|
|
5
6
|
import pythoncom
|
|
6
7
|
import win32com.client
|
|
7
|
-
from typing import Union
|
|
8
|
+
from typing import Union, Iterable
|
|
8
9
|
from datetime import datetime
|
|
9
10
|
|
|
10
11
|
# import internal modules here
|
|
11
|
-
from .
|
|
12
|
+
from .py_canoe_utils.logging_collection import LoggingCollection, Logging, ExporterSymbol, Message
|
|
13
|
+
from .py_canoe_utils.py_canoe_logger import PyCanoeLogger
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class PyCanoeException(Exception):
|
|
@@ -53,8 +55,8 @@ class CANoe:
|
|
|
53
55
|
|
|
54
56
|
def __init_canoe_application_bus(self):
|
|
55
57
|
self.bus_com_obj = win32com.client.Dispatch(self.application_com_obj.Bus)
|
|
56
|
-
self.
|
|
57
|
-
self.
|
|
58
|
+
self.bus_databases_com_obj = win32com.client.Dispatch(self.bus_com_obj.Databases)
|
|
59
|
+
self.bus_nodes_com_obj = win32com.client.Dispatch(self.bus_com_obj.Nodes)
|
|
58
60
|
|
|
59
61
|
def __init_canoe_application_capl(self):
|
|
60
62
|
self.capl_obj = lambda: CanoeCapl(self.application_com_obj)
|
|
@@ -63,16 +65,18 @@ class CANoe:
|
|
|
63
65
|
self.configuration_com_obj = win32com.client.Dispatch(self.application_com_obj.Configuration)
|
|
64
66
|
if self.configuration_events_enabled:
|
|
65
67
|
win32com.client.WithEvents(self.configuration_com_obj, CanoeConfigurationEvents)
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
68
|
-
self.
|
|
69
|
-
|
|
68
|
+
self.configuration_offline_setup_com_obj = win32com.client.Dispatch(self.configuration_com_obj.OfflineSetup)
|
|
69
|
+
self.configuration_offline_setup_source_com_obj = win32com.client.Dispatch(self.configuration_offline_setup_com_obj.Source)
|
|
70
|
+
self.configuration_offline_setup_logging_collection = lambda: win32com.client.Dispatch(self.configuration_offline_setup_com_obj.LoggingCollection)
|
|
71
|
+
self.configuration_offline_setup_source_sources_com_obj = win32com.client.Dispatch(self.configuration_offline_setup_source_com_obj.Sources)
|
|
72
|
+
sources = self.configuration_offline_setup_source_sources_com_obj
|
|
70
73
|
sources_count = sources.Count + 1
|
|
71
74
|
self.configuration_offline_setup_source_sources_paths = lambda: [sources.Item(index) for index in range(1, sources_count)]
|
|
72
|
-
self.
|
|
73
|
-
self.
|
|
74
|
-
self.configuration_online_setup_bus_statistics_bus_statistic = lambda bus_type, channel: win32com.client.Dispatch(self.
|
|
75
|
-
self.
|
|
75
|
+
self.configuration_online_setup_com_obj = win32com.client.Dispatch(self.configuration_com_obj.OnlineSetup)
|
|
76
|
+
self.configuration_online_setup_bus_statistics_com_obj = win32com.client.Dispatch(self.configuration_online_setup_com_obj.BusStatistics)
|
|
77
|
+
self.configuration_online_setup_bus_statistics_bus_statistic = lambda bus_type, channel: win32com.client.Dispatch(self.configuration_online_setup_bus_statistics_com_obj.BusStatistic(bus_type, channel))
|
|
78
|
+
self.configuration_online_setup_logging_collection = lambda: win32com.client.Dispatch(self.configuration_online_setup_com_obj.LoggingCollection)
|
|
79
|
+
self.configuration_general_setup_obj = CanoeConfigurationGeneralSetup(self.configuration_com_obj)
|
|
76
80
|
self.configuration_simulation_setup = lambda: CanoeConfigurationSimulationSetup(self.configuration_com_obj)
|
|
77
81
|
self.__replay_blocks = self.configuration_simulation_setup().replay_collection.fetch_replay_blocks()
|
|
78
82
|
self.configuration_test_setup = lambda: CanoeConfigurationTestSetup(self.configuration_com_obj)
|
|
@@ -113,6 +117,9 @@ class CANoe:
|
|
|
113
117
|
def __init_canoe_application_version(self):
|
|
114
118
|
self.version_com_obj = win32com.client.Dispatch(self.application_com_obj.Version)
|
|
115
119
|
|
|
120
|
+
def __init_logging_collection(self):
|
|
121
|
+
self.logging_collection = LoggingCollection(self.configuration_com_obj.OnlineSetup.LoggingCollection)
|
|
122
|
+
|
|
116
123
|
def new(self, auto_save=False, prompt_user=False) -> None:
|
|
117
124
|
try:
|
|
118
125
|
self.__init_canoe_application()
|
|
@@ -155,6 +162,7 @@ class CANoe:
|
|
|
155
162
|
self.__init_canoe_application_networks()
|
|
156
163
|
self.__init_canoe_application_system()
|
|
157
164
|
self.__init_canoe_application_ui()
|
|
165
|
+
self.__init_logging_collection()
|
|
158
166
|
self.__log.debug(f'📢 CANoe configuration successfully opened 🎉')
|
|
159
167
|
else:
|
|
160
168
|
raise PyCanoeException(f'CANoe configuration "{canoe_cfg}" not found')
|
|
@@ -283,7 +291,7 @@ class CANoe:
|
|
|
283
291
|
if file_already_added:
|
|
284
292
|
self.__log.warning(f'⚠️ File "{absolute_log_file_path}" already added as offline source')
|
|
285
293
|
else:
|
|
286
|
-
self.
|
|
294
|
+
self.configuration_offline_setup_source_sources_com_obj.Add(absolute_log_file_path)
|
|
287
295
|
self.__log.debug(f'📢 File "{absolute_log_file_path}" added as offline source')
|
|
288
296
|
return True
|
|
289
297
|
else:
|
|
@@ -1281,14 +1289,14 @@ class CANoe:
|
|
|
1281
1289
|
self.__log.warning('⚠️ measurement is running. not possible to add database')
|
|
1282
1290
|
return False
|
|
1283
1291
|
else:
|
|
1284
|
-
databases = self.
|
|
1292
|
+
databases = self.configuration_general_setup_obj.database_setup.databases.fetch_databases()
|
|
1285
1293
|
if database_file in [database.full_name for database in databases.values()]:
|
|
1286
1294
|
self.__log.warning(f'⚠️ database "{database_file}" already added')
|
|
1287
1295
|
return False
|
|
1288
1296
|
else:
|
|
1289
|
-
self.
|
|
1297
|
+
self.configuration_general_setup_obj.database_setup.databases.add_network(database_file, database_network)
|
|
1290
1298
|
wait(1)
|
|
1291
|
-
databases = self.
|
|
1299
|
+
databases = self.configuration_general_setup_obj.database_setup.databases.fetch_databases()
|
|
1292
1300
|
for database in databases.values():
|
|
1293
1301
|
if database.full_name == database_file:
|
|
1294
1302
|
database.channel = database_channel
|
|
@@ -1311,7 +1319,7 @@ class CANoe:
|
|
|
1311
1319
|
self.__log.warning('⚠️ measurement is running. not possible to remove database')
|
|
1312
1320
|
return False
|
|
1313
1321
|
else:
|
|
1314
|
-
databases = self.
|
|
1322
|
+
databases = self.configuration_general_setup_obj.database_setup.databases
|
|
1315
1323
|
if database_file not in [database.full_name for database in databases.fetch_databases().values()]:
|
|
1316
1324
|
self.__log.warning(f'⚠️ database "{database_file}" not available to remove')
|
|
1317
1325
|
return False
|
|
@@ -1319,7 +1327,7 @@ class CANoe:
|
|
|
1319
1327
|
for i in range(1, databases.count + 1):
|
|
1320
1328
|
database_com_obj = databases.com_obj.Item(i)
|
|
1321
1329
|
if database_com_obj.FullName == database_file and database_com_obj.Channel == database_channel:
|
|
1322
|
-
self.
|
|
1330
|
+
self.configuration_general_setup_obj.database_setup.databases.remove(i)
|
|
1323
1331
|
wait(1)
|
|
1324
1332
|
self.__log.debug(f'👉 database "{database_file}" removed from channel {database_channel}')
|
|
1325
1333
|
return True
|
|
@@ -1327,6 +1335,81 @@ class CANoe:
|
|
|
1327
1335
|
self.__log.error(f'😡 failed to remove database "{database_file}". {e}')
|
|
1328
1336
|
return False
|
|
1329
1337
|
|
|
1338
|
+
def get_logging_blocks(self) -> list[Logging]:
|
|
1339
|
+
"""Return all available logging blocks."""
|
|
1340
|
+
blocks = []
|
|
1341
|
+
for i in range(1, self.logging_collection.count + 1):
|
|
1342
|
+
blocks.append(self.logging_collection.item(i))
|
|
1343
|
+
return blocks
|
|
1344
|
+
|
|
1345
|
+
def add_logging_block(self, full_name: str) -> Logging:
|
|
1346
|
+
"""Adds new logging block.
|
|
1347
|
+
|
|
1348
|
+
:param full_name: full path to log file as "C:/file.(asc|blf|mf4|...)", may have
|
|
1349
|
+
field functions like {IncMeasurement} in the file name
|
|
1350
|
+
|
|
1351
|
+
"""
|
|
1352
|
+
return self.logging_collection.add(full_name)
|
|
1353
|
+
|
|
1354
|
+
def remove_logging_block(self, index: int) -> None:
|
|
1355
|
+
"""Remove logging block at given index.
|
|
1356
|
+
|
|
1357
|
+
Blocks` indexes starts from 1 instead of 0. After one block is removed
|
|
1358
|
+
the rest shift up e.g. having 3 loggers [L1, L2, L3] if we remove index=2
|
|
1359
|
+
the new order will be [L1, L3] with indexes 1 and 2.
|
|
1360
|
+
|
|
1361
|
+
"""
|
|
1362
|
+
if index==0:
|
|
1363
|
+
raise ValueError("Logging blocks indexing starts from 1 and not 0.")
|
|
1364
|
+
|
|
1365
|
+
self.logging_collection.remove(index)
|
|
1366
|
+
|
|
1367
|
+
def load_logs_for_exporter(self, logger_index: int) -> None:
|
|
1368
|
+
"""Load all source files of exporter and determine symbols/messages.
|
|
1369
|
+
|
|
1370
|
+
:param logger_index: indicates logger and its log files
|
|
1371
|
+
"""
|
|
1372
|
+
self.logging_collection.item(logger_index).exporter.load()
|
|
1373
|
+
|
|
1374
|
+
def get_symbols(self, logger_index: int) -> list[ExporterSymbol]:
|
|
1375
|
+
"""Return all exporter symbols from given logger."""
|
|
1376
|
+
return self.logging_collection.item(logger_index).exporter.symbols
|
|
1377
|
+
|
|
1378
|
+
def get_messages(self, logger_index: int) -> list[Message]:
|
|
1379
|
+
"""Return all messages from given logger."""
|
|
1380
|
+
return self.logging_collection.item(logger_index).exporter.messages
|
|
1381
|
+
|
|
1382
|
+
def add_filters_to_exporter(self, logger_index: int, full_names: Iterable):
|
|
1383
|
+
"""Add messages and symbols to exporter filter by their full names.
|
|
1384
|
+
|
|
1385
|
+
:param logger_index: indicates logger
|
|
1386
|
+
:param full_names: of messages and symbols
|
|
1387
|
+
|
|
1388
|
+
"""
|
|
1389
|
+
expo_filter = self.logging_collection.item(logger_index).exporter.filter
|
|
1390
|
+
for name in full_names:
|
|
1391
|
+
expo_filter.add(name)
|
|
1392
|
+
|
|
1393
|
+
def start_export(self, logger_index: int):
|
|
1394
|
+
"""Starts the export/conversion of exporter.
|
|
1395
|
+
|
|
1396
|
+
:param logger_index: indicates logger
|
|
1397
|
+
|
|
1398
|
+
"""
|
|
1399
|
+
self.logging_collection.item(logger_index).exporter.save()
|
|
1400
|
+
|
|
1401
|
+
def set_configuration_modified(self, modified: bool) -> None:
|
|
1402
|
+
"""Change status of configuration.
|
|
1403
|
+
|
|
1404
|
+
Modified=False makes it possible to load other config or close
|
|
1405
|
+
the application without the popup "Do you want to save changes?".
|
|
1406
|
+
Without that the application will stop on that popup.
|
|
1407
|
+
|
|
1408
|
+
:param modified: change configuration flag
|
|
1409
|
+
|
|
1410
|
+
"""
|
|
1411
|
+
self.configuration_com_obj.Modified = modified
|
|
1412
|
+
|
|
1330
1413
|
|
|
1331
1414
|
def wait(timeout_seconds=0.1):
|
|
1332
1415
|
"""Waits for a specified timeout, pumping Windows messages.
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""CANoe COM objects related to logging setup"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import win32com
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger('CANOE_LOG')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoggingCollection:
|
|
11
|
+
"""Collection of all Logging Blocks of the current configuration."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, logging_collection_com):
|
|
14
|
+
self._com = win32com.client.Dispatch(logging_collection_com)
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def count(self) -> int:
|
|
18
|
+
"""This property returns the number of objects inside the collection."""
|
|
19
|
+
return int(self._com.Count)
|
|
20
|
+
|
|
21
|
+
def item(self, index: int) -> "Logging":
|
|
22
|
+
"""This property returns an object from the collection."""
|
|
23
|
+
return Logging(self._com.Item(index))
|
|
24
|
+
|
|
25
|
+
def add(self, full_name: str) -> "Logging":
|
|
26
|
+
"""This method adds a logging block to the Measurement Setup.
|
|
27
|
+
|
|
28
|
+
:param full_name: full path to log file as "C:/file.(asc|blf|mf4)", may have
|
|
29
|
+
field functions like {IncMeasurement} in the file name
|
|
30
|
+
"""
|
|
31
|
+
return Logging(self._com.Add(full_name))
|
|
32
|
+
|
|
33
|
+
def remove(self, index: int):
|
|
34
|
+
"""This method removes a logging block (Logging) from a logging collection."""
|
|
35
|
+
self._com.Remove(index)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Logging:
|
|
39
|
+
|
|
40
|
+
"""The Logging object represents a Logging Block in the current configuration.
|
|
41
|
+
|
|
42
|
+
The filename extension of the logging file which is specified with this property
|
|
43
|
+
determines the logging format. The full name can now also contain field functions;
|
|
44
|
+
e.g. Logging.FullName = "LOGFILE_M{IncMeasurement}.ASC
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self, logging_com):
|
|
49
|
+
self._com = win32com.client.Dispatch(logging_com)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def exporter(self) -> "Exporter":
|
|
53
|
+
"""This property returns an Exporter object."""
|
|
54
|
+
return Exporter(self._com.Exporter)
|
|
55
|
+
|
|
56
|
+
def file_name_options(self):
|
|
57
|
+
"""This property returns a LoggingFileNameOptions object."""
|
|
58
|
+
raise NotImplementedError("FileNameOptions access is not implemented yet.")
|
|
59
|
+
|
|
60
|
+
def filter(self):
|
|
61
|
+
"""This property returns a LoggingFilter object."""
|
|
62
|
+
raise NotImplementedError("FileNameOptions access is not implemented yet.")
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def full_name(self) -> str:
|
|
66
|
+
"""This property sets or determines the complete path to the logging file."""
|
|
67
|
+
return self._com.FullName
|
|
68
|
+
|
|
69
|
+
@full_name.setter
|
|
70
|
+
def full_name(self, fullname: str):
|
|
71
|
+
self._com.FullName = fullname
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def trigger(self) -> "Trigger":
|
|
75
|
+
"""This property returns a Trigger object."""
|
|
76
|
+
return Trigger(self._com.Trigger)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Trigger:
|
|
80
|
+
|
|
81
|
+
"""Trigger block located before the Logging Block in the Measurement Setup."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, trigger_com):
|
|
84
|
+
self._com = win32com.client.Dispatch(trigger_com)
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def active(self) -> bool:
|
|
88
|
+
"""This property sets or returns the status of the trigger."""
|
|
89
|
+
return self._com.Active
|
|
90
|
+
|
|
91
|
+
@active.setter
|
|
92
|
+
def active(self, _active: bool):
|
|
93
|
+
self._com.Active = _active
|
|
94
|
+
|
|
95
|
+
def start(self):
|
|
96
|
+
"""This method starts the trigger."""
|
|
97
|
+
self._com.Start()
|
|
98
|
+
|
|
99
|
+
def stop(self):
|
|
100
|
+
"""This method stops the trigger."""
|
|
101
|
+
self._com.Stop()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class Exporter:
|
|
105
|
+
|
|
106
|
+
"""Export dialog.
|
|
107
|
+
|
|
108
|
+
The Exporter object represents an export dialog, as it can
|
|
109
|
+
be used e.g. in a Logging Block in the Measurement Setup.
|
|
110
|
+
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def __init__(self, exporter_com):
|
|
114
|
+
self._com = win32com.client.Dispatch(exporter_com)
|
|
115
|
+
|
|
116
|
+
def destinations(self):
|
|
117
|
+
"""Returns a Files object for the destination files of an exporter.
|
|
118
|
+
|
|
119
|
+
On the first access to the destination files of an exporter the Files object
|
|
120
|
+
contains the destination file as it is defined in the according Export dialog.
|
|
121
|
+
|
|
122
|
+
If no destination file is set a destination file is derived from the source
|
|
123
|
+
file. Name and path of this destination file correspond to the name and path
|
|
124
|
+
of the source file. The format is CSV. If as many destination files as source
|
|
125
|
+
files are given, an according destination file will be generated for each
|
|
126
|
+
source file (n to n).
|
|
127
|
+
|
|
128
|
+
If the numbers of destination files and source files don't match, the number of
|
|
129
|
+
destination files must be 1. All source files will be merged into the
|
|
130
|
+
destination file (n to 1).
|
|
131
|
+
"""
|
|
132
|
+
raise NotImplementedError("Destinations access is not implemented yet.")
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def filter(self) -> "Filter":
|
|
136
|
+
"""Returns a Filter object"""
|
|
137
|
+
return Filter(self._com.Filter)
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def messages(self) -> list["Message"]:
|
|
141
|
+
"""Returns all messages that have been detected during loading.
|
|
142
|
+
|
|
143
|
+
Consist of messages that come from the source files of an exporter
|
|
144
|
+
and can be exported/converted.
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
messages_collection = Messages(self._com.Symbols)
|
|
148
|
+
messages = []
|
|
149
|
+
for i in range(1, messages_collection.count + 1):
|
|
150
|
+
messages.append(messages_collection.item(i))
|
|
151
|
+
return messages
|
|
152
|
+
|
|
153
|
+
def settings(self):
|
|
154
|
+
"""Returns an ExporterSettings object."""
|
|
155
|
+
raise NotImplementedError("FileNameOptions access is not implemented yet.")
|
|
156
|
+
|
|
157
|
+
def sources(self):
|
|
158
|
+
"""Returns Files object for each source file of the exporter or offline source.
|
|
159
|
+
|
|
160
|
+
On the first access to the source files of the exporter the Files object
|
|
161
|
+
contains the source file as defined in the Export dialog.
|
|
162
|
+
|
|
163
|
+
Typically this is the logging file as set in the Logging Block, as long as no
|
|
164
|
+
other source file has been defined manually.
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
raise NotImplementedError("FileNameOptions access is not implemented yet.")
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def symbols(self) -> list["ExporterSymbol"]:
|
|
171
|
+
"""Returns all symbols that have been detected during loading the source files.
|
|
172
|
+
|
|
173
|
+
This includes signals, system variables and bus statistics information
|
|
174
|
+
that can be exported/converted.
|
|
175
|
+
|
|
176
|
+
"""
|
|
177
|
+
symbols_collection = ExporterSymbols(self._com.Symbols)
|
|
178
|
+
symbols = []
|
|
179
|
+
for i in range(1, symbols_collection.count + 1):
|
|
180
|
+
symbols.append(symbols_collection.item(i))
|
|
181
|
+
return symbols
|
|
182
|
+
|
|
183
|
+
def time_section(self):
|
|
184
|
+
"""Returns the TimeSection object."""
|
|
185
|
+
raise NotImplementedError("TimeSection access is not implemented yet.")
|
|
186
|
+
|
|
187
|
+
def load(self):
|
|
188
|
+
"""Loads source files of an exporter and determines the signals and messages.
|
|
189
|
+
|
|
190
|
+
If several source files are set, all signals and messages of all
|
|
191
|
+
source files are determined.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
self._com.Load()
|
|
195
|
+
|
|
196
|
+
def save(self, no_prompt_user: bool = True):
|
|
197
|
+
"""Starts the export/conversion.
|
|
198
|
+
|
|
199
|
+
Although the parameter noPromptUser is set to True, the function will fail
|
|
200
|
+
if a failure situation occurs and the storage cannot be performed. Possible
|
|
201
|
+
failure situations are e.g. write-protection or disk full.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
self._com.Save(noPromptUser=no_prompt_user)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Filter:
|
|
208
|
+
"""Represents a Pass Filter for messages and signals in usage with an exporter."""
|
|
209
|
+
|
|
210
|
+
def __init__(self, filter_com):
|
|
211
|
+
self._com = win32com.client.Dispatch(filter_com)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def count(self) -> int:
|
|
215
|
+
"""This property returns the number of objects inside the collection."""
|
|
216
|
+
return int(self._com.Count)
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def enabled(self) -> bool:
|
|
220
|
+
"""This property activates/deactivates a Filter object or returns its state.
|
|
221
|
+
|
|
222
|
+
The initial value of this property is False.
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
return self._com.Enabled
|
|
226
|
+
|
|
227
|
+
@enabled.setter
|
|
228
|
+
def enabled(self, enabled: bool):
|
|
229
|
+
self._com.Enabled = enabled
|
|
230
|
+
|
|
231
|
+
def item(self, index):
|
|
232
|
+
"""This property returns an object from the collection."""
|
|
233
|
+
raise NotImplementedError("Item access is not implemented yet.")
|
|
234
|
+
|
|
235
|
+
def add(self, fullname: str):
|
|
236
|
+
"""This method adds a message or a signal to the explorer's filter.
|
|
237
|
+
|
|
238
|
+
The Exporter object needs fully qualified names of all messages and signals
|
|
239
|
+
that have to be taken into consideration during export or conversion:
|
|
240
|
+
|
|
241
|
+
Messages:
|
|
242
|
+
<DatabaseName>::<MessageName>
|
|
243
|
+
Signals:
|
|
244
|
+
<DatabaseName>::<MessageName>::<SignalName>
|
|
245
|
+
System variables:
|
|
246
|
+
<Namespace>::<SystemVariable>
|
|
247
|
+
Environment variables:
|
|
248
|
+
<DatabaseName>::<EnvironmentVariable>
|
|
249
|
+
|
|
250
|
+
When performing a message-oriented conversion only messages, system variables
|
|
251
|
+
and environment variables are taken into consideration. Any (even specified)
|
|
252
|
+
signals will be ignored.
|
|
253
|
+
|
|
254
|
+
When performing a signal-orientierted export messages, signals, system variables
|
|
255
|
+
and environment variables are taken into consideration. All signals included in
|
|
256
|
+
specified messages, system variables and environment variables will be
|
|
257
|
+
exported as well.
|
|
258
|
+
|
|
259
|
+
Besides using their symbolic name messages can be declared by using their
|
|
260
|
+
numeric ID, which either can be decimal or hexadecimal.
|
|
261
|
+
|
|
262
|
+
:param fullname: of the signal or message
|
|
263
|
+
|
|
264
|
+
"""
|
|
265
|
+
self._com.Add(fullname)
|
|
266
|
+
|
|
267
|
+
def clear(self):
|
|
268
|
+
"""This method clears all messages and signals of the filter."""
|
|
269
|
+
self._com.Clear()
|
|
270
|
+
|
|
271
|
+
def remove(self, index: int):
|
|
272
|
+
"""This method removes a message or a signal from the filter.
|
|
273
|
+
|
|
274
|
+
:param index: number
|
|
275
|
+
|
|
276
|
+
"""
|
|
277
|
+
self._com.Remove(index)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class Messages:
|
|
281
|
+
|
|
282
|
+
"""Collection of messages."""
|
|
283
|
+
|
|
284
|
+
def __init__(self, messages_com):
|
|
285
|
+
self._com = win32com.client.Dispatch(messages_com)
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def count(self) -> int:
|
|
289
|
+
"""This property returns the number of objects inside the collection."""
|
|
290
|
+
return int(self._com.Count)
|
|
291
|
+
|
|
292
|
+
def item(self, index: int) -> "Message":
|
|
293
|
+
"""This property returns an object from the collection."""
|
|
294
|
+
return Message(self._com.Item(index))
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class ExporterSymbols:
|
|
298
|
+
|
|
299
|
+
"""Collection of signals."""
|
|
300
|
+
|
|
301
|
+
def __init__(self, symbols_com):
|
|
302
|
+
self._com = win32com.client.Dispatch(symbols_com)
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def count(self) -> int:
|
|
306
|
+
"""This property returns the number of objects inside the collection."""
|
|
307
|
+
return int(self._com.Count)
|
|
308
|
+
|
|
309
|
+
def item(self, index: int) -> "ExporterSymbol":
|
|
310
|
+
"""This property returns an object from the collection."""
|
|
311
|
+
return ExporterSymbol(self._com.Item(index))
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class Message:
|
|
315
|
+
|
|
316
|
+
"""The Message object represents a single message."""
|
|
317
|
+
|
|
318
|
+
def __init__(self, message_com):
|
|
319
|
+
self._com = win32com.client.Dispatch(message_com)
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def full_name(self) -> str:
|
|
323
|
+
"""This property determines the fully qualified name of a message.
|
|
324
|
+
|
|
325
|
+
The following format is used: <DatabaseName>::<MessageName>
|
|
326
|
+
|
|
327
|
+
"""
|
|
328
|
+
return self._com.FullName
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class ExporterSymbol:
|
|
332
|
+
|
|
333
|
+
"""Symbol (signal, system variable or bus statistics information).
|
|
334
|
+
|
|
335
|
+
Found in a source file, loaded by the Exporter.
|
|
336
|
+
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
def __init__(self, message_com):
|
|
340
|
+
self._com = win32com.client.Dispatch(message_com)
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def full_name(self) -> str:
|
|
344
|
+
"""This property returns the fully qualified symbol name."""
|
|
345
|
+
return self._com.FullName
|
|
@@ -16,17 +16,14 @@ class PyCanoeLogger:
|
|
|
16
16
|
self.log = logging.getLogger('CANOE_LOG')
|
|
17
17
|
self.log.handlers.clear()
|
|
18
18
|
self.log.propagate = False
|
|
19
|
-
self.__py_canoe_log_initialization(py_canoe_log_dir)
|
|
20
|
-
|
|
21
|
-
def __py_canoe_log_initialization(self, py_canoe_log_dir):
|
|
22
19
|
self.log.setLevel(logging.DEBUG)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
self.log.addHandler(
|
|
20
|
+
self.__log_format = logging.Formatter("%(asctime)s [CANOE_LOG] [%(levelname)-4.8s] %(message)s")
|
|
21
|
+
self.__handler = logging.StreamHandler(sys.stdout)
|
|
22
|
+
self.__handler.setFormatter(self.__log_format)
|
|
23
|
+
self.log.addHandler(self.__handler)
|
|
27
24
|
if py_canoe_log_dir != '' and not os.path.exists(py_canoe_log_dir):
|
|
28
25
|
os.makedirs(py_canoe_log_dir, exist_ok=True)
|
|
29
26
|
if os.path.exists(py_canoe_log_dir):
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
self.log.addHandler(
|
|
27
|
+
file_handler = handlers.RotatingFileHandler(fr'{py_canoe_log_dir}\py_canoe.log', maxBytes=0, encoding='utf-8')
|
|
28
|
+
file_handler.setFormatter(self.__log_format)
|
|
29
|
+
self.log.addHandler(file_handler)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
[
|
|
1
|
+
[project]
|
|
2
2
|
name = "py_canoe"
|
|
3
|
-
version = "3.0.
|
|
3
|
+
version = "3.0.4"
|
|
4
4
|
description = "Python CANoe Package"
|
|
5
|
-
|
|
6
|
-
license = "LICENSE"
|
|
5
|
+
license = { text = "LICENSE" }
|
|
7
6
|
readme = "README.md"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
documentation = "https://chaitu-ycr.github.io/py_canoe/"
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
|
+
authors = [{ name = "chaitu-ycr", email = "chaitu.ycr@gmail.com" }]
|
|
11
9
|
keywords = ["Vector", "CANoe", "py_canoe"]
|
|
12
10
|
classifiers = [
|
|
13
11
|
"Programming Language :: Python :: 3",
|
|
@@ -15,16 +13,23 @@ classifiers = [
|
|
|
15
13
|
"Operating System :: Microsoft :: Windows",
|
|
16
14
|
"Topic :: Software Development :: Embedded Systems"
|
|
17
15
|
]
|
|
16
|
+
dynamic = [ "dependencies" ]
|
|
17
|
+
|
|
18
|
+
[project.urls]
|
|
19
|
+
homepage = "https://github.com/chaitu-ycr/py_canoe"
|
|
20
|
+
repository = "https://github.com/chaitu-ycr/py_canoe"
|
|
21
|
+
documentation = "https://chaitu-ycr.github.io/py_canoe/"
|
|
18
22
|
|
|
19
23
|
[tool.poetry.dependencies]
|
|
20
|
-
python = "
|
|
21
|
-
pywin32 = "
|
|
24
|
+
python = ">=3.9"
|
|
25
|
+
pywin32 = "*"
|
|
22
26
|
|
|
23
27
|
[tool.poetry.group.dev.dependencies]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pytest
|
|
28
|
+
mkdocs-material = "*"
|
|
29
|
+
mkdocstrings-python = "*"
|
|
30
|
+
mkdocs-include-markdown-plugin = "*"
|
|
31
|
+
pytest = "*"
|
|
32
|
+
pytest-html = "*"
|
|
28
33
|
|
|
29
34
|
[build-system]
|
|
30
35
|
requires = ["poetry-core"]
|
|
File without changes
|