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
powertrain_build/a2l.py CHANGED
@@ -1,582 +1,582 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- # -*- coding: utf-8 -*-
5
- """Module for a2l-file generation."""
6
-
7
- import sys
8
- import re
9
- import logging
10
- from string import Template
11
- from pprint import pformat
12
- from powertrain_build.problem_logger import ProblemLogger
13
- from powertrain_build.types import a2l_type, a2l_range
14
-
15
- LOG = logging.getLogger()
16
-
17
-
18
- class A2l(ProblemLogger):
19
- """Class for a2l-file generation."""
20
-
21
- def __init__(self, var_data_dict, prj_cfg):
22
- """Generate a2l-file from provided data dictionary.
23
-
24
- Args:
25
- var_data_dict (dict): dict defining all variables and parameters in a2l
26
-
27
- Sample indata structure:
28
- ::
29
-
30
- {
31
- "function": "rVcAesSupM",
32
- "vars": {
33
- "rVcAesSupM_p_SupMonrSCTarDly": {
34
- "var": {
35
- "type": "Float32",
36
- "cvc_type": "CVC_DISP"
37
- },
38
- "a2l_data": {
39
- "unit": "kPa",
40
- "description": "Low pass filtered supercharger target pressure",
41
- "max": "300",
42
- "min": "0",
43
- "lsb": "1",
44
- "offset": "0",
45
- "bitmask": None,
46
- "x_axis": None,
47
- "y_axis": None,
48
- },
49
- "array": None
50
- }
51
- }
52
- }
53
- """
54
- super().__init__()
55
- self._var_dd = var_data_dict
56
- self._prj_cfg = prj_cfg
57
- self._axis_ref = None
58
- self._axis_data = None
59
- self._compu_meths = None
60
- self._rec_layouts = None
61
- self._fnc_outputs = None
62
- self._fnc_inputs = None
63
- self._fnc_locals = None
64
- self._fnc_char = None
65
- # generate the a2l string
66
- self._gen_a2l()
67
-
68
- def gen_a2l(self, filename):
69
- """Write a2l-data to file.
70
-
71
- Args:
72
- filename (str): Name of the generated a2l-file.
73
- """
74
- with open(filename, 'w', encoding="utf-8") as a2l:
75
- a2l.write(self._a2lstr)
76
- self.debug('Generated %s', filename)
77
-
78
- def _gen_a2l(self):
79
- """Generate an a2l-file based on the supplied data dictionary."""
80
- self._gen_compu_methods()
81
- # self.debug('_compu_meths')
82
- # self.debug(pp.pformat(self._compu_meths))
83
- self._find_axis_ref()
84
- # self.debug("_axis_ref")
85
- # self.debug(pp.pformat(self._axis_ref))
86
- self._find_axis_data()
87
- self._gen_record_layouts_data()
88
- # self.debug("_axis_data")
89
- # self.debug(pp.pformat(self._axis_data))
90
- # self.debug("_rec_layouts")
91
- # self.debug(pp.pformat(self._rec_layouts))
92
- self._check_axis_ref()
93
-
94
- output_meas = ''
95
- output_char = ''
96
- output_axis = ''
97
-
98
- self._fnc_outputs = []
99
- self._fnc_inputs = []
100
- self._fnc_locals = []
101
- self._fnc_char = []
102
-
103
- for var, data in self._var_dd['vars'].items():
104
- try:
105
- cvc_type = data['var']['cvc_type']
106
- if 'CVC_DISP' in cvc_type:
107
- output_meas += self._gen_a2l_measurement_blk(var, data)
108
- self._fnc_locals.append(var)
109
- elif 'CVC_CAL' in cvc_type:
110
- self._fnc_char.append(var)
111
- srch = re.search('_[rcxyXY]$', var)
112
- if srch is None:
113
- output_char += self._gen_a2l_characteristic_blk(var, data)
114
- else:
115
- output_axis += self._gen_a2l_axis_pts_blk(var, data)
116
- elif cvc_type == 'CVC_NVM':
117
- output_meas += self._gen_a2l_measurement_blk(var, data)
118
- self._fnc_locals.append(var)
119
- elif cvc_type == 'CVC_IN':
120
- self._outputs = None
121
- self._inputs = None
122
- self._fnc_inputs.append(var)
123
- elif cvc_type == 'CVC_OUT':
124
- self._fnc_outputs.append(var)
125
- except TypeError:
126
- self.warning("Warning: %s has no A2l-data", var)
127
- except Exception as e:
128
- self.critical("Unexpected error: %s", sys.exc_info()[0])
129
- raise e
130
- # generate COMPU_METHS
131
- output_compu_m = ''
132
- for k in self._compu_meths:
133
- output_compu_m += self._gen_a2l_compu_metod_blk(k)
134
-
135
- # generate FUNCTIONS
136
- output_funcs = self._gen_a2l_function_blk()
137
-
138
- # generate RECORD_LAYOUTS
139
- output_recl = ''
140
- for k in self._rec_layouts:
141
- output_recl += self._gen_a2l_rec_layout_blk(k)
142
-
143
- output = output_char + output_axis + output_meas + \
144
- output_compu_m + output_funcs + output_recl
145
- # self.debug(pp.pformat(self._var_dd))
146
- # self.debug('Output:')
147
- # self.debug(output)
148
- self._a2lstr = output
149
-
150
- def _find_axis_data(self):
151
- """Parse all variables and identify axis points.
152
-
153
- TODO: Change this function to check for names with _r | _c
154
- suffixes
155
- """
156
- self._axis_data = {}
157
- variable_names = self._var_dd['vars'].keys()
158
- for name in variable_names:
159
- x_nm = y_nm = None
160
- if name + '_x' in variable_names:
161
- x_nm = name + '_x'
162
- if name + '_y' in variable_names:
163
- y_nm = name + '_x'
164
- if x_nm is not None or y_nm is not None:
165
- self._axis_data[name] = (x_nm, y_nm)
166
-
167
- def _find_axis_ref(self):
168
- """Parse all variables and identify which are defined as axis points."""
169
- self._axis_ref = {}
170
-
171
- for var, data in self._var_dd['vars'].items():
172
- if data.get('a2l_data') is not None:
173
- x_axis = data['a2l_data'].get('x_axis')
174
- y_axis = data['a2l_data'].get('y_axis')
175
- if x_axis is not None:
176
- if x_axis in self._axis_ref:
177
- self._axis_ref[x_axis]['used_in'].append((var, 'x'))
178
- else:
179
- self._axis_ref[x_axis] = {'used_in': [(var, 'x')]}
180
- if y_axis is not None:
181
- if y_axis in self._axis_ref:
182
- self._axis_ref[y_axis]['used_in'].append((var, 'y'))
183
- else:
184
- self._axis_ref[y_axis] = {'used_in': [(var, 'y')]}
185
-
186
- @classmethod
187
- def _get_a2d_minmax(cls, a2d, ctype=None):
188
- """Get min max limits from a2l data.
189
-
190
- Gives max limits if min/max limits are undefined.
191
- """
192
- typelim = a2l_range(ctype)
193
- minlim = a2d.get('min')
194
- if minlim is None or minlim == '-':
195
- minlim = typelim[0]
196
- maxlim = a2d.get('max')
197
- if maxlim is None or maxlim == '-':
198
- maxlim = typelim[1]
199
- return minlim, maxlim
200
-
201
- def _check_axis_ref(self):
202
- """Check that the axis definitions are defined in the code."""
203
- undef_axis = [ax for ax in self._axis_ref
204
- if ax not in self._var_dd['vars']]
205
- if undef_axis:
206
- self.warning(f'Undefined axis {pformat(undef_axis)}')
207
-
208
- def _gen_compu_methods(self):
209
- """Generate COMPU_METHOD data, and add it into the var_data_dict."""
210
- self._compu_meths = {}
211
- for var, data in self._var_dd['vars'].items():
212
- a2d = data.get('a2l_data')
213
- if a2d is not None:
214
-
215
- lsb = self._calc_lsb(a2d['lsb'])
216
- offset_str = str(a2d['offset'])
217
- is_offset_num = bool(re.match('[0-9]', offset_str))
218
- if is_offset_num:
219
- offset = float(offset_str)
220
- else:
221
- offset = 0
222
- key = (lsb, offset, a2d['unit'])
223
- self._var_dd['vars'][var]['compu_meth'] = key
224
- name = self._compu_key_2_name(key)
225
- if key in self._compu_meths:
226
- self._compu_meths[key]['vars'].append(var)
227
- else:
228
- self._compu_meths[key] = {'name': name,
229
- 'vars': [var],
230
- 'coeffs': self._get_coefs_str(lsb,
231
- offset)}
232
-
233
- def _compu_key_2_name(self, key):
234
- """Generate a COMPU_METHOD name from the keys in the name.
235
-
236
- Args:
237
- key (tuple): a list with compumethod keys (lsb, offset, unit)
238
- """
239
- conversion_list = [(r'[\./]', '_'), ('%', 'percent'),
240
- ('-', 'None'), (r'\W', '_')]
241
- name = f"{self._var_dd['function']}_{key[0]}_{key[1]}_{key[2]}"
242
- for frm, to_ in conversion_list:
243
- name = re.sub(frm, to_, name)
244
- return name
245
-
246
- @staticmethod
247
- def _array_to_a2l_string(array):
248
- """Convert c-style array definitions to A2L MATRIX_DIM style."""
249
- if not isinstance(array, list):
250
- array = [array]
251
- dims = [1, 1, 1]
252
- for i, res in enumerate(array):
253
- dims[i] = res
254
- return f"MATRIX_DIM {dims[0]} {dims[1]} {dims[2]}"
255
-
256
- @staticmethod
257
- def _get_coefs_str(lsb, offset):
258
- """Calculate the a2l-coeffs from the lsb and offs fields.
259
-
260
- The fields are defined in the a2l_data dictionary.
261
- """
262
- return f"COEFFS 0 1 {offset} 0 0 {lsb}"
263
-
264
- @staticmethod
265
- def _calc_lsb(lsb):
266
- """Convert 2^-2, style lsbs to numericals."""
267
- if isinstance(lsb, str):
268
- if lsb == '-':
269
- return 1
270
- shift = re.match(r'(\d+)\^([\-+0-9]+)', lsb)
271
- if shift is not None:
272
- lsb_num = pow(int(shift.group(1)), int(shift.group(2)))
273
- else:
274
- lsb_num = float(lsb)
275
- return lsb_num
276
- return lsb
277
-
278
- def _gen_record_layouts_data(self):
279
- """Generate record layouts."""
280
- self._rec_layouts = {}
281
- for var, data in self._var_dd['vars'].items():
282
- if data.get('a2l_data') is not None:
283
- a2l_unit = a2l_type(data['var']['type'])
284
- # if calibration data has a suffix of _x of _y it is a axis_pts
285
- srch = re.search('_[xyXY]$', var)
286
- if srch is not None:
287
- name = a2l_unit + "_X_INCR_DIRECT"
288
- self._rec_layouts[name] = f"AXIS_PTS_X 1 {a2l_unit} INDEX_INCR DIRECT"
289
- data['rec_layout'] = name
290
- else:
291
- name = a2l_type(data['var']['type']) + "_COL_DIRECT"
292
- self._rec_layouts[name] = f"FNC_VALUES 1 {a2l_unit} COLUMN_DIR DIRECT"
293
- data['rec_layout'] = name
294
-
295
- def _get_inpq_data(self, inp_quant):
296
- """Get the necessary InputQuantity parameters."""
297
- if inp_quant is not None:
298
- if inp_quant in self._var_dd['vars']:
299
- return inp_quant
300
- return 'NO_INPUT_QUANTITY'
301
-
302
- # Bosh template
303
- _meas_tmplt = Template("""
304
- /begin MEASUREMENT
305
- $Name /* Name */
306
- "$LongIdent" /* LongIdentifier */
307
- $Datatype /* Datatype */
308
- $Conversion /* Conversion */
309
- 1 /* Resolution */
310
- 0 /* Accuracy */
311
- $LowerLimit /* LowerLimit */
312
- $UpperLimit /* UpperLimit */
313
- $OptionalData
314
- ECU_ADDRESS 0x00000000
315
- /end MEASUREMENT
316
- """)
317
-
318
- # Denso template
319
- _meas_tmplt_nvm = Template("""
320
- /begin MEASUREMENT
321
- $Name /* Name */
322
- "$LongIdent" /* LongIdentifier */
323
- $Datatype /* Datatype */
324
- $Conversion /* Conversion */
325
- 1 /* Resolution */
326
- 0 /* Accuracy */
327
- $LowerLimit /* LowerLimit */
328
- $UpperLimit /* UpperLimit */
329
- $OptionalData
330
- /end MEASUREMENT
331
- """)
332
-
333
- def _gen_a2l_measurement_blk(self, var_name, data):
334
- """Generate an a2l MEASUREMENT block."""
335
- opt_data = 'READ_WRITE'
336
- a2d = data.get('a2l_data')
337
- if a2d is not None:
338
- c_type = data['var']['type']
339
- # if c_type == 'Bool':
340
- # opt_data += '\n' + ' ' * 8 + "BIT_MASK 0x1"
341
- if a2d.get('bitmask') is not None:
342
- opt_data += '\n' + ' ' * 8 + "BIT_MASK %s" % a2d['bitmask']
343
- if data.get('array'):
344
- opt_data += '\n' + ' ' * 8 + \
345
- self._array_to_a2l_string(data['array'])
346
-
347
- use_symbol_links = self._prj_cfg.get_code_generation_config(item='useA2lSymbolLinks')
348
- if a2d.get('symbol'):
349
- if use_symbol_links:
350
- opt_data += '\n' + ' ' * 8 + 'SYMBOL_LINK "%s" %s' % (a2d['symbol'], a2d.get('symbol_offset'))
351
- LOG.debug('This a2l is using SYMBOL_LINK for %s', opt_data)
352
- else:
353
- var_name = a2d['symbol'] + '._' + var_name
354
- LOG.debug('This a2l is not using SYMBOL_LINK for %s', var_name)
355
-
356
- dtype = a2l_type(c_type)
357
- minlim, maxlim = self._get_a2d_minmax(a2d, c_type)
358
- conv = self._compu_meths[data['compu_meth']]['name']
359
-
360
- if a2d.get('symbol') and use_symbol_links:
361
- res = self._meas_tmplt_nvm.substitute(Name=var_name,
362
- LongIdent=a2d['description'].replace('"', '\\"'),
363
- Datatype=dtype,
364
- Conversion=conv,
365
- LowerLimit=minlim,
366
- UpperLimit=maxlim,
367
- OptionalData=opt_data)
368
- else:
369
- res = self._meas_tmplt.substitute(Name=var_name,
370
- LongIdent=a2d['description'].replace('"', '\\"'),
371
- Datatype=dtype,
372
- Conversion=conv,
373
- LowerLimit=minlim,
374
- UpperLimit=maxlim,
375
- OptionalData=opt_data)
376
- return res
377
- return None
378
-
379
- _char_tmplt = Template("""
380
- /begin CHARACTERISTIC
381
- $Name /* Name */
382
- "$LongIdent" /* LongIdentifier */
383
- $Type /* Datatype */
384
- 0x00000000 /* address: $Name */
385
- $Deposit /* Deposit */
386
- 0 /* MaxDiff */
387
- $Conversion /* Conversion */
388
- $LowerLimit /* LowerLimit */
389
- $UpperLimit /* UpperLimit */$OptionalData
390
- /end CHARACTERISTIC
391
- """)
392
-
393
- def _gen_a2l_characteristic_blk(self, var, data):
394
- """Generate an a2l CHARACTERISTIC block."""
395
- opt_data = ''
396
- a2d = data.get('a2l_data')
397
- type_ = 'WRONG_TYPE'
398
- if a2d is not None:
399
- arr = data.get('array')
400
- if arr is not None:
401
- arr_dim = len(arr)
402
- else:
403
- arr_dim = 0
404
- # Check is axis_pts are defined for the axis, if not make the
405
- # type a VAL_BLK with matrix dimension, otherwise set
406
- # a CURVE or MAP type
407
-
408
- # If arr_dim is 0 the CHARACTERISTIC is a value
409
- if arr_dim == 0:
410
- type_ = 'VALUE'
411
- elif arr_dim == 1:
412
- x_axis_name = var + '_x'
413
- # Check if axis variable is defined
414
- if x_axis_name in self._var_dd['vars'].keys():
415
- type_ = 'CURVE'
416
- opt_data += self._gen_a2l_axis_desc_blk(self._get_inpq_data(a2d.get('x_axis')),
417
- x_axis_name)
418
- else:
419
- type_ = 'VAL_BLK'
420
- opt_data += self._array_to_a2l_string(data['array'])
421
- elif arr_dim == 2:
422
- x_axis_name = var + '_x'
423
- y_axis_name = var + '_y'
424
- # Check if axis variable is defined
425
- nbr_def_axis = 0
426
- if x_axis_name in self._var_dd['vars'].keys():
427
- nbr_def_axis += 1
428
- if y_axis_name in self._var_dd['vars'].keys():
429
- nbr_def_axis += 1
430
- if nbr_def_axis == 2:
431
- type_ = 'MAP'
432
- inpq_x = self._get_inpq_data(a2d['x_axis'])
433
- opt_data += self._gen_a2l_axis_desc_blk(inpq_x, x_axis_name)
434
- inpq_y = self._get_inpq_data(a2d['y_axis'])
435
- opt_data += self._gen_a2l_axis_desc_blk(inpq_y, y_axis_name)
436
- elif nbr_def_axis == 0:
437
- type_ = 'VAL_BLK'
438
- opt_data += self._array_to_a2l_string(data['array'])
439
- else:
440
- self.warning(
441
- 'MAP %s has only one AXIS_PTS defined, shall be none or two', var)
442
-
443
- minlim, maxlim = self._get_a2d_minmax(a2d)
444
-
445
- res = self._char_tmplt.substitute(Name=var,
446
- LongIdent=a2d['description'].replace('"', '\\"'),
447
- Type=type_,
448
- Deposit=data['rec_layout'],
449
- Conversion=self._compu_meths[data['compu_meth']]['name'],
450
- LowerLimit=minlim,
451
- UpperLimit=maxlim,
452
- OptionalData=opt_data)
453
- return res
454
- self.warning("%s has no A2L-data", var)
455
- return None
456
- # Types ASCII CURVE MAP VAL_BLK VALUE
457
-
458
- _axis_desc_tmplt = Template("""
459
- /begin AXIS_DESCR
460
- COM_AXIS /* Attribute */
461
- $inp_quant /* InputQuantity */
462
- $conv /* Conversion */
463
- $maxaxispts /* MaxAxisPoints */
464
- $minlim /* LowerLimit */
465
- $maxlim /* UpperLimit */
466
- AXIS_PTS_REF $axis_pts_ref
467
- DEPOSIT ABSOLUTE
468
- /end AXIS_DESCR""")
469
-
470
- def _gen_a2l_axis_desc_blk(self, inp_quant, axis_pts_ref):
471
- """Generate an a2l AXIS_DESCR block.
472
-
473
- TODO: Check that the AXIS_PTS_REF blocks are defined
474
- """
475
- out = ''
476
- inp_quant_txt = self._get_inpq_data(inp_quant)
477
- axis_pts = self._var_dd['vars'][axis_pts_ref]
478
- conv = self._compu_meths[axis_pts['compu_meth']]['name']
479
- max_axis_pts = axis_pts['array'][0]
480
- min_lim, max_lim = self._get_a2d_minmax(axis_pts['a2l_data'])
481
- out += self._axis_desc_tmplt.substitute(inp_quant=inp_quant_txt,
482
- conv=conv,
483
- maxaxispts=max_axis_pts,
484
- minlim=min_lim,
485
- maxlim=max_lim,
486
- axis_pts_ref=axis_pts_ref)
487
- return out
488
-
489
- _compu_meth_tmplt = Template("""
490
- /begin COMPU_METHOD
491
- $name /* Name */
492
- "$longident" /* LongIdentifier */
493
- RAT_FUNC /* ConversionType */
494
- "$format" /* Format */
495
- "$unit" /* Unit */
496
- $coeffs
497
- /end COMPU_METHOD
498
- """)
499
-
500
- def _gen_a2l_compu_metod_blk(self, key):
501
- """Generate an a2l COMPU_METHOD block."""
502
- cmeth = self._compu_meths[key]
503
- name = self._compu_key_2_name(key)
504
- out = self._compu_meth_tmplt.substitute(name=name,
505
- longident='',
506
- format='%11.3',
507
- unit=key[2],
508
- coeffs=cmeth['coeffs'])
509
- return out
510
-
511
- _axis_tmplt = Template("""
512
- /begin AXIS_PTS
513
- $name /* Name */
514
- "$longident" /* LongIdentifier */
515
- 0x00000000
516
- NO_INPUT_QUANTITY /* InputQuantity */
517
- $deposit /* Deposit */
518
- 0 /* MaxDiff */
519
- $convert /* Conversion */
520
- $max_ax_pts /* MaxAxisPoints */
521
- $minlim /* LowerLimit */
522
- $maxlim /* UpperLimit */
523
- DEPOSIT ABSOLUTE
524
- /end AXIS_PTS
525
- """)
526
-
527
- def _gen_a2l_axis_pts_blk(self, var, data):
528
- """Generate an a2l AXIS_PTS block."""
529
- deposit = data['rec_layout']
530
- conv = self._compu_meths[data['compu_meth']]['name']
531
- max_axis_pts = data['array'][0]
532
- min_lim, max_lim = self._get_a2d_minmax(data['a2l_data'])
533
- out = self._axis_tmplt.substitute(name=var,
534
- longident=data['a2l_data']['description'],
535
- deposit=deposit,
536
- convert=conv,
537
- max_ax_pts=max_axis_pts,
538
- minlim=min_lim,
539
- maxlim=max_lim)
540
- return out
541
-
542
- _rec_layout_tmplt = Template("""
543
- /begin RECORD_LAYOUT
544
- $name /* Name */
545
- $string
546
- /end RECORD_LAYOUT
547
- """)
548
-
549
- def _gen_a2l_rec_layout_blk(self, key):
550
- """Generate an a2l AXIS_PTS block."""
551
- string = self._rec_layouts[key]
552
- out = self._rec_layout_tmplt.substitute(name=key,
553
- string=string)
554
- return out
555
-
556
- def _gen_a2l_function_blk(self):
557
- """Generate an a2l FUNCTION block."""
558
- out = '\n /begin FUNCTION\n'
559
- out += f' {self._var_dd["function"]} /* Name */\n'
560
- out += ' "" /* LongIdentifier */\n'
561
- if self._fnc_char:
562
- out += ' /begin DEF_CHARACTERISTIC\n'
563
- for idf in self._fnc_char:
564
- out += f' {idf} /* Identifier */\n'
565
- out += ' /end DEF_CHARACTERISTIC\n'
566
- if self._fnc_inputs:
567
- out += ' /begin IN_MEASUREMENT\n'
568
- for idf in self._fnc_inputs:
569
- out += f' {idf} /* Identifier */\n'
570
- out += ' /end IN_MEASUREMENT\n'
571
- if self._fnc_locals:
572
- out += ' /begin LOC_MEASUREMENT\n'
573
- for idf in self._fnc_locals:
574
- out += f' {idf} /* Identifier */\n'
575
- out += ' /end LOC_MEASUREMENT\n'
576
- if self._fnc_outputs:
577
- out += ' /begin OUT_MEASUREMENT\n'
578
- for idf in self._fnc_outputs:
579
- out += f' {idf} /* Identifier */\n'
580
- out += ' /end OUT_MEASUREMENT\n'
581
- out += ' /end FUNCTION\n'
582
- return out
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ # -*- coding: utf-8 -*-
5
+ """Module for a2l-file generation."""
6
+
7
+ import sys
8
+ import re
9
+ import logging
10
+ from string import Template
11
+ from pprint import pformat
12
+ from powertrain_build.problem_logger import ProblemLogger
13
+ from powertrain_build.types import a2l_type, a2l_range
14
+
15
+ LOG = logging.getLogger()
16
+
17
+
18
+ class A2l(ProblemLogger):
19
+ """Class for a2l-file generation."""
20
+
21
+ def __init__(self, var_data_dict, prj_cfg):
22
+ """Generate a2l-file from provided data dictionary.
23
+
24
+ Args:
25
+ var_data_dict (dict): dict defining all variables and parameters in a2l
26
+
27
+ Sample indata structure:
28
+ ::
29
+
30
+ {
31
+ "function": "rVcAesSupM",
32
+ "vars": {
33
+ "rVcAesSupM_p_SupMonrSCTarDly": {
34
+ "var": {
35
+ "type": "Float32",
36
+ "cvc_type": "CVC_DISP"
37
+ },
38
+ "a2l_data": {
39
+ "unit": "kPa",
40
+ "description": "Low pass filtered supercharger target pressure",
41
+ "max": "300",
42
+ "min": "0",
43
+ "lsb": "1",
44
+ "offset": "0",
45
+ "bitmask": None,
46
+ "x_axis": None,
47
+ "y_axis": None,
48
+ },
49
+ "array": None
50
+ }
51
+ }
52
+ }
53
+ """
54
+ super().__init__()
55
+ self._var_dd = var_data_dict
56
+ self._prj_cfg = prj_cfg
57
+ self._axis_ref = None
58
+ self._axis_data = None
59
+ self._compu_meths = None
60
+ self._rec_layouts = None
61
+ self._fnc_outputs = None
62
+ self._fnc_inputs = None
63
+ self._fnc_locals = None
64
+ self._fnc_char = None
65
+ # generate the a2l string
66
+ self._gen_a2l()
67
+
68
+ def gen_a2l(self, filename):
69
+ """Write a2l-data to file.
70
+
71
+ Args:
72
+ filename (str): Name of the generated a2l-file.
73
+ """
74
+ with open(filename, 'w', encoding="utf-8") as a2l:
75
+ a2l.write(self._a2lstr)
76
+ self.debug('Generated %s', filename)
77
+
78
+ def _gen_a2l(self):
79
+ """Generate an a2l-file based on the supplied data dictionary."""
80
+ self._gen_compu_methods()
81
+ # self.debug('_compu_meths')
82
+ # self.debug(pp.pformat(self._compu_meths))
83
+ self._find_axis_ref()
84
+ # self.debug("_axis_ref")
85
+ # self.debug(pp.pformat(self._axis_ref))
86
+ self._find_axis_data()
87
+ self._gen_record_layouts_data()
88
+ # self.debug("_axis_data")
89
+ # self.debug(pp.pformat(self._axis_data))
90
+ # self.debug("_rec_layouts")
91
+ # self.debug(pp.pformat(self._rec_layouts))
92
+ self._check_axis_ref()
93
+
94
+ output_meas = ''
95
+ output_char = ''
96
+ output_axis = ''
97
+
98
+ self._fnc_outputs = []
99
+ self._fnc_inputs = []
100
+ self._fnc_locals = []
101
+ self._fnc_char = []
102
+
103
+ for var, data in self._var_dd['vars'].items():
104
+ try:
105
+ cvc_type = data['var']['cvc_type']
106
+ if 'CVC_DISP' in cvc_type:
107
+ output_meas += self._gen_a2l_measurement_blk(var, data)
108
+ self._fnc_locals.append(var)
109
+ elif 'CVC_CAL' in cvc_type:
110
+ self._fnc_char.append(var)
111
+ srch = re.search('_[rcxyXY]$', var)
112
+ if srch is None:
113
+ output_char += self._gen_a2l_characteristic_blk(var, data)
114
+ else:
115
+ output_axis += self._gen_a2l_axis_pts_blk(var, data)
116
+ elif cvc_type == 'CVC_NVM':
117
+ output_meas += self._gen_a2l_measurement_blk(var, data)
118
+ self._fnc_locals.append(var)
119
+ elif cvc_type == 'CVC_IN':
120
+ self._outputs = None
121
+ self._inputs = None
122
+ self._fnc_inputs.append(var)
123
+ elif cvc_type == 'CVC_OUT':
124
+ self._fnc_outputs.append(var)
125
+ except TypeError:
126
+ self.warning("Warning: %s has no A2l-data", var)
127
+ except Exception as e:
128
+ self.critical("Unexpected error: %s", sys.exc_info()[0])
129
+ raise e
130
+ # generate COMPU_METHS
131
+ output_compu_m = ''
132
+ for k in self._compu_meths:
133
+ output_compu_m += self._gen_a2l_compu_metod_blk(k)
134
+
135
+ # generate FUNCTIONS
136
+ output_funcs = self._gen_a2l_function_blk()
137
+
138
+ # generate RECORD_LAYOUTS
139
+ output_recl = ''
140
+ for k in self._rec_layouts:
141
+ output_recl += self._gen_a2l_rec_layout_blk(k)
142
+
143
+ output = output_char + output_axis + output_meas + \
144
+ output_compu_m + output_funcs + output_recl
145
+ # self.debug(pp.pformat(self._var_dd))
146
+ # self.debug('Output:')
147
+ # self.debug(output)
148
+ self._a2lstr = output
149
+
150
+ def _find_axis_data(self):
151
+ """Parse all variables and identify axis points.
152
+
153
+ TODO: Change this function to check for names with _r | _c
154
+ suffixes
155
+ """
156
+ self._axis_data = {}
157
+ variable_names = self._var_dd['vars'].keys()
158
+ for name in variable_names:
159
+ x_nm = y_nm = None
160
+ if name + '_x' in variable_names:
161
+ x_nm = name + '_x'
162
+ if name + '_y' in variable_names:
163
+ y_nm = name + '_x'
164
+ if x_nm is not None or y_nm is not None:
165
+ self._axis_data[name] = (x_nm, y_nm)
166
+
167
+ def _find_axis_ref(self):
168
+ """Parse all variables and identify which are defined as axis points."""
169
+ self._axis_ref = {}
170
+
171
+ for var, data in self._var_dd['vars'].items():
172
+ if data.get('a2l_data') is not None:
173
+ x_axis = data['a2l_data'].get('x_axis')
174
+ y_axis = data['a2l_data'].get('y_axis')
175
+ if x_axis is not None:
176
+ if x_axis in self._axis_ref:
177
+ self._axis_ref[x_axis]['used_in'].append((var, 'x'))
178
+ else:
179
+ self._axis_ref[x_axis] = {'used_in': [(var, 'x')]}
180
+ if y_axis is not None:
181
+ if y_axis in self._axis_ref:
182
+ self._axis_ref[y_axis]['used_in'].append((var, 'y'))
183
+ else:
184
+ self._axis_ref[y_axis] = {'used_in': [(var, 'y')]}
185
+
186
+ @classmethod
187
+ def _get_a2d_minmax(cls, a2d, ctype=None):
188
+ """Get min max limits from a2l data.
189
+
190
+ Gives max limits if min/max limits are undefined.
191
+ """
192
+ typelim = a2l_range(ctype)
193
+ minlim = a2d.get('min')
194
+ if minlim is None or minlim == '-':
195
+ minlim = typelim[0]
196
+ maxlim = a2d.get('max')
197
+ if maxlim is None or maxlim == '-':
198
+ maxlim = typelim[1]
199
+ return minlim, maxlim
200
+
201
+ def _check_axis_ref(self):
202
+ """Check that the axis definitions are defined in the code."""
203
+ undef_axis = [ax for ax in self._axis_ref
204
+ if ax not in self._var_dd['vars']]
205
+ if undef_axis:
206
+ self.warning(f'Undefined axis {pformat(undef_axis)}')
207
+
208
+ def _gen_compu_methods(self):
209
+ """Generate COMPU_METHOD data, and add it into the var_data_dict."""
210
+ self._compu_meths = {}
211
+ for var, data in self._var_dd['vars'].items():
212
+ a2d = data.get('a2l_data')
213
+ if a2d is not None:
214
+
215
+ lsb = self._calc_lsb(a2d['lsb'])
216
+ offset_str = str(a2d['offset'])
217
+ is_offset_num = bool(re.match('[0-9]', offset_str))
218
+ if is_offset_num:
219
+ offset = float(offset_str)
220
+ else:
221
+ offset = 0
222
+ key = (lsb, offset, a2d['unit'])
223
+ self._var_dd['vars'][var]['compu_meth'] = key
224
+ name = self._compu_key_2_name(key)
225
+ if key in self._compu_meths:
226
+ self._compu_meths[key]['vars'].append(var)
227
+ else:
228
+ self._compu_meths[key] = {'name': name,
229
+ 'vars': [var],
230
+ 'coeffs': self._get_coefs_str(lsb,
231
+ offset)}
232
+
233
+ def _compu_key_2_name(self, key):
234
+ """Generate a COMPU_METHOD name from the keys in the name.
235
+
236
+ Args:
237
+ key (tuple): a list with compumethod keys (lsb, offset, unit)
238
+ """
239
+ conversion_list = [(r'[\./]', '_'), ('%', 'percent'),
240
+ ('-', 'None'), (r'\W', '_')]
241
+ name = f"{self._var_dd['function']}_{key[0]}_{key[1]}_{key[2]}"
242
+ for frm, to_ in conversion_list:
243
+ name = re.sub(frm, to_, name)
244
+ return name
245
+
246
+ @staticmethod
247
+ def _array_to_a2l_string(array):
248
+ """Convert c-style array definitions to A2L MATRIX_DIM style."""
249
+ if not isinstance(array, list):
250
+ array = [array]
251
+ dims = [1, 1, 1]
252
+ for i, res in enumerate(array):
253
+ dims[i] = res
254
+ return f"MATRIX_DIM {dims[0]} {dims[1]} {dims[2]}"
255
+
256
+ @staticmethod
257
+ def _get_coefs_str(lsb, offset):
258
+ """Calculate the a2l-coeffs from the lsb and offs fields.
259
+
260
+ The fields are defined in the a2l_data dictionary.
261
+ """
262
+ return f"COEFFS 0 1 {offset} 0 0 {lsb}"
263
+
264
+ @staticmethod
265
+ def _calc_lsb(lsb):
266
+ """Convert 2^-2, style lsbs to numericals."""
267
+ if isinstance(lsb, str):
268
+ if lsb == '-':
269
+ return 1
270
+ shift = re.match(r'(\d+)\^([\-+0-9]+)', lsb)
271
+ if shift is not None:
272
+ lsb_num = pow(int(shift.group(1)), int(shift.group(2)))
273
+ else:
274
+ lsb_num = float(lsb)
275
+ return lsb_num
276
+ return lsb
277
+
278
+ def _gen_record_layouts_data(self):
279
+ """Generate record layouts."""
280
+ self._rec_layouts = {}
281
+ for var, data in self._var_dd['vars'].items():
282
+ if data.get('a2l_data') is not None:
283
+ a2l_unit = a2l_type(data['var']['type'])
284
+ # if calibration data has a suffix of _x of _y it is a axis_pts
285
+ srch = re.search('_[xyXY]$', var)
286
+ if srch is not None:
287
+ name = a2l_unit + "_X_INCR_DIRECT"
288
+ self._rec_layouts[name] = f"AXIS_PTS_X 1 {a2l_unit} INDEX_INCR DIRECT"
289
+ data['rec_layout'] = name
290
+ else:
291
+ name = a2l_type(data['var']['type']) + "_COL_DIRECT"
292
+ self._rec_layouts[name] = f"FNC_VALUES 1 {a2l_unit} COLUMN_DIR DIRECT"
293
+ data['rec_layout'] = name
294
+
295
+ def _get_inpq_data(self, inp_quant):
296
+ """Get the necessary InputQuantity parameters."""
297
+ if inp_quant is not None:
298
+ if inp_quant in self._var_dd['vars']:
299
+ return inp_quant
300
+ return 'NO_INPUT_QUANTITY'
301
+
302
+ # Bosh template
303
+ _meas_tmplt = Template("""
304
+ /begin MEASUREMENT
305
+ $Name /* Name */
306
+ "$LongIdent" /* LongIdentifier */
307
+ $Datatype /* Datatype */
308
+ $Conversion /* Conversion */
309
+ 1 /* Resolution */
310
+ 0 /* Accuracy */
311
+ $LowerLimit /* LowerLimit */
312
+ $UpperLimit /* UpperLimit */
313
+ $OptionalData
314
+ ECU_ADDRESS 0x00000000
315
+ /end MEASUREMENT
316
+ """)
317
+
318
+ # Denso template
319
+ _meas_tmplt_nvm = Template("""
320
+ /begin MEASUREMENT
321
+ $Name /* Name */
322
+ "$LongIdent" /* LongIdentifier */
323
+ $Datatype /* Datatype */
324
+ $Conversion /* Conversion */
325
+ 1 /* Resolution */
326
+ 0 /* Accuracy */
327
+ $LowerLimit /* LowerLimit */
328
+ $UpperLimit /* UpperLimit */
329
+ $OptionalData
330
+ /end MEASUREMENT
331
+ """)
332
+
333
+ def _gen_a2l_measurement_blk(self, var_name, data):
334
+ """Generate an a2l MEASUREMENT block."""
335
+ opt_data = 'READ_WRITE'
336
+ a2d = data.get('a2l_data')
337
+ if a2d is not None:
338
+ c_type = data['var']['type']
339
+ # if c_type == 'Bool':
340
+ # opt_data += '\n' + ' ' * 8 + "BIT_MASK 0x1"
341
+ if a2d.get('bitmask') is not None:
342
+ opt_data += '\n' + ' ' * 8 + "BIT_MASK %s" % a2d['bitmask']
343
+ if data.get('array'):
344
+ opt_data += '\n' + ' ' * 8 + \
345
+ self._array_to_a2l_string(data['array'])
346
+
347
+ use_symbol_links = self._prj_cfg.get_code_generation_config(item='useA2lSymbolLinks')
348
+ if a2d.get('symbol'):
349
+ if use_symbol_links:
350
+ opt_data += '\n' + ' ' * 8 + 'SYMBOL_LINK "%s" %s' % (a2d['symbol'], a2d.get('symbol_offset'))
351
+ LOG.debug('This a2l is using SYMBOL_LINK for %s', opt_data)
352
+ else:
353
+ var_name = a2d['symbol'] + '._' + var_name
354
+ LOG.debug('This a2l is not using SYMBOL_LINK for %s', var_name)
355
+
356
+ dtype = a2l_type(c_type)
357
+ minlim, maxlim = self._get_a2d_minmax(a2d, c_type)
358
+ conv = self._compu_meths[data['compu_meth']]['name']
359
+
360
+ if a2d.get('symbol') and use_symbol_links:
361
+ res = self._meas_tmplt_nvm.substitute(Name=var_name,
362
+ LongIdent=a2d['description'].replace('"', '\\"'),
363
+ Datatype=dtype,
364
+ Conversion=conv,
365
+ LowerLimit=minlim,
366
+ UpperLimit=maxlim,
367
+ OptionalData=opt_data)
368
+ else:
369
+ res = self._meas_tmplt.substitute(Name=var_name,
370
+ LongIdent=a2d['description'].replace('"', '\\"'),
371
+ Datatype=dtype,
372
+ Conversion=conv,
373
+ LowerLimit=minlim,
374
+ UpperLimit=maxlim,
375
+ OptionalData=opt_data)
376
+ return res
377
+ return None
378
+
379
+ _char_tmplt = Template("""
380
+ /begin CHARACTERISTIC
381
+ $Name /* Name */
382
+ "$LongIdent" /* LongIdentifier */
383
+ $Type /* Datatype */
384
+ 0x00000000 /* address: $Name */
385
+ $Deposit /* Deposit */
386
+ 0 /* MaxDiff */
387
+ $Conversion /* Conversion */
388
+ $LowerLimit /* LowerLimit */
389
+ $UpperLimit /* UpperLimit */$OptionalData
390
+ /end CHARACTERISTIC
391
+ """)
392
+
393
+ def _gen_a2l_characteristic_blk(self, var, data):
394
+ """Generate an a2l CHARACTERISTIC block."""
395
+ opt_data = ''
396
+ a2d = data.get('a2l_data')
397
+ type_ = 'WRONG_TYPE'
398
+ if a2d is not None:
399
+ arr = data.get('array')
400
+ if arr is not None:
401
+ arr_dim = len(arr)
402
+ else:
403
+ arr_dim = 0
404
+ # Check is axis_pts are defined for the axis, if not make the
405
+ # type a VAL_BLK with matrix dimension, otherwise set
406
+ # a CURVE or MAP type
407
+
408
+ # If arr_dim is 0 the CHARACTERISTIC is a value
409
+ if arr_dim == 0:
410
+ type_ = 'VALUE'
411
+ elif arr_dim == 1:
412
+ x_axis_name = var + '_x'
413
+ # Check if axis variable is defined
414
+ if x_axis_name in self._var_dd['vars'].keys():
415
+ type_ = 'CURVE'
416
+ opt_data += self._gen_a2l_axis_desc_blk(self._get_inpq_data(a2d.get('x_axis')),
417
+ x_axis_name)
418
+ else:
419
+ type_ = 'VAL_BLK'
420
+ opt_data += self._array_to_a2l_string(data['array'])
421
+ elif arr_dim == 2:
422
+ x_axis_name = var + '_x'
423
+ y_axis_name = var + '_y'
424
+ # Check if axis variable is defined
425
+ nbr_def_axis = 0
426
+ if x_axis_name in self._var_dd['vars'].keys():
427
+ nbr_def_axis += 1
428
+ if y_axis_name in self._var_dd['vars'].keys():
429
+ nbr_def_axis += 1
430
+ if nbr_def_axis == 2:
431
+ type_ = 'MAP'
432
+ inpq_x = self._get_inpq_data(a2d['x_axis'])
433
+ opt_data += self._gen_a2l_axis_desc_blk(inpq_x, x_axis_name)
434
+ inpq_y = self._get_inpq_data(a2d['y_axis'])
435
+ opt_data += self._gen_a2l_axis_desc_blk(inpq_y, y_axis_name)
436
+ elif nbr_def_axis == 0:
437
+ type_ = 'VAL_BLK'
438
+ opt_data += self._array_to_a2l_string(data['array'])
439
+ else:
440
+ self.warning(
441
+ 'MAP %s has only one AXIS_PTS defined, shall be none or two', var)
442
+
443
+ minlim, maxlim = self._get_a2d_minmax(a2d)
444
+
445
+ res = self._char_tmplt.substitute(Name=var,
446
+ LongIdent=a2d['description'].replace('"', '\\"'),
447
+ Type=type_,
448
+ Deposit=data['rec_layout'],
449
+ Conversion=self._compu_meths[data['compu_meth']]['name'],
450
+ LowerLimit=minlim,
451
+ UpperLimit=maxlim,
452
+ OptionalData=opt_data)
453
+ return res
454
+ self.warning("%s has no A2L-data", var)
455
+ return None
456
+ # Types ASCII CURVE MAP VAL_BLK VALUE
457
+
458
+ _axis_desc_tmplt = Template("""
459
+ /begin AXIS_DESCR
460
+ COM_AXIS /* Attribute */
461
+ $inp_quant /* InputQuantity */
462
+ $conv /* Conversion */
463
+ $maxaxispts /* MaxAxisPoints */
464
+ $minlim /* LowerLimit */
465
+ $maxlim /* UpperLimit */
466
+ AXIS_PTS_REF $axis_pts_ref
467
+ DEPOSIT ABSOLUTE
468
+ /end AXIS_DESCR""")
469
+
470
+ def _gen_a2l_axis_desc_blk(self, inp_quant, axis_pts_ref):
471
+ """Generate an a2l AXIS_DESCR block.
472
+
473
+ TODO: Check that the AXIS_PTS_REF blocks are defined
474
+ """
475
+ out = ''
476
+ inp_quant_txt = self._get_inpq_data(inp_quant)
477
+ axis_pts = self._var_dd['vars'][axis_pts_ref]
478
+ conv = self._compu_meths[axis_pts['compu_meth']]['name']
479
+ max_axis_pts = axis_pts['array'][0]
480
+ min_lim, max_lim = self._get_a2d_minmax(axis_pts['a2l_data'])
481
+ out += self._axis_desc_tmplt.substitute(inp_quant=inp_quant_txt,
482
+ conv=conv,
483
+ maxaxispts=max_axis_pts,
484
+ minlim=min_lim,
485
+ maxlim=max_lim,
486
+ axis_pts_ref=axis_pts_ref)
487
+ return out
488
+
489
+ _compu_meth_tmplt = Template("""
490
+ /begin COMPU_METHOD
491
+ $name /* Name */
492
+ "$longident" /* LongIdentifier */
493
+ RAT_FUNC /* ConversionType */
494
+ "$format" /* Format */
495
+ "$unit" /* Unit */
496
+ $coeffs
497
+ /end COMPU_METHOD
498
+ """)
499
+
500
+ def _gen_a2l_compu_metod_blk(self, key):
501
+ """Generate an a2l COMPU_METHOD block."""
502
+ cmeth = self._compu_meths[key]
503
+ name = self._compu_key_2_name(key)
504
+ out = self._compu_meth_tmplt.substitute(name=name,
505
+ longident='',
506
+ format='%11.3',
507
+ unit=key[2],
508
+ coeffs=cmeth['coeffs'])
509
+ return out
510
+
511
+ _axis_tmplt = Template("""
512
+ /begin AXIS_PTS
513
+ $name /* Name */
514
+ "$longident" /* LongIdentifier */
515
+ 0x00000000
516
+ NO_INPUT_QUANTITY /* InputQuantity */
517
+ $deposit /* Deposit */
518
+ 0 /* MaxDiff */
519
+ $convert /* Conversion */
520
+ $max_ax_pts /* MaxAxisPoints */
521
+ $minlim /* LowerLimit */
522
+ $maxlim /* UpperLimit */
523
+ DEPOSIT ABSOLUTE
524
+ /end AXIS_PTS
525
+ """)
526
+
527
+ def _gen_a2l_axis_pts_blk(self, var, data):
528
+ """Generate an a2l AXIS_PTS block."""
529
+ deposit = data['rec_layout']
530
+ conv = self._compu_meths[data['compu_meth']]['name']
531
+ max_axis_pts = data['array'][0]
532
+ min_lim, max_lim = self._get_a2d_minmax(data['a2l_data'])
533
+ out = self._axis_tmplt.substitute(name=var,
534
+ longident=data['a2l_data']['description'],
535
+ deposit=deposit,
536
+ convert=conv,
537
+ max_ax_pts=max_axis_pts,
538
+ minlim=min_lim,
539
+ maxlim=max_lim)
540
+ return out
541
+
542
+ _rec_layout_tmplt = Template("""
543
+ /begin RECORD_LAYOUT
544
+ $name /* Name */
545
+ $string
546
+ /end RECORD_LAYOUT
547
+ """)
548
+
549
+ def _gen_a2l_rec_layout_blk(self, key):
550
+ """Generate an a2l AXIS_PTS block."""
551
+ string = self._rec_layouts[key]
552
+ out = self._rec_layout_tmplt.substitute(name=key,
553
+ string=string)
554
+ return out
555
+
556
+ def _gen_a2l_function_blk(self):
557
+ """Generate an a2l FUNCTION block."""
558
+ out = '\n /begin FUNCTION\n'
559
+ out += f' {self._var_dd["function"]} /* Name */\n'
560
+ out += ' "" /* LongIdentifier */\n'
561
+ if self._fnc_char:
562
+ out += ' /begin DEF_CHARACTERISTIC\n'
563
+ for idf in self._fnc_char:
564
+ out += f' {idf} /* Identifier */\n'
565
+ out += ' /end DEF_CHARACTERISTIC\n'
566
+ if self._fnc_inputs:
567
+ out += ' /begin IN_MEASUREMENT\n'
568
+ for idf in self._fnc_inputs:
569
+ out += f' {idf} /* Identifier */\n'
570
+ out += ' /end IN_MEASUREMENT\n'
571
+ if self._fnc_locals:
572
+ out += ' /begin LOC_MEASUREMENT\n'
573
+ for idf in self._fnc_locals:
574
+ out += f' {idf} /* Identifier */\n'
575
+ out += ' /end LOC_MEASUREMENT\n'
576
+ if self._fnc_outputs:
577
+ out += ' /begin OUT_MEASUREMENT\n'
578
+ for idf in self._fnc_outputs:
579
+ out += f' {idf} /* Identifier */\n'
580
+ out += ' /end OUT_MEASUREMENT\n'
581
+ out += ' /end FUNCTION\n'
582
+ return out