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,878 +1,880 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- """Module for handling ZoneController composition yaml generation."""
5
-
6
- import re
7
- from pathlib import Path
8
- from ruamel.yaml import YAML, scalarstring
9
-
10
- from powertrain_build.problem_logger import ProblemLogger
11
- from powertrain_build.types import a2l_range
12
- from powertrain_build.zone_controller.calibration import ZoneControllerCalibration as ZCC
13
-
14
-
15
- class CompositionYaml(ProblemLogger):
16
- """Class for handling ZoneController composition yaml generation."""
17
-
18
- def __init__(self, build_cfg, signal_if, unit_cfg, zc_core, zc_dids, zc_nvm, a2l_axis_data, enums):
19
- """Init.
20
-
21
- Args:
22
- build_cfg (BuildProjConfig): Object with build configuration settings.
23
- signal_if (SignalInterfaces): Class holding signal interface information.
24
- unit_cfg (UnitConfig): Object with unit configurations.
25
- zc_core (ZCCore): Object with zone controller diagnositic event information.
26
- zc_dids (ZCDIDs): Object with zone controller diagnostic DID information.
27
- zc_nvm (ZCNVMDef): Object with NVM definition information.
28
- a2l_axis_data (dict): Dict with characteristic axis data from A2L file.
29
- enums (dict): Dict with enum data.
30
- """
31
- self.tl_to_autosar_base_types = {
32
- "Bool": "boolean",
33
- "Float32": "float32",
34
- "Int16": "sint16",
35
- "Int32": "sint32",
36
- "Int8": "sint8",
37
- "UInt16": "uint16",
38
- "UInt32": "uint32",
39
- "UInt8": "uint8",
40
- }
41
- self.build_cfg = build_cfg
42
- self.unit_src_dirs = build_cfg.get_unit_src_dirs()
43
- self.composition_spec = signal_if.composition_spec
44
- self.external_io = signal_if.get_external_io()
45
- self.unit_cfg = unit_cfg
46
- self.zc_core = zc_core
47
- self.zc_dids = zc_dids
48
- self.zc_nvm = zc_nvm
49
- self.enums = enums
50
- self.a2l_axis_data = a2l_axis_data
51
- base_data_types = self.get_base_data_types() # Might not be necessary in the long run
52
- self.data_types = {
53
- **base_data_types,
54
- **self.composition_spec.get("data_types", {}),
55
- }
56
- self.port_interfaces = self.composition_spec.get("port_interfaces", {})
57
- self.sharedSwAddrMethod = self.build_cfg.get_composition_config("includeSharedSwAddrMethod")
58
- if self.sharedSwAddrMethod is not None and not isinstance(self.sharedSwAddrMethod, str):
59
- self.sharedSwAddrMethod = None
60
- self.critical("includeSharedSwAddrMethod must be a string if set.")
61
-
62
- calibration_variables, measurable_variables = self._get_variables()
63
- self.calibration_init_values = self.get_init_values(calibration_variables)
64
- self.cal_class_info = self._get_class_info(calibration_variables)
65
- self.meas_class_info = self._get_class_info(measurable_variables)
66
- self.include_calibration_interface_files = False
67
- if self.build_cfg.get_code_generation_config(item="generateCalibrationInterfaceFiles"):
68
- self.include_calibration_interface_files = True
69
- if self.build_cfg.get_code_generation_config(item="useCalibrationRteMacroExpansion"):
70
- self.include_calibration_interface_files = False
71
- if self.include_calibration_interface_files:
72
- trigger_read_rte_cdata_signal_name = self._get_calibration_trigger_signal_name(calibration_variables)
73
- self.cal_class_info["tl"]["class_info"].update({
74
- trigger_read_rte_cdata_signal_name: {
75
- "type": ZCC.trigger_read_rte_cdata_signal['data_type'],
76
- "width": 1,
77
- }
78
- })
79
- self.cal_class_info["autosar"]["class_info"].update(
80
- {
81
- trigger_read_rte_cdata_signal_name: {
82
- "type": ZCC.trigger_read_rte_cdata_signal["data_type"],
83
- "access": "READ-WRITE",
84
- "init": 0,
85
- }
86
- }
87
- )
88
-
89
- @staticmethod
90
- def _cast_init_value(value_str):
91
- """Cast initialization value to correct type.
92
-
93
- Args:
94
- value_str (str): String representation of the value.
95
- Returns:
96
- (int/float): Value casted to correct type.
97
- """
98
- if value_str.endswith("F"):
99
- return float(value_str[:-1])
100
- return int(value_str)
101
-
102
- def _prepare_for_xml(self, signal_name, string):
103
- """Prepare a string for XML serialization.
104
-
105
- Args:
106
- signal_name (str): The name of the signal.
107
- string (str): The string to prepare.
108
- Returns:
109
- xml_string (str): The prepared string.
110
- """
111
- illegal_xml_characters = {
112
- "&": "&", # needs to be first in list
113
- "<": "&lt;",
114
- ">": "&gt;",
115
- "'": "&apos;",
116
- '"': "&quot;"
117
- }
118
- xml_string_tmp = "".join(string.splitlines())
119
- for char, replacement in illegal_xml_characters.items():
120
- xml_string_tmp = xml_string_tmp.replace(char, replacement)
121
- if len(xml_string_tmp) > 255:
122
- self.warning(f"Converted description for {signal_name} exceeds 255 characters and will be truncated.")
123
- for replacement in illegal_xml_characters.values():
124
- found = xml_string_tmp.find(replacement, 255-len(replacement), 255+len(replacement))
125
- if found < 255 and found + len(replacement) > 255:
126
- xml_string_tmp = xml_string_tmp[:found]
127
- xml_string_tmp = xml_string_tmp[:255] # Since "found" is always < 255 this is safe
128
- xml_string = scalarstring.DoubleQuotedScalarString(xml_string_tmp)
129
- return xml_string
130
-
131
- def get_base_data_types(self):
132
- """Create base data types in expected Autosar/yaml2arxml format."""
133
- base_data_types = {
134
- "Bool": {"type": "ENUMERATION", "enums": {"False": 0, "True": 1}},
135
- "Float32": {
136
- "type": "FLOAT",
137
- "limits": {"lower": -3.4e38, "upper": 3.4e38},
138
- },
139
- }
140
- int_data_types = [data_type for data_type in self.tl_to_autosar_base_types if "Int" in data_type]
141
- for data_type in int_data_types:
142
- lower, upper = a2l_range(data_type)
143
- base_data_types[data_type] = {
144
- "type": "INTEGER",
145
- "limits": {"lower": lower, "upper": upper},
146
- }
147
- return base_data_types
148
-
149
- def generate_yaml(self):
150
- """Generates a yaml from project/model information."""
151
- composition_name = self.build_cfg.get_composition_config("compositionName")
152
- composition_ending = self.build_cfg.get_composition_config("compositionEnding")
153
- all_info = self.gather_yaml_info()
154
-
155
- output_directory = self.build_cfg.get_src_code_dst_dir()
156
- self.info(
157
- "Writing Yaml into %s/%s.%s",
158
- output_directory,
159
- composition_name,
160
- composition_ending,
161
- )
162
- Path(output_directory).mkdir(parents=True, exist_ok=True)
163
-
164
- with open(
165
- f"{output_directory}/{composition_name}.{composition_ending}",
166
- "w",
167
- encoding="utf-8",
168
- ) as file:
169
- yaml = YAML()
170
- yaml.width = 1000 # We don't want to wrap lines in the yaml file
171
-
172
- def modify_float_representation(s):
173
- return re.sub(r"(\s-?\d+)e(?=\+|-\d)", r"\1.e", s)
174
-
175
- yaml.dump(all_info, file, transform=modify_float_representation)
176
-
177
- def gather_yaml_info(self):
178
- """Creates dict with relevant project/model information.
179
-
180
- Returns:
181
- all_info (dict): Dict to be written to yaml.
182
- """
183
- software_components, pt_build_data_types = self._get_software_components()
184
-
185
- all_info = {
186
- "ExternalFiles": {
187
- "Composition": self.build_cfg.get_composition_config("compositionArxml"),
188
- "GenerateExternalImplementationTypes": self.build_cfg.get_composition_config(
189
- "generateExternalImplementationType"
190
- ),
191
- },
192
- "SoftwareComponents": software_components,
193
- "DataTypes": {**self.data_types, **pt_build_data_types},
194
- "PortInterfaces": self.port_interfaces,
195
- }
196
-
197
- return all_info
198
-
199
- def get_init_values(self, calibration_variables):
200
- """Get initialization values for calibration variables.
201
-
202
- Args:
203
- calibration_variables (dict): Dict of existing calibration variables.
204
- Returns:
205
- init_values (dict): Dictionary with initialization values for calibration variables.
206
- """
207
- value_extraction_regexes = [
208
- (
209
- re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\s*=\s*(?P<value>[-\d\.e]+F?)\s*;"),
210
- lambda regex_match, _: self._cast_init_value(regex_match.group("value")),
211
- ),
212
- (
213
- re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\[(?P<size>[\d]+)\]\s*=\s*"),
214
- self._get_array_init_values,
215
- ),
216
- (
217
- re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\[(?P<rows>[\d]+)\]\[(?P<cols>[\d]+)\]\s*=\s*"),
218
- self._get_matrix_init_values,
219
- ),
220
- (
221
- re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\s*=\s*(?P<enum>[a-zA-Z_$][\w_]*?)*;"),
222
- lambda regex_match, _: regex_match.group("enum"),
223
- ),
224
- ]
225
-
226
- init_values = {}
227
- calibration_definitions = self._get_all_calibration_definitions()
228
- calibration_definitions.reverse() # Reverse to pop from the end for performance
229
- while calibration_definitions:
230
- line = calibration_definitions.pop()
231
- for regex, extraction_function in value_extraction_regexes:
232
- regex_match = regex.match(line)
233
- if regex_match is not None and regex_match.group("name") in calibration_variables:
234
- if regex_match.group("name") in init_values:
235
- self.critical("Variable definition for %s already found.", regex_match.group("name"))
236
- init_values[regex_match.group("name")] = extraction_function(regex_match, calibration_definitions)
237
-
238
- missing_init_values = set(calibration_variables) - set(init_values.keys())
239
- if missing_init_values:
240
- self.critical("Missing init values for calibration variables:\n%s", "\n".join(missing_init_values))
241
-
242
- return init_values
243
-
244
- def _get_all_calibration_definitions(self):
245
- """Get all calibration definitions from the source files.
246
-
247
- Returns:
248
- (iter): Iterator with calibration definitions.
249
- """
250
- calibration_definitions = []
251
- end_of_definitions_regex = re.compile(r"^void\s*RESTART_.*")
252
- c_files = [Path(src_dir, unit.split("__")[0] + ".c").resolve() for unit, src_dir in self.unit_src_dirs.items()]
253
- for c_file in c_files:
254
- read_lines = ""
255
- with c_file.open(mode="r", encoding="latin-1") as file_handle:
256
- for line in file_handle:
257
- if end_of_definitions_regex.match(line):
258
- break
259
- read_lines += line
260
- calibration_definitions.extend(re.sub(r"/\*.*?\*/", "", read_lines, flags=re.S).splitlines())
261
- return calibration_definitions
262
-
263
- def _get_array_init_values(self, array_regex_match, definitions_list):
264
- """Get initialization values for an array.
265
-
266
- NOTES:
267
- Modifies the argument definitions_list by popping elements.
268
- Popping from the end since list is reversed.
269
-
270
- Args:
271
- array_regex_match (re.Match): Match object with array definition.
272
- definitions_list (list): List (reversed) with lines to parse.
273
- Returns:
274
- (list): List of initialization values for the array.
275
- """
276
- array_init_values_str = ""
277
- line = definitions_list.pop() # Skip array definition line
278
- while "};" not in line:
279
- array_init_values_str += line.strip()
280
- line = definitions_list.pop()
281
- array_init_values_str += line.strip()
282
- array_init_values = re.findall(r"([-\d\.e]+F?),?", array_init_values_str)
283
-
284
- if int(array_regex_match.group("size")) != len(array_init_values):
285
- self.critical("Could not parse init values for array definition %s.", array_regex_match.group("name"))
286
-
287
- return [self._cast_init_value(value) for value in array_init_values]
288
-
289
- def _get_matrix_init_values(self, matrix_regex_match, definitions_list):
290
- """Get initialization values for a matrix.
291
-
292
- NOTES:
293
- Modifies the argument definitions_list by popping elements.
294
- Popping from the end since list is reversed.
295
-
296
- Args:
297
- matrix_regex_match (re.Match): Match object with matrix definition.
298
- definitions_list (list): List (reversed) with lines to parse.
299
- Returns:
300
- (list(list)): List of initialization values for the matrix.
301
- """
302
- matrix_init_values = []
303
- matrix_init_values_str = ""
304
- line = definitions_list.pop() # Skip matrix definition line
305
- while "};" not in line:
306
- matrix_init_values_str += line.strip()
307
- if "}" in line:
308
- matrix_init_values.append(re.findall(r"([-\d\.e]+F?),?", matrix_init_values_str))
309
- matrix_init_values_str = ""
310
- line = definitions_list.pop()
311
-
312
- row_check = int(matrix_regex_match.group("rows")) != len(matrix_init_values)
313
- col_check = any(int(matrix_regex_match.group("cols")) != len(row) for row in matrix_init_values)
314
- if row_check or col_check:
315
- self.critical("Could not parse init values for matrix definition %s.", matrix_regex_match.group("name"))
316
-
317
- return [[self._cast_init_value(value) for value in row] for row in matrix_init_values]
318
-
319
- def _get_calibration_trigger_signal_name(self, calibration_variables):
320
- """Get the variable of the calibration trigger.
321
-
322
- Make sure it is not present already.
323
-
324
- Args:
325
- calibration_variables (dict): Dict of existing calibration variables.
326
- Returns:
327
- trigger_signal (str): Name of variable for triggering calibration.
328
- """
329
- software_component_name = self.build_cfg.get_composition_config("softwareComponentName")
330
- trigger_signal = ZCC.trigger_read_rte_cdata_signal["name_template"].format(swc_name=software_component_name)
331
-
332
- if trigger_signal in calibration_variables:
333
- self.critical("Signal %s already defined in project.", trigger_signal)
334
-
335
- return trigger_signal
336
-
337
- def _edit_event_dict(self, event_dict):
338
- """Edit event dictionary to use double quoted strings on certain element values.
339
-
340
- The elements that need to be double quoted are JumpDown, JumpUp and EnaDEMInd.
341
- The values are either "on" or "off", which yaml loader still interprets as boolean.
342
- Hence the need for double quotes.
343
- For some reason, "true" is not interpreted as boolean though.
344
-
345
- Args:
346
- event_dict (dict): Dict with diagnostic event data.
347
- Returns:
348
- event_dict (dict): Dict with diagnostic event data,
349
- updated with double quoted strings on certain element values."""
350
- for event_data in event_dict.values():
351
- for key, value in event_data.items():
352
- if key in ["ACC2", "EnaDEMInd", "JumpDown", "JumpUp"]:
353
- event_data[key] = scalarstring.DoubleQuotedScalarString(value)
354
- return event_dict
355
-
356
- def _get_diagnostic_info(self):
357
- """Get diagnostic information from composition spec.
358
-
359
- NOTE: This function sets the valid_dids property of the ZCDIDs object.
360
-
361
- Returns:
362
- diag_dict (dict): Dict containing diagnostic information.
363
- """
364
- diag_dict = {}
365
- diagnostics = self.composition_spec.get("diagnostics", {})
366
-
367
- if self.build_cfg.get_composition_config("includeDiagnostics") == "manual":
368
- dids = diagnostics.get("dids", {})
369
- events = self._edit_event_dict(diagnostics.get("events", {}))
370
- elif self.build_cfg.get_composition_config("includeDiagnostics") == "manual_dids":
371
- dids = diagnostics.get("dids", {})
372
- events_tmp = self._edit_event_dict(diagnostics.get("events", {}))
373
- events = self.zc_core.get_diagnostic_trouble_codes(events_tmp)
374
- elif self.build_cfg.get_composition_config("includeDiagnostics") == "manual_dtcs":
375
- self.zc_dids.valid_dids = diagnostics.get("dids", {})
376
- dids = self.zc_dids.valid_dids
377
- events = self._edit_event_dict(diagnostics.get("events", {}))
378
- else:
379
- self.zc_dids.valid_dids = diagnostics.get("dids", {})
380
- dids = self.zc_dids.valid_dids
381
- events_tmp = self._edit_event_dict(diagnostics.get("events", {}))
382
- events = self.zc_core.get_diagnostic_trouble_codes(events_tmp)
383
- rids = diagnostics.get("rids", {})
384
-
385
- if dids:
386
- diag_dict["dids"] = dids
387
- if events:
388
- diag_dict["events"] = events
389
- if rids:
390
- diag_dict["rids"] = rids
391
- self.warning("Will not generate code for RIDs, add manually.")
392
- return diag_dict
393
-
394
- def _get_nvm_info(self):
395
- """Creates a dict with NVM information.
396
-
397
- NVM dicts also needs to be added to the "static" field in the generated yaml.
398
-
399
- NOTE: This function sets the valid_nvm_definitions property of the ZCNVMDef object.
400
-
401
- Returns:
402
- nvm_dict (dict): Dict containing NVM information.
403
- data_types (dict): Dict containing data types for NVM information.
404
- static_variables (dict): Dict containing "static" variables to add to the "static" field.
405
- """
406
- data_types = {}
407
- static_variables = {}
408
- yaml_nvm_definitions = self.composition_spec.get("nv-needs", {})
409
- self.zc_nvm.valid_nvm_definitions = yaml_nvm_definitions
410
- nvm_dict = self.zc_nvm.valid_nvm_definitions
411
-
412
- for nvm_name, nvm_data in nvm_dict.items():
413
- init = []
414
- nr_of_unused_signals = self.zc_nvm.project_nvm_definitions[nvm_name]["size"]
415
- data_type_name = f"dt_{nvm_name}"
416
- data_types[data_type_name] = {"type": "RECORD", "elements": {}}
417
- for signal in self.zc_nvm.project_nvm_definitions[nvm_name]["signals"]:
418
- element_name = f'{self.zc_nvm.struct_member_prefix}{signal["name"]}'
419
- nr_of_unused_signals -= signal["x_size"] * signal["y_size"]
420
- size = max(signal["x_size"], 1) * max(signal["y_size"], 1)
421
- if size > 1:
422
- x_data_type_name = f"dt_{signal['name']}_x"
423
- y_data_type_name = f"dt_{signal['name']}_y"
424
- if signal["x_size"] > 1 and signal["y_size"] == 1:
425
- init.append([0] * signal["x_size"])
426
- data_types[data_type_name]["elements"][element_name] = x_data_type_name
427
- data_types[x_data_type_name] = {
428
- "type": "ARRAY",
429
- "size": signal["x_size"],
430
- "element": signal["type"],
431
- }
432
- elif signal["x_size"] > 1 and signal["y_size"] > 1:
433
- init.append([[0] * signal["y_size"]] * signal["x_size"])
434
- data_types[data_type_name]["elements"][element_name] = y_data_type_name
435
- data_types[y_data_type_name] = {
436
- "type": "ARRAY",
437
- "size": signal["y_size"],
438
- "element": x_data_type_name,
439
- }
440
- data_types[x_data_type_name] = {
441
- "type": "ARRAY",
442
- "size": signal["x_size"],
443
- "element": signal["type"],
444
- }
445
- else:
446
- self.critical("NVM signal size incorrect. x_size should not be 1 if y_size > 1.")
447
- else:
448
- init.append(0)
449
- data_types[data_type_name]["elements"][element_name] = signal["type"]
450
-
451
- nvm_data.update({
452
- "datatype": data_type_name,
453
- "init": init
454
- })
455
- static_variables[nvm_name.lower()] = {
456
- "type": data_type_name,
457
- "access": "READ-ONLY",
458
- "init": init,
459
- }
460
-
461
- if nr_of_unused_signals > 0:
462
- # Mimics how we generate the unused member of the structs in nvm_def.py
463
- nvm_data["init"].append([0] * nr_of_unused_signals)
464
- data_types[data_type_name]["elements"]["unused"] = f"{data_type_name}_Unused"
465
- data_types[f"{data_type_name}_Unused"] = {
466
- "type": "ARRAY",
467
- "size": nr_of_unused_signals,
468
- "element": self.zc_nvm.project_nvm_definitions[nvm_name]["default_datatype"],
469
- }
470
-
471
- return nvm_dict, data_types, static_variables
472
-
473
- def _get_ports_info(self):
474
- """Creates a dict containing port information.
475
-
476
- Returns:
477
- ports (dict): Dict containing port information.
478
- """
479
- ports = self.composition_spec.get("ports", {})
480
- for call, call_data in self.composition_spec.get("calls", {}).items():
481
- if call in ports:
482
- continue
483
- ports[call] = {
484
- "interface": call_data.get("interface", call),
485
- "direction": call_data["direction"],
486
- }
487
- return ports
488
-
489
- def _get_runnable_calls_info(self):
490
- """Creates a dict containing desired calls for the SWC.
491
-
492
- Returns:
493
- call_dict(dict): Dict containing runnable calls information.
494
- """
495
- call_dict = {}
496
- for call, call_data in self.composition_spec.get("calls", {}).items():
497
- call_dict[call] = {"operation": call_data["operation"]}
498
- if "timeout" in call_data:
499
- call_dict[call]["timeout"] = call_data["timeout"]
500
- return call_dict
501
-
502
- def _get_runnable_info(self):
503
- """Creates a dict containing runnables information.
504
-
505
- Returns:
506
- dict: Dict containing runnables information.
507
- """
508
- autosar_prefix = "AR_"
509
- swc_prefix = self.build_cfg.get_scheduler_prefix()
510
- custom_step_function = self.build_cfg.get_composition_config("customYamlStepFunctionName")
511
- custom_init_function = self.build_cfg.get_composition_config("customYamlInitFunctionName")
512
- standard_init_function = autosar_prefix + swc_prefix + "VcExtINI"
513
- init_function = custom_init_function if custom_init_function is not None else standard_init_function
514
- calibration_variables = list(
515
- self.cal_class_info["autosar"]["class_info"].keys()
516
- ) + list(
517
- self.composition_spec.get("shared", {}).keys()
518
- )
519
- swc_content = {
520
- init_function: {
521
- "type": "INIT",
522
- "mode_ref": {
523
- "port": "EcuMVccActivationMode",
524
- "mode": ["VCC_ACTIVE"],
525
- "trigger": "ON-ENTRY"
526
- },
527
- "accesses": calibration_variables
528
- }
529
- }
530
-
531
- if self.include_calibration_interface_files:
532
- swc_name = self.build_cfg.get_composition_config("softwareComponentName")
533
- cal_init_function = autosar_prefix + ZCC.calibration_function_init_template.format(swc_name=swc_name)
534
- cal_step_function = autosar_prefix + ZCC.calibration_function_step_template.format(swc_name=swc_name)
535
- swc_content[cal_init_function] = {
536
- "type": "INIT",
537
- "mode_ref": {
538
- "port": "EcuMVccActivationMode",
539
- "mode": ["VCC_ACTIVE"],
540
- "trigger": "ON-ENTRY"
541
- },
542
- "accesses": calibration_variables
543
- }
544
- swc_content[cal_step_function] = {
545
- "type": "PERIODIC",
546
- "period": 0.1,
547
- "mode_suppression": [
548
- {"port": "EcuMVccActivationMode", "disabled_mode": ["VCC_NOT_ACTIVE"]}
549
- ],
550
- "accesses": calibration_variables
551
- }
552
-
553
- call_dict = self._get_runnable_calls_info()
554
- mode_switch_points_dict = self.composition_spec.get("mode_switch_points", {})
555
- reads = []
556
- writes = []
557
- for port, port_data in self._get_ports_info().items():
558
- if port_data["direction"] in ["IN", "CLIENT"]:
559
- reads.append(port)
560
- else:
561
- writes.append(port)
562
- runnables = self.build_cfg.get_units_raster_cfg()["SampleTimes"]
563
-
564
- if len(runnables) == 1 and custom_step_function is not None:
565
- swc_content[custom_step_function] = {
566
- "type": "PERIODIC",
567
- "period": list(runnables.values())[0],
568
- "mode_suppression": [
569
- {"port": "EcuMVccActivationMode", "disabled_mode": ["VCC_NOT_ACTIVE"]}
570
- ],
571
- "accesses": calibration_variables,
572
- }
573
- if call_dict:
574
- swc_content[custom_step_function]["calls"] = call_dict
575
- if mode_switch_points_dict:
576
- swc_content[custom_step_function]["mode_switch_points"] = mode_switch_points_dict
577
- if reads:
578
- swc_content[custom_step_function]["reads"] = reads
579
- if writes:
580
- swc_content[custom_step_function]["writes"] = writes
581
- return swc_content
582
-
583
- if custom_step_function is not None:
584
- self.warning(
585
- "Custom step function specified, but multiple runnables defined. Ignoring custom step function."
586
- )
587
-
588
- for runnable, period in runnables.items():
589
- key = autosar_prefix + swc_prefix + runnable
590
- swc_content[key] = {
591
- "period": period,
592
- "type": "PERIODIC",
593
- "mode_suppression": [
594
- {"port": "EcuMVccActivationMode", "disabled_mode": ["VCC_NOT_ACTIVE"]}
595
- ],
596
- "accesses": calibration_variables,
597
- }
598
- if call_dict:
599
- swc_content[key]["calls"] = call_dict
600
- if mode_switch_points_dict:
601
- swc_content[key]["mode_switch_points"] = mode_switch_points_dict
602
- if reads:
603
- swc_content[key]["reads"] = reads
604
- if writes:
605
- swc_content[key]["writes"] = writes
606
-
607
- return swc_content
608
-
609
- def _get_software_components(self):
610
- """Creates a dict with swc information and referred data types.
611
-
612
- Returns:
613
- swcs (dict): SWC information.
614
- data_types (dict): Data types information.
615
- """
616
- software_component_name = self.build_cfg.get_composition_config("softwareComponentName")
617
- swcs = {software_component_name: {}}
618
- swcs[software_component_name]["type"] = "SWC" # Other types than swc??
619
- swcs[software_component_name]["asil"] = self.build_cfg.get_composition_config("asil")
620
- swcs[software_component_name]["secure"] = self.build_cfg.get_composition_config("secure")
621
- swcs[software_component_name]["runnables"] = self._get_runnable_info()
622
- if self.build_cfg.get_composition_config("includeShared") is True:
623
- swcs[software_component_name]["shared"] = self.cal_class_info["autosar"]["class_info"]
624
- for variable_name, variable_info in self.composition_spec.get("shared", {}).items():
625
- if variable_name in swcs[software_component_name]["shared"]:
626
- self.critical("Shared variable %s already defined in project.", variable_name)
627
- else:
628
- swcs[software_component_name]["shared"][variable_name] = variable_info
629
- elif self.build_cfg.get_composition_config("includeShared") == "manual":
630
- swcs[software_component_name]["shared"] = {}
631
- for variable_name, variable_info in self.composition_spec.get("shared", {}).items():
632
- swcs[software_component_name]["shared"][variable_name] = variable_info
633
- if self.build_cfg.get_composition_config("includeStatic") is True:
634
- swcs[software_component_name]["static"] = self.meas_class_info["autosar"]["class_info"]
635
- for variable_name, variable_info in self.composition_spec.get("static", {}).items():
636
- if variable_name in swcs[software_component_name]["static"]:
637
- self.critical("Static variable %s already defined in project.", variable_name)
638
- else:
639
- swcs[software_component_name]["static"][variable_name] = variable_info
640
- elif self.build_cfg.get_composition_config("includeStatic") == "manual":
641
- swcs[software_component_name]["static"] = {}
642
- for variable_name, variable_info in self.composition_spec.get("static", {}).items():
643
- swcs[software_component_name]["static"][variable_name] = variable_info
644
- swcs[software_component_name]["ports"] = self._get_ports_info()
645
- if self.composition_spec.get("io") is not None:
646
- swcs[software_component_name]["io"] = self.composition_spec["io"]
647
- if self.composition_spec.get("ecu") is not None:
648
- swcs[software_component_name]["ecu"] = self.composition_spec["ecu"]
649
- diagnostic_info = self._get_diagnostic_info()
650
- if self.build_cfg.get_composition_config("includeDiagnostics") is not False:
651
- swcs[software_component_name]["diagnostics"] = diagnostic_info
652
- nvm_info, nvm_data_types_tmp, static_variables = self._get_nvm_info()
653
- if self.build_cfg.get_composition_config("includeNvm"):
654
- swcs[software_component_name]["nv-needs"] = nvm_info
655
- nvm_data_types = nvm_data_types_tmp
656
- if self.build_cfg.get_composition_config("includeStatic") is True:
657
- swcs[software_component_name]["static"].update(static_variables)
658
- else:
659
- nvm_data_types = {}
660
-
661
- data_types = {
662
- **self.cal_class_info["autosar"]["data_types"],
663
- **self.meas_class_info["autosar"]["data_types"],
664
- **nvm_data_types,
665
- }
666
-
667
- return swcs, data_types
668
-
669
- def _get_variables(self):
670
- """Get calibration and measurable variables from the unit configuration.
671
-
672
- Returns:
673
- calibration_variables (dict): Dict with calibration variables.
674
- measurable_variables (dict): Dict with measurable variables.
675
- """
676
- calibration_variables = {}
677
- measurable_variables = {}
678
- config = self.unit_cfg.get_per_cfg_unit_cfg()
679
- valid_configs = ["outports", "local_vars", "calib_consts"]
680
- for valid_config in valid_configs:
681
- for signal_name, unit_info in config.get(valid_config, {}).items():
682
- if len(unit_info) > 1:
683
- self.critical("Multiple definitions for %s in config json files.", signal_name)
684
- for info in unit_info.values():
685
- if "CVC_CAL" in info["class"]:
686
- calibration_variables[signal_name] = info
687
- elif "CVC_DISP" in info["class"]:
688
- measurable_variables[signal_name] = info
689
- # External inports should also be considered as measurable variables
690
- for io_type in self.external_io:
691
- for signal_name in io_type.get("input", {}).keys():
692
- for signal_data in config["inports"][signal_name].values():
693
- measurable_variables[signal_name] = signal_data
694
- continue # Inports can appear in several units, pick first one
695
- return calibration_variables, measurable_variables
696
-
697
- def _get_class_info(self, variable_dict):
698
- """Creates a dict with parameter information and referred data types.
699
-
700
- Args:
701
- variable_dict (dict): Dictionary with variables and data.
702
- Returns:
703
- (dict): Dictionary with variables and data types (Autosar and TL).
704
- """
705
- autosar_class_info = {}
706
- autosar_data_types = {}
707
- tl_class_info = {}
708
- for signal_name, info in variable_dict.items():
709
- (
710
- autosar_class_info,
711
- autosar_data_types,
712
- ) = self._add_autosar_data_types(autosar_class_info, autosar_data_types, signal_name, info)
713
- if signal_name in autosar_class_info:
714
- tl_class_info[signal_name] = {
715
- "type": info["type"],
716
- "autosar_type": autosar_class_info[signal_name]["type"].split("/")[-1],
717
- "width": info["width"],
718
- }
719
- return {
720
- "autosar": {
721
- "class_info": autosar_class_info,
722
- "data_types": autosar_data_types,
723
- },
724
- "tl": {"class_info": tl_class_info, "data_types": {}},
725
- }
726
-
727
- def _add_autosar_data_types(self, class_info, data_types, signal_name, info):
728
- """Process a variable for inclusion in composition, adding it's data type to
729
- data_types and the variable to class_info.
730
-
731
- Args:
732
- class_info (dict): Dictionary with variables.
733
- data_types (dict): Dictionary with data types.
734
- signal_name (string): Name of signal to process.
735
- info (dict): signal data.
736
- Returns:
737
-
738
- class_info (dict): Updated dictionary with variables.
739
- data_types (dict): Updated dictionary with data types.
740
- """
741
- if info["type"] in self.enums.keys():
742
- return class_info, data_types
743
-
744
- isReadOnly = "CVC_DISP" in info["class"] or info["class"] == "CVC_EXT"
745
- if "Bool" in info["type"]:
746
- upper = 1
747
- lower = 0
748
- else:
749
- base_type_lower = self.data_types[info["type"]]["limits"]["lower"]
750
- base_type_upper = self.data_types[info["type"]]["limits"]["upper"]
751
- lower = info["min"] if info["min"] != "-" else base_type_lower
752
- upper = info["max"] if info["max"] != "-" else base_type_upper
753
-
754
- if not isinstance(info["width"], list):
755
- class_info[signal_name] = {
756
- "type": info["type"],
757
- "access": "READ-ONLY" if isReadOnly else "READ-WRITE",
758
- "init": self.calibration_init_values.get(signal_name, max(min(0, upper), lower)),
759
- }
760
- if info["description"]:
761
- class_info[signal_name]["longname"] = self._prepare_for_xml(signal_name, info["description"])
762
- if info["unit"] and info["unit"] != "-":
763
- class_info[signal_name]["unit"] = info["unit"]
764
- if self.sharedSwAddrMethod is not None and not isReadOnly:
765
- class_info[signal_name]["swAddrMethod"] = f"{self.sharedSwAddrMethod}_{signal_name.split('_')[0]}"
766
- return class_info, data_types
767
-
768
- if isinstance(lower, list) or isinstance(upper, list):
769
- if info["width"][0] > 1:
770
- self.critical(
771
- "%s is a multidimentional array of elements with different constraints, not supported.", signal_name
772
- )
773
- init = []
774
- for idx in range(info["width"][1]):
775
- lower_val = lower[idx] if isinstance(lower, list) else lower
776
- lower_val = lower_val if lower_val != "-" else base_type_lower
777
- upper_val = upper[idx] if isinstance(upper, list) else upper
778
- upper_val = upper_val if upper_val != "-" else base_type_upper
779
- init.append(max(min(0, upper_val), lower_val))
780
- else:
781
- init = max(min(0, upper), lower)
782
- if info["width"][0] > 1:
783
- init = [[init] * info["width"][1] for _ in range(info["width"][0])]
784
- else:
785
- init = [init] * info["width"][1]
786
-
787
- init = self.calibration_init_values.get(signal_name, init)
788
-
789
- new_data_type = {}
790
- new_data_type_name = f"dt_{signal_name}"
791
- if signal_name.startswith("t"):
792
- if signal_name.endswith("_x"):
793
- new_data_type_data = {
794
- "type": "COM_AXIS",
795
- "axis-index": 1,
796
- "size": info["width"][1],
797
- "limits": {"lower": lower, "upper": upper},
798
- "swrecordlayout": {
799
- "name": f"Distr_{signal_name}",
800
- "type": "INDEX_INCR",
801
- "basetype": self.tl_to_autosar_base_types[info["type"]],
802
- "label": "X",
803
- },
804
- }
805
- else:
806
- axis = self.a2l_axis_data.get(signal_name, {}).get("axes", [signal_name + "_x"])[0]
807
- new_data_type_data = {
808
- "type": "CURVE",
809
- "axis": f"dt_{axis}",
810
- "limits": {"lower": lower, "upper": upper},
811
- "swrecordlayout": {
812
- "name": f"Curve_{signal_name}",
813
- "type": "COLUMN_DIR",
814
- "basetype": self.tl_to_autosar_base_types[info["type"]],
815
- "label": "Val",
816
- },
817
- }
818
- if self.build_cfg.get_composition_config("scaleMapsAndCurves") and "int" in info["type"].lower():
819
- new_data_type_data["slope"] = info["lsb"]
820
- new_data_type_data["bias"] = info["offset"]
821
- elif signal_name.startswith("m"):
822
- new_data_type_data = {
823
- "type": "COM_AXIS",
824
- "size": info["width"][1],
825
- "limits": {"lower": lower, "upper": upper},
826
- "swrecordlayout": {
827
- "name": f"Distr_{signal_name}",
828
- "type": "INDEX_INCR",
829
- "basetype": self.tl_to_autosar_base_types[info["type"]],
830
- },
831
- }
832
- if signal_name.endswith("_r"):
833
- new_data_type_data["axis-index"] = 1
834
- new_data_type_data["swrecordlayout"]["label"] = "X"
835
- elif signal_name.endswith("_c"):
836
- new_data_type_data["axis-index"] = 2
837
- new_data_type_data["swrecordlayout"]["label"] = "Y"
838
- else:
839
- default_names = [signal_name + "_r", signal_name + "_c"]
840
- axis_r, axis_c = self.a2l_axis_data.get(signal_name, {}).get("axes", default_names)
841
- new_data_type_data = {
842
- "type": "MAP",
843
- "x-axis": f"dt_{axis_r}",
844
- "y-axis": f"dt_{axis_c}",
845
- "limits": {"lower": lower, "upper": upper},
846
- "swrecordlayout": {
847
- "name": f"Map_{signal_name}",
848
- "type": "COLUMN_DIR",
849
- "basetype": self.tl_to_autosar_base_types[info["type"]],
850
- "label": "Val",
851
- },
852
- }
853
- if self.build_cfg.get_composition_config("scaleMapsAndCurves") and "int" in info["type"].lower():
854
- new_data_type_data["slope"] = info["lsb"]
855
- new_data_type_data["bias"] = info["offset"]
856
- elif info["width"][0] == 1:
857
- new_data_type_name = f"dt_{signal_name}_{info['width'][1]}"
858
- new_data_type_data = {
859
- "type": "ARRAY",
860
- "size": info["width"][1],
861
- "element": info["type"],
862
- }
863
- else:
864
- self.critical("Signal config error for %s.", signal_name)
865
- return class_info, data_types
866
-
867
- new_data_type[new_data_type_name] = new_data_type_data
868
- class_info[signal_name] = {
869
- "type": new_data_type_name,
870
- "access": "READ-ONLY" if isReadOnly else "READ-WRITE",
871
- "init": init,
872
- }
873
- if info["description"]:
874
- class_info[signal_name]["longname"] = self._prepare_for_xml(signal_name, info["description"])
875
- if self.sharedSwAddrMethod is not None and not isReadOnly:
876
- class_info[signal_name]["swAddrMethod"] = f"{self.sharedSwAddrMethod}_{signal_name.split('_')[0]}"
877
- data_types = {**data_types, **new_data_type}
878
- return class_info, data_types
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ """Module for handling ZoneController composition yaml generation."""
5
+
6
+ import re
7
+ from pathlib import Path
8
+ from ruamel.yaml import YAML, scalarstring
9
+
10
+ from powertrain_build.problem_logger import ProblemLogger
11
+ from powertrain_build.types import a2l_range
12
+ from powertrain_build.zone_controller.calibration import ZoneControllerCalibration as ZCC
13
+
14
+
15
+ class CompositionYaml(ProblemLogger):
16
+ """Class for handling ZoneController composition yaml generation."""
17
+
18
+ def __init__(self, build_cfg, signal_if, unit_cfg, zc_core, zc_dids, zc_nvm, a2l_axis_data, enums):
19
+ """Init.
20
+
21
+ Args:
22
+ build_cfg (BuildProjConfig): Object with build configuration settings.
23
+ signal_if (SignalInterfaces): Class holding signal interface information.
24
+ unit_cfg (UnitConfig): Object with unit configurations.
25
+ zc_core (ZCCore): Object with zone controller diagnositic event information.
26
+ zc_dids (ZCDIDs): Object with zone controller diagnostic DID information.
27
+ zc_nvm (ZCNVMDef): Object with NVM definition information.
28
+ a2l_axis_data (dict): Dict with characteristic axis data from A2L file.
29
+ enums (dict): Dict with enum data.
30
+ """
31
+ self.tl_to_autosar_base_types = {
32
+ "Bool": "boolean",
33
+ "Float32": "float32",
34
+ "Int16": "sint16",
35
+ "Int32": "sint32",
36
+ "Int8": "sint8",
37
+ "UInt16": "uint16",
38
+ "UInt32": "uint32",
39
+ "UInt8": "uint8",
40
+ }
41
+ self.build_cfg = build_cfg
42
+ self.unit_src_dirs = build_cfg.get_unit_src_dirs()
43
+ self.composition_spec = signal_if.composition_spec
44
+ self.external_io = signal_if.get_external_io()
45
+ self.unit_cfg = unit_cfg
46
+ self.zc_core = zc_core
47
+ self.zc_dids = zc_dids
48
+ self.zc_nvm = zc_nvm
49
+ self.enums = enums
50
+ self.a2l_axis_data = a2l_axis_data
51
+ base_data_types = self.get_base_data_types() # Might not be necessary in the long run
52
+ self.data_types = {
53
+ **base_data_types,
54
+ **self.composition_spec.get("data_types", {}),
55
+ }
56
+ self.port_interfaces = self.composition_spec.get("port_interfaces", {})
57
+ self.sharedSwAddrMethod = self.build_cfg.get_composition_config("includeSharedSwAddrMethod")
58
+ if self.sharedSwAddrMethod is not None and not isinstance(self.sharedSwAddrMethod, str):
59
+ self.sharedSwAddrMethod = None
60
+ self.critical("includeSharedSwAddrMethod must be a string if set.")
61
+
62
+ calibration_variables, measurable_variables = self._get_variables()
63
+ self.calibration_init_values = self.get_init_values(calibration_variables)
64
+ self.cal_class_info = self._get_class_info(calibration_variables)
65
+ self.meas_class_info = self._get_class_info(measurable_variables)
66
+ self.include_calibration_interface_files = False
67
+ if self.build_cfg.get_code_generation_config(item="generateCalibrationInterfaceFiles"):
68
+ self.include_calibration_interface_files = True
69
+ if self.build_cfg.get_code_generation_config(item="useCalibrationRteMacroExpansion"):
70
+ self.include_calibration_interface_files = False
71
+ if self.include_calibration_interface_files:
72
+ trigger_read_rte_cdata_signal_name = self._get_calibration_trigger_signal_name(calibration_variables)
73
+ self.cal_class_info["tl"]["class_info"].update({
74
+ trigger_read_rte_cdata_signal_name: {
75
+ "type": ZCC.trigger_read_rte_cdata_signal['data_type'],
76
+ "width": 1,
77
+ }
78
+ })
79
+ self.cal_class_info["autosar"]["class_info"].update(
80
+ {
81
+ trigger_read_rte_cdata_signal_name: {
82
+ "type": ZCC.trigger_read_rte_cdata_signal["data_type"],
83
+ "access": "READ-WRITE",
84
+ "init": 0,
85
+ }
86
+ }
87
+ )
88
+
89
+ @staticmethod
90
+ def _cast_init_value(value_str):
91
+ """Cast initialization value to correct type.
92
+
93
+ Args:
94
+ value_str (str): String representation of the value.
95
+ Returns:
96
+ (int/float): Value casted to correct type.
97
+ """
98
+ if value_str.endswith("F"):
99
+ return float(value_str[:-1])
100
+ return int(value_str)
101
+
102
+ def _prepare_for_xml(self, signal_name, string):
103
+ """Prepare a string for XML serialization.
104
+
105
+ Args:
106
+ signal_name (str): The name of the signal.
107
+ string (str): The string to prepare.
108
+ Returns:
109
+ xml_string (str): The prepared string.
110
+ """
111
+ illegal_xml_characters = {
112
+ "&": "&amp;", # needs to be first in list
113
+ "<": "&lt;",
114
+ ">": "&gt;",
115
+ "'": "&apos;",
116
+ '"': "&quot;"
117
+ }
118
+ xml_string_tmp = "".join(string.splitlines())
119
+ for char, replacement in illegal_xml_characters.items():
120
+ xml_string_tmp = xml_string_tmp.replace(char, replacement)
121
+ if len(xml_string_tmp) > 255:
122
+ self.warning(f"Converted description for {signal_name} exceeds 255 characters and will be truncated.")
123
+ for replacement in illegal_xml_characters.values():
124
+ found = xml_string_tmp.find(replacement, 255-len(replacement), 255+len(replacement))
125
+ if found < 255 and found + len(replacement) > 255:
126
+ xml_string_tmp = xml_string_tmp[:found]
127
+ xml_string_tmp = xml_string_tmp[:255] # Since "found" is always < 255 this is safe
128
+ xml_string = scalarstring.DoubleQuotedScalarString(xml_string_tmp)
129
+ return xml_string
130
+
131
+ def get_base_data_types(self):
132
+ """Create base data types in expected Autosar/yaml2arxml format."""
133
+ base_data_types = {
134
+ "Bool": {"type": "ENUMERATION", "enums": {"False": 0, "True": 1}},
135
+ "Float32": {
136
+ "type": "FLOAT",
137
+ "limits": {"lower": -3.4e38, "upper": 3.4e38},
138
+ },
139
+ }
140
+ int_data_types = [data_type for data_type in self.tl_to_autosar_base_types if "Int" in data_type]
141
+ for data_type in int_data_types:
142
+ lower, upper = a2l_range(data_type)
143
+ base_data_types[data_type] = {
144
+ "type": "INTEGER",
145
+ "limits": {"lower": lower, "upper": upper},
146
+ }
147
+ return base_data_types
148
+
149
+ def generate_yaml(self):
150
+ """Generates a yaml from project/model information."""
151
+ composition_name = self.build_cfg.get_composition_config("compositionName")
152
+ composition_ending = self.build_cfg.get_composition_config("compositionEnding")
153
+ all_info = self.gather_yaml_info()
154
+
155
+ output_directory = self.build_cfg.get_src_code_dst_dir()
156
+ self.info(
157
+ "Writing Yaml into %s/%s.%s",
158
+ output_directory,
159
+ composition_name,
160
+ composition_ending,
161
+ )
162
+ Path(output_directory).mkdir(parents=True, exist_ok=True)
163
+
164
+ with open(
165
+ f"{output_directory}/{composition_name}.{composition_ending}",
166
+ "w",
167
+ encoding="utf-8",
168
+ ) as file:
169
+ yaml = YAML()
170
+ yaml.width = 1000 # We don't want to wrap lines in the yaml file
171
+
172
+ def modify_float_representation(s):
173
+ return re.sub(r"(\s-?\d+)e(?=\+|-\d)", r"\1.e", s)
174
+
175
+ yaml.dump(all_info, file, transform=modify_float_representation)
176
+
177
+ def gather_yaml_info(self):
178
+ """Creates dict with relevant project/model information.
179
+
180
+ Returns:
181
+ all_info (dict): Dict to be written to yaml.
182
+ """
183
+ software_components, pt_build_data_types = self._get_software_components()
184
+
185
+ all_info = {
186
+ "ExternalFiles": {
187
+ "Composition": self.build_cfg.get_composition_config("compositionArxml"),
188
+ "GenerateExternalImplementationTypes": self.build_cfg.get_composition_config(
189
+ "generateExternalImplementationType"
190
+ ),
191
+ },
192
+ "SoftwareComponents": software_components,
193
+ "DataTypes": {**self.data_types, **pt_build_data_types},
194
+ "PortInterfaces": self.port_interfaces,
195
+ }
196
+
197
+ return all_info
198
+
199
+ def get_init_values(self, calibration_variables):
200
+ """Get initialization values for calibration variables.
201
+
202
+ Args:
203
+ calibration_variables (dict): Dict of existing calibration variables.
204
+ Returns:
205
+ init_values (dict): Dictionary with initialization values for calibration variables.
206
+ """
207
+ value_extraction_regexes = [
208
+ (
209
+ re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\s*=\s*(?P<value>[-\d\.e]+F?)\s*;"),
210
+ lambda regex_match, _: self._cast_init_value(regex_match.group("value")),
211
+ ),
212
+ (
213
+ re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\[(?P<size>[\d]+)\]\s*=\s*"),
214
+ self._get_array_init_values,
215
+ ),
216
+ (
217
+ re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\[(?P<rows>[\d]+)\]\[(?P<cols>[\d]+)\]\s*=\s*"),
218
+ self._get_matrix_init_values,
219
+ ),
220
+ (
221
+ re.compile(r"^\s*CVC_CAL[A-Z_]*\s+\w+\s+(?P<name>\w+)\s*=\s*(?P<enum>[a-zA-Z_$][\w_]*?)*;"),
222
+ lambda regex_match, _: regex_match.group("enum"),
223
+ ),
224
+ ]
225
+
226
+ init_values = {}
227
+ calibration_definitions = self._get_all_calibration_definitions()
228
+ calibration_definitions.reverse() # Reverse to pop from the end for performance
229
+ while calibration_definitions:
230
+ line = calibration_definitions.pop()
231
+ for regex, extraction_function in value_extraction_regexes:
232
+ regex_match = regex.match(line)
233
+ if regex_match is not None and regex_match.group("name") in calibration_variables:
234
+ if regex_match.group("name") in init_values:
235
+ self.critical("Variable definition for %s already found.", regex_match.group("name"))
236
+ init_values[regex_match.group("name")] = extraction_function(regex_match, calibration_definitions)
237
+
238
+ missing_init_values = set(calibration_variables) - set(init_values.keys())
239
+ if missing_init_values:
240
+ self.critical("Missing init values for calibration variables:\n%s", "\n".join(missing_init_values))
241
+
242
+ return init_values
243
+
244
+ def _get_all_calibration_definitions(self):
245
+ """Get all calibration definitions from the source files.
246
+
247
+ Returns:
248
+ (iter): Iterator with calibration definitions.
249
+ """
250
+ calibration_definitions = []
251
+ end_of_definitions_regex = re.compile(r"^void\s*RESTART_.*")
252
+ c_files = [Path(src_dir, unit.split("__")[0] + ".c").resolve() for unit, src_dir in self.unit_src_dirs.items()]
253
+ for c_file in c_files:
254
+ read_lines = ""
255
+ with c_file.open(mode="r", encoding="latin-1") as file_handle:
256
+ for line in file_handle:
257
+ if end_of_definitions_regex.match(line):
258
+ break
259
+ read_lines += line
260
+ calibration_definitions.extend(re.sub(r"/\*.*?\*/", "", read_lines, flags=re.S).splitlines())
261
+ return calibration_definitions
262
+
263
+ def _get_array_init_values(self, array_regex_match, definitions_list):
264
+ """Get initialization values for an array.
265
+
266
+ NOTES:
267
+ Modifies the argument definitions_list by popping elements.
268
+ Popping from the end since list is reversed.
269
+
270
+ Args:
271
+ array_regex_match (re.Match): Match object with array definition.
272
+ definitions_list (list): List (reversed) with lines to parse.
273
+ Returns:
274
+ (list): List of initialization values for the array.
275
+ """
276
+ array_init_values_str = ""
277
+ line = definitions_list.pop() # Skip array definition line
278
+ while "};" not in line:
279
+ array_init_values_str += line.strip()
280
+ line = definitions_list.pop()
281
+ array_init_values_str += line.strip()
282
+ array_init_values = re.findall(r"([-\d\.e]+F?),?", array_init_values_str)
283
+
284
+ if int(array_regex_match.group("size")) != len(array_init_values):
285
+ self.critical("Could not parse init values for array definition %s.", array_regex_match.group("name"))
286
+
287
+ return [self._cast_init_value(value) for value in array_init_values]
288
+
289
+ def _get_matrix_init_values(self, matrix_regex_match, definitions_list):
290
+ """Get initialization values for a matrix.
291
+
292
+ NOTES:
293
+ Modifies the argument definitions_list by popping elements.
294
+ Popping from the end since list is reversed.
295
+
296
+ Args:
297
+ matrix_regex_match (re.Match): Match object with matrix definition.
298
+ definitions_list (list): List (reversed) with lines to parse.
299
+ Returns:
300
+ (list(list)): List of initialization values for the matrix.
301
+ """
302
+ matrix_init_values = []
303
+ matrix_init_values_str = ""
304
+ line = definitions_list.pop() # Skip matrix definition line
305
+ while "};" not in line:
306
+ matrix_init_values_str += line.strip()
307
+ if "}" in line:
308
+ matrix_init_values.append(re.findall(r"([-\d\.e]+F?),?", matrix_init_values_str))
309
+ matrix_init_values_str = ""
310
+ line = definitions_list.pop()
311
+
312
+ row_check = int(matrix_regex_match.group("rows")) != len(matrix_init_values)
313
+ col_check = any(int(matrix_regex_match.group("cols")) != len(row) for row in matrix_init_values)
314
+ if row_check or col_check:
315
+ self.critical("Could not parse init values for matrix definition %s.", matrix_regex_match.group("name"))
316
+
317
+ return [[self._cast_init_value(value) for value in row] for row in matrix_init_values]
318
+
319
+ def _get_calibration_trigger_signal_name(self, calibration_variables):
320
+ """Get the variable of the calibration trigger.
321
+
322
+ Make sure it is not present already.
323
+
324
+ Args:
325
+ calibration_variables (dict): Dict of existing calibration variables.
326
+ Returns:
327
+ trigger_signal (str): Name of variable for triggering calibration.
328
+ """
329
+ software_component_name = self.build_cfg.get_composition_config("softwareComponentName")
330
+ trigger_signal = ZCC.trigger_read_rte_cdata_signal["name_template"].format(swc_name=software_component_name)
331
+
332
+ if trigger_signal in calibration_variables:
333
+ self.critical("Signal %s already defined in project.", trigger_signal)
334
+
335
+ return trigger_signal
336
+
337
+ def _edit_event_dict(self, event_dict):
338
+ """Edit event dictionary to use double quoted strings on certain element values.
339
+
340
+ The elements that need to be double quoted are JumpDown, JumpUp and EnaDEMInd.
341
+ The values are either "on" or "off", which yaml loader still interprets as boolean.
342
+ Hence the need for double quotes.
343
+ For some reason, "true" is not interpreted as boolean though.
344
+
345
+ Args:
346
+ event_dict (dict): Dict with diagnostic event data.
347
+ Returns:
348
+ event_dict (dict): Dict with diagnostic event data,
349
+ updated with double quoted strings on certain element values."""
350
+ for event_data in event_dict.values():
351
+ for key, value in event_data.items():
352
+ if key in ["ACC2", "EnaDEMInd", "JumpDown", "JumpUp"]:
353
+ event_data[key] = scalarstring.DoubleQuotedScalarString(value)
354
+ return event_dict
355
+
356
+ def _get_diagnostic_info(self):
357
+ """Get diagnostic information from composition spec.
358
+
359
+ NOTE: This function sets the valid_dids property of the ZCDIDs object.
360
+
361
+ Returns:
362
+ diag_dict (dict): Dict containing diagnostic information.
363
+ """
364
+ diag_dict = {}
365
+ diagnostics = self.composition_spec.get("diagnostics", {})
366
+
367
+ if self.build_cfg.get_composition_config("includeDiagnostics") == "manual":
368
+ dids = diagnostics.get("dids", {})
369
+ events = self._edit_event_dict(diagnostics.get("events", {}))
370
+ elif self.build_cfg.get_composition_config("includeDiagnostics") == "manual_dids":
371
+ dids = diagnostics.get("dids", {})
372
+ events_tmp = self._edit_event_dict(diagnostics.get("events", {}))
373
+ events = self.zc_core.get_diagnostic_trouble_codes(events_tmp)
374
+ elif self.build_cfg.get_composition_config("includeDiagnostics") == "manual_dtcs":
375
+ self.zc_dids.valid_dids = diagnostics.get("dids", {})
376
+ dids = self.zc_dids.valid_dids
377
+ events = self._edit_event_dict(diagnostics.get("events", {}))
378
+ else:
379
+ self.zc_dids.valid_dids = diagnostics.get("dids", {})
380
+ dids = self.zc_dids.valid_dids
381
+ events_tmp = self._edit_event_dict(diagnostics.get("events", {}))
382
+ events = self.zc_core.get_diagnostic_trouble_codes(events_tmp)
383
+ rids = diagnostics.get("rids", {})
384
+
385
+ if dids:
386
+ diag_dict["dids"] = dids
387
+ if events:
388
+ diag_dict["events"] = events
389
+ if rids:
390
+ diag_dict["rids"] = rids
391
+ self.warning("Will not generate code for RIDs, add manually.")
392
+ return diag_dict
393
+
394
+ def _get_nvm_info(self):
395
+ """Creates a dict with NVM information.
396
+
397
+ NVM dicts also needs to be added to the "static" field in the generated yaml.
398
+
399
+ NOTE: This function sets the valid_nvm_definitions property of the ZCNVMDef object.
400
+
401
+ Returns:
402
+ nvm_dict (dict): Dict containing NVM information.
403
+ data_types (dict): Dict containing data types for NVM information.
404
+ static_variables (dict): Dict containing "static" variables to add to the "static" field.
405
+ """
406
+ data_types = {}
407
+ static_variables = {}
408
+ yaml_nvm_definitions = self.composition_spec.get("nv-needs", {})
409
+ self.zc_nvm.valid_nvm_definitions = yaml_nvm_definitions
410
+ nvm_dict = self.zc_nvm.valid_nvm_definitions
411
+
412
+ for nvm_name, nvm_data in nvm_dict.items():
413
+ init = []
414
+ nr_of_unused_signals = self.zc_nvm.project_nvm_definitions[nvm_name]["size"]
415
+ data_type_name = f"dt_{nvm_name}"
416
+ data_types[data_type_name] = {"type": "RECORD", "elements": {}}
417
+ for signal in self.zc_nvm.project_nvm_definitions[nvm_name]["signals"]:
418
+ element_name = f'{self.zc_nvm.struct_member_prefix}{signal["name"]}'
419
+ nr_of_unused_signals -= signal["x_size"] * signal["y_size"]
420
+ size = max(signal["x_size"], 1) * max(signal["y_size"], 1)
421
+ if size > 1:
422
+ x_data_type_name = f"dt_{signal['name']}_x"
423
+ y_data_type_name = f"dt_{signal['name']}_y"
424
+ if signal["x_size"] > 1 and signal["y_size"] == 1:
425
+ init.append([0] * signal["x_size"])
426
+ data_types[data_type_name]["elements"][element_name] = x_data_type_name
427
+ data_types[x_data_type_name] = {
428
+ "type": "ARRAY",
429
+ "size": signal["x_size"],
430
+ "element": signal["type"],
431
+ }
432
+ elif signal["x_size"] > 1 and signal["y_size"] > 1:
433
+ init.append([[0] * signal["y_size"]] * signal["x_size"])
434
+ data_types[data_type_name]["elements"][element_name] = y_data_type_name
435
+ data_types[y_data_type_name] = {
436
+ "type": "ARRAY",
437
+ "size": signal["y_size"],
438
+ "element": x_data_type_name,
439
+ }
440
+ data_types[x_data_type_name] = {
441
+ "type": "ARRAY",
442
+ "size": signal["x_size"],
443
+ "element": signal["type"],
444
+ }
445
+ else:
446
+ self.critical("NVM signal size incorrect. x_size should not be 1 if y_size > 1.")
447
+ else:
448
+ init.append(0)
449
+ data_types[data_type_name]["elements"][element_name] = signal["type"]
450
+
451
+ nvm_data.update({
452
+ "datatype": data_type_name,
453
+ "init": init
454
+ })
455
+ static_variables[nvm_name.lower()] = {
456
+ "type": data_type_name,
457
+ "access": "READ-ONLY",
458
+ "init": init,
459
+ }
460
+
461
+ if nr_of_unused_signals > 0:
462
+ # Mimics how we generate the unused member of the structs in nvm_def.py
463
+ nvm_data["init"].append([0] * nr_of_unused_signals)
464
+ data_types[data_type_name]["elements"]["unused"] = f"{data_type_name}_Unused"
465
+ data_types[f"{data_type_name}_Unused"] = {
466
+ "type": "ARRAY",
467
+ "size": nr_of_unused_signals,
468
+ "element": self.zc_nvm.project_nvm_definitions[nvm_name]["default_datatype"],
469
+ }
470
+
471
+ return nvm_dict, data_types, static_variables
472
+
473
+ def _get_ports_info(self):
474
+ """Creates a dict containing port information.
475
+
476
+ Returns:
477
+ ports (dict): Dict containing port information.
478
+ """
479
+ ports = self.composition_spec.get("ports", {})
480
+ for call, call_data in self.composition_spec.get("calls", {}).items():
481
+ if call in ports:
482
+ continue
483
+ ports[call] = {
484
+ "interface": call_data.get("interface", call),
485
+ "direction": call_data["direction"],
486
+ }
487
+ return ports
488
+
489
+ def _get_runnable_calls_info(self):
490
+ """Creates a dict containing desired calls for the SWC.
491
+
492
+ Returns:
493
+ call_dict(dict): Dict containing runnable calls information.
494
+ """
495
+ call_dict = {}
496
+ for call, call_data in self.composition_spec.get("calls", {}).items():
497
+ call_dict[call] = {"operation": call_data["operation"]}
498
+ if "timeout" in call_data:
499
+ call_dict[call]["timeout"] = call_data["timeout"]
500
+ return call_dict
501
+
502
+ def _get_runnable_info(self):
503
+ """Creates a dict containing runnables information.
504
+
505
+ Returns:
506
+ dict: Dict containing runnables information.
507
+ """
508
+ autosar_prefix = "AR_"
509
+ swc_prefix = self.build_cfg.get_scheduler_prefix()
510
+ custom_step_function = self.build_cfg.get_composition_config("customYamlStepFunctionName")
511
+ custom_init_function = self.build_cfg.get_composition_config("customYamlInitFunctionName")
512
+ standard_init_function = autosar_prefix + swc_prefix + "VcExtINI"
513
+ init_function = custom_init_function if custom_init_function is not None else standard_init_function
514
+ calibration_variables = list(
515
+ self.cal_class_info["autosar"]["class_info"].keys()
516
+ ) + list(
517
+ self.composition_spec.get("shared", {}).keys()
518
+ )
519
+ swc_content = {
520
+ init_function: {
521
+ "type": "INIT",
522
+ "mode_ref": {
523
+ "port": "EcuMVccActivationMode",
524
+ "mode": ["VCC_ACTIVE"],
525
+ "trigger": "ON-ENTRY"
526
+ },
527
+ "accesses": calibration_variables
528
+ }
529
+ }
530
+
531
+ if self.include_calibration_interface_files:
532
+ swc_name = self.build_cfg.get_composition_config("softwareComponentName")
533
+ cal_init_function = autosar_prefix + ZCC.calibration_function_init_template.format(swc_name=swc_name)
534
+ cal_step_function = autosar_prefix + ZCC.calibration_function_step_template.format(swc_name=swc_name)
535
+ swc_content[cal_init_function] = {
536
+ "type": "INIT",
537
+ "mode_ref": {
538
+ "port": "EcuMVccActivationMode",
539
+ "mode": ["VCC_ACTIVE"],
540
+ "trigger": "ON-ENTRY"
541
+ },
542
+ "generateAccessPoints": False,
543
+ "accesses": calibration_variables
544
+ }
545
+ swc_content[cal_step_function] = {
546
+ "type": "PERIODIC",
547
+ "period": 0.1,
548
+ "mode_suppression": [
549
+ {"port": "EcuMVccActivationMode", "disabled_mode": ["VCC_NOT_ACTIVE"]}
550
+ ],
551
+ "generateAccessPoints": False,
552
+ "accesses": calibration_variables
553
+ }
554
+
555
+ call_dict = self._get_runnable_calls_info()
556
+ mode_switch_points_dict = self.composition_spec.get("mode_switch_points", {})
557
+ reads = []
558
+ writes = []
559
+ for port, port_data in self._get_ports_info().items():
560
+ if port_data["direction"] in ["IN", "CLIENT"]:
561
+ reads.append(port)
562
+ else:
563
+ writes.append(port)
564
+ runnables = self.build_cfg.get_units_raster_cfg()["SampleTimes"]
565
+
566
+ if len(runnables) == 1 and custom_step_function is not None:
567
+ swc_content[custom_step_function] = {
568
+ "type": "PERIODIC",
569
+ "period": list(runnables.values())[0],
570
+ "mode_suppression": [
571
+ {"port": "EcuMVccActivationMode", "disabled_mode": ["VCC_NOT_ACTIVE"]}
572
+ ],
573
+ "accesses": calibration_variables,
574
+ }
575
+ if call_dict:
576
+ swc_content[custom_step_function]["calls"] = call_dict
577
+ if mode_switch_points_dict:
578
+ swc_content[custom_step_function]["mode_switch_points"] = mode_switch_points_dict
579
+ if reads:
580
+ swc_content[custom_step_function]["reads"] = reads
581
+ if writes:
582
+ swc_content[custom_step_function]["writes"] = writes
583
+ return swc_content
584
+
585
+ if custom_step_function is not None:
586
+ self.warning(
587
+ "Custom step function specified, but multiple runnables defined. Ignoring custom step function."
588
+ )
589
+
590
+ for runnable, period in runnables.items():
591
+ key = autosar_prefix + swc_prefix + runnable
592
+ swc_content[key] = {
593
+ "period": period,
594
+ "type": "PERIODIC",
595
+ "mode_suppression": [
596
+ {"port": "EcuMVccActivationMode", "disabled_mode": ["VCC_NOT_ACTIVE"]}
597
+ ],
598
+ "accesses": calibration_variables,
599
+ }
600
+ if call_dict:
601
+ swc_content[key]["calls"] = call_dict
602
+ if mode_switch_points_dict:
603
+ swc_content[key]["mode_switch_points"] = mode_switch_points_dict
604
+ if reads:
605
+ swc_content[key]["reads"] = reads
606
+ if writes:
607
+ swc_content[key]["writes"] = writes
608
+
609
+ return swc_content
610
+
611
+ def _get_software_components(self):
612
+ """Creates a dict with swc information and referred data types.
613
+
614
+ Returns:
615
+ swcs (dict): SWC information.
616
+ data_types (dict): Data types information.
617
+ """
618
+ software_component_name = self.build_cfg.get_composition_config("softwareComponentName")
619
+ swcs = {software_component_name: {}}
620
+ swcs[software_component_name]["type"] = "SWC" # Other types than swc??
621
+ swcs[software_component_name]["asil"] = self.build_cfg.get_composition_config("asil")
622
+ swcs[software_component_name]["secure"] = self.build_cfg.get_composition_config("secure")
623
+ swcs[software_component_name]["runnables"] = self._get_runnable_info()
624
+ if self.build_cfg.get_composition_config("includeShared") is True:
625
+ swcs[software_component_name]["shared"] = self.cal_class_info["autosar"]["class_info"]
626
+ for variable_name, variable_info in self.composition_spec.get("shared", {}).items():
627
+ if variable_name in swcs[software_component_name]["shared"]:
628
+ self.critical("Shared variable %s already defined in project.", variable_name)
629
+ else:
630
+ swcs[software_component_name]["shared"][variable_name] = variable_info
631
+ elif self.build_cfg.get_composition_config("includeShared") == "manual":
632
+ swcs[software_component_name]["shared"] = {}
633
+ for variable_name, variable_info in self.composition_spec.get("shared", {}).items():
634
+ swcs[software_component_name]["shared"][variable_name] = variable_info
635
+ if self.build_cfg.get_composition_config("includeStatic") is True:
636
+ swcs[software_component_name]["static"] = self.meas_class_info["autosar"]["class_info"]
637
+ for variable_name, variable_info in self.composition_spec.get("static", {}).items():
638
+ if variable_name in swcs[software_component_name]["static"]:
639
+ self.critical("Static variable %s already defined in project.", variable_name)
640
+ else:
641
+ swcs[software_component_name]["static"][variable_name] = variable_info
642
+ elif self.build_cfg.get_composition_config("includeStatic") == "manual":
643
+ swcs[software_component_name]["static"] = {}
644
+ for variable_name, variable_info in self.composition_spec.get("static", {}).items():
645
+ swcs[software_component_name]["static"][variable_name] = variable_info
646
+ swcs[software_component_name]["ports"] = self._get_ports_info()
647
+ if self.composition_spec.get("io") is not None:
648
+ swcs[software_component_name]["io"] = self.composition_spec["io"]
649
+ if self.composition_spec.get("ecu") is not None:
650
+ swcs[software_component_name]["ecu"] = self.composition_spec["ecu"]
651
+ diagnostic_info = self._get_diagnostic_info()
652
+ if self.build_cfg.get_composition_config("includeDiagnostics") is not False:
653
+ swcs[software_component_name]["diagnostics"] = diagnostic_info
654
+ nvm_info, nvm_data_types_tmp, static_variables = self._get_nvm_info()
655
+ if self.build_cfg.get_composition_config("includeNvm"):
656
+ swcs[software_component_name]["nv-needs"] = nvm_info
657
+ nvm_data_types = nvm_data_types_tmp
658
+ if self.build_cfg.get_composition_config("includeStatic") is True:
659
+ swcs[software_component_name]["static"].update(static_variables)
660
+ else:
661
+ nvm_data_types = {}
662
+
663
+ data_types = {
664
+ **self.cal_class_info["autosar"]["data_types"],
665
+ **self.meas_class_info["autosar"]["data_types"],
666
+ **nvm_data_types,
667
+ }
668
+
669
+ return swcs, data_types
670
+
671
+ def _get_variables(self):
672
+ """Get calibration and measurable variables from the unit configuration.
673
+
674
+ Returns:
675
+ calibration_variables (dict): Dict with calibration variables.
676
+ measurable_variables (dict): Dict with measurable variables.
677
+ """
678
+ calibration_variables = {}
679
+ measurable_variables = {}
680
+ config = self.unit_cfg.get_per_cfg_unit_cfg()
681
+ valid_configs = ["outports", "local_vars", "calib_consts"]
682
+ for valid_config in valid_configs:
683
+ for signal_name, unit_info in config.get(valid_config, {}).items():
684
+ if len(unit_info) > 1:
685
+ self.critical("Multiple definitions for %s in config json files.", signal_name)
686
+ for info in unit_info.values():
687
+ if "CVC_CAL" in info["class"]:
688
+ calibration_variables[signal_name] = info
689
+ elif "CVC_DISP" in info["class"]:
690
+ measurable_variables[signal_name] = info
691
+ # External inports should also be considered as measurable variables
692
+ for io_type in self.external_io:
693
+ for signal_name in io_type.get("input", {}).keys():
694
+ for signal_data in config["inports"][signal_name].values():
695
+ measurable_variables[signal_name] = signal_data
696
+ continue # Inports can appear in several units, pick first one
697
+ return calibration_variables, measurable_variables
698
+
699
+ def _get_class_info(self, variable_dict):
700
+ """Creates a dict with parameter information and referred data types.
701
+
702
+ Args:
703
+ variable_dict (dict): Dictionary with variables and data.
704
+ Returns:
705
+ (dict): Dictionary with variables and data types (Autosar and TL).
706
+ """
707
+ autosar_class_info = {}
708
+ autosar_data_types = {}
709
+ tl_class_info = {}
710
+ for signal_name, info in variable_dict.items():
711
+ (
712
+ autosar_class_info,
713
+ autosar_data_types,
714
+ ) = self._add_autosar_data_types(autosar_class_info, autosar_data_types, signal_name, info)
715
+ if signal_name in autosar_class_info:
716
+ tl_class_info[signal_name] = {
717
+ "type": info["type"],
718
+ "autosar_type": autosar_class_info[signal_name]["type"].split("/")[-1],
719
+ "width": info["width"],
720
+ }
721
+ return {
722
+ "autosar": {
723
+ "class_info": autosar_class_info,
724
+ "data_types": autosar_data_types,
725
+ },
726
+ "tl": {"class_info": tl_class_info, "data_types": {}},
727
+ }
728
+
729
+ def _add_autosar_data_types(self, class_info, data_types, signal_name, info):
730
+ """Process a variable for inclusion in composition, adding it's data type to
731
+ data_types and the variable to class_info.
732
+
733
+ Args:
734
+ class_info (dict): Dictionary with variables.
735
+ data_types (dict): Dictionary with data types.
736
+ signal_name (string): Name of signal to process.
737
+ info (dict): signal data.
738
+ Returns:
739
+
740
+ class_info (dict): Updated dictionary with variables.
741
+ data_types (dict): Updated dictionary with data types.
742
+ """
743
+ if info["type"] in self.enums.keys():
744
+ return class_info, data_types
745
+
746
+ isReadOnly = "CVC_DISP" in info["class"] or info["class"] == "CVC_EXT"
747
+ if "Bool" in info["type"]:
748
+ upper = 1
749
+ lower = 0
750
+ else:
751
+ base_type_lower = self.data_types[info["type"]]["limits"]["lower"]
752
+ base_type_upper = self.data_types[info["type"]]["limits"]["upper"]
753
+ lower = info["min"] if info["min"] != "-" else base_type_lower
754
+ upper = info["max"] if info["max"] != "-" else base_type_upper
755
+
756
+ if not isinstance(info["width"], list):
757
+ class_info[signal_name] = {
758
+ "type": info["type"],
759
+ "access": "READ-ONLY" if isReadOnly else "READ-WRITE",
760
+ "init": self.calibration_init_values.get(signal_name, max(min(0, upper), lower)),
761
+ }
762
+ if info["description"]:
763
+ class_info[signal_name]["longname"] = self._prepare_for_xml(signal_name, info["description"])
764
+ if info["unit"] and info["unit"] != "-":
765
+ class_info[signal_name]["unit"] = info["unit"]
766
+ if self.sharedSwAddrMethod is not None and not isReadOnly:
767
+ class_info[signal_name]["swAddrMethod"] = f"{self.sharedSwAddrMethod}_{signal_name.split('_')[0]}"
768
+ return class_info, data_types
769
+
770
+ if isinstance(lower, list) or isinstance(upper, list):
771
+ if info["width"][0] > 1:
772
+ self.critical(
773
+ "%s is a multidimentional array of elements with different constraints, not supported.", signal_name
774
+ )
775
+ init = []
776
+ for idx in range(info["width"][1]):
777
+ lower_val = lower[idx] if isinstance(lower, list) else lower
778
+ lower_val = lower_val if lower_val != "-" else base_type_lower
779
+ upper_val = upper[idx] if isinstance(upper, list) else upper
780
+ upper_val = upper_val if upper_val != "-" else base_type_upper
781
+ init.append(max(min(0, upper_val), lower_val))
782
+ else:
783
+ init = max(min(0, upper), lower)
784
+ if info["width"][0] > 1:
785
+ init = [[init] * info["width"][1] for _ in range(info["width"][0])]
786
+ else:
787
+ init = [init] * info["width"][1]
788
+
789
+ init = self.calibration_init_values.get(signal_name, init)
790
+
791
+ new_data_type = {}
792
+ new_data_type_name = f"dt_{signal_name}"
793
+ if signal_name.startswith("t"):
794
+ if signal_name.endswith("_x"):
795
+ new_data_type_data = {
796
+ "type": "COM_AXIS",
797
+ "axis-index": 1,
798
+ "size": info["width"][1],
799
+ "limits": {"lower": lower, "upper": upper},
800
+ "swrecordlayout": {
801
+ "name": f"Distr_{signal_name}",
802
+ "type": "INDEX_INCR",
803
+ "basetype": self.tl_to_autosar_base_types[info["type"]],
804
+ "label": "X",
805
+ },
806
+ }
807
+ else:
808
+ axis = self.a2l_axis_data.get(signal_name, {}).get("axes", [signal_name + "_x"])[0]
809
+ new_data_type_data = {
810
+ "type": "CURVE",
811
+ "axis": f"dt_{axis}",
812
+ "limits": {"lower": lower, "upper": upper},
813
+ "swrecordlayout": {
814
+ "name": f"Curve_{signal_name}",
815
+ "type": "COLUMN_DIR",
816
+ "basetype": self.tl_to_autosar_base_types[info["type"]],
817
+ "label": "Val",
818
+ },
819
+ }
820
+ if self.build_cfg.get_composition_config("scaleMapsAndCurves") and "int" in info["type"].lower():
821
+ new_data_type_data["slope"] = info["lsb"]
822
+ new_data_type_data["bias"] = info["offset"]
823
+ elif signal_name.startswith("m"):
824
+ new_data_type_data = {
825
+ "type": "COM_AXIS",
826
+ "size": info["width"][1],
827
+ "limits": {"lower": lower, "upper": upper},
828
+ "swrecordlayout": {
829
+ "name": f"Distr_{signal_name}",
830
+ "type": "INDEX_INCR",
831
+ "basetype": self.tl_to_autosar_base_types[info["type"]],
832
+ },
833
+ }
834
+ if signal_name.endswith("_r"):
835
+ new_data_type_data["axis-index"] = 1
836
+ new_data_type_data["swrecordlayout"]["label"] = "X"
837
+ elif signal_name.endswith("_c"):
838
+ new_data_type_data["axis-index"] = 2
839
+ new_data_type_data["swrecordlayout"]["label"] = "Y"
840
+ else:
841
+ default_names = [signal_name + "_r", signal_name + "_c"]
842
+ axis_r, axis_c = self.a2l_axis_data.get(signal_name, {}).get("axes", default_names)
843
+ new_data_type_data = {
844
+ "type": "MAP",
845
+ "x-axis": f"dt_{axis_r}",
846
+ "y-axis": f"dt_{axis_c}",
847
+ "limits": {"lower": lower, "upper": upper},
848
+ "swrecordlayout": {
849
+ "name": f"Map_{signal_name}",
850
+ "type": "COLUMN_DIR",
851
+ "basetype": self.tl_to_autosar_base_types[info["type"]],
852
+ "label": "Val",
853
+ },
854
+ }
855
+ if self.build_cfg.get_composition_config("scaleMapsAndCurves") and "int" in info["type"].lower():
856
+ new_data_type_data["slope"] = info["lsb"]
857
+ new_data_type_data["bias"] = info["offset"]
858
+ elif info["width"][0] == 1:
859
+ new_data_type_name = f"dt_{signal_name}_{info['width'][1]}"
860
+ new_data_type_data = {
861
+ "type": "ARRAY",
862
+ "size": info["width"][1],
863
+ "element": info["type"],
864
+ }
865
+ else:
866
+ self.critical("Signal config error for %s.", signal_name)
867
+ return class_info, data_types
868
+
869
+ new_data_type[new_data_type_name] = new_data_type_data
870
+ class_info[signal_name] = {
871
+ "type": new_data_type_name,
872
+ "access": "READ-ONLY" if isReadOnly else "READ-WRITE",
873
+ "init": init,
874
+ }
875
+ if info["description"]:
876
+ class_info[signal_name]["longname"] = self._prepare_for_xml(signal_name, info["description"])
877
+ if self.sharedSwAddrMethod is not None and not isReadOnly:
878
+ class_info[signal_name]["swAddrMethod"] = f"{self.sharedSwAddrMethod}_{signal_name.split('_')[0]}"
879
+ data_types = {**data_types, **new_data_type}
880
+ return class_info, data_types