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.
Files changed (132) hide show
  1. powertrain_build/__init__.py +40 -40
  2. powertrain_build/__main__.py +6 -6
  3. powertrain_build/a2l.py +582 -582
  4. powertrain_build/a2l_merge.py +650 -650
  5. powertrain_build/a2l_templates.py +717 -717
  6. powertrain_build/build.py +985 -985
  7. powertrain_build/build_defs.py +309 -309
  8. powertrain_build/build_proj_config.py +690 -690
  9. powertrain_build/check_interface.py +575 -575
  10. powertrain_build/cli.py +141 -141
  11. powertrain_build/config.py +542 -542
  12. powertrain_build/core.py +395 -395
  13. powertrain_build/core_dummy.py +343 -343
  14. powertrain_build/create_conversion_table.py +73 -73
  15. powertrain_build/dids.py +916 -916
  16. powertrain_build/dummy.py +157 -157
  17. powertrain_build/dummy_spm.py +252 -252
  18. powertrain_build/environmentcheck.py +52 -52
  19. powertrain_build/ext_dbg.py +255 -255
  20. powertrain_build/ext_var.py +327 -327
  21. powertrain_build/feature_configs.py +301 -301
  22. powertrain_build/gen_allsysteminfo.py +227 -227
  23. powertrain_build/gen_label_split.py +449 -449
  24. powertrain_build/handcode_replacer.py +124 -124
  25. powertrain_build/html_report.py +133 -133
  26. powertrain_build/interface/__init__.py +4 -4
  27. powertrain_build/interface/application.py +511 -511
  28. powertrain_build/interface/base.py +500 -500
  29. powertrain_build/interface/csp_api.py +490 -490
  30. powertrain_build/interface/device_proxy.py +677 -677
  31. powertrain_build/interface/ems.py +67 -67
  32. powertrain_build/interface/export_global_vars.py +121 -121
  33. powertrain_build/interface/generate_adapters.py +132 -132
  34. powertrain_build/interface/generate_hi_interface.py +87 -87
  35. powertrain_build/interface/generate_service.py +69 -69
  36. powertrain_build/interface/generate_wrappers.py +147 -147
  37. powertrain_build/interface/generation_utils.py +142 -142
  38. powertrain_build/interface/hal.py +194 -194
  39. powertrain_build/interface/model_yaml_verification.py +348 -348
  40. powertrain_build/interface/service.py +296 -296
  41. powertrain_build/interface/simulink.py +249 -249
  42. powertrain_build/interface/update_call_sources.py +180 -180
  43. powertrain_build/interface/update_model_yaml.py +186 -186
  44. powertrain_build/interface/zone_controller.py +362 -362
  45. powertrain_build/lib/__init__.py +4 -4
  46. powertrain_build/lib/helper_functions.py +127 -127
  47. powertrain_build/lib/logger.py +55 -55
  48. powertrain_build/matlab_scripts/CodeGen/BuildAutomationPyBuild.m +78 -78
  49. powertrain_build/matlab_scripts/CodeGen/Generate_A2L.m +154 -154
  50. powertrain_build/matlab_scripts/CodeGen/generateTLUnit.m +239 -239
  51. powertrain_build/matlab_scripts/CodeGen/getAsilClassification.m +28 -28
  52. powertrain_build/matlab_scripts/CodeGen/modelConfiguredForTL.m +28 -28
  53. powertrain_build/matlab_scripts/CodeGen/moveDefOutports.m +88 -88
  54. powertrain_build/matlab_scripts/CodeGen/parseCalMeasData.m +410 -410
  55. powertrain_build/matlab_scripts/CodeGen/parseCoreIdentifiers.m +139 -139
  56. powertrain_build/matlab_scripts/CodeGen/parseDIDs.m +141 -141
  57. powertrain_build/matlab_scripts/CodeGen/parseInPorts.m +106 -106
  58. powertrain_build/matlab_scripts/CodeGen/parseIncludeConfigs.m +25 -25
  59. powertrain_build/matlab_scripts/CodeGen/parseModelInfo.m +38 -38
  60. powertrain_build/matlab_scripts/CodeGen/parseNVM.m +81 -81
  61. powertrain_build/matlab_scripts/CodeGen/parseOutPorts.m +120 -120
  62. powertrain_build/matlab_scripts/CodeGen/parsePreProcBlks.m +23 -23
  63. powertrain_build/matlab_scripts/CodeGen/struct2JSON.m +128 -128
  64. powertrain_build/matlab_scripts/CodeGen/updateCodeSwConfig.m +31 -31
  65. powertrain_build/matlab_scripts/Init_PyBuild.m +91 -91
  66. powertrain_build/matlab_scripts/__init__.py +2 -2
  67. powertrain_build/matlab_scripts/helperFunctions/Get_Full_Name.m +46 -46
  68. powertrain_build/matlab_scripts/helperFunctions/Get_SrcLines.m +12 -12
  69. powertrain_build/matlab_scripts/helperFunctions/Init_Models.m +78 -78
  70. powertrain_build/matlab_scripts/helperFunctions/Init_Projects.m +67 -67
  71. powertrain_build/matlab_scripts/helperFunctions/Read_Units.m +34 -34
  72. powertrain_build/matlab_scripts/helperFunctions/SetProjectTimeSamples.m +26 -26
  73. powertrain_build/matlab_scripts/helperFunctions/Strip_Suffix.m +16 -16
  74. powertrain_build/matlab_scripts/helperFunctions/followLink.m +118 -118
  75. powertrain_build/matlab_scripts/helperFunctions/getCodeSwitches.m +50 -50
  76. powertrain_build/matlab_scripts/helperFunctions/getConsumerBlocks.m +30 -30
  77. powertrain_build/matlab_scripts/helperFunctions/getDefBlock.m +39 -39
  78. powertrain_build/matlab_scripts/helperFunctions/getDefOutport.m +58 -58
  79. powertrain_build/matlab_scripts/helperFunctions/getDstBlocks.m +19 -19
  80. powertrain_build/matlab_scripts/helperFunctions/getDstLines.m +13 -13
  81. powertrain_build/matlab_scripts/helperFunctions/getInterfaceSignals.m +37 -37
  82. powertrain_build/matlab_scripts/helperFunctions/getName.m +37 -37
  83. powertrain_build/matlab_scripts/helperFunctions/getPath.m +6 -6
  84. powertrain_build/matlab_scripts/helperFunctions/getProperValue.m +21 -21
  85. powertrain_build/matlab_scripts/helperFunctions/getSrcBlocks.m +19 -19
  86. powertrain_build/matlab_scripts/helperFunctions/getSrcLines.m +13 -13
  87. powertrain_build/matlab_scripts/helperFunctions/loadLibraries.m +10 -10
  88. powertrain_build/matlab_scripts/helperFunctions/loadjson.m +6 -6
  89. powertrain_build/matlab_scripts/helperFunctions/modifyEnumStructField.m +21 -21
  90. powertrain_build/matlab_scripts/helperFunctions/removeConfigDuplicates.m +31 -31
  91. powertrain_build/matlab_scripts/helperFunctions/sortSystemByClass.m +26 -26
  92. powertrain_build/matlab_scripts/helperFunctions/tl_getfast.m +89 -89
  93. powertrain_build/matlab_scripts/helperFunctions/topLevelSystem.m +20 -20
  94. powertrain_build/matlab_scripts/helperFunctions/updateModels.m +131 -131
  95. powertrain_build/memory_section.py +224 -224
  96. powertrain_build/nvm_def.py +729 -729
  97. powertrain_build/problem_logger.py +86 -86
  98. powertrain_build/pt_matlab.py +430 -430
  99. powertrain_build/pt_win32.py +144 -144
  100. powertrain_build/replace_compu_tab_ref.py +105 -105
  101. powertrain_build/rte_dummy.py +254 -254
  102. powertrain_build/sched_funcs.py +209 -207
  103. powertrain_build/signal.py +7 -7
  104. powertrain_build/signal_if_html_rep.py +221 -221
  105. powertrain_build/signal_if_html_rep_all.py +302 -302
  106. powertrain_build/signal_incons_html_rep.py +180 -180
  107. powertrain_build/signal_incons_html_rep_all.py +366 -366
  108. powertrain_build/signal_incons_html_rep_base.py +168 -168
  109. powertrain_build/signal_inconsistency_check.py +641 -641
  110. powertrain_build/signal_interfaces.py +864 -864
  111. powertrain_build/templates/Index_SigCheck_All.html +22 -22
  112. powertrain_build/templates/Index_SigIf_All.html +19 -19
  113. powertrain_build/types.py +218 -218
  114. powertrain_build/unit_configs.py +419 -419
  115. powertrain_build/user_defined_types.py +660 -660
  116. powertrain_build/versioncheck.py +66 -66
  117. powertrain_build/wrapper.py +512 -512
  118. powertrain_build/xlrd_csv.py +87 -87
  119. powertrain_build/zone_controller/__init__.py +4 -4
  120. powertrain_build/zone_controller/calibration.py +176 -176
  121. powertrain_build/zone_controller/composition_yaml.py +880 -878
  122. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/METADATA +100 -100
  123. powertrain_build-1.13.3.dev3.dist-info/RECORD +130 -0
  124. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/WHEEL +1 -1
  125. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/LICENSE +202 -202
  126. powertrain_build-1.13.3.dev3.dist-info/pbr.json +1 -0
  127. powertrain_build-1.13.1.dist-info/RECORD +0 -130
  128. powertrain_build-1.13.1.dist-info/pbr.json +0 -1
  129. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/entry_points.txt +0 -0
  130. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/AUTHORS +0 -0
  131. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/licenses/NOTICE +0 -0
  132. {powertrain_build-1.13.1.dist-info → powertrain_build-1.13.3.dev3.dist-info}/top_level.txt +0 -0
@@ -1,449 +1,449 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- """Module for labelsplit files."""
5
- import glob
6
- import re
7
- import json
8
- import sys
9
- from xml.etree import ElementTree
10
- from pathlib import Path
11
- from powertrain_build.feature_configs import FeatureConfigs
12
- from powertrain_build.unit_configs import UnitConfigs
13
- from powertrain_build.signal_interfaces import CsvSignalInterfaces
14
- from powertrain_build.lib import helper_functions, logger
15
-
16
- LOGGER = logger.create_logger(__file__)
17
-
18
-
19
- class LabelSplit:
20
- """ Provides common LabelSplit functions for multiple repos.
21
- """
22
- def __init__(self, project, build_cfg, cfg_json, cmt_source_folder):
23
- """Read project configuration file to internal an representation.
24
-
25
- Args:
26
- project (str): Project name.
27
- build_cfg(BuildProjConfig): configures which units are active in the current project and where
28
- the code switch files are located.
29
- cfg_json(Path): Path to label split configuration file.
30
- cmt_source_folder (Path): Path to CMT source folder.
31
- """
32
- super().__init__()
33
- self.project = project
34
- self.build_cfg = build_cfg
35
- project_a2l_file_path = Path(self.build_cfg.get_src_code_dst_dir(),
36
- self.build_cfg.get_a2l_name())
37
- self.feature_cfg = FeatureConfigs(self.build_cfg)
38
- self.unit_cfg = UnitConfigs(self.build_cfg, self.feature_cfg)
39
- self.csv_if = CsvSignalInterfaces(self.build_cfg, self.unit_cfg)
40
- self.project_a2l_symbols = self.get_project_a2l_symbols(project_a2l_file_path)
41
- self.labelsplit_cfg = self.read_json(cfg_json)
42
- self.cmt_source_folder = cmt_source_folder
43
-
44
- @staticmethod
45
- def read_json(cfg_json):
46
- """Read label split configuration file from given location
47
- If the file does not exsit in the given location, program
48
- exits with error message.
49
-
50
- Args:
51
- cfg_json(Path): Path to label split configuration file
52
- Returns:
53
- labelsplit_cfg (dict): Dict of given file content
54
- """
55
- labelsplit_cfg = None
56
- if cfg_json.exists():
57
- with cfg_json.open() as json_file:
58
- labelsplit_cfg = json.load(json_file)
59
- return labelsplit_cfg
60
- LOGGER.error('Cannot find label split config file: %s', cfg_json)
61
- sys.exit(1)
62
-
63
- @staticmethod
64
- def get_project_a2l_symbols(project_a2l_file_path):
65
- """Get a list of calibration symbols found in a given project A2L file.
66
-
67
- Args:
68
- project_a2l_file_path (Path): Path to project A2L file.
69
- Returns:
70
- symbols_in_a2l (list): List of calibration symbols found in the project A2L file.
71
- """
72
- symbols_in_a2l = []
73
-
74
- with project_a2l_file_path.open() as a2l_fh:
75
- a2l_text = a2l_fh.read()
76
-
77
- calibration_blocks = re.findall(r'(?:\s*\n)*(\s*/begin (CHARACTERISTIC|AXIS_PTS)[\n\s]*'
78
- r'(\w+)([\[\d+\]]*).*?\n.*?/end \2)',
79
- a2l_text,
80
- flags=re.M | re.DOTALL)
81
-
82
- for blk in calibration_blocks:
83
- symbols_in_a2l.append(blk[2])
84
-
85
- return symbols_in_a2l
86
-
87
- @staticmethod
88
- def get_sgp_symbols(sgp_file: Path):
89
- """Get symbols and symbol_groups found in a given _sgp.xml file.
90
-
91
- Example output: {sVcExample: [(3, EC_EX_1), (4, EC_EX_2)]}, where the indices are column indices:
92
- 1 -> symbol name (therefore not in list of symbol groups).
93
- 2 -> diesel group.
94
- 3 -> petrol group.
95
- 4 -> hybrid group.
96
- 5 -> subsystem (therefore not in list of symbol groups).
97
-
98
- Args:
99
- sgp_file (Path): Path to an _sgp.xml file.
100
- Returns:
101
- found_sgp_symbols (dict): A symbol to symbol_groups dictionary found in the sgp_file.
102
- """
103
- tree = ElementTree.parse(sgp_file)
104
- root = tree.getroot()
105
- search_string = '{{urn:schemas-microsoft-com:office:spreadsheet}}{tag}'
106
- label_sheet = root.find(search_string.format(tag='Worksheet'))
107
- table = label_sheet.find(search_string.format(tag='Table'))
108
- rows = table.findall(search_string.format(tag='Row'))
109
-
110
- found_sgp_symbols = {}
111
- for row in rows:
112
- symbol = None
113
- column_counter = 1
114
- cells = row.findall(search_string.format(tag='Cell'))
115
- for cell in cells:
116
- data = cell.find(search_string.format(tag='Data'))
117
- if data is not None:
118
- # Sometimes there are spaces in the symbol cell
119
- # Sometimes there is a weird \ufeff character (VcDebug_sgp.xml) in the symbol cell
120
- value = data.text.replace(' ', '').replace('\ufeff', '')
121
- if symbol is None:
122
- symbol = value
123
- found_sgp_symbols[symbol] = []
124
- else:
125
- new_index = search_string.format(tag='Index')
126
- if new_index in cell.attrib:
127
- column_counter = int(cell.attrib[new_index])
128
- found_sgp_symbols[symbol].append((column_counter, value))
129
- column_counter += 1
130
-
131
- return found_sgp_symbols
132
-
133
- def get_sgp_symbol_group(self, symbol_groups_by_index):
134
- """Match _sgp.xml file indices (symbol groups) with a given project.
135
-
136
- Args:
137
- symbol_groups_by_index (list(tuple)): List of (index, symbol_group) pairs.
138
- Returns:
139
- symbol_group (str): The symbol group corresponding to the given project.
140
- """
141
- symbol_group = ''
142
- symbol_dict = self.labelsplit_cfg.get("SGP_SYMBOL_GROUPS")
143
- symbol_list = [val for key, val in symbol_dict.items() if key in self.project]
144
- if len(symbol_list) >= 1:
145
- for index, group in symbol_groups_by_index:
146
- if index == symbol_list[0]:
147
- symbol_group = group
148
- else:
149
- LOGGER.error('Cannot match symbol group type for project: %s', self.project)
150
- return symbol_group
151
-
152
- def get_interface_symbols_and_groups(self, interface_dict, in_symbol_sgp_dict, out_symbol_sgp_dict):
153
- """Get a list of (symbol, symbol_group) pairs found in given interface and sgp files.
154
-
155
- Args:
156
- interface_dict (dict): interface to symbol map, matching a certain IO type.
157
- in_symbol_sgp_dict (dict): An input symbol to symbol_groups dictionary found in an sgp_file,
158
- to be compared with interface_dict inputs.
159
- out_symbol_sgp_dict (dict): An output symbol to symbol_groups dictionary found in an sgp_file,
160
- to be compared with interface_dict outputs.
161
- Returns:
162
- symbols_and_groups (list(tuple)): List of (symbol, symbol_group) pairs in: interface and sgp file.
163
- """
164
- symbols_and_groups = []
165
- for interface, symbol_data in interface_dict.items():
166
- for symbol in symbol_data.keys():
167
- debug_name = re.sub(r'\w(\w+)', r'c\1_db', symbol)
168
- switch_name = re.sub(r'\w(\w+)', r'c\1_sw', symbol)
169
- if 'Input' in interface and debug_name in in_symbol_sgp_dict and switch_name in in_symbol_sgp_dict:
170
- debug_symbol_group = self.get_sgp_symbol_group(in_symbol_sgp_dict[debug_name])
171
- switch_symbol_group = self.get_sgp_symbol_group(in_symbol_sgp_dict[switch_name])
172
- symbols_and_groups.extend([(debug_name, debug_symbol_group), (switch_name, switch_symbol_group)])
173
- elif 'Output' in interface and debug_name in out_symbol_sgp_dict and switch_name in out_symbol_sgp_dict:
174
- debug_symbol_group = self.get_sgp_symbol_group(out_symbol_sgp_dict[debug_name])
175
- switch_symbol_group = self.get_sgp_symbol_group(out_symbol_sgp_dict[switch_name])
176
- symbols_and_groups.extend([(debug_name, debug_symbol_group), (switch_name, switch_symbol_group)])
177
- return symbols_and_groups
178
-
179
- def get_debug_symbols_and_groups(self):
180
- """Get a list of (symbol, symbol_group) pairs found in project interface and VcDebug*_sgp.xml files.
181
-
182
- Returns:
183
- debug_symbols_and_groups (list(tuple)): List of (symbol, symbol_group) pairs in:
184
- interface and VcDebug*_sgp.xmlfiles.
185
- """
186
- _unused, dep, _unused_two, debug = self.csv_if.get_io_config()
187
- sgp_file_dict = self.labelsplit_cfg.get("SGP_FILE")
188
- debug_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('debug'))
189
- debug_output_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('debug_output'))
190
- dep_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('dep'))
191
- dep_output_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('dep_output'))
192
- debug_sgp_symbols = self.get_sgp_symbols(debug_sgp_file)
193
- debug_output_sgp_symbols = self.get_sgp_symbols(debug_output_sgp_file)
194
- dep_sgp_symbols = self.get_sgp_symbols(dep_sgp_file)
195
- dep_output_sgp_symbols = self.get_sgp_symbols(dep_output_sgp_file)
196
-
197
- symbols_and_groups_tmp = []
198
- debug_tmp = self.get_interface_symbols_and_groups(debug,
199
- debug_sgp_symbols,
200
- debug_output_sgp_symbols)
201
- dep_tmp = self.get_interface_symbols_and_groups(dep,
202
- dep_sgp_symbols,
203
- dep_output_sgp_symbols)
204
- symbols_and_groups_tmp.extend(debug_tmp)
205
- symbols_and_groups_tmp.extend(dep_tmp)
206
-
207
- debug_symbols_and_groups = []
208
- for symbol, symbol_group in symbols_and_groups_tmp:
209
- if symbol_group == '':
210
- LOGGER.info('Debug symbol %s is missing symbol group and will be removed.', symbol)
211
- else:
212
- debug_symbols_and_groups.append((symbol, symbol_group))
213
-
214
- return debug_symbols_and_groups
215
-
216
- def check_unit_par_file(self, unit):
217
- """Check <unit>_par.m file for default sgp symbol group.
218
-
219
- Args:
220
- unit (str): Current unit/model name.
221
- Returns:
222
- has_sgp_default (Bool): True/False if unit is associated with default sgp value.
223
- default_symbol_group (str): Name of default symbol group.
224
- """
225
- has_sgp_default = False
226
- default_symbol_group = ''
227
- base_search_string = r'SgpDefault\.{unit}\.[A-Za-z]+\s*=\s*[\'\"]([A-Za-z_]+)[\'\"]'
228
- search_string = base_search_string.format(unit=unit)
229
-
230
- non_existent_par_file = Path('non_existent_par_file.m')
231
- found_par_files = glob.glob('Models/*/' + unit + '/' + unit + '_par.m')
232
- if len(found_par_files) > 1:
233
- LOGGER.warning('Found more than one _par.m file, using %s', found_par_files[0])
234
- par_file = Path(found_par_files[0]) if found_par_files else non_existent_par_file
235
-
236
- if self.labelsplit_cfg.get("special_unit_prefixes"):
237
- for special_prefix in self.labelsplit_cfg.get("special_unit_prefixes"):
238
- if unit.startswith(special_prefix) and not par_file.is_file():
239
- # Some units require special handling.
240
- if '__' in unit:
241
- parent = unit.replace('__', 'Mdl__')
242
- else:
243
- parent = unit + 'Mdl'
244
- found_par_files = glob.glob('Models/*/' + parent + '/' + parent + '_par.m')
245
- par_file = Path(found_par_files[0]) if found_par_files else Path(non_existent_par_file)
246
- # Default symbol group is based on c-file name
247
- c_name = re.sub('(Mdl)?(__.*)?', '', unit)
248
- search_string = base_search_string.format(unit=c_name)
249
-
250
- if par_file.is_file():
251
- with par_file.open(encoding="latin-1") as par_fh:
252
- par_text = par_fh.read()
253
- sgp_default_match = re.search(search_string, par_text)
254
- if sgp_default_match is not None:
255
- has_sgp_default = True
256
- default_symbol_group = sgp_default_match.group(1)
257
- else:
258
- LOGGER.info('Missing _par file for model: %s', unit)
259
-
260
- return has_sgp_default, default_symbol_group
261
-
262
- def get_unit_sgp_file(self, unit):
263
- """Get path to <unit>_sgp.xml file.
264
-
265
- Args:
266
- unit (str): Current unit/model name.
267
- Returns:
268
- sgp_file (Path): Path to <unit>_sgp.xml file.
269
- """
270
- non_existent_sgp_file = Path('non_existent_sgp_file.xml')
271
- found_sgp_files = glob.glob('Models/*/' + unit + '/' + unit + '_sgp.xml')
272
- if len(found_sgp_files) > 1:
273
- LOGGER.warning('Found more than one _sgp.xml file, using %s', found_sgp_files[0])
274
- sgp_file = Path(found_sgp_files[0]) if found_sgp_files else Path(non_existent_sgp_file)
275
-
276
- if self.labelsplit_cfg.get("special_unit_prefixes"):
277
- for special_prefix in self.labelsplit_cfg.get("special_unit_prefixes"):
278
- if unit.startswith(special_prefix) and not sgp_file.is_file():
279
- # Some units require special handling.
280
- if '__' in unit:
281
- parent = unit.replace('__', 'Mdl__')
282
- else:
283
- parent = unit + 'Mdl'
284
- found_sgp_files = glob.glob('Models/*/' + parent + '/' + parent + '_sgp.xml')
285
- sgp_file = Path(found_sgp_files[0]) if found_sgp_files else Path(non_existent_sgp_file)
286
-
287
- return sgp_file
288
-
289
- def get_unit_symbols_and_groups(self, unit, calibration_symbols):
290
- """Get a list of (symbol, symbol_group) pairs found in A2L, <unit>_sgp/par and config_<unit>.json files.
291
-
292
- Args:
293
- unit (str): Current unit/model name.
294
- calibration_symbols (list): All calibration symbols for the unit (from config_<unit>.json).
295
- Returns:
296
- unit_symbols_and_groups (list(tuple)): List of (symbol, symbol_group) pairs in: A2L, _sgp/_par and
297
- config files.
298
- """
299
- unit_symbols_and_groups = []
300
- has_sgp_default, default_symbol_group = self.check_unit_par_file(unit)
301
- sgp_file = self.get_unit_sgp_file(unit)
302
-
303
- if sgp_file.is_file():
304
- found_sgp_symbols = self.get_sgp_symbols(sgp_file)
305
- else:
306
- found_sgp_symbols = {}
307
- LOGGER.info('Missing _sgp file for model: %s', unit)
308
-
309
- for symbol in calibration_symbols:
310
- if symbol not in self.project_a2l_symbols:
311
- LOGGER.info('Symbol %s not in project A2L file and will be removed.', symbol)
312
- continue
313
-
314
- if symbol in found_sgp_symbols:
315
- symbol_group = self.get_sgp_symbol_group(found_sgp_symbols[symbol])
316
- unit_symbols_and_groups.append((symbol, symbol_group))
317
- elif has_sgp_default:
318
- if symbol.endswith('_sw') or symbol.endswith('_db'):
319
- LOGGER.info('Debug symbol %s not in sgp file and will be removed.', symbol)
320
- else:
321
- unit_symbols_and_groups.append((symbol, default_symbol_group))
322
- else:
323
- LOGGER.info('Symbol %s missing in _sgp file and lack SgpDefault value.', symbol)
324
-
325
- return unit_symbols_and_groups
326
-
327
- def get_calibration_constants(self):
328
- """Get all calibration symbols for each unit in the project.
329
-
330
- Returns:
331
- calibration_symbols_per_unit (dict): A unit to symbol list dictionary.
332
- """
333
- security_variables = self.labelsplit_cfg.get("security_variables")
334
- u_conf_dict = self.unit_cfg.get_per_cfg_unit_cfg()
335
-
336
- safe_calibration_symbols = {}
337
- for symbol, symbol_data in u_conf_dict['calib_consts'].items():
338
- if symbol not in security_variables:
339
- safe_calibration_symbols.update({symbol: symbol_data})
340
-
341
- calibration_symbols_per_unit = {}
342
- for symbol, symbol_data in safe_calibration_symbols.items():
343
- for unit, unit_data in symbol_data.items():
344
- if 'CVC_CAL' in unit_data['class']:
345
- if unit in calibration_symbols_per_unit:
346
- calibration_symbols_per_unit[unit].append(symbol)
347
- else:
348
- calibration_symbols_per_unit[unit] = [symbol]
349
-
350
- return calibration_symbols_per_unit
351
-
352
- def get_symbols_and_groups(self):
353
- """Get a list of (symbol, symbol_group) pairs found in A2L, <unit>_sgp/par and config_<unit>.json files.
354
-
355
- Returns:
356
- exit_code (int): 0/1 based on successful collection of symbols and symbol groups.
357
- all_symbols_and_groups (dict): A symbol to symbol_group dictionary for all A2L, _sgp/_par and config files,
358
- """
359
- exit_code = 0
360
- calibration_symbols_per_unit = self.get_calibration_constants()
361
- debug_symbols = self.get_debug_symbols_and_groups()
362
-
363
- all_symbol_and_group_pairs = []
364
- if self.labelsplit_cfg.get("project_symbols"):
365
- special_project_dict = self.labelsplit_cfg.get("project_symbols")
366
- pair_list = [val for key, val in special_project_dict.items() if key in self.project]
367
- if len(pair_list) == 1:
368
- symbol_and_group_pairs_list = pair_list[0].items()
369
- all_symbol_and_group_pairs += symbol_and_group_pairs_list
370
- elif len(pair_list) > 1:
371
- LOGGER.error('Project %s has does not follow the name rule', self.project)
372
- return 1, {}
373
-
374
- all_symbol_and_group_pairs.extend(debug_symbols)
375
-
376
- for unit, symbols in calibration_symbols_per_unit.items():
377
- if self.labelsplit_cfg.get("special_units"):
378
- special_unit_dict = self.labelsplit_cfg.get("special_units")
379
- if unit in special_unit_dict.keys():
380
- # Some units require special handling.
381
- LOGGER.warning('Found %s, assuming %s is used.', unit, special_unit_dict.get(unit))
382
- labels = self.get_unit_symbols_and_groups(special_unit_dict.get(unit), symbols)
383
- else:
384
- labels = self.get_unit_symbols_and_groups(unit, symbols)
385
- else:
386
- labels = self.get_unit_symbols_and_groups(unit, symbols)
387
- all_symbol_and_group_pairs.extend(labels)
388
-
389
- symbol_to_group_dict = {}
390
- for symbol, symbol_group in all_symbol_and_group_pairs:
391
- if symbol in symbol_to_group_dict:
392
- if symbol_to_group_dict[symbol] != symbol_group:
393
- LOGGER.error('Symbol %s multiply defined with different symbol groups.', symbol)
394
- exit_code = 1
395
- else:
396
- symbol_to_group_dict[symbol] = symbol_group
397
-
398
- return exit_code, symbol_to_group_dict
399
-
400
- def generate_label_split_xml_file(self, symbols_and_groups):
401
- """Generate a label split file, given a directory plus labels and groups to add.
402
-
403
- Args:
404
- symbols_and_groups (dict): A symbol to symbol_group dictionary given a project.
405
- Returns:
406
- exit_code (int): 0/1 based on successful generation of Labelsplit.xls.
407
- """
408
- errors = []
409
- project_root_dir = self.build_cfg.get_root_dir()
410
- cmt_output_folder = helper_functions.create_dir(Path(project_root_dir, 'output', 'CMT'))
411
- start_file_name = Path(self.cmt_source_folder, 'template_labelsplit_sgp_start.xml_')
412
- row_count_file_name = Path(cmt_output_folder, 'labelsplit_rowcount.xml_')
413
- start_2_file_name = Path(self.cmt_source_folder, 'template_labelsplit_sgp_start_2.xml_')
414
- label_split_rows_filename = Path(cmt_output_folder, 'labelsplit_rows.xml_')
415
- end_file_name = Path(self.cmt_source_folder, 'template_labelsplit_sgp_end.xml_')
416
- files_to_merge = [start_file_name, row_count_file_name, start_2_file_name,
417
- label_split_rows_filename, end_file_name]
418
-
419
- with row_count_file_name.open('w', encoding="utf-8") as rc_fh:
420
- rc_fh.write(f'{len(symbols_and_groups) + 1}') # header + data
421
-
422
- with label_split_rows_filename.open('w', encoding="utf-8") as lsrf_fh:
423
- for symbol, symbol_group in symbols_and_groups.items():
424
- if symbol_group == '':
425
- errors.append(f'Missing symbol group for symbol: {symbol}')
426
- elif symbol_group == 'VCC_SPM_DEBUG':
427
- LOGGER.info('Ignoring undistributed debug symbol: %s', symbol)
428
- else:
429
- lsrf_fh.write(
430
- ' <Row ss:AutoFitHeight="0">\n'
431
- f' <Cell><Data ss:Type="String">{symbol}'
432
- '</Data><NamedCell ss:Name="_FilterDatabase"/></Cell>\n'
433
- f' <Cell ss:Index="5"><Data ss:Type="String">{symbol_group}'
434
- '</Data><NamedCell ss:Name="_FilterDatabase"/></Cell>\n'
435
- ' </Row>\n'
436
- )
437
-
438
- if errors:
439
- LOGGER.error('\n'.join(errors))
440
- return 1
441
-
442
- output_file_name = Path(project_root_dir, 'output', 'CMT', 'Labelsplit.xls')
443
- with output_file_name.open('w', encoding="utf-8") as output_fh:
444
- for file_name in files_to_merge:
445
- with file_name.open(encoding="utf-8") as input_fh:
446
- content = input_fh.read()
447
- output_fh.write(content)
448
- LOGGER.info('Delivery to: %s', str(output_file_name))
449
- return 0
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ """Module for labelsplit files."""
5
+ import glob
6
+ import re
7
+ import json
8
+ import sys
9
+ from xml.etree import ElementTree
10
+ from pathlib import Path
11
+ from powertrain_build.feature_configs import FeatureConfigs
12
+ from powertrain_build.unit_configs import UnitConfigs
13
+ from powertrain_build.signal_interfaces import CsvSignalInterfaces
14
+ from powertrain_build.lib import helper_functions, logger
15
+
16
+ LOGGER = logger.create_logger(__file__)
17
+
18
+
19
+ class LabelSplit:
20
+ """ Provides common LabelSplit functions for multiple repos.
21
+ """
22
+ def __init__(self, project, build_cfg, cfg_json, cmt_source_folder):
23
+ """Read project configuration file to internal an representation.
24
+
25
+ Args:
26
+ project (str): Project name.
27
+ build_cfg(BuildProjConfig): configures which units are active in the current project and where
28
+ the code switch files are located.
29
+ cfg_json(Path): Path to label split configuration file.
30
+ cmt_source_folder (Path): Path to CMT source folder.
31
+ """
32
+ super().__init__()
33
+ self.project = project
34
+ self.build_cfg = build_cfg
35
+ project_a2l_file_path = Path(self.build_cfg.get_src_code_dst_dir(),
36
+ self.build_cfg.get_a2l_name())
37
+ self.feature_cfg = FeatureConfigs(self.build_cfg)
38
+ self.unit_cfg = UnitConfigs(self.build_cfg, self.feature_cfg)
39
+ self.csv_if = CsvSignalInterfaces(self.build_cfg, self.unit_cfg)
40
+ self.project_a2l_symbols = self.get_project_a2l_symbols(project_a2l_file_path)
41
+ self.labelsplit_cfg = self.read_json(cfg_json)
42
+ self.cmt_source_folder = cmt_source_folder
43
+
44
+ @staticmethod
45
+ def read_json(cfg_json):
46
+ """Read label split configuration file from given location
47
+ If the file does not exsit in the given location, program
48
+ exits with error message.
49
+
50
+ Args:
51
+ cfg_json(Path): Path to label split configuration file
52
+ Returns:
53
+ labelsplit_cfg (dict): Dict of given file content
54
+ """
55
+ labelsplit_cfg = None
56
+ if cfg_json.exists():
57
+ with cfg_json.open() as json_file:
58
+ labelsplit_cfg = json.load(json_file)
59
+ return labelsplit_cfg
60
+ LOGGER.error('Cannot find label split config file: %s', cfg_json)
61
+ sys.exit(1)
62
+
63
+ @staticmethod
64
+ def get_project_a2l_symbols(project_a2l_file_path):
65
+ """Get a list of calibration symbols found in a given project A2L file.
66
+
67
+ Args:
68
+ project_a2l_file_path (Path): Path to project A2L file.
69
+ Returns:
70
+ symbols_in_a2l (list): List of calibration symbols found in the project A2L file.
71
+ """
72
+ symbols_in_a2l = []
73
+
74
+ with project_a2l_file_path.open() as a2l_fh:
75
+ a2l_text = a2l_fh.read()
76
+
77
+ calibration_blocks = re.findall(r'(?:\s*\n)*(\s*/begin (CHARACTERISTIC|AXIS_PTS)[\n\s]*'
78
+ r'(\w+)([\[\d+\]]*).*?\n.*?/end \2)',
79
+ a2l_text,
80
+ flags=re.M | re.DOTALL)
81
+
82
+ for blk in calibration_blocks:
83
+ symbols_in_a2l.append(blk[2])
84
+
85
+ return symbols_in_a2l
86
+
87
+ @staticmethod
88
+ def get_sgp_symbols(sgp_file: Path):
89
+ """Get symbols and symbol_groups found in a given _sgp.xml file.
90
+
91
+ Example output: {sVcExample: [(3, EC_EX_1), (4, EC_EX_2)]}, where the indices are column indices:
92
+ 1 -> symbol name (therefore not in list of symbol groups).
93
+ 2 -> diesel group.
94
+ 3 -> petrol group.
95
+ 4 -> hybrid group.
96
+ 5 -> subsystem (therefore not in list of symbol groups).
97
+
98
+ Args:
99
+ sgp_file (Path): Path to an _sgp.xml file.
100
+ Returns:
101
+ found_sgp_symbols (dict): A symbol to symbol_groups dictionary found in the sgp_file.
102
+ """
103
+ tree = ElementTree.parse(sgp_file)
104
+ root = tree.getroot()
105
+ search_string = '{{urn:schemas-microsoft-com:office:spreadsheet}}{tag}'
106
+ label_sheet = root.find(search_string.format(tag='Worksheet'))
107
+ table = label_sheet.find(search_string.format(tag='Table'))
108
+ rows = table.findall(search_string.format(tag='Row'))
109
+
110
+ found_sgp_symbols = {}
111
+ for row in rows:
112
+ symbol = None
113
+ column_counter = 1
114
+ cells = row.findall(search_string.format(tag='Cell'))
115
+ for cell in cells:
116
+ data = cell.find(search_string.format(tag='Data'))
117
+ if data is not None:
118
+ # Sometimes there are spaces in the symbol cell
119
+ # Sometimes there is a weird \ufeff character (VcDebug_sgp.xml) in the symbol cell
120
+ value = data.text.replace(' ', '').replace('\ufeff', '')
121
+ if symbol is None:
122
+ symbol = value
123
+ found_sgp_symbols[symbol] = []
124
+ else:
125
+ new_index = search_string.format(tag='Index')
126
+ if new_index in cell.attrib:
127
+ column_counter = int(cell.attrib[new_index])
128
+ found_sgp_symbols[symbol].append((column_counter, value))
129
+ column_counter += 1
130
+
131
+ return found_sgp_symbols
132
+
133
+ def get_sgp_symbol_group(self, symbol_groups_by_index):
134
+ """Match _sgp.xml file indices (symbol groups) with a given project.
135
+
136
+ Args:
137
+ symbol_groups_by_index (list(tuple)): List of (index, symbol_group) pairs.
138
+ Returns:
139
+ symbol_group (str): The symbol group corresponding to the given project.
140
+ """
141
+ symbol_group = ''
142
+ symbol_dict = self.labelsplit_cfg.get("SGP_SYMBOL_GROUPS")
143
+ symbol_list = [val for key, val in symbol_dict.items() if key in self.project]
144
+ if len(symbol_list) >= 1:
145
+ for index, group in symbol_groups_by_index:
146
+ if index == symbol_list[0]:
147
+ symbol_group = group
148
+ else:
149
+ LOGGER.error('Cannot match symbol group type for project: %s', self.project)
150
+ return symbol_group
151
+
152
+ def get_interface_symbols_and_groups(self, interface_dict, in_symbol_sgp_dict, out_symbol_sgp_dict):
153
+ """Get a list of (symbol, symbol_group) pairs found in given interface and sgp files.
154
+
155
+ Args:
156
+ interface_dict (dict): interface to symbol map, matching a certain IO type.
157
+ in_symbol_sgp_dict (dict): An input symbol to symbol_groups dictionary found in an sgp_file,
158
+ to be compared with interface_dict inputs.
159
+ out_symbol_sgp_dict (dict): An output symbol to symbol_groups dictionary found in an sgp_file,
160
+ to be compared with interface_dict outputs.
161
+ Returns:
162
+ symbols_and_groups (list(tuple)): List of (symbol, symbol_group) pairs in: interface and sgp file.
163
+ """
164
+ symbols_and_groups = []
165
+ for interface, symbol_data in interface_dict.items():
166
+ for symbol in symbol_data.keys():
167
+ debug_name = re.sub(r'\w(\w+)', r'c\1_db', symbol)
168
+ switch_name = re.sub(r'\w(\w+)', r'c\1_sw', symbol)
169
+ if 'Input' in interface and debug_name in in_symbol_sgp_dict and switch_name in in_symbol_sgp_dict:
170
+ debug_symbol_group = self.get_sgp_symbol_group(in_symbol_sgp_dict[debug_name])
171
+ switch_symbol_group = self.get_sgp_symbol_group(in_symbol_sgp_dict[switch_name])
172
+ symbols_and_groups.extend([(debug_name, debug_symbol_group), (switch_name, switch_symbol_group)])
173
+ elif 'Output' in interface and debug_name in out_symbol_sgp_dict and switch_name in out_symbol_sgp_dict:
174
+ debug_symbol_group = self.get_sgp_symbol_group(out_symbol_sgp_dict[debug_name])
175
+ switch_symbol_group = self.get_sgp_symbol_group(out_symbol_sgp_dict[switch_name])
176
+ symbols_and_groups.extend([(debug_name, debug_symbol_group), (switch_name, switch_symbol_group)])
177
+ return symbols_and_groups
178
+
179
+ def get_debug_symbols_and_groups(self):
180
+ """Get a list of (symbol, symbol_group) pairs found in project interface and VcDebug*_sgp.xml files.
181
+
182
+ Returns:
183
+ debug_symbols_and_groups (list(tuple)): List of (symbol, symbol_group) pairs in:
184
+ interface and VcDebug*_sgp.xmlfiles.
185
+ """
186
+ _unused, dep, _unused_two, debug = self.csv_if.get_io_config()
187
+ sgp_file_dict = self.labelsplit_cfg.get("SGP_FILE")
188
+ debug_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('debug'))
189
+ debug_output_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('debug_output'))
190
+ dep_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('dep'))
191
+ dep_output_sgp_file = Path(sgp_file_dict.get('cfg_folder'), sgp_file_dict.get('dep_output'))
192
+ debug_sgp_symbols = self.get_sgp_symbols(debug_sgp_file)
193
+ debug_output_sgp_symbols = self.get_sgp_symbols(debug_output_sgp_file)
194
+ dep_sgp_symbols = self.get_sgp_symbols(dep_sgp_file)
195
+ dep_output_sgp_symbols = self.get_sgp_symbols(dep_output_sgp_file)
196
+
197
+ symbols_and_groups_tmp = []
198
+ debug_tmp = self.get_interface_symbols_and_groups(debug,
199
+ debug_sgp_symbols,
200
+ debug_output_sgp_symbols)
201
+ dep_tmp = self.get_interface_symbols_and_groups(dep,
202
+ dep_sgp_symbols,
203
+ dep_output_sgp_symbols)
204
+ symbols_and_groups_tmp.extend(debug_tmp)
205
+ symbols_and_groups_tmp.extend(dep_tmp)
206
+
207
+ debug_symbols_and_groups = []
208
+ for symbol, symbol_group in symbols_and_groups_tmp:
209
+ if symbol_group == '':
210
+ LOGGER.info('Debug symbol %s is missing symbol group and will be removed.', symbol)
211
+ else:
212
+ debug_symbols_and_groups.append((symbol, symbol_group))
213
+
214
+ return debug_symbols_and_groups
215
+
216
+ def check_unit_par_file(self, unit):
217
+ """Check <unit>_par.m file for default sgp symbol group.
218
+
219
+ Args:
220
+ unit (str): Current unit/model name.
221
+ Returns:
222
+ has_sgp_default (Bool): True/False if unit is associated with default sgp value.
223
+ default_symbol_group (str): Name of default symbol group.
224
+ """
225
+ has_sgp_default = False
226
+ default_symbol_group = ''
227
+ base_search_string = r'SgpDefault\.{unit}\.[A-Za-z]+\s*=\s*[\'\"]([A-Za-z_]+)[\'\"]'
228
+ search_string = base_search_string.format(unit=unit)
229
+
230
+ non_existent_par_file = Path('non_existent_par_file.m')
231
+ found_par_files = glob.glob('Models/*/' + unit + '/' + unit + '_par.m')
232
+ if len(found_par_files) > 1:
233
+ LOGGER.warning('Found more than one _par.m file, using %s', found_par_files[0])
234
+ par_file = Path(found_par_files[0]) if found_par_files else non_existent_par_file
235
+
236
+ if self.labelsplit_cfg.get("special_unit_prefixes"):
237
+ for special_prefix in self.labelsplit_cfg.get("special_unit_prefixes"):
238
+ if unit.startswith(special_prefix) and not par_file.is_file():
239
+ # Some units require special handling.
240
+ if '__' in unit:
241
+ parent = unit.replace('__', 'Mdl__')
242
+ else:
243
+ parent = unit + 'Mdl'
244
+ found_par_files = glob.glob('Models/*/' + parent + '/' + parent + '_par.m')
245
+ par_file = Path(found_par_files[0]) if found_par_files else Path(non_existent_par_file)
246
+ # Default symbol group is based on c-file name
247
+ c_name = re.sub('(Mdl)?(__.*)?', '', unit)
248
+ search_string = base_search_string.format(unit=c_name)
249
+
250
+ if par_file.is_file():
251
+ with par_file.open(encoding="latin-1") as par_fh:
252
+ par_text = par_fh.read()
253
+ sgp_default_match = re.search(search_string, par_text)
254
+ if sgp_default_match is not None:
255
+ has_sgp_default = True
256
+ default_symbol_group = sgp_default_match.group(1)
257
+ else:
258
+ LOGGER.info('Missing _par file for model: %s', unit)
259
+
260
+ return has_sgp_default, default_symbol_group
261
+
262
+ def get_unit_sgp_file(self, unit):
263
+ """Get path to <unit>_sgp.xml file.
264
+
265
+ Args:
266
+ unit (str): Current unit/model name.
267
+ Returns:
268
+ sgp_file (Path): Path to <unit>_sgp.xml file.
269
+ """
270
+ non_existent_sgp_file = Path('non_existent_sgp_file.xml')
271
+ found_sgp_files = glob.glob('Models/*/' + unit + '/' + unit + '_sgp.xml')
272
+ if len(found_sgp_files) > 1:
273
+ LOGGER.warning('Found more than one _sgp.xml file, using %s', found_sgp_files[0])
274
+ sgp_file = Path(found_sgp_files[0]) if found_sgp_files else Path(non_existent_sgp_file)
275
+
276
+ if self.labelsplit_cfg.get("special_unit_prefixes"):
277
+ for special_prefix in self.labelsplit_cfg.get("special_unit_prefixes"):
278
+ if unit.startswith(special_prefix) and not sgp_file.is_file():
279
+ # Some units require special handling.
280
+ if '__' in unit:
281
+ parent = unit.replace('__', 'Mdl__')
282
+ else:
283
+ parent = unit + 'Mdl'
284
+ found_sgp_files = glob.glob('Models/*/' + parent + '/' + parent + '_sgp.xml')
285
+ sgp_file = Path(found_sgp_files[0]) if found_sgp_files else Path(non_existent_sgp_file)
286
+
287
+ return sgp_file
288
+
289
+ def get_unit_symbols_and_groups(self, unit, calibration_symbols):
290
+ """Get a list of (symbol, symbol_group) pairs found in A2L, <unit>_sgp/par and config_<unit>.json files.
291
+
292
+ Args:
293
+ unit (str): Current unit/model name.
294
+ calibration_symbols (list): All calibration symbols for the unit (from config_<unit>.json).
295
+ Returns:
296
+ unit_symbols_and_groups (list(tuple)): List of (symbol, symbol_group) pairs in: A2L, _sgp/_par and
297
+ config files.
298
+ """
299
+ unit_symbols_and_groups = []
300
+ has_sgp_default, default_symbol_group = self.check_unit_par_file(unit)
301
+ sgp_file = self.get_unit_sgp_file(unit)
302
+
303
+ if sgp_file.is_file():
304
+ found_sgp_symbols = self.get_sgp_symbols(sgp_file)
305
+ else:
306
+ found_sgp_symbols = {}
307
+ LOGGER.info('Missing _sgp file for model: %s', unit)
308
+
309
+ for symbol in calibration_symbols:
310
+ if symbol not in self.project_a2l_symbols:
311
+ LOGGER.info('Symbol %s not in project A2L file and will be removed.', symbol)
312
+ continue
313
+
314
+ if symbol in found_sgp_symbols:
315
+ symbol_group = self.get_sgp_symbol_group(found_sgp_symbols[symbol])
316
+ unit_symbols_and_groups.append((symbol, symbol_group))
317
+ elif has_sgp_default:
318
+ if symbol.endswith('_sw') or symbol.endswith('_db'):
319
+ LOGGER.info('Debug symbol %s not in sgp file and will be removed.', symbol)
320
+ else:
321
+ unit_symbols_and_groups.append((symbol, default_symbol_group))
322
+ else:
323
+ LOGGER.info('Symbol %s missing in _sgp file and lack SgpDefault value.', symbol)
324
+
325
+ return unit_symbols_and_groups
326
+
327
+ def get_calibration_constants(self):
328
+ """Get all calibration symbols for each unit in the project.
329
+
330
+ Returns:
331
+ calibration_symbols_per_unit (dict): A unit to symbol list dictionary.
332
+ """
333
+ security_variables = self.labelsplit_cfg.get("security_variables")
334
+ u_conf_dict = self.unit_cfg.get_per_cfg_unit_cfg()
335
+
336
+ safe_calibration_symbols = {}
337
+ for symbol, symbol_data in u_conf_dict['calib_consts'].items():
338
+ if symbol not in security_variables:
339
+ safe_calibration_symbols.update({symbol: symbol_data})
340
+
341
+ calibration_symbols_per_unit = {}
342
+ for symbol, symbol_data in safe_calibration_symbols.items():
343
+ for unit, unit_data in symbol_data.items():
344
+ if 'CVC_CAL' in unit_data['class']:
345
+ if unit in calibration_symbols_per_unit:
346
+ calibration_symbols_per_unit[unit].append(symbol)
347
+ else:
348
+ calibration_symbols_per_unit[unit] = [symbol]
349
+
350
+ return calibration_symbols_per_unit
351
+
352
+ def get_symbols_and_groups(self):
353
+ """Get a list of (symbol, symbol_group) pairs found in A2L, <unit>_sgp/par and config_<unit>.json files.
354
+
355
+ Returns:
356
+ exit_code (int): 0/1 based on successful collection of symbols and symbol groups.
357
+ all_symbols_and_groups (dict): A symbol to symbol_group dictionary for all A2L, _sgp/_par and config files,
358
+ """
359
+ exit_code = 0
360
+ calibration_symbols_per_unit = self.get_calibration_constants()
361
+ debug_symbols = self.get_debug_symbols_and_groups()
362
+
363
+ all_symbol_and_group_pairs = []
364
+ if self.labelsplit_cfg.get("project_symbols"):
365
+ special_project_dict = self.labelsplit_cfg.get("project_symbols")
366
+ pair_list = [val for key, val in special_project_dict.items() if key in self.project]
367
+ if len(pair_list) == 1:
368
+ symbol_and_group_pairs_list = pair_list[0].items()
369
+ all_symbol_and_group_pairs += symbol_and_group_pairs_list
370
+ elif len(pair_list) > 1:
371
+ LOGGER.error('Project %s has does not follow the name rule', self.project)
372
+ return 1, {}
373
+
374
+ all_symbol_and_group_pairs.extend(debug_symbols)
375
+
376
+ for unit, symbols in calibration_symbols_per_unit.items():
377
+ if self.labelsplit_cfg.get("special_units"):
378
+ special_unit_dict = self.labelsplit_cfg.get("special_units")
379
+ if unit in special_unit_dict.keys():
380
+ # Some units require special handling.
381
+ LOGGER.warning('Found %s, assuming %s is used.', unit, special_unit_dict.get(unit))
382
+ labels = self.get_unit_symbols_and_groups(special_unit_dict.get(unit), symbols)
383
+ else:
384
+ labels = self.get_unit_symbols_and_groups(unit, symbols)
385
+ else:
386
+ labels = self.get_unit_symbols_and_groups(unit, symbols)
387
+ all_symbol_and_group_pairs.extend(labels)
388
+
389
+ symbol_to_group_dict = {}
390
+ for symbol, symbol_group in all_symbol_and_group_pairs:
391
+ if symbol in symbol_to_group_dict:
392
+ if symbol_to_group_dict[symbol] != symbol_group:
393
+ LOGGER.error('Symbol %s multiply defined with different symbol groups.', symbol)
394
+ exit_code = 1
395
+ else:
396
+ symbol_to_group_dict[symbol] = symbol_group
397
+
398
+ return exit_code, symbol_to_group_dict
399
+
400
+ def generate_label_split_xml_file(self, symbols_and_groups):
401
+ """Generate a label split file, given a directory plus labels and groups to add.
402
+
403
+ Args:
404
+ symbols_and_groups (dict): A symbol to symbol_group dictionary given a project.
405
+ Returns:
406
+ exit_code (int): 0/1 based on successful generation of Labelsplit.xls.
407
+ """
408
+ errors = []
409
+ project_root_dir = self.build_cfg.get_root_dir()
410
+ cmt_output_folder = helper_functions.create_dir(Path(project_root_dir, 'output', 'CMT'))
411
+ start_file_name = Path(self.cmt_source_folder, 'template_labelsplit_sgp_start.xml_')
412
+ row_count_file_name = Path(cmt_output_folder, 'labelsplit_rowcount.xml_')
413
+ start_2_file_name = Path(self.cmt_source_folder, 'template_labelsplit_sgp_start_2.xml_')
414
+ label_split_rows_filename = Path(cmt_output_folder, 'labelsplit_rows.xml_')
415
+ end_file_name = Path(self.cmt_source_folder, 'template_labelsplit_sgp_end.xml_')
416
+ files_to_merge = [start_file_name, row_count_file_name, start_2_file_name,
417
+ label_split_rows_filename, end_file_name]
418
+
419
+ with row_count_file_name.open('w', encoding="utf-8") as rc_fh:
420
+ rc_fh.write(f'{len(symbols_and_groups) + 1}') # header + data
421
+
422
+ with label_split_rows_filename.open('w', encoding="utf-8") as lsrf_fh:
423
+ for symbol, symbol_group in symbols_and_groups.items():
424
+ if symbol_group == '':
425
+ errors.append(f'Missing symbol group for symbol: {symbol}')
426
+ elif symbol_group == 'VCC_SPM_DEBUG':
427
+ LOGGER.info('Ignoring undistributed debug symbol: %s', symbol)
428
+ else:
429
+ lsrf_fh.write(
430
+ ' <Row ss:AutoFitHeight="0">\n'
431
+ f' <Cell><Data ss:Type="String">{symbol}'
432
+ '</Data><NamedCell ss:Name="_FilterDatabase"/></Cell>\n'
433
+ f' <Cell ss:Index="5"><Data ss:Type="String">{symbol_group}'
434
+ '</Data><NamedCell ss:Name="_FilterDatabase"/></Cell>\n'
435
+ ' </Row>\n'
436
+ )
437
+
438
+ if errors:
439
+ LOGGER.error('\n'.join(errors))
440
+ return 1
441
+
442
+ output_file_name = Path(project_root_dir, 'output', 'CMT', 'Labelsplit.xls')
443
+ with output_file_name.open('w', encoding="utf-8") as output_fh:
444
+ for file_name in files_to_merge:
445
+ with file_name.open(encoding="utf-8") as input_fh:
446
+ content = input_fh.read()
447
+ output_fh.write(content)
448
+ LOGGER.info('Delivery to: %s', str(output_file_name))
449
+ return 0