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,362 +1,362 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- """Python module used for handling zone controller specifications"""
5
- from ruamel.yaml import YAML
6
- from powertrain_build.lib import logger
7
- from powertrain_build.interface.base import BaseApplication
8
-
9
- LOGGER = logger.create_logger("base")
10
-
11
-
12
- class BadYamlFormat(Exception):
13
- """Exception to raise when in/out signal is not defined."""
14
- def __init__(self, message):
15
- self.message = message
16
-
17
-
18
- class ZCAL(BaseApplication):
19
- """Zone controller abstraction layer"""
20
-
21
- def __repr__(self):
22
- """String representation of ZCAL"""
23
- return (
24
- f"<ZCAL {self.name}"
25
- f" app_side insignals: {len(self.signal_names['other']['insignals'])}"
26
- f" app_side outsignals: {len(self.signal_names['other']['outsignals'])}>"
27
- )
28
-
29
- def __init__(self, base_application):
30
- """Create the interface object
31
-
32
- Currently, there is no verification that the signal is used.
33
-
34
- TODO: Check if the signal is a set or a get somewhere. Maybe in here.
35
-
36
- Args:
37
- base_application (BaseApplication): Primary object of an interface
38
- Usually a raster, but can be an application or a model too.
39
- """
40
- self.name = ""
41
- self.zc_translations = {}
42
- self.composition_spec = {}
43
- self.signal_names = {}
44
- self.e2e_sts_signals = set()
45
- self.update_bit_signals = set()
46
- self.clear_signal_names()
47
- self.interface_enums = set()
48
- self.base_application = base_application
49
- self.translations_files = []
50
- self.device_domain = base_application.get_domain_mapping()
51
- self.signal_primitives_list = set()
52
-
53
- @staticmethod
54
- def read_translation_files(translation_files, keys=None):
55
- """ Searches translation files (yaml) for given keys at top level. Raises an
56
- error if conflicting configurations are found among the files. Return a dict
57
- containing the aggregated data found in the translation files for the keys
58
- provided in the function call.
59
-
60
- Args:
61
- translation_files (list): List of paths to files to search.
62
- keys (list): List of keys to search among translation files for (all present keys by default).
63
-
64
- Returns:
65
- specs (dict): Dictionary containing provided keys with the aggregated data
66
- stored under those keys in translation files.
67
- """
68
-
69
- specs = {}
70
- for translation_file in translation_files:
71
- with open(translation_file, encoding="utf-8") as translation:
72
- yaml = YAML(typ='safe', pure=True)
73
- raw = yaml.load(translation)
74
- used_keys = raw.keys() if keys is None else keys
75
- for key in used_keys:
76
- new_elements = raw.get(key, None)
77
- if isinstance(new_elements, list):
78
- specs[key] = specs.get(key, []) + new_elements
79
- elif isinstance(new_elements, dict):
80
- specs[key] = specs.get(key, {})
81
- if {**specs[key], **new_elements} != {**new_elements, **specs[key]}:
82
- LOGGER.error(
83
- "Conflicting configuration found for key '%s' among translation files: %s",
84
- key,
85
- translation_files,
86
- )
87
- specs[key].update(new_elements)
88
- return specs
89
-
90
- def add_signals(self, signals, signal_type="insignal", *args):
91
- """Add signal names and properties
92
-
93
- Args:
94
- signals (list(Signals)): Signals to use
95
- signal_type (str): 'insignals' or 'outsignals'
96
- """
97
- for signal in signals:
98
- if signal.name not in self.zc_translations:
99
- continue
100
- LOGGER.debug("Adding signal: %s", signal)
101
- for translation in self.zc_translations[signal.name]:
102
- signal_property = translation["property"]
103
- port_name = translation["port_name"]
104
- struct_name = translation["struct_name"]
105
- self.check_signal_property(port_name, struct_name, signal_property, signal_type)
106
- self.signal_names["zc"][signal_type].add(signal_property)
107
- self.signal_names["other"][signal_type].add(signal.name)
108
- for e2e_sts_signal_name in self.e2e_sts_signals:
109
- if e2e_sts_signal_name not in self.signal_names["other"]["insignals"]:
110
- LOGGER.warning("E2E check signal %s not used in any model.", e2e_sts_signal_name)
111
- else:
112
- self.signal_names["other"][signal_type].add(e2e_sts_signal_name)
113
- for update_bit_signal_name in self.update_bit_signals:
114
- if update_bit_signal_name not in self.signal_names["other"]["insignals"]:
115
- LOGGER.warning("Update bit signal %s not used in any model.", update_bit_signal_name)
116
- else:
117
- self.signal_names["other"][signal_type].add(update_bit_signal_name)
118
- LOGGER.debug("Registered signal names: %s", self.signal_names)
119
-
120
- def check_signal_property(self, port_name, struct_name, property_name, signal_type):
121
- """Check if we have only one signal written for the same property.
122
-
123
- Args:
124
- port_name (str): port name
125
- struct_name (str): signal struct name
126
- property_name (str): signal property
127
- signal_type (str): insignal or outsignal
128
- """
129
- signal_primitive_spec = ".".join([port_name, struct_name, property_name, signal_type])
130
- if signal_primitive_spec in self.signal_primitives_list and signal_type == "outsignal":
131
- error_msg = (f"You can't write {property_name} in {struct_name} as"
132
- f" {signal_type} since this primitive has been used.")
133
- raise Exception(error_msg)
134
- self.signal_primitives_list.add(signal_primitive_spec)
135
-
136
- def parse_definition(self, definition):
137
- """Parses all translation files and populates class interface data.
138
-
139
- Args:
140
- definition (list(Path)): Definition files
141
- """
142
- raw = self.read_translation_files(definition)
143
- self.composition_spec = {
144
- key: value for key, value in raw.items() if key not in ["ports"]
145
- }
146
- ports_info = {}
147
- for port_name, port in raw.get("ports", {}).items():
148
- ports_info[port_name] = {k: v for k, v in port.items() if k not in ["element"]}
149
- signal_struct = port.get("element", {})
150
- direction = port.get("direction", None)
151
- if signal_struct:
152
- for element_name, element_data in signal_struct.items():
153
- self.populate_signal_translations(port_name, element_name, element_data)
154
- # Loop inside function to make sure direction is consistent
155
- ports_info[port_name].update(self.get_port_info(port_name, direction, signal_struct))
156
- self.composition_spec["ports"] = ports_info
157
-
158
- @staticmethod
159
- def get_port_info(port_name, direction, signal_struct):
160
- """Extract port information from signal elements in port. Raises exception
161
- if signal elements are not exclusively sent in one direction.
162
-
163
- Args:
164
- port_name (str): Name of the port.
165
- direction (str): Direction of the port.
166
- signal_struct (dict): Signal dict containing list of signal elements.
167
- Returns:
168
- port_info (dict): Dict containing information if any elements
169
- should have an update bit associated with them.
170
- """
171
- if direction is None:
172
- raise BadYamlFormat(f'Port {port_name} is missing required property "direction".')
173
-
174
- port_info = {}
175
- update_elements = []
176
- end_to_end_elements = {}
177
- for element_name, element_data in signal_struct.items():
178
- for element in element_data:
179
- if "insignal" in element:
180
- temp_dir = "IN"
181
- elif "outsignal" in element:
182
- temp_dir = "OUT"
183
- else:
184
- raise BadYamlFormat(f"in-/out-signal for element in { element_name } is missing.")
185
- # CLIENT and SERVER direction support both in and out signals
186
- if direction not in ["CLIENT", "SERVER"] and direction != temp_dir:
187
- raise BadYamlFormat(f"Signal { element_name } has both in and out elements.")
188
- if element.get("updateBit", False) and element_name not in update_elements:
189
- update_elements.append(element_name)
190
- if element.get("e2eStatus", False) and element_name not in end_to_end_elements:
191
- end_to_end_elements[element_name] = {}
192
-
193
- if update_elements:
194
- port_info.update({"enable_update": update_elements})
195
- if end_to_end_elements:
196
- port_info.update({"end_to_end": [{k: v} for k, v in end_to_end_elements.items()]})
197
- return port_info
198
-
199
- def populate_signal_translations(self, port_name, element_name, element_data):
200
- """Populate class translations data.
201
-
202
- Args:
203
- port_name (str): Port name.
204
- element_name (str): Name of a port element.
205
- element_data (dict): Dict with signal structs to/from a port of an element.
206
- """
207
- enumerations = self.base_application.enumerations
208
-
209
- for signal_definition in element_data:
210
- if "insignal" in signal_definition:
211
- signal_name = signal_definition["insignal"]
212
- base_signals = self.base_application.insignals
213
- elif "outsignal" in signal_definition:
214
- signal_name = signal_definition["outsignal"]
215
- base_signals = self.base_application.outsignals
216
- else:
217
- raise BadYamlFormat(f"{ signal_name } is neither insignal nor outsignal in interface yaml file.")
218
- base_properties = None
219
- for base_signal in base_signals:
220
- if signal_name == base_signal.name:
221
- matching_base_signal = base_signal
222
- base_properties = self.base_application.get_signal_properties(
223
- matching_base_signal
224
- )
225
- if base_properties is None:
226
- raise BadYamlFormat(
227
- f"{ signal_name } properties is missing. "
228
- "Make sure that the model is in the raster file and that it is active in the current project."
229
- )
230
-
231
- if base_properties["type"] in enumerations:
232
- if 'init' in signal_definition:
233
- init_value = signal_definition["init"]
234
- else:
235
- if enumerations[base_properties['type']]['default_value'] is not None:
236
- init_value = enumerations[base_properties['type']]['default_value']
237
- else:
238
- LOGGER.warning('Initializing enumeration %s to "zero".', base_properties['type'])
239
- init_value = [
240
- k for k, v in enumerations[base_properties['type']]['members'].items() if v == 0
241
- ][0]
242
- else:
243
- init_value = signal_definition.get("init", 0)
244
-
245
- update_bit = signal_definition.get("updateBit", False)
246
- e2e_status = signal_definition.get("e2eStatus", False)
247
- group = signal_definition.get("group", element_name)
248
- translation = {
249
- "range": {
250
- "min": base_properties.get("min", "-"),
251
- "max": base_properties.get("max", "-")
252
- },
253
- "offset": base_properties.get("offset", "-"),
254
- "factor": base_properties.get("factor", "-"),
255
- "property": signal_definition["property"],
256
- "init": init_value,
257
- "port_name": port_name,
258
- "struct_name": element_name,
259
- "variable_type": base_properties.get("type"),
260
- "description": base_properties.get("description"),
261
- "unit": base_properties.get("unit"),
262
- "width": base_properties.get("width"),
263
- "debug": base_properties.get("debug", False),
264
- "dependability": e2e_status,
265
- "update_bit": update_bit
266
- }
267
-
268
- if signal_name not in self.zc_translations:
269
- self.zc_translations[signal_name] = []
270
- self.zc_translations[signal_name].append(translation)
271
- if update_bit:
272
- update_bit_property = f"{element_name}UpdateBit"
273
- update_signal_name = f"yVc{group}_B_{update_bit_property}"
274
- if signal_name == update_signal_name:
275
- error_msg = f"Don't put updateBit status signals ({update_signal_name}) in yaml interface files."
276
- raise BadYamlFormat(error_msg)
277
- self.zc_translations[update_signal_name] = [
278
- {
279
- "property": update_bit_property,
280
- "variable_type": "Bool",
281
- "property_interface_type": "Bool",
282
- "offset": "-",
283
- "factor": "-",
284
- "range": {
285
- "min": "-",
286
- "max": "-",
287
- },
288
- "init": 1,
289
- "port_name": port_name,
290
- "struct_name": element_name,
291
- "description": f"Update bit signal for signal {element_name}.",
292
- "unit": None,
293
- "dependability": False,
294
- "update_bit": True
295
- }
296
- ]
297
- self.update_bit_signals.add(update_signal_name)
298
-
299
- if e2e_status:
300
- if "outsignal" in signal_definition:
301
- error_msg = "E2e status not expected for outsignals"
302
- raise BadYamlFormat(error_msg)
303
-
304
- e2e_sts_property = f"{element_name}E2eSts"
305
- e2e_sts_signal_name = f"sVc{group}_D_{e2e_sts_property}"
306
-
307
- if signal_name == e2e_sts_signal_name:
308
- error_msg = f"Don't put E2E status signals ({e2e_sts_signal_name}) in yaml interface files."
309
- raise BadYamlFormat(error_msg)
310
- if e2e_sts_signal_name not in self.zc_translations:
311
- self.zc_translations[e2e_sts_signal_name] = [
312
- {
313
- "property": e2e_sts_property,
314
- "variable_type": "UInt8",
315
- "property_interface_type": "UInt8",
316
- "offset": 0,
317
- "factor": 1,
318
- "range": {
319
- "min": 0,
320
- "max": 255
321
- },
322
- "init": 255,
323
- "port_name": port_name,
324
- "struct_name": element_name,
325
- "description": f"E2E status code for E2E protected signal(s) {signal_name}.",
326
- "unit": None,
327
- "debug": False,
328
- "dependability": True,
329
- "update_bit": False
330
- }
331
- ]
332
- self.e2e_sts_signals.add(e2e_sts_signal_name)
333
- else:
334
- translation = self.zc_translations[e2e_sts_signal_name][0]
335
- translation["description"] = translation["description"][:-1] + f", {signal_name}."
336
-
337
- def clear_signal_names(self):
338
- """Clear signal names
339
-
340
- Clears defined signal names (but not signal properties).
341
- """
342
- self.signal_names = {
343
- "zc": {"insignals": set(), "outsignals": set()},
344
- "other": {"insignals": set(), "outsignals": set()}
345
- }
346
-
347
- def to_dict(self):
348
- """Method to generate dict to be saved as yaml
349
-
350
- Returns:
351
- spec (dict): Signalling specification
352
- """
353
- spec = {"consumer": [], "producer": []}
354
-
355
- signal_roles = ["consumer", "producer"]
356
- signal_types = ["insignals", "outsignals"]
357
- for signal_role, signal_type in zip(signal_roles, signal_types):
358
- for signal_name in self.signal_names["other"][signal_type]:
359
- for signal_spec in self.zc_translations[signal_name]:
360
- spec[signal_role].append({**signal_spec, "variable": signal_name})
361
-
362
- return spec
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ """Python module used for handling zone controller specifications"""
5
+ from ruamel.yaml import YAML
6
+ from powertrain_build.lib import logger
7
+ from powertrain_build.interface.base import BaseApplication
8
+
9
+ LOGGER = logger.create_logger("base")
10
+
11
+
12
+ class BadYamlFormat(Exception):
13
+ """Exception to raise when in/out signal is not defined."""
14
+ def __init__(self, message):
15
+ self.message = message
16
+
17
+
18
+ class ZCAL(BaseApplication):
19
+ """Zone controller abstraction layer"""
20
+
21
+ def __repr__(self):
22
+ """String representation of ZCAL"""
23
+ return (
24
+ f"<ZCAL {self.name}"
25
+ f" app_side insignals: {len(self.signal_names['other']['insignals'])}"
26
+ f" app_side outsignals: {len(self.signal_names['other']['outsignals'])}>"
27
+ )
28
+
29
+ def __init__(self, base_application):
30
+ """Create the interface object
31
+
32
+ Currently, there is no verification that the signal is used.
33
+
34
+ TODO: Check if the signal is a set or a get somewhere. Maybe in here.
35
+
36
+ Args:
37
+ base_application (BaseApplication): Primary object of an interface
38
+ Usually a raster, but can be an application or a model too.
39
+ """
40
+ self.name = ""
41
+ self.zc_translations = {}
42
+ self.composition_spec = {}
43
+ self.signal_names = {}
44
+ self.e2e_sts_signals = set()
45
+ self.update_bit_signals = set()
46
+ self.clear_signal_names()
47
+ self.interface_enums = set()
48
+ self.base_application = base_application
49
+ self.translations_files = []
50
+ self.device_domain = base_application.get_domain_mapping()
51
+ self.signal_primitives_list = set()
52
+
53
+ @staticmethod
54
+ def read_translation_files(translation_files, keys=None):
55
+ """ Searches translation files (yaml) for given keys at top level. Raises an
56
+ error if conflicting configurations are found among the files. Return a dict
57
+ containing the aggregated data found in the translation files for the keys
58
+ provided in the function call.
59
+
60
+ Args:
61
+ translation_files (list): List of paths to files to search.
62
+ keys (list): List of keys to search among translation files for (all present keys by default).
63
+
64
+ Returns:
65
+ specs (dict): Dictionary containing provided keys with the aggregated data
66
+ stored under those keys in translation files.
67
+ """
68
+
69
+ specs = {}
70
+ for translation_file in translation_files:
71
+ with open(translation_file, encoding="utf-8") as translation:
72
+ yaml = YAML(typ='safe', pure=True)
73
+ raw = yaml.load(translation)
74
+ used_keys = raw.keys() if keys is None else keys
75
+ for key in used_keys:
76
+ new_elements = raw.get(key, None)
77
+ if isinstance(new_elements, list):
78
+ specs[key] = specs.get(key, []) + new_elements
79
+ elif isinstance(new_elements, dict):
80
+ specs[key] = specs.get(key, {})
81
+ if {**specs[key], **new_elements} != {**new_elements, **specs[key]}:
82
+ LOGGER.error(
83
+ "Conflicting configuration found for key '%s' among translation files: %s",
84
+ key,
85
+ translation_files,
86
+ )
87
+ specs[key].update(new_elements)
88
+ return specs
89
+
90
+ def add_signals(self, signals, signal_type="insignal", *args):
91
+ """Add signal names and properties
92
+
93
+ Args:
94
+ signals (list(Signals)): Signals to use
95
+ signal_type (str): 'insignals' or 'outsignals'
96
+ """
97
+ for signal in signals:
98
+ if signal.name not in self.zc_translations:
99
+ continue
100
+ LOGGER.debug("Adding signal: %s", signal)
101
+ for translation in self.zc_translations[signal.name]:
102
+ signal_property = translation["property"]
103
+ port_name = translation["port_name"]
104
+ struct_name = translation["struct_name"]
105
+ self.check_signal_property(port_name, struct_name, signal_property, signal_type)
106
+ self.signal_names["zc"][signal_type].add(signal_property)
107
+ self.signal_names["other"][signal_type].add(signal.name)
108
+ for e2e_sts_signal_name in self.e2e_sts_signals:
109
+ if e2e_sts_signal_name not in self.signal_names["other"]["insignals"]:
110
+ LOGGER.warning("E2E check signal %s not used in any model.", e2e_sts_signal_name)
111
+ else:
112
+ self.signal_names["other"][signal_type].add(e2e_sts_signal_name)
113
+ for update_bit_signal_name in self.update_bit_signals:
114
+ if update_bit_signal_name not in self.signal_names["other"]["insignals"]:
115
+ LOGGER.warning("Update bit signal %s not used in any model.", update_bit_signal_name)
116
+ else:
117
+ self.signal_names["other"][signal_type].add(update_bit_signal_name)
118
+ LOGGER.debug("Registered signal names: %s", self.signal_names)
119
+
120
+ def check_signal_property(self, port_name, struct_name, property_name, signal_type):
121
+ """Check if we have only one signal written for the same property.
122
+
123
+ Args:
124
+ port_name (str): port name
125
+ struct_name (str): signal struct name
126
+ property_name (str): signal property
127
+ signal_type (str): insignal or outsignal
128
+ """
129
+ signal_primitive_spec = ".".join([port_name, struct_name, property_name, signal_type])
130
+ if signal_primitive_spec in self.signal_primitives_list and signal_type == "outsignal":
131
+ error_msg = (f"You can't write {property_name} in {struct_name} as"
132
+ f" {signal_type} since this primitive has been used.")
133
+ raise Exception(error_msg)
134
+ self.signal_primitives_list.add(signal_primitive_spec)
135
+
136
+ def parse_definition(self, definition):
137
+ """Parses all translation files and populates class interface data.
138
+
139
+ Args:
140
+ definition (list(Path)): Definition files
141
+ """
142
+ raw = self.read_translation_files(definition)
143
+ self.composition_spec = {
144
+ key: value for key, value in raw.items() if key not in ["ports"]
145
+ }
146
+ ports_info = {}
147
+ for port_name, port in raw.get("ports", {}).items():
148
+ ports_info[port_name] = {k: v for k, v in port.items() if k not in ["element"]}
149
+ signal_struct = port.get("element", {})
150
+ direction = port.get("direction", None)
151
+ if signal_struct:
152
+ for element_name, element_data in signal_struct.items():
153
+ self.populate_signal_translations(port_name, element_name, element_data)
154
+ # Loop inside function to make sure direction is consistent
155
+ ports_info[port_name].update(self.get_port_info(port_name, direction, signal_struct))
156
+ self.composition_spec["ports"] = ports_info
157
+
158
+ @staticmethod
159
+ def get_port_info(port_name, direction, signal_struct):
160
+ """Extract port information from signal elements in port. Raises exception
161
+ if signal elements are not exclusively sent in one direction.
162
+
163
+ Args:
164
+ port_name (str): Name of the port.
165
+ direction (str): Direction of the port.
166
+ signal_struct (dict): Signal dict containing list of signal elements.
167
+ Returns:
168
+ port_info (dict): Dict containing information if any elements
169
+ should have an update bit associated with them.
170
+ """
171
+ if direction is None:
172
+ raise BadYamlFormat(f'Port {port_name} is missing required property "direction".')
173
+
174
+ port_info = {}
175
+ update_elements = []
176
+ end_to_end_elements = {}
177
+ for element_name, element_data in signal_struct.items():
178
+ for element in element_data:
179
+ if "insignal" in element:
180
+ temp_dir = "IN"
181
+ elif "outsignal" in element:
182
+ temp_dir = "OUT"
183
+ else:
184
+ raise BadYamlFormat(f"in-/out-signal for element in { element_name } is missing.")
185
+ # CLIENT and SERVER direction support both in and out signals
186
+ if direction not in ["CLIENT", "SERVER"] and direction != temp_dir:
187
+ raise BadYamlFormat(f"Signal { element_name } has both in and out elements.")
188
+ if element.get("updateBit", False) and element_name not in update_elements:
189
+ update_elements.append(element_name)
190
+ if element.get("e2eStatus", False) and element_name not in end_to_end_elements:
191
+ end_to_end_elements[element_name] = {}
192
+
193
+ if update_elements:
194
+ port_info.update({"enable_update": update_elements})
195
+ if end_to_end_elements:
196
+ port_info.update({"end_to_end": [{k: v} for k, v in end_to_end_elements.items()]})
197
+ return port_info
198
+
199
+ def populate_signal_translations(self, port_name, element_name, element_data):
200
+ """Populate class translations data.
201
+
202
+ Args:
203
+ port_name (str): Port name.
204
+ element_name (str): Name of a port element.
205
+ element_data (dict): Dict with signal structs to/from a port of an element.
206
+ """
207
+ enumerations = self.base_application.enumerations
208
+
209
+ for signal_definition in element_data:
210
+ if "insignal" in signal_definition:
211
+ signal_name = signal_definition["insignal"]
212
+ base_signals = self.base_application.insignals
213
+ elif "outsignal" in signal_definition:
214
+ signal_name = signal_definition["outsignal"]
215
+ base_signals = self.base_application.outsignals
216
+ else:
217
+ raise BadYamlFormat(f"{ signal_name } is neither insignal nor outsignal in interface yaml file.")
218
+ base_properties = None
219
+ for base_signal in base_signals:
220
+ if signal_name == base_signal.name:
221
+ matching_base_signal = base_signal
222
+ base_properties = self.base_application.get_signal_properties(
223
+ matching_base_signal
224
+ )
225
+ if base_properties is None:
226
+ raise BadYamlFormat(
227
+ f"{ signal_name } properties is missing. "
228
+ "Make sure that the model is in the raster file and that it is active in the current project."
229
+ )
230
+
231
+ if base_properties["type"] in enumerations:
232
+ if 'init' in signal_definition:
233
+ init_value = signal_definition["init"]
234
+ else:
235
+ if enumerations[base_properties['type']]['default_value'] is not None:
236
+ init_value = enumerations[base_properties['type']]['default_value']
237
+ else:
238
+ LOGGER.warning('Initializing enumeration %s to "zero".', base_properties['type'])
239
+ init_value = [
240
+ k for k, v in enumerations[base_properties['type']]['members'].items() if v == 0
241
+ ][0]
242
+ else:
243
+ init_value = signal_definition.get("init", 0)
244
+
245
+ update_bit = signal_definition.get("updateBit", False)
246
+ e2e_status = signal_definition.get("e2eStatus", False)
247
+ group = signal_definition.get("group", element_name)
248
+ translation = {
249
+ "range": {
250
+ "min": base_properties.get("min", "-"),
251
+ "max": base_properties.get("max", "-")
252
+ },
253
+ "offset": base_properties.get("offset", "-"),
254
+ "factor": base_properties.get("factor", "-"),
255
+ "property": signal_definition["property"],
256
+ "init": init_value,
257
+ "port_name": port_name,
258
+ "struct_name": element_name,
259
+ "variable_type": base_properties.get("type"),
260
+ "description": base_properties.get("description"),
261
+ "unit": base_properties.get("unit"),
262
+ "width": base_properties.get("width"),
263
+ "debug": base_properties.get("debug", False),
264
+ "dependability": e2e_status,
265
+ "update_bit": update_bit
266
+ }
267
+
268
+ if signal_name not in self.zc_translations:
269
+ self.zc_translations[signal_name] = []
270
+ self.zc_translations[signal_name].append(translation)
271
+ if update_bit:
272
+ update_bit_property = f"{element_name}UpdateBit"
273
+ update_signal_name = f"yVc{group}_B_{update_bit_property}"
274
+ if signal_name == update_signal_name:
275
+ error_msg = f"Don't put updateBit status signals ({update_signal_name}) in yaml interface files."
276
+ raise BadYamlFormat(error_msg)
277
+ self.zc_translations[update_signal_name] = [
278
+ {
279
+ "property": update_bit_property,
280
+ "variable_type": "Bool",
281
+ "property_interface_type": "Bool",
282
+ "offset": "-",
283
+ "factor": "-",
284
+ "range": {
285
+ "min": "-",
286
+ "max": "-",
287
+ },
288
+ "init": 1,
289
+ "port_name": port_name,
290
+ "struct_name": element_name,
291
+ "description": f"Update bit signal for signal {element_name}.",
292
+ "unit": None,
293
+ "dependability": False,
294
+ "update_bit": True
295
+ }
296
+ ]
297
+ self.update_bit_signals.add(update_signal_name)
298
+
299
+ if e2e_status:
300
+ if "outsignal" in signal_definition:
301
+ error_msg = "E2e status not expected for outsignals"
302
+ raise BadYamlFormat(error_msg)
303
+
304
+ e2e_sts_property = f"{element_name}E2eSts"
305
+ e2e_sts_signal_name = f"sVc{group}_D_{e2e_sts_property}"
306
+
307
+ if signal_name == e2e_sts_signal_name:
308
+ error_msg = f"Don't put E2E status signals ({e2e_sts_signal_name}) in yaml interface files."
309
+ raise BadYamlFormat(error_msg)
310
+ if e2e_sts_signal_name not in self.zc_translations:
311
+ self.zc_translations[e2e_sts_signal_name] = [
312
+ {
313
+ "property": e2e_sts_property,
314
+ "variable_type": "UInt8",
315
+ "property_interface_type": "UInt8",
316
+ "offset": 0,
317
+ "factor": 1,
318
+ "range": {
319
+ "min": 0,
320
+ "max": 255
321
+ },
322
+ "init": 255,
323
+ "port_name": port_name,
324
+ "struct_name": element_name,
325
+ "description": f"E2E status code for E2E protected signal(s) {signal_name}.",
326
+ "unit": None,
327
+ "debug": False,
328
+ "dependability": True,
329
+ "update_bit": False
330
+ }
331
+ ]
332
+ self.e2e_sts_signals.add(e2e_sts_signal_name)
333
+ else:
334
+ translation = self.zc_translations[e2e_sts_signal_name][0]
335
+ translation["description"] = translation["description"][:-1] + f", {signal_name}."
336
+
337
+ def clear_signal_names(self):
338
+ """Clear signal names
339
+
340
+ Clears defined signal names (but not signal properties).
341
+ """
342
+ self.signal_names = {
343
+ "zc": {"insignals": set(), "outsignals": set()},
344
+ "other": {"insignals": set(), "outsignals": set()}
345
+ }
346
+
347
+ def to_dict(self):
348
+ """Method to generate dict to be saved as yaml
349
+
350
+ Returns:
351
+ spec (dict): Signalling specification
352
+ """
353
+ spec = {"consumer": [], "producer": []}
354
+
355
+ signal_roles = ["consumer", "producer"]
356
+ signal_types = ["insignals", "outsignals"]
357
+ for signal_role, signal_type in zip(signal_roles, signal_types):
358
+ for signal_name in self.signal_names["other"][signal_type]:
359
+ for signal_spec in self.zc_translations[signal_name]:
360
+ spec[signal_role].append({**signal_spec, "variable": signal_name})
361
+
362
+ return spec