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,490 +1,490 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- """Module for CSP API abstraction."""
5
-
6
- import enum
7
- from ruamel.yaml import YAML
8
- from abc import abstractmethod
9
- from powertrain_build.interface.base import BaseApplication, Signal
10
- from powertrain_build.lib import logger
11
-
12
- LOGGER = logger.create_logger("service")
13
-
14
-
15
- class MissingApi(Exception):
16
- """Exception to raise when api is missing"""
17
- def __init__(self, api, map_file):
18
- self.message = f"Api {api} missing from {map_file}"
19
-
20
-
21
- class BadYamlFormat(Exception):
22
- """Exception to raise when in/out signal is not defined."""
23
- def __init__(self, api, signal_name):
24
- self.message = f"Signal {signal_name} for {api} should be set as insignal or outsignal."
25
-
26
-
27
- class CspApi(BaseApplication):
28
- """Abstraction for HAL and SFW"""
29
- position = enum.Enum(
30
- 'Position',
31
- names=[
32
- "property_name",
33
- "property_type",
34
- "variable_type",
35
- "offset",
36
- "factor",
37
- "default",
38
- "length",
39
- "min",
40
- "max",
41
- "enum",
42
- "init",
43
- "description",
44
- "unit",
45
- "endpoint",
46
- "api",
47
- "variant",
48
- "strategy",
49
- "debug",
50
- "dependability"
51
- ]
52
- )
53
-
54
- def __init__(self, base_application, read_strategy='Always'):
55
- """Create the interface object
56
-
57
- Args:
58
- base_application (BaseApplication): Primary object of an interface
59
- Usually a raster, but can be an application or a model too.
60
- """
61
- self.name = ""
62
- self.translations = {}
63
- self.api_side = False
64
- # we only care when generating models for csp.
65
- self.filter = None
66
- self.signal_names = {
67
- "api": {"insignals": set(), "outsignals": set()},
68
- "app": {"insignals": set(), "outsignals": set()},
69
- }
70
- self.base_application = base_application
71
- self.map_file = self.get_map_file()
72
- self.api_map = self.get_map()
73
- self.translations_files = []
74
- self.signal_primitives_list = []
75
- self.default_read_strategy = read_strategy
76
-
77
- def get_signal_properties(self, signal):
78
- """Get signal properties for signal
79
-
80
- Calls self.base_application to get signal properties
81
-
82
- Args:
83
- signal (Signal): Signal to get properties for
84
- """
85
- self.base_application.get_signal_properties(signal)
86
-
87
- def _get_signals(self):
88
- """Read signals"""
89
- self.parse_definition(self.translations_files)
90
-
91
- @property
92
- def insignals(self):
93
- return self.get_signals(self.hal_name, 'insignals')
94
-
95
- @property
96
- def outsignals(self):
97
- return self.get_signals(self.hal_name, 'outsignals')
98
-
99
- def get_signals(self, api, signal_type='insignals'):
100
- """Get signals to and from an api abstraction
101
-
102
- self.api_side configures if we look at the api side.
103
- If it is set to False, we look at the application side.
104
-
105
- Args:
106
- api (str): Name of the api
107
- signal_type (str): insignals or outsignals
108
- Returns:
109
- signals (list): Signals in the interface
110
- """
111
- if self.api_side:
112
- signal_names = self.signal_names['api'][signal_type]
113
- else:
114
- signal_names = self.signal_names['app'][signal_type]
115
-
116
- signals = []
117
- for signal_name, specs in self.translations.items():
118
- for spec in specs:
119
- signal = None
120
- if api is not None:
121
- if spec['api'] == api:
122
- if self.api_side:
123
- if spec['property'] in signal_names:
124
- signal = Signal(spec['property'], self)
125
- else:
126
- if signal_name in signal_names:
127
- signal = Signal(signal_name, self)
128
- else:
129
- if self.api_side:
130
- if spec['property'] in signal_names:
131
- signal = Signal(spec['property'], self)
132
- else:
133
- if signal_name in signal_names:
134
- signal = Signal(signal_name, self)
135
- if signal is not None:
136
- signals.append(signal)
137
- return signals
138
-
139
- def clear_signal_names(self):
140
- """Clear signal names
141
-
142
- Clears defined signal names (but not signal properties).
143
- """
144
- self.signal_names = {
145
- "api": {"insignals": set(), "outsignals": set()},
146
- "app": {"insignals": set(), "outsignals": set()},
147
- }
148
-
149
- def parse_definition(self, definition):
150
- """Parses all definition files
151
-
152
- Args:
153
- definition (list(Path)): Definition files
154
- """
155
- for translation in definition:
156
- raw = self.read_translation(translation)
157
- self.extract_endpoint_definitions(raw)
158
-
159
- @staticmethod
160
- def get_api_name(api_name):
161
- """Return the api name
162
-
163
- Args:
164
- api_name (str): Name of the api
165
-
166
- Returns:
167
- (str): Name of the api
168
- """
169
- return api_name
170
-
171
- def verify_api(self, api_name):
172
- """Verify that the api is in the map
173
-
174
- Args:
175
- api_name (str): Name of the api
176
- """
177
- if api_name not in self.api_map:
178
- raise MissingApi(api_name, self.map_file)
179
-
180
- def add_signals(self, signals, signal_type='insignals', properties=None):
181
- """Add signal names and properties to already set ones
182
-
183
- Args:
184
- signals (list(Signals)): Signals to use
185
- signal_type (str): 'insignals' or 'outsignals'
186
- properties (list(str)): signal definition properties, default = []
187
- """
188
- opposite = {'insignals': 'outsignals', 'outsignals': 'insignals'}
189
- api_type = opposite[signal_type]
190
- properties = [] if properties is None else properties
191
- for signal in signals:
192
- LOGGER.debug("Adding signal: %s", signal)
193
- temp_set = set()
194
- for translation in self.translations.get(signal.name, []):
195
- temp_list = list(translation)
196
- api_name = translation[self.position.api.value]
197
- variant_name = translation[self.position.variant.value]
198
- endpoint = translation[self.position.endpoint.value]
199
- api_signal = translation[self.position.property_name.value]
200
- self.check_signal_property(api_name, variant_name, endpoint,
201
- api_signal, signal_type)
202
- self.signal_names['api'][api_type].add(api_signal)
203
- for enum_property in properties:
204
- LOGGER.debug("Modifying property: %s", enum_property)
205
- value = signal.properties[enum_property["source"]]
206
- if value == "-":
207
- value = enum_property["default"]
208
- temp_list[
209
- self.position[enum_property["destination"]].value
210
- ] = value
211
- temp_set.add(tuple(temp_list))
212
- self.translations[signal.name] = temp_set
213
- self.signal_names['app'][signal_type].add(signal.name)
214
- self.check_endpoints()
215
- LOGGER.debug('Registered signal names: %s', self.signal_names)
216
-
217
- def check_signal_property(self, api, variant, endpoint, property_name, signal_type):
218
- """Check if we have only one signal written for the same property.
219
-
220
- Args:
221
- api (str): interface name
222
- variant (str): variant value. "properties" or "methods" for service
223
- "hals" for hal
224
- endpoint (str): signal endpoint
225
- property_name (str): signal property
226
- signal_type (str): 'insignals' or 'outsignals'
227
- """
228
- primitive_value = ""
229
- for value in [api, variant, endpoint, property_name]:
230
- if value:
231
- if primitive_value == "":
232
- primitive_value = value
233
- else:
234
- primitive_value = primitive_value + '.' + value
235
- if primitive_value == "":
236
- raise Exception("The primitive does not contain any value!")
237
- directional_primitive = f"{primitive_value}.{signal_type}"
238
- self.check_property(directional_primitive, signal_type)
239
-
240
- def check_property(self, property_spec, signal_type):
241
- """Check if we have only one signal written for the same property.
242
-
243
- Args:
244
- property_spec (str): property specification
245
- signal_type (str): 'insignals' or 'outsignals'
246
- """
247
- if property_spec in self.signal_primitives_list:
248
- error_msg = (f"You can't write {property_spec} as "
249
- f"{signal_type} since this primitive has been used."
250
- " Run model_yaml_verification to identify exact models.")
251
- raise Exception(error_msg)
252
- self.signal_primitives_list.append(property_spec)
253
-
254
- @abstractmethod
255
- def check_endpoints(self):
256
- """Should be implemented by subclasses."""
257
-
258
- @staticmethod
259
- def read_translation(translation_file):
260
- """Read specification of the format:
261
-
262
- service:
263
- interface:
264
- properties:
265
- - endpoint_name:
266
- - signal: name
267
- property: name
268
- - signal: name
269
- property: name
270
- hal:
271
- hal_name:
272
- - primitive_endpoint:
273
- - insignal: name
274
- hal_name:
275
- - struct_endpoint:
276
- - insignal: name1
277
- property: member1
278
- - insignal: name2
279
- property: member2
280
- ecm:
281
- - signal: name
282
- signals:
283
- tvrl:
284
- - signal: name
285
- property: can_name
286
- offset: offset
287
- factor: scaling
288
-
289
- Args:
290
- translation_file (Path): file with specs
291
-
292
- Returns:
293
- yaml_data (dict): Loaded YAML data as dict, empty if not found
294
- """
295
- if not translation_file.is_file():
296
- LOGGER.warning("No file found for %s", translation_file)
297
- return {}
298
- with open(translation_file, encoding="utf-8") as translation:
299
- yaml = YAML(typ='safe', pure=True)
300
- raw = yaml.load(translation)
301
- return raw
302
-
303
- def parse_api_definitions(self, api_definitions):
304
- """Parses group definitions.
305
-
306
- Args:
307
- api_definitions (dict): endpoints in parsed yaml file.
308
- """
309
- for api_from_spec, definition in api_definitions.items():
310
- for variant, variant_endpoints in self.extract_definition(definition).items():
311
- for endpoints in variant_endpoints:
312
- for endpoint, signals in endpoints.items():
313
- self.parse_property_definitions({api_from_spec: signals}, endpoint, variant)
314
-
315
- def parse_property_definitions(self, endpoint_definitions, endpoint, variant):
316
- """Parse signal definitions.
317
-
318
- Args:
319
- endpoint_definitions (dict): parsed yaml file.
320
- endpoint (str): Name of the endpoint to use
321
- """
322
- def _get_property_name(specification):
323
- """ Handle cases when there are no propery name.
324
-
325
- If there is no property name, the "group" is set to the signal.
326
- This should be used when the property is not a struct in the interface api.
327
-
328
- Args:
329
- specification (dict): signal specification
330
- Returns:
331
- property_name (str): name of the potential internal property
332
- """
333
- property_name = specification.get('property', '')
334
- if not property_name:
335
- return None
336
- return property_name
337
-
338
- enumerations = self.base_application.enumerations
339
-
340
- for api, specifications in endpoint_definitions.items():
341
- self.verify_api(api)
342
- for specification in specifications:
343
- in_out_signal = [key for key in specification.keys() if 'signal' in key]
344
- base_signal = None
345
- signal_name = None
346
- if "in" in in_out_signal[0]:
347
- for signal in self.base_application.insignals:
348
- if signal.name == specification["insignal"]:
349
- base_signal = signal
350
- signal_name = signal.name
351
- elif "out" in in_out_signal[0]:
352
- for signal in self.base_application.outsignals:
353
- if signal.name == specification["outsignal"]:
354
- base_signal = signal
355
- signal_name = signal.name
356
- else:
357
- raise BadYamlFormat(api, specification[in_out_signal[0]])
358
- if base_signal is None:
359
- continue
360
- base_properties = self.base_application.get_signal_properties(
361
- base_signal
362
- )
363
-
364
- if base_properties["type"] in enumerations:
365
- underlying_data_type = enumerations[base_properties['type']]['underlying_data_type']
366
- interface_type = underlying_data_type
367
- if 'init' not in specification:
368
- if enumerations[base_properties['type']]['default_value'] is not None:
369
- init_value = enumerations[base_properties['type']]['default_value']
370
- else:
371
- LOGGER.warning('Initializing enumeration %s to "zero".', base_properties['type'])
372
- init_value = [
373
- k for k, v in enumerations[base_properties['type']]['members'].items() if v == 0
374
- ][0]
375
- else:
376
- init_value = specification.get("init", 0)
377
- else:
378
- interface_type = base_properties["type"]
379
- init_value = specification.get("init", 0)
380
-
381
- if "out" in in_out_signal[0] and "strategy" in specification:
382
- LOGGER.warning('Cannot set read strategy for outsignal %s, using "Always".', signal_name)
383
- strategy = "Always"
384
- else:
385
- strategy = specification.get("strategy", self.default_read_strategy)
386
- if strategy not in self.read_strategies:
387
- LOGGER.warning('Invalid strategy %s, using "Always" instead.', strategy)
388
- strategy = self.default_read_strategy
389
-
390
- if signal_name not in self.translations:
391
- self.translations[signal_name] = set()
392
- self.translations[signal_name].add(
393
- (
394
- "enum_0", # Enum starts at position 1.
395
- _get_property_name(specification),
396
- interface_type,
397
- specification.get("type"),
398
- specification.get("offset"),
399
- specification.get("factor"),
400
- specification.get("default"),
401
- specification.get("length"),
402
- specification.get("min"),
403
- specification.get("max"),
404
- specification.get("enum"),
405
- init_value,
406
- specification.get("description"),
407
- specification.get("unit"),
408
- endpoint,
409
- api,
410
- variant,
411
- strategy,
412
- specification.get("debug", False),
413
- specification.get("dependability", False)
414
- )
415
- )
416
-
417
- def spec_to_dict(self, signal_spec, signal_name):
418
- """Convert signal specification to dict
419
-
420
- Args:
421
- signal_spec (dict): signal specification
422
- signal_name (str): signal name
423
- Returns:
424
- (dict): signal specification as dict
425
- """
426
- return {
427
- 'variable': signal_name,
428
- 'variable_type': signal_spec[self.position.variable_type.value],
429
- 'property': signal_spec[self.position.property_name.value],
430
- 'property_type': signal_spec[self.position.property_type.value],
431
- "default": signal_spec[self.position.default.value],
432
- "length": signal_spec[self.position.length.value],
433
- 'offset': signal_spec[self.position.offset.value],
434
- 'factor': signal_spec[self.position.factor.value],
435
- 'range': {
436
- 'min': signal_spec[self.position.min.value],
437
- 'max': signal_spec[self.position.max.value]
438
- },
439
- 'init': signal_spec[self.position.init.value],
440
- 'description': signal_spec[self.position.description.value],
441
- 'unit': signal_spec[self.position.unit.value],
442
- 'endpoint': signal_spec[self.position.endpoint.value],
443
- 'api': self.get_api_name(signal_spec[self.position.api.value]),
444
- 'variant': signal_spec[self.position.variant.value],
445
- 'strategy': signal_spec[self.position.strategy.value],
446
- 'debug': signal_spec[self.position.debug.value],
447
- 'dependability': signal_spec[self.position.dependability.value]
448
- }
449
-
450
- def to_dict(self, client="app"):
451
- """Method to generate dict to be saved as yaml
452
-
453
- Returns:
454
- spec (dict): Signalling specification
455
- """
456
- spec = {"consumer": [], "producer": []}
457
- direction = {
458
- "app": ["consumer", "producer"],
459
- "api": ["producer", "consumer"]}
460
- for signal_name, signal_spec in self._generator(self.signal_names["app"]["insignals"]):
461
- spec[direction[client][0]].append(
462
- self.spec_to_dict(signal_spec, signal_name)
463
- )
464
- for signal_name, signal_spec in self._generator(self.signal_names["app"]["outsignals"]):
465
- spec[direction[client][1]].append(
466
- self.spec_to_dict(signal_spec, signal_name)
467
- )
468
- return spec
469
-
470
- def _generator(self, signal_names, unique_names=False):
471
- """Iterate over signals for allowed services
472
-
473
- If unique_names is True, the iterator does not yield the same signal twice
474
- if unique_names is False, it yields each allowed signal spec with the signal name
475
-
476
- Args:
477
- signal_names (list): allowed signals
478
- Yields:
479
- name (str): Name of the signal
480
- specification (dict): signal specification for allowed service
481
- """
482
- for signal_name, specifications in (
483
- (name, spec) for name, spec in sorted(
484
- self.translations.items())
485
- if name in signal_names):
486
- for specification in specifications:
487
- if unique_names:
488
- yield signal_name, specification
489
- break
490
- yield signal_name, specification
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ """Module for CSP API abstraction."""
5
+
6
+ import enum
7
+ from ruamel.yaml import YAML
8
+ from abc import abstractmethod
9
+ from powertrain_build.interface.base import BaseApplication, Signal
10
+ from powertrain_build.lib import logger
11
+
12
+ LOGGER = logger.create_logger("service")
13
+
14
+
15
+ class MissingApi(Exception):
16
+ """Exception to raise when api is missing"""
17
+ def __init__(self, api, map_file):
18
+ self.message = f"Api {api} missing from {map_file}"
19
+
20
+
21
+ class BadYamlFormat(Exception):
22
+ """Exception to raise when in/out signal is not defined."""
23
+ def __init__(self, api, signal_name):
24
+ self.message = f"Signal {signal_name} for {api} should be set as insignal or outsignal."
25
+
26
+
27
+ class CspApi(BaseApplication):
28
+ """Abstraction for HAL and SFW"""
29
+ position = enum.Enum(
30
+ 'Position',
31
+ names=[
32
+ "property_name",
33
+ "property_type",
34
+ "variable_type",
35
+ "offset",
36
+ "factor",
37
+ "default",
38
+ "length",
39
+ "min",
40
+ "max",
41
+ "enum",
42
+ "init",
43
+ "description",
44
+ "unit",
45
+ "endpoint",
46
+ "api",
47
+ "variant",
48
+ "strategy",
49
+ "debug",
50
+ "dependability"
51
+ ]
52
+ )
53
+
54
+ def __init__(self, base_application, read_strategy='Always'):
55
+ """Create the interface object
56
+
57
+ Args:
58
+ base_application (BaseApplication): Primary object of an interface
59
+ Usually a raster, but can be an application or a model too.
60
+ """
61
+ self.name = ""
62
+ self.translations = {}
63
+ self.api_side = False
64
+ # we only care when generating models for csp.
65
+ self.filter = None
66
+ self.signal_names = {
67
+ "api": {"insignals": set(), "outsignals": set()},
68
+ "app": {"insignals": set(), "outsignals": set()},
69
+ }
70
+ self.base_application = base_application
71
+ self.map_file = self.get_map_file()
72
+ self.api_map = self.get_map()
73
+ self.translations_files = []
74
+ self.signal_primitives_list = []
75
+ self.default_read_strategy = read_strategy
76
+
77
+ def get_signal_properties(self, signal):
78
+ """Get signal properties for signal
79
+
80
+ Calls self.base_application to get signal properties
81
+
82
+ Args:
83
+ signal (Signal): Signal to get properties for
84
+ """
85
+ self.base_application.get_signal_properties(signal)
86
+
87
+ def _get_signals(self):
88
+ """Read signals"""
89
+ self.parse_definition(self.translations_files)
90
+
91
+ @property
92
+ def insignals(self):
93
+ return self.get_signals(self.hal_name, 'insignals')
94
+
95
+ @property
96
+ def outsignals(self):
97
+ return self.get_signals(self.hal_name, 'outsignals')
98
+
99
+ def get_signals(self, api, signal_type='insignals'):
100
+ """Get signals to and from an api abstraction
101
+
102
+ self.api_side configures if we look at the api side.
103
+ If it is set to False, we look at the application side.
104
+
105
+ Args:
106
+ api (str): Name of the api
107
+ signal_type (str): insignals or outsignals
108
+ Returns:
109
+ signals (list): Signals in the interface
110
+ """
111
+ if self.api_side:
112
+ signal_names = self.signal_names['api'][signal_type]
113
+ else:
114
+ signal_names = self.signal_names['app'][signal_type]
115
+
116
+ signals = []
117
+ for signal_name, specs in self.translations.items():
118
+ for spec in specs:
119
+ signal = None
120
+ if api is not None:
121
+ if spec['api'] == api:
122
+ if self.api_side:
123
+ if spec['property'] in signal_names:
124
+ signal = Signal(spec['property'], self)
125
+ else:
126
+ if signal_name in signal_names:
127
+ signal = Signal(signal_name, self)
128
+ else:
129
+ if self.api_side:
130
+ if spec['property'] in signal_names:
131
+ signal = Signal(spec['property'], self)
132
+ else:
133
+ if signal_name in signal_names:
134
+ signal = Signal(signal_name, self)
135
+ if signal is not None:
136
+ signals.append(signal)
137
+ return signals
138
+
139
+ def clear_signal_names(self):
140
+ """Clear signal names
141
+
142
+ Clears defined signal names (but not signal properties).
143
+ """
144
+ self.signal_names = {
145
+ "api": {"insignals": set(), "outsignals": set()},
146
+ "app": {"insignals": set(), "outsignals": set()},
147
+ }
148
+
149
+ def parse_definition(self, definition):
150
+ """Parses all definition files
151
+
152
+ Args:
153
+ definition (list(Path)): Definition files
154
+ """
155
+ for translation in definition:
156
+ raw = self.read_translation(translation)
157
+ self.extract_endpoint_definitions(raw)
158
+
159
+ @staticmethod
160
+ def get_api_name(api_name):
161
+ """Return the api name
162
+
163
+ Args:
164
+ api_name (str): Name of the api
165
+
166
+ Returns:
167
+ (str): Name of the api
168
+ """
169
+ return api_name
170
+
171
+ def verify_api(self, api_name):
172
+ """Verify that the api is in the map
173
+
174
+ Args:
175
+ api_name (str): Name of the api
176
+ """
177
+ if api_name not in self.api_map:
178
+ raise MissingApi(api_name, self.map_file)
179
+
180
+ def add_signals(self, signals, signal_type='insignals', properties=None):
181
+ """Add signal names and properties to already set ones
182
+
183
+ Args:
184
+ signals (list(Signals)): Signals to use
185
+ signal_type (str): 'insignals' or 'outsignals'
186
+ properties (list(str)): signal definition properties, default = []
187
+ """
188
+ opposite = {'insignals': 'outsignals', 'outsignals': 'insignals'}
189
+ api_type = opposite[signal_type]
190
+ properties = [] if properties is None else properties
191
+ for signal in signals:
192
+ LOGGER.debug("Adding signal: %s", signal)
193
+ temp_set = set()
194
+ for translation in self.translations.get(signal.name, []):
195
+ temp_list = list(translation)
196
+ api_name = translation[self.position.api.value]
197
+ variant_name = translation[self.position.variant.value]
198
+ endpoint = translation[self.position.endpoint.value]
199
+ api_signal = translation[self.position.property_name.value]
200
+ self.check_signal_property(api_name, variant_name, endpoint,
201
+ api_signal, signal_type)
202
+ self.signal_names['api'][api_type].add(api_signal)
203
+ for enum_property in properties:
204
+ LOGGER.debug("Modifying property: %s", enum_property)
205
+ value = signal.properties[enum_property["source"]]
206
+ if value == "-":
207
+ value = enum_property["default"]
208
+ temp_list[
209
+ self.position[enum_property["destination"]].value
210
+ ] = value
211
+ temp_set.add(tuple(temp_list))
212
+ self.translations[signal.name] = temp_set
213
+ self.signal_names['app'][signal_type].add(signal.name)
214
+ self.check_endpoints()
215
+ LOGGER.debug('Registered signal names: %s', self.signal_names)
216
+
217
+ def check_signal_property(self, api, variant, endpoint, property_name, signal_type):
218
+ """Check if we have only one signal written for the same property.
219
+
220
+ Args:
221
+ api (str): interface name
222
+ variant (str): variant value. "properties" or "methods" for service
223
+ "hals" for hal
224
+ endpoint (str): signal endpoint
225
+ property_name (str): signal property
226
+ signal_type (str): 'insignals' or 'outsignals'
227
+ """
228
+ primitive_value = ""
229
+ for value in [api, variant, endpoint, property_name]:
230
+ if value:
231
+ if primitive_value == "":
232
+ primitive_value = value
233
+ else:
234
+ primitive_value = primitive_value + '.' + value
235
+ if primitive_value == "":
236
+ raise Exception("The primitive does not contain any value!")
237
+ directional_primitive = f"{primitive_value}.{signal_type}"
238
+ self.check_property(directional_primitive, signal_type)
239
+
240
+ def check_property(self, property_spec, signal_type):
241
+ """Check if we have only one signal written for the same property.
242
+
243
+ Args:
244
+ property_spec (str): property specification
245
+ signal_type (str): 'insignals' or 'outsignals'
246
+ """
247
+ if property_spec in self.signal_primitives_list:
248
+ error_msg = (f"You can't write {property_spec} as "
249
+ f"{signal_type} since this primitive has been used."
250
+ " Run model_yaml_verification to identify exact models.")
251
+ raise Exception(error_msg)
252
+ self.signal_primitives_list.append(property_spec)
253
+
254
+ @abstractmethod
255
+ def check_endpoints(self):
256
+ """Should be implemented by subclasses."""
257
+
258
+ @staticmethod
259
+ def read_translation(translation_file):
260
+ """Read specification of the format:
261
+
262
+ service:
263
+ interface:
264
+ properties:
265
+ - endpoint_name:
266
+ - signal: name
267
+ property: name
268
+ - signal: name
269
+ property: name
270
+ hal:
271
+ hal_name:
272
+ - primitive_endpoint:
273
+ - insignal: name
274
+ hal_name:
275
+ - struct_endpoint:
276
+ - insignal: name1
277
+ property: member1
278
+ - insignal: name2
279
+ property: member2
280
+ ecm:
281
+ - signal: name
282
+ signals:
283
+ tvrl:
284
+ - signal: name
285
+ property: can_name
286
+ offset: offset
287
+ factor: scaling
288
+
289
+ Args:
290
+ translation_file (Path): file with specs
291
+
292
+ Returns:
293
+ yaml_data (dict): Loaded YAML data as dict, empty if not found
294
+ """
295
+ if not translation_file.is_file():
296
+ LOGGER.warning("No file found for %s", translation_file)
297
+ return {}
298
+ with open(translation_file, encoding="utf-8") as translation:
299
+ yaml = YAML(typ='safe', pure=True)
300
+ raw = yaml.load(translation)
301
+ return raw
302
+
303
+ def parse_api_definitions(self, api_definitions):
304
+ """Parses group definitions.
305
+
306
+ Args:
307
+ api_definitions (dict): endpoints in parsed yaml file.
308
+ """
309
+ for api_from_spec, definition in api_definitions.items():
310
+ for variant, variant_endpoints in self.extract_definition(definition).items():
311
+ for endpoints in variant_endpoints:
312
+ for endpoint, signals in endpoints.items():
313
+ self.parse_property_definitions({api_from_spec: signals}, endpoint, variant)
314
+
315
+ def parse_property_definitions(self, endpoint_definitions, endpoint, variant):
316
+ """Parse signal definitions.
317
+
318
+ Args:
319
+ endpoint_definitions (dict): parsed yaml file.
320
+ endpoint (str): Name of the endpoint to use
321
+ """
322
+ def _get_property_name(specification):
323
+ """ Handle cases when there are no propery name.
324
+
325
+ If there is no property name, the "group" is set to the signal.
326
+ This should be used when the property is not a struct in the interface api.
327
+
328
+ Args:
329
+ specification (dict): signal specification
330
+ Returns:
331
+ property_name (str): name of the potential internal property
332
+ """
333
+ property_name = specification.get('property', '')
334
+ if not property_name:
335
+ return None
336
+ return property_name
337
+
338
+ enumerations = self.base_application.enumerations
339
+
340
+ for api, specifications in endpoint_definitions.items():
341
+ self.verify_api(api)
342
+ for specification in specifications:
343
+ in_out_signal = [key for key in specification.keys() if 'signal' in key]
344
+ base_signal = None
345
+ signal_name = None
346
+ if "in" in in_out_signal[0]:
347
+ for signal in self.base_application.insignals:
348
+ if signal.name == specification["insignal"]:
349
+ base_signal = signal
350
+ signal_name = signal.name
351
+ elif "out" in in_out_signal[0]:
352
+ for signal in self.base_application.outsignals:
353
+ if signal.name == specification["outsignal"]:
354
+ base_signal = signal
355
+ signal_name = signal.name
356
+ else:
357
+ raise BadYamlFormat(api, specification[in_out_signal[0]])
358
+ if base_signal is None:
359
+ continue
360
+ base_properties = self.base_application.get_signal_properties(
361
+ base_signal
362
+ )
363
+
364
+ if base_properties["type"] in enumerations:
365
+ underlying_data_type = enumerations[base_properties['type']]['underlying_data_type']
366
+ interface_type = underlying_data_type
367
+ if 'init' not in specification:
368
+ if enumerations[base_properties['type']]['default_value'] is not None:
369
+ init_value = enumerations[base_properties['type']]['default_value']
370
+ else:
371
+ LOGGER.warning('Initializing enumeration %s to "zero".', base_properties['type'])
372
+ init_value = [
373
+ k for k, v in enumerations[base_properties['type']]['members'].items() if v == 0
374
+ ][0]
375
+ else:
376
+ init_value = specification.get("init", 0)
377
+ else:
378
+ interface_type = base_properties["type"]
379
+ init_value = specification.get("init", 0)
380
+
381
+ if "out" in in_out_signal[0] and "strategy" in specification:
382
+ LOGGER.warning('Cannot set read strategy for outsignal %s, using "Always".', signal_name)
383
+ strategy = "Always"
384
+ else:
385
+ strategy = specification.get("strategy", self.default_read_strategy)
386
+ if strategy not in self.read_strategies:
387
+ LOGGER.warning('Invalid strategy %s, using "Always" instead.', strategy)
388
+ strategy = self.default_read_strategy
389
+
390
+ if signal_name not in self.translations:
391
+ self.translations[signal_name] = set()
392
+ self.translations[signal_name].add(
393
+ (
394
+ "enum_0", # Enum starts at position 1.
395
+ _get_property_name(specification),
396
+ interface_type,
397
+ specification.get("type"),
398
+ specification.get("offset"),
399
+ specification.get("factor"),
400
+ specification.get("default"),
401
+ specification.get("length"),
402
+ specification.get("min"),
403
+ specification.get("max"),
404
+ specification.get("enum"),
405
+ init_value,
406
+ specification.get("description"),
407
+ specification.get("unit"),
408
+ endpoint,
409
+ api,
410
+ variant,
411
+ strategy,
412
+ specification.get("debug", False),
413
+ specification.get("dependability", False)
414
+ )
415
+ )
416
+
417
+ def spec_to_dict(self, signal_spec, signal_name):
418
+ """Convert signal specification to dict
419
+
420
+ Args:
421
+ signal_spec (dict): signal specification
422
+ signal_name (str): signal name
423
+ Returns:
424
+ (dict): signal specification as dict
425
+ """
426
+ return {
427
+ 'variable': signal_name,
428
+ 'variable_type': signal_spec[self.position.variable_type.value],
429
+ 'property': signal_spec[self.position.property_name.value],
430
+ 'property_type': signal_spec[self.position.property_type.value],
431
+ "default": signal_spec[self.position.default.value],
432
+ "length": signal_spec[self.position.length.value],
433
+ 'offset': signal_spec[self.position.offset.value],
434
+ 'factor': signal_spec[self.position.factor.value],
435
+ 'range': {
436
+ 'min': signal_spec[self.position.min.value],
437
+ 'max': signal_spec[self.position.max.value]
438
+ },
439
+ 'init': signal_spec[self.position.init.value],
440
+ 'description': signal_spec[self.position.description.value],
441
+ 'unit': signal_spec[self.position.unit.value],
442
+ 'endpoint': signal_spec[self.position.endpoint.value],
443
+ 'api': self.get_api_name(signal_spec[self.position.api.value]),
444
+ 'variant': signal_spec[self.position.variant.value],
445
+ 'strategy': signal_spec[self.position.strategy.value],
446
+ 'debug': signal_spec[self.position.debug.value],
447
+ 'dependability': signal_spec[self.position.dependability.value]
448
+ }
449
+
450
+ def to_dict(self, client="app"):
451
+ """Method to generate dict to be saved as yaml
452
+
453
+ Returns:
454
+ spec (dict): Signalling specification
455
+ """
456
+ spec = {"consumer": [], "producer": []}
457
+ direction = {
458
+ "app": ["consumer", "producer"],
459
+ "api": ["producer", "consumer"]}
460
+ for signal_name, signal_spec in self._generator(self.signal_names["app"]["insignals"]):
461
+ spec[direction[client][0]].append(
462
+ self.spec_to_dict(signal_spec, signal_name)
463
+ )
464
+ for signal_name, signal_spec in self._generator(self.signal_names["app"]["outsignals"]):
465
+ spec[direction[client][1]].append(
466
+ self.spec_to_dict(signal_spec, signal_name)
467
+ )
468
+ return spec
469
+
470
+ def _generator(self, signal_names, unique_names=False):
471
+ """Iterate over signals for allowed services
472
+
473
+ If unique_names is True, the iterator does not yield the same signal twice
474
+ if unique_names is False, it yields each allowed signal spec with the signal name
475
+
476
+ Args:
477
+ signal_names (list): allowed signals
478
+ Yields:
479
+ name (str): Name of the signal
480
+ specification (dict): signal specification for allowed service
481
+ """
482
+ for signal_name, specifications in (
483
+ (name, spec) for name, spec in sorted(
484
+ self.translations.items())
485
+ if name in signal_names):
486
+ for specification in specifications:
487
+ if unique_names:
488
+ yield signal_name, specification
489
+ break
490
+ yield signal_name, specification