powertrain-build 1.13.1__py3-none-any.whl → 1.13.3.dev3__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.
- powertrain_build/__init__.py +40 -40
- powertrain_build/__main__.py +6 -6
- powertrain_build/a2l.py +582 -582
- powertrain_build/a2l_merge.py +650 -650
- powertrain_build/a2l_templates.py +717 -717
- powertrain_build/build.py +985 -985
- powertrain_build/build_defs.py +309 -309
- powertrain_build/build_proj_config.py +690 -690
- powertrain_build/check_interface.py +575 -575
- powertrain_build/cli.py +141 -141
- powertrain_build/config.py +542 -542
- powertrain_build/core.py +395 -395
- powertrain_build/core_dummy.py +343 -343
- powertrain_build/create_conversion_table.py +73 -73
- powertrain_build/dids.py +916 -916
- powertrain_build/dummy.py +157 -157
- powertrain_build/dummy_spm.py +252 -252
- powertrain_build/environmentcheck.py +52 -52
- powertrain_build/ext_dbg.py +255 -255
- powertrain_build/ext_var.py +327 -327
- powertrain_build/feature_configs.py +301 -301
- powertrain_build/gen_allsysteminfo.py +227 -227
- powertrain_build/gen_label_split.py +449 -449
- powertrain_build/handcode_replacer.py +124 -124
- powertrain_build/html_report.py +133 -133
- powertrain_build/interface/__init__.py +4 -4
- powertrain_build/interface/application.py +511 -511
- powertrain_build/interface/base.py +500 -500
- powertrain_build/interface/csp_api.py +490 -490
- powertrain_build/interface/device_proxy.py +677 -677
- powertrain_build/interface/ems.py +67 -67
- powertrain_build/interface/export_global_vars.py +121 -121
- powertrain_build/interface/generate_adapters.py +132 -132
- powertrain_build/interface/generate_hi_interface.py +87 -87
- powertrain_build/interface/generate_service.py +69 -69
- powertrain_build/interface/generate_wrappers.py +147 -147
- powertrain_build/interface/generation_utils.py +142 -142
- powertrain_build/interface/hal.py +194 -194
- powertrain_build/interface/model_yaml_verification.py +348 -348
- powertrain_build/interface/service.py +296 -296
- powertrain_build/interface/simulink.py +249 -249
- powertrain_build/interface/update_call_sources.py +180 -180
- powertrain_build/interface/update_model_yaml.py +186 -186
- powertrain_build/interface/zone_controller.py +362 -362
- powertrain_build/lib/__init__.py +4 -4
- powertrain_build/lib/helper_functions.py +127 -127
- powertrain_build/lib/logger.py +55 -55
- powertrain_build/matlab_scripts/CodeGen/BuildAutomationPyBuild.m +78 -78
- powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m +154 -154
- powertrain_build/matlab_scripts/CodeGen/generateTLUnit.m +239 -239
- powertrain_build/matlab_scripts/CodeGen/getAsilClassification.m +28 -28
- powertrain_build/matlab_scripts/CodeGen/modelConfiguredForTL.m +28 -28
- powertrain_build/matlab_scripts/CodeGen/moveDefOutports.m +88 -88
- powertrain_build/matlab_scripts/CodeGen/parseCalMeasData.m +410 -410
- powertrain_build/matlab_scripts/CodeGen/parseCoreIdentifiers.m +139 -139
- powertrain_build/matlab_scripts/CodeGen/parseDIDs.m +141 -141
- powertrain_build/matlab_scripts/CodeGen/parseInPorts.m +106 -106
- powertrain_build/matlab_scripts/CodeGen/parseIncludeConfigs.m +25 -25
- powertrain_build/matlab_scripts/CodeGen/parseModelInfo.m +38 -38
- powertrain_build/matlab_scripts/CodeGen/parseNVM.m +81 -81
- powertrain_build/matlab_scripts/CodeGen/parseOutPorts.m +120 -120
- powertrain_build/matlab_scripts/CodeGen/parsePreProcBlks.m +23 -23
- powertrain_build/matlab_scripts/CodeGen/struct2JSON.m +128 -128
- powertrain_build/matlab_scripts/CodeGen/updateCodeSwConfig.m +31 -31
- powertrain_build/matlab_scripts/Init_PyBuild.m +91 -91
- powertrain_build/matlab_scripts/__init__.py +2 -2
- powertrain_build/matlab_scripts/helperFunctions/Get_Full_Name.m +46 -46
- powertrain_build/matlab_scripts/helperFunctions/Get_SrcLines.m +12 -12
- powertrain_build/matlab_scripts/helperFunctions/Init_Models.m +78 -78
- powertrain_build/matlab_scripts/helperFunctions/Init_Projects.m +67 -67
- powertrain_build/matlab_scripts/helperFunctions/Read_Units.m +34 -34
- powertrain_build/matlab_scripts/helperFunctions/SetProjectTimeSamples.m +26 -26
- powertrain_build/matlab_scripts/helperFunctions/Strip_Suffix.m +16 -16
- powertrain_build/matlab_scripts/helperFunctions/followLink.m +118 -118
- powertrain_build/matlab_scripts/helperFunctions/getCodeSwitches.m +50 -50
- powertrain_build/matlab_scripts/helperFunctions/getConsumerBlocks.m +30 -30
- powertrain_build/matlab_scripts/helperFunctions/getDefBlock.m +39 -39
- powertrain_build/matlab_scripts/helperFunctions/getDefOutport.m +58 -58
- powertrain_build/matlab_scripts/helperFunctions/getDstBlocks.m +19 -19
- powertrain_build/matlab_scripts/helperFunctions/getDstLines.m +13 -13
- powertrain_build/matlab_scripts/helperFunctions/getInterfaceSignals.m +37 -37
- powertrain_build/matlab_scripts/helperFunctions/getName.m +37 -37
- powertrain_build/matlab_scripts/helperFunctions/getPath.m +6 -6
- powertrain_build/matlab_scripts/helperFunctions/getProperValue.m +21 -21
- powertrain_build/matlab_scripts/helperFunctions/getSrcBlocks.m +19 -19
- powertrain_build/matlab_scripts/helperFunctions/getSrcLines.m +13 -13
- powertrain_build/matlab_scripts/helperFunctions/loadLibraries.m +10 -10
- powertrain_build/matlab_scripts/helperFunctions/loadjson.m +6 -6
- powertrain_build/matlab_scripts/helperFunctions/modifyEnumStructField.m +21 -21
- powertrain_build/matlab_scripts/helperFunctions/removeConfigDuplicates.m +31 -31
- powertrain_build/matlab_scripts/helperFunctions/sortSystemByClass.m +26 -26
- powertrain_build/matlab_scripts/helperFunctions/tl_getfast.m +89 -89
- powertrain_build/matlab_scripts/helperFunctions/topLevelSystem.m +20 -20
- powertrain_build/matlab_scripts/helperFunctions/updateModels.m +131 -131
- powertrain_build/memory_section.py +224 -224
- powertrain_build/nvm_def.py +729 -729
- powertrain_build/problem_logger.py +86 -86
- powertrain_build/pt_matlab.py +430 -430
- powertrain_build/pt_win32.py +144 -144
- powertrain_build/replace_compu_tab_ref.py +105 -105
- powertrain_build/rte_dummy.py +254 -254
- powertrain_build/sched_funcs.py +209 -207
- powertrain_build/signal.py +7 -7
- powertrain_build/signal_if_html_rep.py +221 -221
- powertrain_build/signal_if_html_rep_all.py +302 -302
- powertrain_build/signal_incons_html_rep.py +180 -180
- powertrain_build/signal_incons_html_rep_all.py +366 -366
- powertrain_build/signal_incons_html_rep_base.py +168 -168
- powertrain_build/signal_inconsistency_check.py +641 -641
- powertrain_build/signal_interfaces.py +864 -864
- powertrain_build/templates/Index_SigCheck_All.html +22 -22
- powertrain_build/templates/Index_SigIf_All.html +19 -19
- powertrain_build/types.py +218 -218
- powertrain_build/unit_configs.py +419 -419
- powertrain_build/user_defined_types.py +660 -660
- powertrain_build/versioncheck.py +66 -66
- powertrain_build/wrapper.py +512 -512
- powertrain_build/xlrd_csv.py +87 -87
- powertrain_build/zone_controller/__init__.py +4 -4
- powertrain_build/zone_controller/calibration.py +176 -176
- powertrain_build/zone_controller/composition_yaml.py +880 -878
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/METADATA +100 -100
- powertrain_build-1.13.3.dev3.dist-info/RECORD +130 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/WHEEL +1 -1
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/LICENSE +202 -202
- powertrain_build-1.13.3.dev3.dist-info/pbr.json +1 -0
- powertrain_build-1.13.1.dist-info/RECORD +0 -130
- powertrain_build-1.13.1.dist-info/pbr.json +0 -1
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/entry_points.txt +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/AUTHORS +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/NOTICE +0 -0
- {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/top_level.txt +0 -0
powertrain_build/a2l_merge.py
CHANGED
|
@@ -1,650 +1,650 @@
|
|
|
1
|
-
# Copyright 2024 Volvo Car Corporation
|
|
2
|
-
# Licensed under Apache 2.0.
|
|
3
|
-
|
|
4
|
-
# -*- coding: utf-8 -*-
|
|
5
|
-
"""Module for merging of a2l-files."""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import os
|
|
9
|
-
import re
|
|
10
|
-
from string import Template
|
|
11
|
-
|
|
12
|
-
from powertrain_build.lib.helper_functions import deep_dict_update
|
|
13
|
-
from powertrain_build.problem_logger import ProblemLogger
|
|
14
|
-
from powertrain_build.a2l_templates import A2lProjectTemplate, A2lSilverTemplate
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class A2lMerge(ProblemLogger):
|
|
18
|
-
"""Class for merging of a2l-files."""
|
|
19
|
-
|
|
20
|
-
def __init__(self, prj_cfg, ucfg, a2l_files_unit, a2l_files_gen):
|
|
21
|
-
"""Merge a2l-files based on provided project configuration.
|
|
22
|
-
|
|
23
|
-
Removes symbols not included in the projects unit-config files.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
prj_cfg (obj): Project config.
|
|
27
|
-
ucfg (obj): Unit config.
|
|
28
|
-
a2l_files_unit (list of str): Files to merge.
|
|
29
|
-
a2l_files_gen (list of str): Files to merge.
|
|
30
|
-
"""
|
|
31
|
-
super().__init__()
|
|
32
|
-
self._prj_cfg = prj_cfg
|
|
33
|
-
self._unit_cfg = ucfg
|
|
34
|
-
self._per_unit_cfg = ucfg.get_per_unit_cfg()
|
|
35
|
-
# generate the a2l string
|
|
36
|
-
self._blks = {}
|
|
37
|
-
self._removed_symbols = []
|
|
38
|
-
self.a2l = ""
|
|
39
|
-
|
|
40
|
-
# ----- Example blocks in a2l (TargetLink) -----
|
|
41
|
-
#
|
|
42
|
-
# /begin CHARACTERISTIC
|
|
43
|
-
# cVc_B_SeriesHev /* Name */
|
|
44
|
-
# "Series hybrid" /* LongIdentifier */
|
|
45
|
-
# VALUE /* Type */
|
|
46
|
-
# 0x00000000 /* address: cVc_B_SeriesHev */
|
|
47
|
-
# UBYTE_COL_DIRECT /* Deposit */
|
|
48
|
-
# 0 /* MaxDiff */
|
|
49
|
-
# Scaling_3 /* Conversion */
|
|
50
|
-
# 0 /* LowerLimit */
|
|
51
|
-
# 1 /* UpperLimit */
|
|
52
|
-
# /end CHARACTERISTIC
|
|
53
|
-
#
|
|
54
|
-
# Example of Bosch-nvm signal in nvm:
|
|
55
|
-
#
|
|
56
|
-
# /begin MEASUREMENT
|
|
57
|
-
# nvm_list_32._sVcDclVu_D_Markow /* Name */
|
|
58
|
-
# "No description given" /* LongIdentifier */
|
|
59
|
-
# ULONG /* Datatype */
|
|
60
|
-
# VcNvm_1_0_None /* Conversion */
|
|
61
|
-
# 1 /* Resolution */
|
|
62
|
-
# 0 /* Accuracy */
|
|
63
|
-
# 0 /* LowerLimit */
|
|
64
|
-
# 4294967295 /* UpperLimit */
|
|
65
|
-
# READ_WRITE
|
|
66
|
-
# MATRIX_DIM 152 1 1
|
|
67
|
-
# ECU_ADDRESS 0x00000000
|
|
68
|
-
# /end MEASUREMENT
|
|
69
|
-
#
|
|
70
|
-
# ----- Example blocks in a2l (Embedded Coder) -----
|
|
71
|
-
#
|
|
72
|
-
# /begin MEASUREMENT
|
|
73
|
-
# /* Name */ sVcAesVe_md_VolmcOffs
|
|
74
|
-
# /* Long identifier */ "Volumetric cylinder mass flow offset"
|
|
75
|
-
# /* Data type */ FLOAT32_IEEE
|
|
76
|
-
# /* Conversion method */ VcAesVe_CM_Float32_g_s
|
|
77
|
-
# /* Resolution (Not used) */ 0
|
|
78
|
-
# /* Accuracy (Not used) */ 0
|
|
79
|
-
# /* Lower limit */ -100.0
|
|
80
|
-
# /* Upper limit */ 100.0
|
|
81
|
-
# ECU_ADDRESS 0x0000 /* @ECU_Address@sVcAesVe_md_VolmcOffs@ */
|
|
82
|
-
# /end MEASUREMENT
|
|
83
|
-
#
|
|
84
|
-
# /begin CHARACTERISTIC
|
|
85
|
-
# /* Name */ cVcAesVe_D_VolmcCmpSel
|
|
86
|
-
# /* Long Identifier */ "Select compensation factor characterizing deviation from nominal voleff"
|
|
87
|
-
# /* Type */ VALUE
|
|
88
|
-
# /* ECU Address */ 0x0000 /* @ECU_Address@cVcAesVe_D_VolmcCmpSel@ */
|
|
89
|
-
# /* Record Layout */ Scalar_UBYTE
|
|
90
|
-
# /* Maximum Difference */ 0
|
|
91
|
-
# /* Conversion Method */ VcAesVe_CM_uint8
|
|
92
|
-
# /* Lower Limit */ 1.0
|
|
93
|
-
# /* Upper Limit */ 3.0
|
|
94
|
-
# /end CHARACTERISTIC
|
|
95
|
-
|
|
96
|
-
self._block_finder = re.compile(r'(?:\s*\n)*' # Optional blank lines
|
|
97
|
-
r'(\s*/begin (\w+)\s*' # begin <something> block
|
|
98
|
-
r'\n\s*([\w.]+).*?\n' # label. (Bosch-nvm contains the .)
|
|
99
|
-
r'.*?' # block definition
|
|
100
|
-
r'/end\s+\2)', # end <something> block. Same something as before
|
|
101
|
-
flags=re.M | re.DOTALL)
|
|
102
|
-
|
|
103
|
-
self._tl_compu_method_parser = re.compile(
|
|
104
|
-
r'(?:\s*\n)*(?P<compu_method>'
|
|
105
|
-
r'\s*/begin COMPU_METHOD\s*\n'
|
|
106
|
-
r'\s*(?P<name>\w*)\s*(/\* Name \*/)?\s*\n' # Name
|
|
107
|
-
r'\s*"(?P<ID>.*?)"\s*(/\* LongIdentifier \*/.*?)\s*\n' # Long Identifier
|
|
108
|
-
r'\s*(?P<conv_type>[A-Z_]*).*\s*\n' # ConversionType
|
|
109
|
-
r'\s*"(?P<disp_format>.*)"\s*(/\* Format \*/)?\s*\n' # Format
|
|
110
|
-
r'\s*"(?P<unit>.*?)"\s*(/\* Unit \*/)?\s*\n' # Unit
|
|
111
|
-
r'\s*(?P<conversion>.*)\s*\n' # COEFFS
|
|
112
|
-
r'(?P<indentation>\s*)/end COMPU_METHOD)', flags=re.M) # No DOTALL, so .* is [^\n]*
|
|
113
|
-
|
|
114
|
-
# COMPU_METHOD parser that works with files generated by Embedded Coder
|
|
115
|
-
self._ec_compu_method_parser = re.compile(
|
|
116
|
-
r'(?:\s*\n)*(?P<compu_method>'
|
|
117
|
-
r'\s*/begin COMPU_METHOD\s*\n'
|
|
118
|
-
r'\s*(/\* Name of CompuMethod\s*\*/)?\s*(?P<name>\w*)\s*\n' # Name
|
|
119
|
-
r'\s*(/\* Long identifier\s*\*/)?\s*"(?P<ID>.*?)"\s*\n' # Long Identifier
|
|
120
|
-
r'\s*/\* Conversion Type\s*\*/\s*(?P<conv_type>[A-Z_]*)\s*\n' # ConversionType
|
|
121
|
-
r'\s*(/\* Format\s*\*/)?\s*"(?P<disp_format>.*?)"\s*\n' # Format
|
|
122
|
-
r'\s*(/\* Units\s*\*/)?\s*"(?P<unit>.*?)"\s*\n' # Unit
|
|
123
|
-
r'\s*/\* Coefficients\s*\*/\s*(?P<conversion>.*?)\s*\n' # COEFFS
|
|
124
|
-
r'(?P<indentation>\s*)/end COMPU_METHOD)', flags=re.M) # No DOTALL, so .* is [^\n]*
|
|
125
|
-
|
|
126
|
-
self._expr_block_meas_kp_blob_parser = re.compile(
|
|
127
|
-
r'\/begin\s+(?P<keyword>\w+)\s+(?P<class>\w+)\s*\n'
|
|
128
|
-
r'\s*KP_BLOB\s+(?P<address>0x[0-9a-fA-F]+)\s*\n'
|
|
129
|
-
r'\s*\/end\s+\1'
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
self._compu_methods = {}
|
|
133
|
-
self._included_compu_methods = []
|
|
134
|
-
|
|
135
|
-
self._tl_compu_method_template = Template(
|
|
136
|
-
'$indentation/begin COMPU_METHOD\n'
|
|
137
|
-
'$indentation $name /* Name */\n'
|
|
138
|
-
'$indentation "$ID" /* LongIdentifier */\n'
|
|
139
|
-
'$indentation $conv_type /* ConversionType */\n'
|
|
140
|
-
'$indentation "$disp_format" /* Format */\n'
|
|
141
|
-
'$indentation "$unit" /* Unit */\n'
|
|
142
|
-
'$indentation $conversion\n'
|
|
143
|
-
'$indentation/end COMPU_METHOD'
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
# COMPU_METHOD template that looks similar to COMPU_METHOD generated by Embedded Coder
|
|
147
|
-
self._ec_compu_method_template = Template(
|
|
148
|
-
'$indentation/begin COMPU_METHOD\n'
|
|
149
|
-
'$indentation /* Name of CompuMethod */ $name\n'
|
|
150
|
-
'$indentation /* Long identifier */ "$ID"\n'
|
|
151
|
-
'$indentation /* Conversion Type */ $conv_type\n'
|
|
152
|
-
'$indentation /* Format */ "$disp_format"\n'
|
|
153
|
-
'$indentation /* Units */ "$unit"\n'
|
|
154
|
-
'$indentation /* Coefficients */ $conversion\n'
|
|
155
|
-
'$indentation/end COMPU_METHOD'
|
|
156
|
-
)
|
|
157
|
-
|
|
158
|
-
for filename in a2l_files_unit:
|
|
159
|
-
removed_symbols = self._parse_unit(filename)
|
|
160
|
-
self._removed_symbols.extend(removed_symbols)
|
|
161
|
-
self.debug('Loaded %s', filename)
|
|
162
|
-
for filename in a2l_files_gen:
|
|
163
|
-
self._parse_gen(filename)
|
|
164
|
-
self.debug('Loaded %s', filename)
|
|
165
|
-
|
|
166
|
-
def _parse_unit(self, filename):
|
|
167
|
-
"""Parse the unit a2l-files and apply a filter to only active parameters."""
|
|
168
|
-
self.debug('Processing %s', filename)
|
|
169
|
-
with open(filename, 'r', encoding="ISO-8859-1") as a2lfp:
|
|
170
|
-
a2ld = a2lfp.read()
|
|
171
|
-
file_path_parts = os.path.split(filename)
|
|
172
|
-
unit = file_path_parts[1].split('.')[0]
|
|
173
|
-
base_path = file_path_parts[0]
|
|
174
|
-
dcl_match = re.search(r'VcDcl[\w]+Mdl(__[\w]+)', base_path)
|
|
175
|
-
if dcl_match is not None and 'Mdl' not in unit:
|
|
176
|
-
# Hand coded model names including "__" will lead to this, due to name mismatch of .a2l and .json files.
|
|
177
|
-
# E.g. VcDclPtrlMdl__denso:
|
|
178
|
-
# 1. config_VcDclPtrlMdl__denso.json vs VcDclPtrlMdl__denso.a2l.
|
|
179
|
-
# 1.1. Match: unit in self._per_unit_cfg.
|
|
180
|
-
# 2. config_VcDclPtrl__denso.json vs VcDclPtrl.a2l.
|
|
181
|
-
# 2.1. No match: unit not in self._per_unit_cfg.
|
|
182
|
-
old_unit = unit
|
|
183
|
-
unit = unit + dcl_match.group(1)
|
|
184
|
-
self.info(
|
|
185
|
-
'Found unit %s with .a2l and .json file name mismatch. Using new unit name: %s',
|
|
186
|
-
old_unit,
|
|
187
|
-
unit
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
if unit in self._per_unit_cfg:
|
|
191
|
-
u_conf = self._per_unit_cfg[unit]
|
|
192
|
-
code_generator = u_conf['code_generator'] if 'code_generator' in u_conf else 'target_link'
|
|
193
|
-
else:
|
|
194
|
-
u_conf = {}
|
|
195
|
-
code_generator = 'target_link'
|
|
196
|
-
|
|
197
|
-
if code_generator == 'embedded_coder':
|
|
198
|
-
blks = re.findall(r'(?:\s*\n)*(\s*/begin '
|
|
199
|
-
r'(?!PROJECT|HEADER|MODULE|MOD_PAR|MOD_COMMON)(\w+)\s*(?:\n\s*)?'
|
|
200
|
-
r'(?:/\*\s*[\w ]+\s*\*/\s*)?(\w+)([\[\d+\]]*).*?\n.*?/end\s+\2)',
|
|
201
|
-
a2ld, flags=re.M | re.DOTALL)
|
|
202
|
-
else:
|
|
203
|
-
blks = re.findall(r'(?:\s*\n)*(\s*/begin (?!PROJECT|MODULE)(\w+)[\n\s]*'
|
|
204
|
-
r'(\w+(?:\.\w+)?)([\[\d+\]]*).*?\n.*?/end\s+\2)', a2ld,
|
|
205
|
-
flags=re.M | re.DOTALL)
|
|
206
|
-
|
|
207
|
-
compu_method_translators = self._parse_compu_methods(a2ld, unit)
|
|
208
|
-
unit_blks = {}
|
|
209
|
-
removed_symbols = []
|
|
210
|
-
if unit not in self._per_unit_cfg:
|
|
211
|
-
# Handcoded a2l without json-files will lead to this.
|
|
212
|
-
# Add json files for the handcoded a2l!
|
|
213
|
-
# NOTE: Assuming TargetLink
|
|
214
|
-
self.debug('%s is not in the units list. Looking for json.', unit)
|
|
215
|
-
config_filename = os.path.join(
|
|
216
|
-
self._prj_cfg.get_unit_cfg_deliv_dir(),
|
|
217
|
-
f'config_{unit}.json')
|
|
218
|
-
self.debug('Looking for %s', config_filename)
|
|
219
|
-
if os.path.isfile(config_filename):
|
|
220
|
-
with open(config_filename, 'r', encoding="utf-8") as config_file:
|
|
221
|
-
u_conf = json.load(config_file)
|
|
222
|
-
self._handle_config(
|
|
223
|
-
code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators
|
|
224
|
-
)
|
|
225
|
-
else:
|
|
226
|
-
self.warning('%s does not have a unit_cfg json, '
|
|
227
|
-
'including all a2l-parameters', unit)
|
|
228
|
-
for blk_def, type_, label, size in blks:
|
|
229
|
-
if type_ == 'COMPU_METHOD':
|
|
230
|
-
blk_def, label = self._replace_compu_method(blk_def, label, compu_method_translators)
|
|
231
|
-
self.add_block_definition(unit_blks, type_, label, size, blk_def, compu_method_translators)
|
|
232
|
-
else:
|
|
233
|
-
self._handle_config(
|
|
234
|
-
code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators
|
|
235
|
-
)
|
|
236
|
-
deep_dict_update(self._blks, unit_blks)
|
|
237
|
-
return removed_symbols
|
|
238
|
-
|
|
239
|
-
def _handle_config(self, code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators):
|
|
240
|
-
"""Merge all types of ram for the unit."""
|
|
241
|
-
ram = u_conf['inports']
|
|
242
|
-
ram.update(u_conf['outports'])
|
|
243
|
-
ram.update(u_conf['local_vars'])
|
|
244
|
-
# TODO: Function the variables and labels needs to be removed from
|
|
245
|
-
# the FUNCTION block too
|
|
246
|
-
for blk_def, type_, label, size in blks:
|
|
247
|
-
remove_excluded_symbol = True
|
|
248
|
-
inc = False
|
|
249
|
-
if type_ == 'AXIS_PTS':
|
|
250
|
-
if label in u_conf['calib_consts']:
|
|
251
|
-
inc = True
|
|
252
|
-
elif type_ == 'CHARACTERISTIC':
|
|
253
|
-
if label in u_conf['calib_consts']:
|
|
254
|
-
if label in [axis_label for _, axis_type, axis_label, _ in blks if axis_type == 'AXIS_PTS']:
|
|
255
|
-
# AXIS_PTS can be used as CHARACTERISTC but not the other way around.
|
|
256
|
-
# If there are duplicates, use the AXIS_PTS.
|
|
257
|
-
self.debug('Will not add the block for CHARACTERISTC %s, but will keep it as a symbol,'
|
|
258
|
-
' since it exists as AXIS_PTS', label)
|
|
259
|
-
remove_excluded_symbol = False
|
|
260
|
-
inc = False
|
|
261
|
-
else:
|
|
262
|
-
inc = self._handle_axis_ptr_ref_config(u_conf, blk_def, unit)
|
|
263
|
-
elif type_ == 'MEASUREMENT':
|
|
264
|
-
if label in ram:
|
|
265
|
-
key = label if size is None else label + size
|
|
266
|
-
if label in u_conf['outports']:
|
|
267
|
-
# This unit is producing the measurement.
|
|
268
|
-
inc = True
|
|
269
|
-
elif key in unit_blks.get(type_, {}):
|
|
270
|
-
# This unit is not producing it, and it has already been added
|
|
271
|
-
inc = False
|
|
272
|
-
else:
|
|
273
|
-
# This unit is not producing it, but it has not been added
|
|
274
|
-
# Could be external signal, etc.
|
|
275
|
-
inc = True
|
|
276
|
-
elif type_ == 'COMPU_METHOD':
|
|
277
|
-
inc = True
|
|
278
|
-
blk_def, label = self._replace_compu_method(blk_def, label, compu_method_translators)
|
|
279
|
-
else:
|
|
280
|
-
inc = True
|
|
281
|
-
if inc:
|
|
282
|
-
self.add_block_definition(unit_blks, type_, label, size, blk_def, compu_method_translators)
|
|
283
|
-
else:
|
|
284
|
-
if remove_excluded_symbol:
|
|
285
|
-
removed_symbols.append(label + size)
|
|
286
|
-
self.debug('Did not include A2L-blk %s%s', label, size)
|
|
287
|
-
if not self._unit_cfg.check_if_in_unit_cfg(unit, label):
|
|
288
|
-
if type_ != 'COMPU_METHOD':
|
|
289
|
-
self.warning('A2l block %s not in config json file for %s', label, unit)
|
|
290
|
-
|
|
291
|
-
if 'FUNCTION' in unit_blks:
|
|
292
|
-
unit_blks['FUNCTION'] = self._remove_symbols_from_func_blks(
|
|
293
|
-
code_generator, unit_blks['FUNCTION'], removed_symbols
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
if 'GROUP' in unit_blks:
|
|
297
|
-
unit_blks['GROUP'] = self._remove_symbols_from_grp_blks(unit_blks['GROUP'], removed_symbols)
|
|
298
|
-
|
|
299
|
-
def _handle_axis_ptr_ref_config(self, u_conf, blk, unit):
|
|
300
|
-
"""Remove blocks referencing undefined blocks."""
|
|
301
|
-
ref_re = re.compile(r'\s*AXIS_PTS_REF\s*([\w]*)')
|
|
302
|
-
for axis_ptr_ref in ref_re.findall(blk):
|
|
303
|
-
if axis_ptr_ref not in u_conf['calib_consts']:
|
|
304
|
-
self.debug('Excluding due to %s missing in config', axis_ptr_ref)
|
|
305
|
-
return False
|
|
306
|
-
if not self._unit_cfg.check_if_in_unit_cfg(unit, axis_ptr_ref):
|
|
307
|
-
self.debug('Excluding due to %s not active in config', axis_ptr_ref)
|
|
308
|
-
return False
|
|
309
|
-
return True
|
|
310
|
-
|
|
311
|
-
def add_block_definition(self, unit_blks, type_, label, size, blk_def, compu_method_translators):
|
|
312
|
-
"""Add block definition to A2L-file."""
|
|
313
|
-
size = '' if size is None else size
|
|
314
|
-
blk_def = self._replace_conversions(blk_def, compu_method_translators)
|
|
315
|
-
if type_ not in unit_blks:
|
|
316
|
-
unit_blks[type_] = {}
|
|
317
|
-
unit_blks[type_][label + size] = blk_def
|
|
318
|
-
|
|
319
|
-
@staticmethod
|
|
320
|
-
def _parse_func_blk(code_generator, fnc_blk):
|
|
321
|
-
"""Remove the unused symbols from the FUNCTION blocks in the A2L-file.
|
|
322
|
-
Parse the FUNCTION block, TL or EC style based on code_generator.
|
|
323
|
-
"""
|
|
324
|
-
|
|
325
|
-
if code_generator == 'target_link':
|
|
326
|
-
pattern = r'\s*/begin\s+FUNCTION\s*?\n\s*(\w+).*?\n\s*"(.*?)".*?\n(.*)'
|
|
327
|
-
else:
|
|
328
|
-
pattern = r'\s*/begin\s+FUNCTION\s*?\n\s*.*\*/\s*(\w+).*?\n\s*.*\*/\s*"(.*?)".*?\n(.*)'
|
|
329
|
-
res = re.match(pattern, fnc_blk, flags=re.M | re.DOTALL)
|
|
330
|
-
fnc_name = res.group(1)
|
|
331
|
-
long_id = res.group(2)
|
|
332
|
-
fnc_dict = {
|
|
333
|
-
'fnc_name': fnc_name,
|
|
334
|
-
'long_id': long_id,
|
|
335
|
-
'body': {}
|
|
336
|
-
}
|
|
337
|
-
fnc_body = res.group(3)
|
|
338
|
-
sb_res = re.findall(r'\s*/begin\s+(\w+[\[\d\]]*)\s*\n\s*'
|
|
339
|
-
r'(.*?\n)\s*/end \1', fnc_body, flags=re.M | re.DOTALL)
|
|
340
|
-
for sb_name, sub_blk in sb_res:
|
|
341
|
-
symbols = set(re.findall(r'\s*(\w+(?:\.\w+)?[\[\d\]]*).*?\n', sub_blk, flags=re.M))
|
|
342
|
-
fnc_dict['body'][sb_name] = symbols
|
|
343
|
-
return fnc_dict
|
|
344
|
-
|
|
345
|
-
@staticmethod
|
|
346
|
-
def _parse_grp_blk(grp_blk):
|
|
347
|
-
"""Remove the unused symbols from the GROUP blocks in the A2L-file."""
|
|
348
|
-
# parse the GROUP block
|
|
349
|
-
res = re.match(r'\s*/begin\s+GROUP\s*?\n\s*.*\*/\s*(\w+).*?\n\s*.*\*/\s*"(.*?)".*?\n(.*)',
|
|
350
|
-
grp_blk, flags=re.M | re.DOTALL)
|
|
351
|
-
fnc_name = res.group(1)
|
|
352
|
-
long_id = res.group(2)
|
|
353
|
-
fnc_dict = {
|
|
354
|
-
'fnc_name': fnc_name,
|
|
355
|
-
'long_id': long_id,
|
|
356
|
-
'body': {}
|
|
357
|
-
}
|
|
358
|
-
fnc_body = res.group(3)
|
|
359
|
-
sb_res = re.findall(r'\s*/begin\s+(\w+[\[\d\]]*)\s*\n\s*'
|
|
360
|
-
r'(.*?\n)\s*/end \1', fnc_body, flags=re.M | re.DOTALL)
|
|
361
|
-
for sb_name, sub_blk in sb_res:
|
|
362
|
-
symbols = set(re.findall(r'\s*(\w+(?:\.\w+)?[\[\d\]]*).*?\n', sub_blk, flags=re.M))
|
|
363
|
-
fnc_dict['body'][sb_name] = symbols
|
|
364
|
-
return fnc_dict
|
|
365
|
-
|
|
366
|
-
def _recursive_remove(self, a2l_dict, name):
|
|
367
|
-
"""Remove symbols from A2L dict (e.g. group or function)."""
|
|
368
|
-
if name in a2l_dict:
|
|
369
|
-
blk = a2l_dict[name]
|
|
370
|
-
if 'SUB_FUNCTION' in blk:
|
|
371
|
-
for sub_fnc in blk['SUB_FUNCTION']:
|
|
372
|
-
if self._recursive_remove(a2l_dict, sub_fnc):
|
|
373
|
-
blk['SUB_FUNCTION'] = blk['SUB_FUNCTION'] - set([sub_fnc])
|
|
374
|
-
elif 'SUB_GROUP' in blk:
|
|
375
|
-
for sub_grp in blk['SUB_GROUP']:
|
|
376
|
-
if self._recursive_remove(a2l_dict, sub_grp):
|
|
377
|
-
blk['SUB_GROUP'] = blk['SUB_GROUP'] - set([sub_grp])
|
|
378
|
-
empty = True
|
|
379
|
-
for key in blk:
|
|
380
|
-
if blk[key]:
|
|
381
|
-
empty = False
|
|
382
|
-
break
|
|
383
|
-
if empty:
|
|
384
|
-
a2l_dict.pop(name)
|
|
385
|
-
return True
|
|
386
|
-
return False
|
|
387
|
-
|
|
388
|
-
def _remove_symbols_from_func_blks(self, code_generator, fnc_blks, removed_symbols):
|
|
389
|
-
"""Remove the unused symbols from function blocks.
|
|
390
|
-
|
|
391
|
-
If the function block is empty, it too will be removed.
|
|
392
|
-
first iteration - remove all symbols that have been removed
|
|
393
|
-
second iteration - recusively remover all functions without symbols
|
|
394
|
-
"""
|
|
395
|
-
fnc_dict = {}
|
|
396
|
-
for fnc_name, fnc_blk in fnc_blks.items():
|
|
397
|
-
fnc_dict[fnc_name] = {}
|
|
398
|
-
u_fnc_bdy = self._parse_func_blk(code_generator, fnc_blk)['body']
|
|
399
|
-
sub_blk_types = set(u_fnc_bdy.keys()) - set(['SUB_FUNCTION'])
|
|
400
|
-
for type_ in list(sub_blk_types):
|
|
401
|
-
fnc_dict[fnc_name][type_] = u_fnc_bdy[type_] - set(removed_symbols)
|
|
402
|
-
if 'SUB_FUNCTION' in u_fnc_bdy:
|
|
403
|
-
fnc_dict[fnc_name]['SUB_FUNCTION'] = u_fnc_bdy['SUB_FUNCTION']
|
|
404
|
-
# second iteration - remove empty FUNCTION blocks
|
|
405
|
-
# TODO: Add functionality which parses the the function tree structures
|
|
406
|
-
# And the run recursive remove on all tree roots.
|
|
407
|
-
for fnc_name in fnc_blks.keys():
|
|
408
|
-
self._recursive_remove(fnc_dict, fnc_name)
|
|
409
|
-
# generate new function blocks
|
|
410
|
-
new_fnc_blks = {}
|
|
411
|
-
for fnc_name, fnc_data in fnc_dict.items():
|
|
412
|
-
fnc_blk = f' /begin FUNCTION\n {fnc_name}\t/* Name */\n'
|
|
413
|
-
fnc_blk += " \"\"\t/* LongIdentifier */\n"
|
|
414
|
-
for sub_sec in sorted(fnc_data.keys()):
|
|
415
|
-
sub_sec_data = fnc_data[sub_sec]
|
|
416
|
-
if sub_sec_data:
|
|
417
|
-
fnc_blk += f" /begin {sub_sec}\n"
|
|
418
|
-
for param in sorted(sub_sec_data):
|
|
419
|
-
fnc_blk += f" {param}\t/* Identifier */\n"
|
|
420
|
-
fnc_blk += f" /end {sub_sec}\n"
|
|
421
|
-
fnc_blk += " /end FUNCTION"
|
|
422
|
-
new_fnc_blks[fnc_name] = fnc_blk
|
|
423
|
-
return new_fnc_blks
|
|
424
|
-
|
|
425
|
-
def _remove_symbols_from_grp_blks(self, grp_blks, removed_symbols):
|
|
426
|
-
"""Remove the unused symbols from group blocks.
|
|
427
|
-
|
|
428
|
-
If the group block is empty, it too will be removed.
|
|
429
|
-
first iteration - remove all symbols that have been removed
|
|
430
|
-
second iteration - recusively remover all groups without symbols
|
|
431
|
-
"""
|
|
432
|
-
grp_dict = {}
|
|
433
|
-
for grp_name, grp_blk in grp_blks.items():
|
|
434
|
-
grp_dict[grp_name] = {}
|
|
435
|
-
u_grp_bdy = self._parse_grp_blk(grp_blk)['body']
|
|
436
|
-
sub_blk_types = set(u_grp_bdy.keys()) - set(['SUB_GROUP'])
|
|
437
|
-
for type_ in list(sub_blk_types):
|
|
438
|
-
grp_dict[grp_name][type_] = u_grp_bdy[type_] - set(removed_symbols)
|
|
439
|
-
if 'SUB_GROUP' in u_grp_bdy:
|
|
440
|
-
grp_dict[grp_name]['SUB_GROUP'] = u_grp_bdy['SUB_GROUP']
|
|
441
|
-
# second iteration - remove empty GROUP blocks
|
|
442
|
-
# TODO: Add functionality which parses the the group tree structures
|
|
443
|
-
# And the run recursive remove on all tree roots.
|
|
444
|
-
for grp_name in grp_blks.keys():
|
|
445
|
-
self._recursive_remove(grp_dict, grp_name)
|
|
446
|
-
# generate new group blocks
|
|
447
|
-
new_grp_blks = {}
|
|
448
|
-
for grp_name, grp_data in grp_dict.items():
|
|
449
|
-
grp_blk = f" /begin GROUP \n /* Name */ {grp_name}\n"
|
|
450
|
-
grp_blk += " /* Long identifier */ \"\"\n"
|
|
451
|
-
for sub_sec in sorted(grp_data.keys()):
|
|
452
|
-
sub_sec_data = grp_data[sub_sec]
|
|
453
|
-
if sub_sec_data:
|
|
454
|
-
grp_blk += f" /begin {sub_sec}\n"
|
|
455
|
-
for param in sorted(sub_sec_data):
|
|
456
|
-
grp_blk += f" {param}\n"
|
|
457
|
-
grp_blk += f" /end {sub_sec}\n"
|
|
458
|
-
grp_blk += " /end GROUP"
|
|
459
|
-
new_grp_blks[grp_name] = grp_blk
|
|
460
|
-
|
|
461
|
-
return new_grp_blks
|
|
462
|
-
|
|
463
|
-
def _parse_gen(self, filename):
|
|
464
|
-
"""Parse the generated a2l-files, without filter."""
|
|
465
|
-
self.debug('parsing gen a2l: %s', filename)
|
|
466
|
-
with open(filename, 'r', encoding="utf-8") as a2lfp:
|
|
467
|
-
a2ld = a2lfp.read()
|
|
468
|
-
for blk_def, type_, label in self._block_finder.findall(a2ld):
|
|
469
|
-
self._blks.setdefault(type_, {}).setdefault(label, blk_def)
|
|
470
|
-
|
|
471
|
-
@staticmethod
|
|
472
|
-
def _replace_compu_method(blk_def, label, compu_method_translators):
|
|
473
|
-
"""Replace the compu method block and label."""
|
|
474
|
-
for translator in compu_method_translators:
|
|
475
|
-
if translator['old_compu_method'] == blk_def:
|
|
476
|
-
return translator['new_compu_method'], translator['new_name']
|
|
477
|
-
return blk_def, label
|
|
478
|
-
|
|
479
|
-
def _store_compu_method(self, ID, conv_type, disp_format, unit, conversion, indentation, u_conf):
|
|
480
|
-
"""Stash compu methods that exists in the resulting a2l."""
|
|
481
|
-
key = (ID, conv_type, disp_format, unit, conversion)
|
|
482
|
-
if key in self._compu_methods:
|
|
483
|
-
new_name = self._compu_methods[key]['name']
|
|
484
|
-
new_compu_method = self._compu_methods[key]['method']
|
|
485
|
-
else:
|
|
486
|
-
new_name = 'Scaling_' + str(len(self._compu_methods))
|
|
487
|
-
if 'code_generator' in u_conf and u_conf['code_generator'] == 'embedded_coder':
|
|
488
|
-
new_compu_method = self._ec_compu_method_template.substitute(
|
|
489
|
-
name=new_name,
|
|
490
|
-
ID=ID,
|
|
491
|
-
conv_type=conv_type,
|
|
492
|
-
disp_format=disp_format,
|
|
493
|
-
unit=unit,
|
|
494
|
-
conversion=conversion,
|
|
495
|
-
indentation=indentation
|
|
496
|
-
)
|
|
497
|
-
else:
|
|
498
|
-
new_compu_method = self._tl_compu_method_template.substitute(
|
|
499
|
-
name=new_name,
|
|
500
|
-
ID=ID,
|
|
501
|
-
conv_type=conv_type,
|
|
502
|
-
disp_format=disp_format,
|
|
503
|
-
unit=unit,
|
|
504
|
-
conversion=conversion,
|
|
505
|
-
indentation=indentation
|
|
506
|
-
)
|
|
507
|
-
self._compu_methods.update({key: {'name': new_name,
|
|
508
|
-
'method': new_compu_method}})
|
|
509
|
-
return new_name, new_compu_method
|
|
510
|
-
|
|
511
|
-
@staticmethod
|
|
512
|
-
def _replace_conversions(blk_def, compu_method_translators):
|
|
513
|
-
"""Replace conversion identifiers in a2l block."""
|
|
514
|
-
for translator in compu_method_translators:
|
|
515
|
-
# The following check is faster than running the regex on the block.
|
|
516
|
-
# It DOES give false positives, which is why the regex is used for substitution
|
|
517
|
-
# and we do not immidiately return after one positive
|
|
518
|
-
if translator['old_name'] in blk_def:
|
|
519
|
-
blk_def = translator['regex'].sub(translator['replacement'], blk_def)
|
|
520
|
-
return blk_def
|
|
521
|
-
|
|
522
|
-
def _parse_compu_methods(self, blk_def, unit):
|
|
523
|
-
"""Replace compu methods to not overwrite any of them."""
|
|
524
|
-
compu_method_translators = [] # Translators for one processed a2l file. Needs to be reset between files
|
|
525
|
-
u_conf = self._per_unit_cfg[unit] if unit in self._per_unit_cfg else {}
|
|
526
|
-
if 'code_generator' in u_conf and u_conf['code_generator'] == 'embedded_coder':
|
|
527
|
-
for match in self._ec_compu_method_parser.finditer(blk_def):
|
|
528
|
-
new_name, new_compu_method = self._store_compu_method(
|
|
529
|
-
match['ID'],
|
|
530
|
-
match['conv_type'],
|
|
531
|
-
match['disp_format'],
|
|
532
|
-
match['unit'],
|
|
533
|
-
match['conversion'],
|
|
534
|
-
match['indentation'],
|
|
535
|
-
u_conf
|
|
536
|
-
)
|
|
537
|
-
compu_method_translators.append(
|
|
538
|
-
{
|
|
539
|
-
'new_name': new_name,
|
|
540
|
-
'old_name': match['name'],
|
|
541
|
-
'regex': re.compile(
|
|
542
|
-
r'(\s*)' # beginning
|
|
543
|
-
r'\s*(/\* Conversion [Mm]ethod\s*\*/\s*)' # optional comment
|
|
544
|
-
r'\b{name}\b' # word
|
|
545
|
-
r'('
|
|
546
|
-
r'\s*\n' # newline
|
|
547
|
-
r')'.format(name=match['name']) # end of end-match
|
|
548
|
-
),
|
|
549
|
-
'replacement': r'\1\2{name}\3'.format(name=new_name),
|
|
550
|
-
'old_compu_method': match['compu_method'],
|
|
551
|
-
'new_compu_method': new_compu_method
|
|
552
|
-
}
|
|
553
|
-
)
|
|
554
|
-
else:
|
|
555
|
-
for match in self._tl_compu_method_parser.finditer(blk_def):
|
|
556
|
-
new_name, new_compu_method = self._store_compu_method(
|
|
557
|
-
match['ID'],
|
|
558
|
-
match['conv_type'],
|
|
559
|
-
match['disp_format'],
|
|
560
|
-
match['unit'],
|
|
561
|
-
match['conversion'],
|
|
562
|
-
match['indentation'],
|
|
563
|
-
u_conf
|
|
564
|
-
)
|
|
565
|
-
compu_method_translators.append(
|
|
566
|
-
{
|
|
567
|
-
'new_name': new_name,
|
|
568
|
-
'old_name': match['name'],
|
|
569
|
-
'regex': re.compile(
|
|
570
|
-
r'(\s*)' # beginning
|
|
571
|
-
r'\b{name}\b' # word
|
|
572
|
-
r'(' # start of end-match
|
|
573
|
-
r'\s*(/\* Conversion \*/)?' # optional comment
|
|
574
|
-
r'\s*\n' # newline
|
|
575
|
-
r')'.format(name=match['name']) # end of end-match
|
|
576
|
-
),
|
|
577
|
-
'replacement': r'\1{name}\2'.format(name=new_name),
|
|
578
|
-
'old_compu_method': match['compu_method'],
|
|
579
|
-
'new_compu_method': new_compu_method
|
|
580
|
-
}
|
|
581
|
-
)
|
|
582
|
-
return compu_method_translators
|
|
583
|
-
|
|
584
|
-
def _patch_kp_blob(self, block):
|
|
585
|
-
"""Return updated measurement block text.
|
|
586
|
-
Args:
|
|
587
|
-
block (str): A2L text block
|
|
588
|
-
Returns:
|
|
589
|
-
a2l_text (str): A2L text without KP_BLOB.
|
|
590
|
-
"""
|
|
591
|
-
ecu_address = '0x00000000'
|
|
592
|
-
for match in self._expr_block_meas_kp_blob_parser.finditer(block):
|
|
593
|
-
start, end = match.span()
|
|
594
|
-
block = f'{block[:start]}ECU_ADDRESS {ecu_address}{block[end:]}'
|
|
595
|
-
return block
|
|
596
|
-
|
|
597
|
-
def merge(self, f_name, complete_a2l=False, silver_a2l=False):
|
|
598
|
-
"""Write merged a2l-file.
|
|
599
|
-
|
|
600
|
-
Args:
|
|
601
|
-
f_name (str): Output filename.
|
|
602
|
-
"""
|
|
603
|
-
a2l = ''
|
|
604
|
-
a2l_config = self._prj_cfg.get_a2l_cfg()
|
|
605
|
-
for _, data in self._blks.items():
|
|
606
|
-
for _, blk in data.items():
|
|
607
|
-
if not a2l_config['allow_kp_blob']:
|
|
608
|
-
blk = self._patch_kp_blob(blk)
|
|
609
|
-
a2l += blk + '\n\n'
|
|
610
|
-
|
|
611
|
-
if complete_a2l:
|
|
612
|
-
events = []
|
|
613
|
-
time_unit_10ms = '0x07'
|
|
614
|
-
rasters = self._prj_cfg.get_units_raster_cfg()
|
|
615
|
-
for xcp_id, evt_data in enumerate(rasters['SampleTimes'].items(), 1):
|
|
616
|
-
events.append({
|
|
617
|
-
'time_cycle': '0x%02X' % int(evt_data[1] * 100),
|
|
618
|
-
'time_unit': time_unit_10ms,
|
|
619
|
-
'name': evt_data[0],
|
|
620
|
-
'channel_id': '0x%04X' % xcp_id
|
|
621
|
-
})
|
|
622
|
-
a2l_template = A2lProjectTemplate(
|
|
623
|
-
a2l,
|
|
624
|
-
a2l_config['asap2_version'],
|
|
625
|
-
a2l_config['name'],
|
|
626
|
-
events,
|
|
627
|
-
a2l_config['ip_address'],
|
|
628
|
-
a2l_config['ip_port']
|
|
629
|
-
)
|
|
630
|
-
a2l = a2l_template.render()
|
|
631
|
-
elif silver_a2l:
|
|
632
|
-
a2l_template = A2lSilverTemplate(a2l)
|
|
633
|
-
a2l = a2l_template.render()
|
|
634
|
-
|
|
635
|
-
self.a2l = a2l
|
|
636
|
-
|
|
637
|
-
with open(f_name, 'w', encoding="ISO-8859-1") as ma2l:
|
|
638
|
-
ma2l.write(a2l)
|
|
639
|
-
self.info('Written the merged A2L-file %s', f_name)
|
|
640
|
-
|
|
641
|
-
def get_characteristic_axis_data(self):
|
|
642
|
-
"""Get characteristic map axis data from merged a2l-file."""
|
|
643
|
-
axis_data = {}
|
|
644
|
-
for blk_def, type_, label in self._block_finder.findall(self.a2l):
|
|
645
|
-
if type_ == "CHARACTERISTIC":
|
|
646
|
-
axes = re.findall('AXIS_PTS_REF (.*)', blk_def)
|
|
647
|
-
if label in axis_data:
|
|
648
|
-
self.critical("Multiple CHARACTERISTIC for %s in merged a2l.", label)
|
|
649
|
-
axis_data[label] = {'axes': axes}
|
|
650
|
-
return axis_data
|
|
1
|
+
# Copyright 2024 Volvo Car Corporation
|
|
2
|
+
# Licensed under Apache 2.0.
|
|
3
|
+
|
|
4
|
+
# -*- coding: utf-8 -*-
|
|
5
|
+
"""Module for merging of a2l-files."""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from string import Template
|
|
11
|
+
|
|
12
|
+
from powertrain_build.lib.helper_functions import deep_dict_update
|
|
13
|
+
from powertrain_build.problem_logger import ProblemLogger
|
|
14
|
+
from powertrain_build.a2l_templates import A2lProjectTemplate, A2lSilverTemplate
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class A2lMerge(ProblemLogger):
|
|
18
|
+
"""Class for merging of a2l-files."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, prj_cfg, ucfg, a2l_files_unit, a2l_files_gen):
|
|
21
|
+
"""Merge a2l-files based on provided project configuration.
|
|
22
|
+
|
|
23
|
+
Removes symbols not included in the projects unit-config files.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
prj_cfg (obj): Project config.
|
|
27
|
+
ucfg (obj): Unit config.
|
|
28
|
+
a2l_files_unit (list of str): Files to merge.
|
|
29
|
+
a2l_files_gen (list of str): Files to merge.
|
|
30
|
+
"""
|
|
31
|
+
super().__init__()
|
|
32
|
+
self._prj_cfg = prj_cfg
|
|
33
|
+
self._unit_cfg = ucfg
|
|
34
|
+
self._per_unit_cfg = ucfg.get_per_unit_cfg()
|
|
35
|
+
# generate the a2l string
|
|
36
|
+
self._blks = {}
|
|
37
|
+
self._removed_symbols = []
|
|
38
|
+
self.a2l = ""
|
|
39
|
+
|
|
40
|
+
# ----- Example blocks in a2l (TargetLink) -----
|
|
41
|
+
#
|
|
42
|
+
# /begin CHARACTERISTIC
|
|
43
|
+
# cVc_B_SeriesHev /* Name */
|
|
44
|
+
# "Series hybrid" /* LongIdentifier */
|
|
45
|
+
# VALUE /* Type */
|
|
46
|
+
# 0x00000000 /* address: cVc_B_SeriesHev */
|
|
47
|
+
# UBYTE_COL_DIRECT /* Deposit */
|
|
48
|
+
# 0 /* MaxDiff */
|
|
49
|
+
# Scaling_3 /* Conversion */
|
|
50
|
+
# 0 /* LowerLimit */
|
|
51
|
+
# 1 /* UpperLimit */
|
|
52
|
+
# /end CHARACTERISTIC
|
|
53
|
+
#
|
|
54
|
+
# Example of Bosch-nvm signal in nvm:
|
|
55
|
+
#
|
|
56
|
+
# /begin MEASUREMENT
|
|
57
|
+
# nvm_list_32._sVcDclVu_D_Markow /* Name */
|
|
58
|
+
# "No description given" /* LongIdentifier */
|
|
59
|
+
# ULONG /* Datatype */
|
|
60
|
+
# VcNvm_1_0_None /* Conversion */
|
|
61
|
+
# 1 /* Resolution */
|
|
62
|
+
# 0 /* Accuracy */
|
|
63
|
+
# 0 /* LowerLimit */
|
|
64
|
+
# 4294967295 /* UpperLimit */
|
|
65
|
+
# READ_WRITE
|
|
66
|
+
# MATRIX_DIM 152 1 1
|
|
67
|
+
# ECU_ADDRESS 0x00000000
|
|
68
|
+
# /end MEASUREMENT
|
|
69
|
+
#
|
|
70
|
+
# ----- Example blocks in a2l (Embedded Coder) -----
|
|
71
|
+
#
|
|
72
|
+
# /begin MEASUREMENT
|
|
73
|
+
# /* Name */ sVcAesVe_md_VolmcOffs
|
|
74
|
+
# /* Long identifier */ "Volumetric cylinder mass flow offset"
|
|
75
|
+
# /* Data type */ FLOAT32_IEEE
|
|
76
|
+
# /* Conversion method */ VcAesVe_CM_Float32_g_s
|
|
77
|
+
# /* Resolution (Not used) */ 0
|
|
78
|
+
# /* Accuracy (Not used) */ 0
|
|
79
|
+
# /* Lower limit */ -100.0
|
|
80
|
+
# /* Upper limit */ 100.0
|
|
81
|
+
# ECU_ADDRESS 0x0000 /* @ECU_Address@sVcAesVe_md_VolmcOffs@ */
|
|
82
|
+
# /end MEASUREMENT
|
|
83
|
+
#
|
|
84
|
+
# /begin CHARACTERISTIC
|
|
85
|
+
# /* Name */ cVcAesVe_D_VolmcCmpSel
|
|
86
|
+
# /* Long Identifier */ "Select compensation factor characterizing deviation from nominal voleff"
|
|
87
|
+
# /* Type */ VALUE
|
|
88
|
+
# /* ECU Address */ 0x0000 /* @ECU_Address@cVcAesVe_D_VolmcCmpSel@ */
|
|
89
|
+
# /* Record Layout */ Scalar_UBYTE
|
|
90
|
+
# /* Maximum Difference */ 0
|
|
91
|
+
# /* Conversion Method */ VcAesVe_CM_uint8
|
|
92
|
+
# /* Lower Limit */ 1.0
|
|
93
|
+
# /* Upper Limit */ 3.0
|
|
94
|
+
# /end CHARACTERISTIC
|
|
95
|
+
|
|
96
|
+
self._block_finder = re.compile(r'(?:\s*\n)*' # Optional blank lines
|
|
97
|
+
r'(\s*/begin (\w+)\s*' # begin <something> block
|
|
98
|
+
r'\n\s*([\w.]+).*?\n' # label. (Bosch-nvm contains the .)
|
|
99
|
+
r'.*?' # block definition
|
|
100
|
+
r'/end\s+\2)', # end <something> block. Same something as before
|
|
101
|
+
flags=re.M | re.DOTALL)
|
|
102
|
+
|
|
103
|
+
self._tl_compu_method_parser = re.compile(
|
|
104
|
+
r'(?:\s*\n)*(?P<compu_method>'
|
|
105
|
+
r'\s*/begin COMPU_METHOD\s*\n'
|
|
106
|
+
r'\s*(?P<name>\w*)\s*(/\* Name \*/)?\s*\n' # Name
|
|
107
|
+
r'\s*"(?P<ID>.*?)"\s*(/\* LongIdentifier \*/.*?)\s*\n' # Long Identifier
|
|
108
|
+
r'\s*(?P<conv_type>[A-Z_]*).*\s*\n' # ConversionType
|
|
109
|
+
r'\s*"(?P<disp_format>.*)"\s*(/\* Format \*/)?\s*\n' # Format
|
|
110
|
+
r'\s*"(?P<unit>.*?)"\s*(/\* Unit \*/)?\s*\n' # Unit
|
|
111
|
+
r'\s*(?P<conversion>.*)\s*\n' # COEFFS
|
|
112
|
+
r'(?P<indentation>\s*)/end COMPU_METHOD)', flags=re.M) # No DOTALL, so .* is [^\n]*
|
|
113
|
+
|
|
114
|
+
# COMPU_METHOD parser that works with files generated by Embedded Coder
|
|
115
|
+
self._ec_compu_method_parser = re.compile(
|
|
116
|
+
r'(?:\s*\n)*(?P<compu_method>'
|
|
117
|
+
r'\s*/begin COMPU_METHOD\s*\n'
|
|
118
|
+
r'\s*(/\* Name of CompuMethod\s*\*/)?\s*(?P<name>\w*)\s*\n' # Name
|
|
119
|
+
r'\s*(/\* Long identifier\s*\*/)?\s*"(?P<ID>.*?)"\s*\n' # Long Identifier
|
|
120
|
+
r'\s*/\* Conversion Type\s*\*/\s*(?P<conv_type>[A-Z_]*)\s*\n' # ConversionType
|
|
121
|
+
r'\s*(/\* Format\s*\*/)?\s*"(?P<disp_format>.*?)"\s*\n' # Format
|
|
122
|
+
r'\s*(/\* Units\s*\*/)?\s*"(?P<unit>.*?)"\s*\n' # Unit
|
|
123
|
+
r'\s*/\* Coefficients\s*\*/\s*(?P<conversion>.*?)\s*\n' # COEFFS
|
|
124
|
+
r'(?P<indentation>\s*)/end COMPU_METHOD)', flags=re.M) # No DOTALL, so .* is [^\n]*
|
|
125
|
+
|
|
126
|
+
self._expr_block_meas_kp_blob_parser = re.compile(
|
|
127
|
+
r'\/begin\s+(?P<keyword>\w+)\s+(?P<class>\w+)\s*\n'
|
|
128
|
+
r'\s*KP_BLOB\s+(?P<address>0x[0-9a-fA-F]+)\s*\n'
|
|
129
|
+
r'\s*\/end\s+\1'
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
self._compu_methods = {}
|
|
133
|
+
self._included_compu_methods = []
|
|
134
|
+
|
|
135
|
+
self._tl_compu_method_template = Template(
|
|
136
|
+
'$indentation/begin COMPU_METHOD\n'
|
|
137
|
+
'$indentation $name /* Name */\n'
|
|
138
|
+
'$indentation "$ID" /* LongIdentifier */\n'
|
|
139
|
+
'$indentation $conv_type /* ConversionType */\n'
|
|
140
|
+
'$indentation "$disp_format" /* Format */\n'
|
|
141
|
+
'$indentation "$unit" /* Unit */\n'
|
|
142
|
+
'$indentation $conversion\n'
|
|
143
|
+
'$indentation/end COMPU_METHOD'
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# COMPU_METHOD template that looks similar to COMPU_METHOD generated by Embedded Coder
|
|
147
|
+
self._ec_compu_method_template = Template(
|
|
148
|
+
'$indentation/begin COMPU_METHOD\n'
|
|
149
|
+
'$indentation /* Name of CompuMethod */ $name\n'
|
|
150
|
+
'$indentation /* Long identifier */ "$ID"\n'
|
|
151
|
+
'$indentation /* Conversion Type */ $conv_type\n'
|
|
152
|
+
'$indentation /* Format */ "$disp_format"\n'
|
|
153
|
+
'$indentation /* Units */ "$unit"\n'
|
|
154
|
+
'$indentation /* Coefficients */ $conversion\n'
|
|
155
|
+
'$indentation/end COMPU_METHOD'
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
for filename in a2l_files_unit:
|
|
159
|
+
removed_symbols = self._parse_unit(filename)
|
|
160
|
+
self._removed_symbols.extend(removed_symbols)
|
|
161
|
+
self.debug('Loaded %s', filename)
|
|
162
|
+
for filename in a2l_files_gen:
|
|
163
|
+
self._parse_gen(filename)
|
|
164
|
+
self.debug('Loaded %s', filename)
|
|
165
|
+
|
|
166
|
+
def _parse_unit(self, filename):
|
|
167
|
+
"""Parse the unit a2l-files and apply a filter to only active parameters."""
|
|
168
|
+
self.debug('Processing %s', filename)
|
|
169
|
+
with open(filename, 'r', encoding="ISO-8859-1") as a2lfp:
|
|
170
|
+
a2ld = a2lfp.read()
|
|
171
|
+
file_path_parts = os.path.split(filename)
|
|
172
|
+
unit = file_path_parts[1].split('.')[0]
|
|
173
|
+
base_path = file_path_parts[0]
|
|
174
|
+
dcl_match = re.search(r'VcDcl[\w]+Mdl(__[\w]+)', base_path)
|
|
175
|
+
if dcl_match is not None and 'Mdl' not in unit:
|
|
176
|
+
# Hand coded model names including "__" will lead to this, due to name mismatch of .a2l and .json files.
|
|
177
|
+
# E.g. VcDclPtrlMdl__denso:
|
|
178
|
+
# 1. config_VcDclPtrlMdl__denso.json vs VcDclPtrlMdl__denso.a2l.
|
|
179
|
+
# 1.1. Match: unit in self._per_unit_cfg.
|
|
180
|
+
# 2. config_VcDclPtrl__denso.json vs VcDclPtrl.a2l.
|
|
181
|
+
# 2.1. No match: unit not in self._per_unit_cfg.
|
|
182
|
+
old_unit = unit
|
|
183
|
+
unit = unit + dcl_match.group(1)
|
|
184
|
+
self.info(
|
|
185
|
+
'Found unit %s with .a2l and .json file name mismatch. Using new unit name: %s',
|
|
186
|
+
old_unit,
|
|
187
|
+
unit
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if unit in self._per_unit_cfg:
|
|
191
|
+
u_conf = self._per_unit_cfg[unit]
|
|
192
|
+
code_generator = u_conf['code_generator'] if 'code_generator' in u_conf else 'target_link'
|
|
193
|
+
else:
|
|
194
|
+
u_conf = {}
|
|
195
|
+
code_generator = 'target_link'
|
|
196
|
+
|
|
197
|
+
if code_generator == 'embedded_coder':
|
|
198
|
+
blks = re.findall(r'(?:\s*\n)*(\s*/begin '
|
|
199
|
+
r'(?!PROJECT|HEADER|MODULE|MOD_PAR|MOD_COMMON)(\w+)\s*(?:\n\s*)?'
|
|
200
|
+
r'(?:/\*\s*[\w ]+\s*\*/\s*)?(\w+)([\[\d+\]]*).*?\n.*?/end\s+\2)',
|
|
201
|
+
a2ld, flags=re.M | re.DOTALL)
|
|
202
|
+
else:
|
|
203
|
+
blks = re.findall(r'(?:\s*\n)*(\s*/begin (?!PROJECT|MODULE)(\w+)[\n\s]*'
|
|
204
|
+
r'(\w+(?:\.\w+)?)([\[\d+\]]*).*?\n.*?/end\s+\2)', a2ld,
|
|
205
|
+
flags=re.M | re.DOTALL)
|
|
206
|
+
|
|
207
|
+
compu_method_translators = self._parse_compu_methods(a2ld, unit)
|
|
208
|
+
unit_blks = {}
|
|
209
|
+
removed_symbols = []
|
|
210
|
+
if unit not in self._per_unit_cfg:
|
|
211
|
+
# Handcoded a2l without json-files will lead to this.
|
|
212
|
+
# Add json files for the handcoded a2l!
|
|
213
|
+
# NOTE: Assuming TargetLink
|
|
214
|
+
self.debug('%s is not in the units list. Looking for json.', unit)
|
|
215
|
+
config_filename = os.path.join(
|
|
216
|
+
self._prj_cfg.get_unit_cfg_deliv_dir(),
|
|
217
|
+
f'config_{unit}.json')
|
|
218
|
+
self.debug('Looking for %s', config_filename)
|
|
219
|
+
if os.path.isfile(config_filename):
|
|
220
|
+
with open(config_filename, 'r', encoding="utf-8") as config_file:
|
|
221
|
+
u_conf = json.load(config_file)
|
|
222
|
+
self._handle_config(
|
|
223
|
+
code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
self.warning('%s does not have a unit_cfg json, '
|
|
227
|
+
'including all a2l-parameters', unit)
|
|
228
|
+
for blk_def, type_, label, size in blks:
|
|
229
|
+
if type_ == 'COMPU_METHOD':
|
|
230
|
+
blk_def, label = self._replace_compu_method(blk_def, label, compu_method_translators)
|
|
231
|
+
self.add_block_definition(unit_blks, type_, label, size, blk_def, compu_method_translators)
|
|
232
|
+
else:
|
|
233
|
+
self._handle_config(
|
|
234
|
+
code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators
|
|
235
|
+
)
|
|
236
|
+
deep_dict_update(self._blks, unit_blks)
|
|
237
|
+
return removed_symbols
|
|
238
|
+
|
|
239
|
+
def _handle_config(self, code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators):
|
|
240
|
+
"""Merge all types of ram for the unit."""
|
|
241
|
+
ram = u_conf['inports']
|
|
242
|
+
ram.update(u_conf['outports'])
|
|
243
|
+
ram.update(u_conf['local_vars'])
|
|
244
|
+
# TODO: Function the variables and labels needs to be removed from
|
|
245
|
+
# the FUNCTION block too
|
|
246
|
+
for blk_def, type_, label, size in blks:
|
|
247
|
+
remove_excluded_symbol = True
|
|
248
|
+
inc = False
|
|
249
|
+
if type_ == 'AXIS_PTS':
|
|
250
|
+
if label in u_conf['calib_consts']:
|
|
251
|
+
inc = True
|
|
252
|
+
elif type_ == 'CHARACTERISTIC':
|
|
253
|
+
if label in u_conf['calib_consts']:
|
|
254
|
+
if label in [axis_label for _, axis_type, axis_label, _ in blks if axis_type == 'AXIS_PTS']:
|
|
255
|
+
# AXIS_PTS can be used as CHARACTERISTC but not the other way around.
|
|
256
|
+
# If there are duplicates, use the AXIS_PTS.
|
|
257
|
+
self.debug('Will not add the block for CHARACTERISTC %s, but will keep it as a symbol,'
|
|
258
|
+
' since it exists as AXIS_PTS', label)
|
|
259
|
+
remove_excluded_symbol = False
|
|
260
|
+
inc = False
|
|
261
|
+
else:
|
|
262
|
+
inc = self._handle_axis_ptr_ref_config(u_conf, blk_def, unit)
|
|
263
|
+
elif type_ == 'MEASUREMENT':
|
|
264
|
+
if label in ram:
|
|
265
|
+
key = label if size is None else label + size
|
|
266
|
+
if label in u_conf['outports']:
|
|
267
|
+
# This unit is producing the measurement.
|
|
268
|
+
inc = True
|
|
269
|
+
elif key in unit_blks.get(type_, {}):
|
|
270
|
+
# This unit is not producing it, and it has already been added
|
|
271
|
+
inc = False
|
|
272
|
+
else:
|
|
273
|
+
# This unit is not producing it, but it has not been added
|
|
274
|
+
# Could be external signal, etc.
|
|
275
|
+
inc = True
|
|
276
|
+
elif type_ == 'COMPU_METHOD':
|
|
277
|
+
inc = True
|
|
278
|
+
blk_def, label = self._replace_compu_method(blk_def, label, compu_method_translators)
|
|
279
|
+
else:
|
|
280
|
+
inc = True
|
|
281
|
+
if inc:
|
|
282
|
+
self.add_block_definition(unit_blks, type_, label, size, blk_def, compu_method_translators)
|
|
283
|
+
else:
|
|
284
|
+
if remove_excluded_symbol:
|
|
285
|
+
removed_symbols.append(label + size)
|
|
286
|
+
self.debug('Did not include A2L-blk %s%s', label, size)
|
|
287
|
+
if not self._unit_cfg.check_if_in_unit_cfg(unit, label):
|
|
288
|
+
if type_ != 'COMPU_METHOD':
|
|
289
|
+
self.warning('A2l block %s not in config json file for %s', label, unit)
|
|
290
|
+
|
|
291
|
+
if 'FUNCTION' in unit_blks:
|
|
292
|
+
unit_blks['FUNCTION'] = self._remove_symbols_from_func_blks(
|
|
293
|
+
code_generator, unit_blks['FUNCTION'], removed_symbols
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if 'GROUP' in unit_blks:
|
|
297
|
+
unit_blks['GROUP'] = self._remove_symbols_from_grp_blks(unit_blks['GROUP'], removed_symbols)
|
|
298
|
+
|
|
299
|
+
def _handle_axis_ptr_ref_config(self, u_conf, blk, unit):
|
|
300
|
+
"""Remove blocks referencing undefined blocks."""
|
|
301
|
+
ref_re = re.compile(r'\s*AXIS_PTS_REF\s*([\w]*)')
|
|
302
|
+
for axis_ptr_ref in ref_re.findall(blk):
|
|
303
|
+
if axis_ptr_ref not in u_conf['calib_consts']:
|
|
304
|
+
self.debug('Excluding due to %s missing in config', axis_ptr_ref)
|
|
305
|
+
return False
|
|
306
|
+
if not self._unit_cfg.check_if_in_unit_cfg(unit, axis_ptr_ref):
|
|
307
|
+
self.debug('Excluding due to %s not active in config', axis_ptr_ref)
|
|
308
|
+
return False
|
|
309
|
+
return True
|
|
310
|
+
|
|
311
|
+
def add_block_definition(self, unit_blks, type_, label, size, blk_def, compu_method_translators):
|
|
312
|
+
"""Add block definition to A2L-file."""
|
|
313
|
+
size = '' if size is None else size
|
|
314
|
+
blk_def = self._replace_conversions(blk_def, compu_method_translators)
|
|
315
|
+
if type_ not in unit_blks:
|
|
316
|
+
unit_blks[type_] = {}
|
|
317
|
+
unit_blks[type_][label + size] = blk_def
|
|
318
|
+
|
|
319
|
+
@staticmethod
|
|
320
|
+
def _parse_func_blk(code_generator, fnc_blk):
|
|
321
|
+
"""Remove the unused symbols from the FUNCTION blocks in the A2L-file.
|
|
322
|
+
Parse the FUNCTION block, TL or EC style based on code_generator.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
if code_generator == 'target_link':
|
|
326
|
+
pattern = r'\s*/begin\s+FUNCTION\s*?\n\s*(\w+).*?\n\s*"(.*?)".*?\n(.*)'
|
|
327
|
+
else:
|
|
328
|
+
pattern = r'\s*/begin\s+FUNCTION\s*?\n\s*.*\*/\s*(\w+).*?\n\s*.*\*/\s*"(.*?)".*?\n(.*)'
|
|
329
|
+
res = re.match(pattern, fnc_blk, flags=re.M | re.DOTALL)
|
|
330
|
+
fnc_name = res.group(1)
|
|
331
|
+
long_id = res.group(2)
|
|
332
|
+
fnc_dict = {
|
|
333
|
+
'fnc_name': fnc_name,
|
|
334
|
+
'long_id': long_id,
|
|
335
|
+
'body': {}
|
|
336
|
+
}
|
|
337
|
+
fnc_body = res.group(3)
|
|
338
|
+
sb_res = re.findall(r'\s*/begin\s+(\w+[\[\d\]]*)\s*\n\s*'
|
|
339
|
+
r'(.*?\n)\s*/end \1', fnc_body, flags=re.M | re.DOTALL)
|
|
340
|
+
for sb_name, sub_blk in sb_res:
|
|
341
|
+
symbols = set(re.findall(r'\s*(\w+(?:\.\w+)?[\[\d\]]*).*?\n', sub_blk, flags=re.M))
|
|
342
|
+
fnc_dict['body'][sb_name] = symbols
|
|
343
|
+
return fnc_dict
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _parse_grp_blk(grp_blk):
|
|
347
|
+
"""Remove the unused symbols from the GROUP blocks in the A2L-file."""
|
|
348
|
+
# parse the GROUP block
|
|
349
|
+
res = re.match(r'\s*/begin\s+GROUP\s*?\n\s*.*\*/\s*(\w+).*?\n\s*.*\*/\s*"(.*?)".*?\n(.*)',
|
|
350
|
+
grp_blk, flags=re.M | re.DOTALL)
|
|
351
|
+
fnc_name = res.group(1)
|
|
352
|
+
long_id = res.group(2)
|
|
353
|
+
fnc_dict = {
|
|
354
|
+
'fnc_name': fnc_name,
|
|
355
|
+
'long_id': long_id,
|
|
356
|
+
'body': {}
|
|
357
|
+
}
|
|
358
|
+
fnc_body = res.group(3)
|
|
359
|
+
sb_res = re.findall(r'\s*/begin\s+(\w+[\[\d\]]*)\s*\n\s*'
|
|
360
|
+
r'(.*?\n)\s*/end \1', fnc_body, flags=re.M | re.DOTALL)
|
|
361
|
+
for sb_name, sub_blk in sb_res:
|
|
362
|
+
symbols = set(re.findall(r'\s*(\w+(?:\.\w+)?[\[\d\]]*).*?\n', sub_blk, flags=re.M))
|
|
363
|
+
fnc_dict['body'][sb_name] = symbols
|
|
364
|
+
return fnc_dict
|
|
365
|
+
|
|
366
|
+
def _recursive_remove(self, a2l_dict, name):
|
|
367
|
+
"""Remove symbols from A2L dict (e.g. group or function)."""
|
|
368
|
+
if name in a2l_dict:
|
|
369
|
+
blk = a2l_dict[name]
|
|
370
|
+
if 'SUB_FUNCTION' in blk:
|
|
371
|
+
for sub_fnc in blk['SUB_FUNCTION']:
|
|
372
|
+
if self._recursive_remove(a2l_dict, sub_fnc):
|
|
373
|
+
blk['SUB_FUNCTION'] = blk['SUB_FUNCTION'] - set([sub_fnc])
|
|
374
|
+
elif 'SUB_GROUP' in blk:
|
|
375
|
+
for sub_grp in blk['SUB_GROUP']:
|
|
376
|
+
if self._recursive_remove(a2l_dict, sub_grp):
|
|
377
|
+
blk['SUB_GROUP'] = blk['SUB_GROUP'] - set([sub_grp])
|
|
378
|
+
empty = True
|
|
379
|
+
for key in blk:
|
|
380
|
+
if blk[key]:
|
|
381
|
+
empty = False
|
|
382
|
+
break
|
|
383
|
+
if empty:
|
|
384
|
+
a2l_dict.pop(name)
|
|
385
|
+
return True
|
|
386
|
+
return False
|
|
387
|
+
|
|
388
|
+
def _remove_symbols_from_func_blks(self, code_generator, fnc_blks, removed_symbols):
|
|
389
|
+
"""Remove the unused symbols from function blocks.
|
|
390
|
+
|
|
391
|
+
If the function block is empty, it too will be removed.
|
|
392
|
+
first iteration - remove all symbols that have been removed
|
|
393
|
+
second iteration - recusively remover all functions without symbols
|
|
394
|
+
"""
|
|
395
|
+
fnc_dict = {}
|
|
396
|
+
for fnc_name, fnc_blk in fnc_blks.items():
|
|
397
|
+
fnc_dict[fnc_name] = {}
|
|
398
|
+
u_fnc_bdy = self._parse_func_blk(code_generator, fnc_blk)['body']
|
|
399
|
+
sub_blk_types = set(u_fnc_bdy.keys()) - set(['SUB_FUNCTION'])
|
|
400
|
+
for type_ in list(sub_blk_types):
|
|
401
|
+
fnc_dict[fnc_name][type_] = u_fnc_bdy[type_] - set(removed_symbols)
|
|
402
|
+
if 'SUB_FUNCTION' in u_fnc_bdy:
|
|
403
|
+
fnc_dict[fnc_name]['SUB_FUNCTION'] = u_fnc_bdy['SUB_FUNCTION']
|
|
404
|
+
# second iteration - remove empty FUNCTION blocks
|
|
405
|
+
# TODO: Add functionality which parses the the function tree structures
|
|
406
|
+
# And the run recursive remove on all tree roots.
|
|
407
|
+
for fnc_name in fnc_blks.keys():
|
|
408
|
+
self._recursive_remove(fnc_dict, fnc_name)
|
|
409
|
+
# generate new function blocks
|
|
410
|
+
new_fnc_blks = {}
|
|
411
|
+
for fnc_name, fnc_data in fnc_dict.items():
|
|
412
|
+
fnc_blk = f' /begin FUNCTION\n {fnc_name}\t/* Name */\n'
|
|
413
|
+
fnc_blk += " \"\"\t/* LongIdentifier */\n"
|
|
414
|
+
for sub_sec in sorted(fnc_data.keys()):
|
|
415
|
+
sub_sec_data = fnc_data[sub_sec]
|
|
416
|
+
if sub_sec_data:
|
|
417
|
+
fnc_blk += f" /begin {sub_sec}\n"
|
|
418
|
+
for param in sorted(sub_sec_data):
|
|
419
|
+
fnc_blk += f" {param}\t/* Identifier */\n"
|
|
420
|
+
fnc_blk += f" /end {sub_sec}\n"
|
|
421
|
+
fnc_blk += " /end FUNCTION"
|
|
422
|
+
new_fnc_blks[fnc_name] = fnc_blk
|
|
423
|
+
return new_fnc_blks
|
|
424
|
+
|
|
425
|
+
def _remove_symbols_from_grp_blks(self, grp_blks, removed_symbols):
|
|
426
|
+
"""Remove the unused symbols from group blocks.
|
|
427
|
+
|
|
428
|
+
If the group block is empty, it too will be removed.
|
|
429
|
+
first iteration - remove all symbols that have been removed
|
|
430
|
+
second iteration - recusively remover all groups without symbols
|
|
431
|
+
"""
|
|
432
|
+
grp_dict = {}
|
|
433
|
+
for grp_name, grp_blk in grp_blks.items():
|
|
434
|
+
grp_dict[grp_name] = {}
|
|
435
|
+
u_grp_bdy = self._parse_grp_blk(grp_blk)['body']
|
|
436
|
+
sub_blk_types = set(u_grp_bdy.keys()) - set(['SUB_GROUP'])
|
|
437
|
+
for type_ in list(sub_blk_types):
|
|
438
|
+
grp_dict[grp_name][type_] = u_grp_bdy[type_] - set(removed_symbols)
|
|
439
|
+
if 'SUB_GROUP' in u_grp_bdy:
|
|
440
|
+
grp_dict[grp_name]['SUB_GROUP'] = u_grp_bdy['SUB_GROUP']
|
|
441
|
+
# second iteration - remove empty GROUP blocks
|
|
442
|
+
# TODO: Add functionality which parses the the group tree structures
|
|
443
|
+
# And the run recursive remove on all tree roots.
|
|
444
|
+
for grp_name in grp_blks.keys():
|
|
445
|
+
self._recursive_remove(grp_dict, grp_name)
|
|
446
|
+
# generate new group blocks
|
|
447
|
+
new_grp_blks = {}
|
|
448
|
+
for grp_name, grp_data in grp_dict.items():
|
|
449
|
+
grp_blk = f" /begin GROUP \n /* Name */ {grp_name}\n"
|
|
450
|
+
grp_blk += " /* Long identifier */ \"\"\n"
|
|
451
|
+
for sub_sec in sorted(grp_data.keys()):
|
|
452
|
+
sub_sec_data = grp_data[sub_sec]
|
|
453
|
+
if sub_sec_data:
|
|
454
|
+
grp_blk += f" /begin {sub_sec}\n"
|
|
455
|
+
for param in sorted(sub_sec_data):
|
|
456
|
+
grp_blk += f" {param}\n"
|
|
457
|
+
grp_blk += f" /end {sub_sec}\n"
|
|
458
|
+
grp_blk += " /end GROUP"
|
|
459
|
+
new_grp_blks[grp_name] = grp_blk
|
|
460
|
+
|
|
461
|
+
return new_grp_blks
|
|
462
|
+
|
|
463
|
+
def _parse_gen(self, filename):
|
|
464
|
+
"""Parse the generated a2l-files, without filter."""
|
|
465
|
+
self.debug('parsing gen a2l: %s', filename)
|
|
466
|
+
with open(filename, 'r', encoding="utf-8") as a2lfp:
|
|
467
|
+
a2ld = a2lfp.read()
|
|
468
|
+
for blk_def, type_, label in self._block_finder.findall(a2ld):
|
|
469
|
+
self._blks.setdefault(type_, {}).setdefault(label, blk_def)
|
|
470
|
+
|
|
471
|
+
@staticmethod
|
|
472
|
+
def _replace_compu_method(blk_def, label, compu_method_translators):
|
|
473
|
+
"""Replace the compu method block and label."""
|
|
474
|
+
for translator in compu_method_translators:
|
|
475
|
+
if translator['old_compu_method'] == blk_def:
|
|
476
|
+
return translator['new_compu_method'], translator['new_name']
|
|
477
|
+
return blk_def, label
|
|
478
|
+
|
|
479
|
+
def _store_compu_method(self, ID, conv_type, disp_format, unit, conversion, indentation, u_conf):
|
|
480
|
+
"""Stash compu methods that exists in the resulting a2l."""
|
|
481
|
+
key = (ID, conv_type, disp_format, unit, conversion)
|
|
482
|
+
if key in self._compu_methods:
|
|
483
|
+
new_name = self._compu_methods[key]['name']
|
|
484
|
+
new_compu_method = self._compu_methods[key]['method']
|
|
485
|
+
else:
|
|
486
|
+
new_name = 'Scaling_' + str(len(self._compu_methods))
|
|
487
|
+
if 'code_generator' in u_conf and u_conf['code_generator'] == 'embedded_coder':
|
|
488
|
+
new_compu_method = self._ec_compu_method_template.substitute(
|
|
489
|
+
name=new_name,
|
|
490
|
+
ID=ID,
|
|
491
|
+
conv_type=conv_type,
|
|
492
|
+
disp_format=disp_format,
|
|
493
|
+
unit=unit,
|
|
494
|
+
conversion=conversion,
|
|
495
|
+
indentation=indentation
|
|
496
|
+
)
|
|
497
|
+
else:
|
|
498
|
+
new_compu_method = self._tl_compu_method_template.substitute(
|
|
499
|
+
name=new_name,
|
|
500
|
+
ID=ID,
|
|
501
|
+
conv_type=conv_type,
|
|
502
|
+
disp_format=disp_format,
|
|
503
|
+
unit=unit,
|
|
504
|
+
conversion=conversion,
|
|
505
|
+
indentation=indentation
|
|
506
|
+
)
|
|
507
|
+
self._compu_methods.update({key: {'name': new_name,
|
|
508
|
+
'method': new_compu_method}})
|
|
509
|
+
return new_name, new_compu_method
|
|
510
|
+
|
|
511
|
+
@staticmethod
|
|
512
|
+
def _replace_conversions(blk_def, compu_method_translators):
|
|
513
|
+
"""Replace conversion identifiers in a2l block."""
|
|
514
|
+
for translator in compu_method_translators:
|
|
515
|
+
# The following check is faster than running the regex on the block.
|
|
516
|
+
# It DOES give false positives, which is why the regex is used for substitution
|
|
517
|
+
# and we do not immidiately return after one positive
|
|
518
|
+
if translator['old_name'] in blk_def:
|
|
519
|
+
blk_def = translator['regex'].sub(translator['replacement'], blk_def)
|
|
520
|
+
return blk_def
|
|
521
|
+
|
|
522
|
+
def _parse_compu_methods(self, blk_def, unit):
|
|
523
|
+
"""Replace compu methods to not overwrite any of them."""
|
|
524
|
+
compu_method_translators = [] # Translators for one processed a2l file. Needs to be reset between files
|
|
525
|
+
u_conf = self._per_unit_cfg[unit] if unit in self._per_unit_cfg else {}
|
|
526
|
+
if 'code_generator' in u_conf and u_conf['code_generator'] == 'embedded_coder':
|
|
527
|
+
for match in self._ec_compu_method_parser.finditer(blk_def):
|
|
528
|
+
new_name, new_compu_method = self._store_compu_method(
|
|
529
|
+
match['ID'],
|
|
530
|
+
match['conv_type'],
|
|
531
|
+
match['disp_format'],
|
|
532
|
+
match['unit'],
|
|
533
|
+
match['conversion'],
|
|
534
|
+
match['indentation'],
|
|
535
|
+
u_conf
|
|
536
|
+
)
|
|
537
|
+
compu_method_translators.append(
|
|
538
|
+
{
|
|
539
|
+
'new_name': new_name,
|
|
540
|
+
'old_name': match['name'],
|
|
541
|
+
'regex': re.compile(
|
|
542
|
+
r'(\s*)' # beginning
|
|
543
|
+
r'\s*(/\* Conversion [Mm]ethod\s*\*/\s*)' # optional comment
|
|
544
|
+
r'\b{name}\b' # word
|
|
545
|
+
r'('
|
|
546
|
+
r'\s*\n' # newline
|
|
547
|
+
r')'.format(name=match['name']) # end of end-match
|
|
548
|
+
),
|
|
549
|
+
'replacement': r'\1\2{name}\3'.format(name=new_name),
|
|
550
|
+
'old_compu_method': match['compu_method'],
|
|
551
|
+
'new_compu_method': new_compu_method
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
else:
|
|
555
|
+
for match in self._tl_compu_method_parser.finditer(blk_def):
|
|
556
|
+
new_name, new_compu_method = self._store_compu_method(
|
|
557
|
+
match['ID'],
|
|
558
|
+
match['conv_type'],
|
|
559
|
+
match['disp_format'],
|
|
560
|
+
match['unit'],
|
|
561
|
+
match['conversion'],
|
|
562
|
+
match['indentation'],
|
|
563
|
+
u_conf
|
|
564
|
+
)
|
|
565
|
+
compu_method_translators.append(
|
|
566
|
+
{
|
|
567
|
+
'new_name': new_name,
|
|
568
|
+
'old_name': match['name'],
|
|
569
|
+
'regex': re.compile(
|
|
570
|
+
r'(\s*)' # beginning
|
|
571
|
+
r'\b{name}\b' # word
|
|
572
|
+
r'(' # start of end-match
|
|
573
|
+
r'\s*(/\* Conversion \*/)?' # optional comment
|
|
574
|
+
r'\s*\n' # newline
|
|
575
|
+
r')'.format(name=match['name']) # end of end-match
|
|
576
|
+
),
|
|
577
|
+
'replacement': r'\1{name}\2'.format(name=new_name),
|
|
578
|
+
'old_compu_method': match['compu_method'],
|
|
579
|
+
'new_compu_method': new_compu_method
|
|
580
|
+
}
|
|
581
|
+
)
|
|
582
|
+
return compu_method_translators
|
|
583
|
+
|
|
584
|
+
def _patch_kp_blob(self, block):
|
|
585
|
+
"""Return updated measurement block text.
|
|
586
|
+
Args:
|
|
587
|
+
block (str): A2L text block
|
|
588
|
+
Returns:
|
|
589
|
+
a2l_text (str): A2L text without KP_BLOB.
|
|
590
|
+
"""
|
|
591
|
+
ecu_address = '0x00000000'
|
|
592
|
+
for match in self._expr_block_meas_kp_blob_parser.finditer(block):
|
|
593
|
+
start, end = match.span()
|
|
594
|
+
block = f'{block[:start]}ECU_ADDRESS {ecu_address}{block[end:]}'
|
|
595
|
+
return block
|
|
596
|
+
|
|
597
|
+
def merge(self, f_name, complete_a2l=False, silver_a2l=False):
|
|
598
|
+
"""Write merged a2l-file.
|
|
599
|
+
|
|
600
|
+
Args:
|
|
601
|
+
f_name (str): Output filename.
|
|
602
|
+
"""
|
|
603
|
+
a2l = ''
|
|
604
|
+
a2l_config = self._prj_cfg.get_a2l_cfg()
|
|
605
|
+
for _, data in self._blks.items():
|
|
606
|
+
for _, blk in data.items():
|
|
607
|
+
if not a2l_config['allow_kp_blob']:
|
|
608
|
+
blk = self._patch_kp_blob(blk)
|
|
609
|
+
a2l += blk + '\n\n'
|
|
610
|
+
|
|
611
|
+
if complete_a2l:
|
|
612
|
+
events = []
|
|
613
|
+
time_unit_10ms = '0x07'
|
|
614
|
+
rasters = self._prj_cfg.get_units_raster_cfg()
|
|
615
|
+
for xcp_id, evt_data in enumerate(rasters['SampleTimes'].items(), 1):
|
|
616
|
+
events.append({
|
|
617
|
+
'time_cycle': '0x%02X' % int(evt_data[1] * 100),
|
|
618
|
+
'time_unit': time_unit_10ms,
|
|
619
|
+
'name': evt_data[0],
|
|
620
|
+
'channel_id': '0x%04X' % xcp_id
|
|
621
|
+
})
|
|
622
|
+
a2l_template = A2lProjectTemplate(
|
|
623
|
+
a2l,
|
|
624
|
+
a2l_config['asap2_version'],
|
|
625
|
+
a2l_config['name'],
|
|
626
|
+
events,
|
|
627
|
+
a2l_config['ip_address'],
|
|
628
|
+
a2l_config['ip_port']
|
|
629
|
+
)
|
|
630
|
+
a2l = a2l_template.render()
|
|
631
|
+
elif silver_a2l:
|
|
632
|
+
a2l_template = A2lSilverTemplate(a2l)
|
|
633
|
+
a2l = a2l_template.render()
|
|
634
|
+
|
|
635
|
+
self.a2l = a2l
|
|
636
|
+
|
|
637
|
+
with open(f_name, 'w', encoding="ISO-8859-1") as ma2l:
|
|
638
|
+
ma2l.write(a2l)
|
|
639
|
+
self.info('Written the merged A2L-file %s', f_name)
|
|
640
|
+
|
|
641
|
+
def get_characteristic_axis_data(self):
|
|
642
|
+
"""Get characteristic map axis data from merged a2l-file."""
|
|
643
|
+
axis_data = {}
|
|
644
|
+
for blk_def, type_, label in self._block_finder.findall(self.a2l):
|
|
645
|
+
if type_ == "CHARACTERISTIC":
|
|
646
|
+
axes = re.findall('AXIS_PTS_REF (.*)', blk_def)
|
|
647
|
+
if label in axis_data:
|
|
648
|
+
self.critical("Multiple CHARACTERISTIC for %s in merged a2l.", label)
|
|
649
|
+
axis_data[label] = {'axes': axes}
|
|
650
|
+
return axis_data
|