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,500 +1,500 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- # -*- coding: utf-8 -*-
5
- """Python module used for abstracting an application that should be interfacing others."""
6
- from abc import abstractmethod
7
- from ruamel.yaml import YAML
8
- from powertrain_build.lib import logger
9
-
10
- LOGGER = logger.create_logger('base')
11
-
12
-
13
- def filter_signals(signals, domain):
14
- """ Take a list of signals and remove all domains belonging to a domain
15
-
16
- If the signal is part of the domain, it is not part of the resulting list
17
-
18
- Arguments:
19
- signals (list(Signal)): signals to filter
20
- domain (Domain): domain that the signals should not be part of
21
- """
22
- filtered_signals = []
23
- for signal in signals:
24
- if signal.name not in domain.signals:
25
- filtered_signals.append(signal)
26
- return filtered_signals
27
-
28
-
29
- class MultipleProducersError(Exception):
30
- """Error when setting a producer and there already exists one"""
31
- def __init__(self, signal, old_producer, new_producer):
32
- """Set error message
33
-
34
- Args:
35
- signal (Signal): Signal object
36
- old_producer (BaseApplication): Producer already registered
37
- new_producer (BaseApplication): Producer attempted to be registered
38
- """
39
- super().__init__()
40
- self.message = (f"{signal.name}:"
41
- f" Attempting to set producer {new_producer}"
42
- f" when {old_producer} is already set")
43
-
44
-
45
- class BaseApplication:
46
- """Base application to build other adapters on"""
47
- name = str()
48
- _signals = None
49
- node = str() # Used to calculate interface
50
-
51
- read_strategies = {
52
- "Always",
53
- "OnChanged",
54
- "OnUpdated"
55
- }
56
-
57
- def __repr__(self):
58
- """String representation for logging and debugging
59
-
60
- Returns:
61
- repr (string): Name of the application, the number of insignals and outsignals
62
- """
63
- return f"<{self.name}" \
64
- f" insignals:{len(self.insignals)}" \
65
- f" outsignals:{len(self.outsignals)}>"
66
-
67
- def parse_signals(self):
68
- """API interface to read all signals in any child object"""
69
- self._get_signals()
70
-
71
- @property
72
- def insignals(self):
73
- """ Insignals to the raster.
74
- Calculated as all read ports - all written ports
75
-
76
- Returns:
77
- signals (list): List of Signal objects.
78
- """
79
- if self._insignals is None:
80
- self._get_signals()
81
- return [self._signals[port] for port in self._insignals - self._outsignals]
82
-
83
- @property
84
- def outsignals(self):
85
- """ All outports.
86
- Since we might consume some of the signals that should also be sent elsewhere,
87
- we do not remove internally consumed signals.
88
-
89
- Returns:
90
- signals (list): List of Signal objects.
91
- """
92
- if self._outsignals is None:
93
- self._get_signals()
94
- return [self._signals[port] for port in self._outsignals]
95
-
96
- @property
97
- def signals(self):
98
- """API interface property in any child object
99
-
100
- All cached signals.
101
- If no cache exists, reads all signals and save to cache.
102
-
103
- Returns:
104
- signals (list): Signal objects
105
- """
106
- if self._signals is None:
107
- self.parse_signals()
108
- return self._signals.values()
109
-
110
- @abstractmethod
111
- def _get_signals(self):
112
- """Stub to implement in child object"""
113
-
114
- @abstractmethod
115
- def get_signal_properties(self, signal):
116
- """Stub to implement in child object
117
-
118
- Ideally, this should be moved to the signal.
119
- Currently, getting the properties depends on how we read and define the signals.
120
- """
121
-
122
- @abstractmethod
123
- def parse_definition(self, definition):
124
- """Stub for parsing a defintion after the object has been initialized.
125
-
126
- Raises NotImplementedError if called without being implemented.
127
-
128
- Args:
129
- definition: Definition of the Application. Type depends on the application.
130
- """
131
- raise NotImplementedError('This is a stub')
132
-
133
-
134
- class Signal:
135
- """Signal object
136
-
137
- The signal should behave the same way independently of where we define it.
138
- """
139
- def __repr__(self):
140
- """String representation for logging and debugging
141
-
142
- Returns:
143
- repr (string): Name of the application, the number of insignals and outsignals
144
- """
145
- return (f"<{self.name} in {self.applications}"
146
- f" producer:{self.producer}"
147
- f" consumers:{self.consumers}>")
148
-
149
- def __init__(self, name, application):
150
- """Define base properties of the signal object
151
-
152
- The application object is used to read properties of a signal.
153
- TODO: Do this when we define the signal and add properties known in other
154
- systems when we encounter them.
155
-
156
- Args:
157
- name (string): Signal name
158
- application (BaseApplication): Application defining the signal
159
- """
160
- self.name = name
161
- self.applications = {} # Add applications to a dict to prevent duplicates
162
- if application is not None:
163
- self.applications[application.name] = application
164
- self._consumers = set()
165
- self._producer = None
166
-
167
- def add_application(self, application):
168
- """Add an application to find properties from
169
-
170
- Args:
171
- application (BaseApplication): Application to read properties from
172
- """
173
- if application.name in self.applications:
174
- return
175
- self.applications[application.name] = application
176
-
177
- @property
178
- def consumers(self):
179
- """Get all consumers of a signal
180
-
181
- Returns:
182
- consumers (set): All consumers of a signal
183
- """
184
- if isinstance(self._consumers, set):
185
- return self._consumers
186
- return set()
187
-
188
- @consumers.setter
189
- def consumers(self, consumers):
190
- """Set consumers of a signal
191
-
192
- If the consumers is a list or set, iterate over each consumer
193
- Otherwise, add the consumer to the set of consumers
194
-
195
- Args:
196
- consumers (list/set/string): consumer(s) of a signal
197
- """
198
- if isinstance(consumers, (list, set)):
199
- for consumer in consumers:
200
- self._consumers.add(consumer)
201
- else:
202
- self._consumers.add(consumers)
203
-
204
- @property
205
- def producer(self):
206
- """Get the producer of a signal
207
-
208
- Since we have some strange signals with multiple producers,
209
- such as counters for dep, this returns a set.
210
- Returns:
211
- producer (set): Producer(s) of a signal
212
- """
213
- if isinstance(self._producer, set):
214
- return self._producer
215
- return set()
216
-
217
- @producer.setter
218
- def producer(self, producer):
219
- """Set producer of a signal
220
-
221
- Args:
222
- producer (string/set): Name of the producer
223
- """
224
- if isinstance(producer, set):
225
- self._producer = producer
226
- else:
227
- self._producer = {producer}
228
-
229
- def set_producer(self, producer):
230
- """Set producer of a signal
231
-
232
- If there already is a registered producer of the signal,
233
- raise MultipleProducersError
234
-
235
- This can be expected and force_producer can be called to override this.
236
- That must be explicit in each instance.
237
-
238
- Args:
239
- producer (string): Name of the producer
240
- application (BaseApplication): Application defining the signal. Optional
241
- """
242
- if isinstance(producer, set):
243
- if self._producer is not None and producer - self._producer:
244
- raise MultipleProducersError(self, self._producer, producer)
245
- self.producer = producer
246
- else:
247
- if self._producer is not None \
248
- and isinstance(producer, str) \
249
- and producer not in self._producer:
250
- raise MultipleProducersError(self, self._producer, producer)
251
- self.producer = {producer}
252
-
253
- def force_producer(self, producer):
254
- """Forcefully update add producers of a signal
255
-
256
- This is needed since we have some signals that are written by multiple model
257
- Args:
258
- producers (string): Producer of a signal
259
- application (BaseApplication): Application defining the signal. Optional
260
- """
261
-
262
- self._producer.add(producer)
263
-
264
- @property
265
- def properties(self):
266
- """Properties of a signal
267
-
268
- Currently not homogenized.
269
- Therefore we read the properties from the application that defined the signal.
270
-
271
- Returns:
272
- properties (dict): properties of a signal
273
- """
274
- properties = {}
275
- for application in self.applications.values():
276
- LOGGER.debug('Getting properties for %s from %s', self.name, application.name)
277
- application_properties = application.get_signal_properties(self)
278
- LOGGER.debug(application_properties)
279
- for key, value in application_properties.items():
280
- LOGGER.debug('Looking at %s: %s', key, value)
281
- if key in properties and value != properties[key]:
282
- LOGGER.debug('Signal %s already has %s with value %s, ignoring %s from %s',
283
- self.name, key, properties[key], value, application.name)
284
- continue
285
- properties[key] = value
286
- return properties
287
-
288
-
289
- class Interface:
290
- """Interface between two objects"""
291
- def __repr__(self):
292
- """String representation for logging and debugging
293
-
294
- Returns:
295
- repr (string): Name of the interface, and the length of received and transmitted signals
296
- """
297
- return (f"<{self.name}"
298
- f" a->b:{len(self.get_directional_signals(self.current, self.corresponding))}"
299
- f" b->a:{len(self.get_directional_signals(self.corresponding, self.current))}>")
300
-
301
- def debug(self):
302
- """Debug an interface object to stdout"""
303
- LOGGER.info('name: %s', self.name)
304
- for signal in self.get_directional_signals(self.current, self.corresponding):
305
- LOGGER.info('insignal: %s', signal)
306
- for signal in self.get_directional_signals(self.corresponding, self.current):
307
- LOGGER.info('outsignal: %s', signal)
308
-
309
- def __init__(self, current, corresponding):
310
- """Create the interface object
311
-
312
- Args:
313
- current (BaseApplication): Primary object of an interface
314
- corresponding (BaseApplication): Secondary object of an interface
315
- """
316
- self.name = current.name + '_' + corresponding.name
317
- self.current = current
318
- self.corresponding = corresponding
319
-
320
- @staticmethod
321
- def get_directional_signals(producer, consumer):
322
- """Get signals going from producer to consumer
323
-
324
- Args:
325
- producer (BaseApplication): producer of the signals
326
- consumer (BaseApplication): consumer of the signals
327
- Returns:
328
- signals (list): Signals sent from producer and received in consumer
329
- """
330
- outsignals = {signal.name: signal for signal in producer.outsignals}
331
- signals = []
332
- for signal in consumer.insignals:
333
- if signal.name in outsignals:
334
- signal.set_producer(outsignals[signal.name].producer)
335
- signal.add_application(producer)
336
- signal.add_application(consumer)
337
- signals.append(signal)
338
- return signals
339
-
340
- def get_produced_signals(self, producer_name):
341
- """Get signals going from producer to consumer
342
-
343
- This function can be used if you are lacking some objects
344
-
345
- Args:
346
- consumer_name (string): name of the consumer of the signals
347
- Returns:
348
- signals (list): Signals sent from producer and received in consumer
349
- """
350
- if producer_name == self.current.name:
351
- consumer = self.corresponding
352
- producer = self.current
353
- elif producer_name == self.corresponding.name:
354
- consumer = self.current
355
- producer = self.corresponding
356
- else:
357
- LOGGER.error('%s not in [%s, %s]',
358
- producer_name,
359
- self.current.name,
360
- self.corresponding.name)
361
- return self.get_directional_signals(producer, consumer)
362
-
363
- def get_consumed_signals(self, consumer_name):
364
- """Get signals going from producer to consumer
365
-
366
- This function can be used if you are lacking some objects
367
-
368
- Args:
369
- consumer_name (string): name of the consumer of the signals
370
- Returns:
371
- signals (list): Signals sent from producer and received in consumer
372
- """
373
- if consumer_name == self.current.name:
374
- consumer = self.current
375
- producer = self.corresponding
376
- elif consumer_name == self.corresponding.name:
377
- consumer = self.corresponding
378
- producer = self.current
379
- else:
380
- LOGGER.error('%s not in [%s, %s]',
381
- consumer_name,
382
- self.current.name,
383
- self.corresponding.name)
384
- return self.get_directional_signals(producer, consumer)
385
-
386
-
387
- class Domain:
388
- """Domain with interacting interfaces"""
389
- def __repr__(self):
390
- """String representation for logging and debugging
391
-
392
- Returns:
393
- repr (string): Name of the domain, and all clients for that domain
394
- """
395
- return f"<{self.name}: {self.clients}>"
396
-
397
- def __init__(self):
398
- """Initialize the object"""
399
- self.name = ''
400
- self.signals = {}
401
- self.clients = set()
402
- self._clients = {}
403
-
404
- def set_name(self, name):
405
- """Set the name of the domain
406
-
407
- Args:
408
- name (string): Name of the domain
409
- """
410
- self.name = name
411
-
412
- def add_interface(self, interface):
413
- """Add an interface to a domain
414
-
415
- Args:
416
- interface (Interface): Interface object
417
- """
418
- self._process_interface(interface)
419
-
420
- def _process_interface(self, interface):
421
- """Process interface to add signals to the domain
422
-
423
- Args:
424
- interface (Interface): Interface object
425
- """
426
- for signal in interface.get_directional_signals(interface.current, interface.corresponding):
427
- self._process_signal(signal.name, interface.current, interface.corresponding)
428
- for signal in interface.get_directional_signals(interface.corresponding, interface.current):
429
- self._process_signal(signal.name, interface.corresponding, interface.current)
430
-
431
- def _process_signal(self, signal_name, producer, consumer):
432
- """Process signal to add to the domain
433
-
434
- Args:
435
- signal_name (string): Name of the signal
436
- consumer (BaseApplication): Consumer application of the signal
437
- producer (BaseApplication): Producer application of the signal
438
- """
439
- if signal_name not in self.signals:
440
- self.signals[signal_name] = Signal(signal_name, producer)
441
- self.signals[signal_name].consumers = consumer.name
442
- self.signals[signal_name].add_application(producer)
443
- self.signals[signal_name].add_application(consumer)
444
- signal = self.signals[signal_name]
445
- if producer.name not in self.clients:
446
- self.clients.add(producer.name)
447
- self._clients[producer.name] = {'producer': [], 'consumer': []}
448
- self._clients[producer.name]['producer'].append(signal)
449
- if consumer.name not in self.clients:
450
- self.clients.add(producer.name)
451
- self._clients[producer.name] = {'producer': [], 'consumer': []}
452
- self._clients[producer.name]['consumer'].append(signal)
453
- signal.consumers = consumer.name
454
- try:
455
- signal.producer = producer.name
456
- except MultipleProducersError as mpe:
457
- LOGGER.debug(mpe.message)
458
-
459
- def create_groups(self):
460
- """Create groups of signals going from each producer
461
-
462
- Returns:
463
- signal_groups (dict): Signal groups
464
- """
465
- signal_groups = {}
466
- for signal in self.signals.values():
467
- # Producer is always a set, to handle pass-through signals
468
- for producer in signal.producer - set(signal_groups.keys()):
469
- signal_groups[producer] = []
470
- for producer in signal.producer:
471
- signal_groups[producer].append(signal)
472
- return signal_groups
473
-
474
- def create_selective_groups(self, a_names, b_names):
475
- """Create groups for the a_list communicating with the b_names
476
-
477
- Returns:
478
- signal_groups (dict): Signal groups
479
- """
480
- signal_groups = {name: {'consumer': [], 'producer': []} for name in a_names}
481
- for signal in self.signals.values():
482
- for producer in set(signal.producer):
483
- if producer in a_names and signal.consumers & b_names:
484
- signal_groups[producer]['producer'].append(signal)
485
- for consumer in signal.consumers:
486
- if consumer in a_names and set(signal.producer) & a_names:
487
- signal_groups[consumer]['consumer'].append(signal)
488
- return signal_groups
489
-
490
- @staticmethod
491
- def to_yaml(spec, output):
492
- """Writes spec to yaml file
493
-
494
- Args:
495
- spec (dict): data for the yaml
496
- output (Path): file to write to
497
- """
498
- with open(output, 'w', encoding="utf-8") as yaml_file:
499
- yaml = YAML()
500
- yaml.dump(spec, yaml_file)
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ # -*- coding: utf-8 -*-
5
+ """Python module used for abstracting an application that should be interfacing others."""
6
+ from abc import abstractmethod
7
+ from ruamel.yaml import YAML
8
+ from powertrain_build.lib import logger
9
+
10
+ LOGGER = logger.create_logger('base')
11
+
12
+
13
+ def filter_signals(signals, domain):
14
+ """ Take a list of signals and remove all domains belonging to a domain
15
+
16
+ If the signal is part of the domain, it is not part of the resulting list
17
+
18
+ Arguments:
19
+ signals (list(Signal)): signals to filter
20
+ domain (Domain): domain that the signals should not be part of
21
+ """
22
+ filtered_signals = []
23
+ for signal in signals:
24
+ if signal.name not in domain.signals:
25
+ filtered_signals.append(signal)
26
+ return filtered_signals
27
+
28
+
29
+ class MultipleProducersError(Exception):
30
+ """Error when setting a producer and there already exists one"""
31
+ def __init__(self, signal, old_producer, new_producer):
32
+ """Set error message
33
+
34
+ Args:
35
+ signal (Signal): Signal object
36
+ old_producer (BaseApplication): Producer already registered
37
+ new_producer (BaseApplication): Producer attempted to be registered
38
+ """
39
+ super().__init__()
40
+ self.message = (f"{signal.name}:"
41
+ f" Attempting to set producer {new_producer}"
42
+ f" when {old_producer} is already set")
43
+
44
+
45
+ class BaseApplication:
46
+ """Base application to build other adapters on"""
47
+ name = str()
48
+ _signals = None
49
+ node = str() # Used to calculate interface
50
+
51
+ read_strategies = {
52
+ "Always",
53
+ "OnChanged",
54
+ "OnUpdated"
55
+ }
56
+
57
+ def __repr__(self):
58
+ """String representation for logging and debugging
59
+
60
+ Returns:
61
+ repr (string): Name of the application, the number of insignals and outsignals
62
+ """
63
+ return f"<{self.name}" \
64
+ f" insignals:{len(self.insignals)}" \
65
+ f" outsignals:{len(self.outsignals)}>"
66
+
67
+ def parse_signals(self):
68
+ """API interface to read all signals in any child object"""
69
+ self._get_signals()
70
+
71
+ @property
72
+ def insignals(self):
73
+ """ Insignals to the raster.
74
+ Calculated as all read ports - all written ports
75
+
76
+ Returns:
77
+ signals (list): List of Signal objects.
78
+ """
79
+ if self._insignals is None:
80
+ self._get_signals()
81
+ return [self._signals[port] for port in self._insignals - self._outsignals]
82
+
83
+ @property
84
+ def outsignals(self):
85
+ """ All outports.
86
+ Since we might consume some of the signals that should also be sent elsewhere,
87
+ we do not remove internally consumed signals.
88
+
89
+ Returns:
90
+ signals (list): List of Signal objects.
91
+ """
92
+ if self._outsignals is None:
93
+ self._get_signals()
94
+ return [self._signals[port] for port in self._outsignals]
95
+
96
+ @property
97
+ def signals(self):
98
+ """API interface property in any child object
99
+
100
+ All cached signals.
101
+ If no cache exists, reads all signals and save to cache.
102
+
103
+ Returns:
104
+ signals (list): Signal objects
105
+ """
106
+ if self._signals is None:
107
+ self.parse_signals()
108
+ return self._signals.values()
109
+
110
+ @abstractmethod
111
+ def _get_signals(self):
112
+ """Stub to implement in child object"""
113
+
114
+ @abstractmethod
115
+ def get_signal_properties(self, signal):
116
+ """Stub to implement in child object
117
+
118
+ Ideally, this should be moved to the signal.
119
+ Currently, getting the properties depends on how we read and define the signals.
120
+ """
121
+
122
+ @abstractmethod
123
+ def parse_definition(self, definition):
124
+ """Stub for parsing a defintion after the object has been initialized.
125
+
126
+ Raises NotImplementedError if called without being implemented.
127
+
128
+ Args:
129
+ definition: Definition of the Application. Type depends on the application.
130
+ """
131
+ raise NotImplementedError('This is a stub')
132
+
133
+
134
+ class Signal:
135
+ """Signal object
136
+
137
+ The signal should behave the same way independently of where we define it.
138
+ """
139
+ def __repr__(self):
140
+ """String representation for logging and debugging
141
+
142
+ Returns:
143
+ repr (string): Name of the application, the number of insignals and outsignals
144
+ """
145
+ return (f"<{self.name} in {self.applications}"
146
+ f" producer:{self.producer}"
147
+ f" consumers:{self.consumers}>")
148
+
149
+ def __init__(self, name, application):
150
+ """Define base properties of the signal object
151
+
152
+ The application object is used to read properties of a signal.
153
+ TODO: Do this when we define the signal and add properties known in other
154
+ systems when we encounter them.
155
+
156
+ Args:
157
+ name (string): Signal name
158
+ application (BaseApplication): Application defining the signal
159
+ """
160
+ self.name = name
161
+ self.applications = {} # Add applications to a dict to prevent duplicates
162
+ if application is not None:
163
+ self.applications[application.name] = application
164
+ self._consumers = set()
165
+ self._producer = None
166
+
167
+ def add_application(self, application):
168
+ """Add an application to find properties from
169
+
170
+ Args:
171
+ application (BaseApplication): Application to read properties from
172
+ """
173
+ if application.name in self.applications:
174
+ return
175
+ self.applications[application.name] = application
176
+
177
+ @property
178
+ def consumers(self):
179
+ """Get all consumers of a signal
180
+
181
+ Returns:
182
+ consumers (set): All consumers of a signal
183
+ """
184
+ if isinstance(self._consumers, set):
185
+ return self._consumers
186
+ return set()
187
+
188
+ @consumers.setter
189
+ def consumers(self, consumers):
190
+ """Set consumers of a signal
191
+
192
+ If the consumers is a list or set, iterate over each consumer
193
+ Otherwise, add the consumer to the set of consumers
194
+
195
+ Args:
196
+ consumers (list/set/string): consumer(s) of a signal
197
+ """
198
+ if isinstance(consumers, (list, set)):
199
+ for consumer in consumers:
200
+ self._consumers.add(consumer)
201
+ else:
202
+ self._consumers.add(consumers)
203
+
204
+ @property
205
+ def producer(self):
206
+ """Get the producer of a signal
207
+
208
+ Since we have some strange signals with multiple producers,
209
+ such as counters for dep, this returns a set.
210
+ Returns:
211
+ producer (set): Producer(s) of a signal
212
+ """
213
+ if isinstance(self._producer, set):
214
+ return self._producer
215
+ return set()
216
+
217
+ @producer.setter
218
+ def producer(self, producer):
219
+ """Set producer of a signal
220
+
221
+ Args:
222
+ producer (string/set): Name of the producer
223
+ """
224
+ if isinstance(producer, set):
225
+ self._producer = producer
226
+ else:
227
+ self._producer = {producer}
228
+
229
+ def set_producer(self, producer):
230
+ """Set producer of a signal
231
+
232
+ If there already is a registered producer of the signal,
233
+ raise MultipleProducersError
234
+
235
+ This can be expected and force_producer can be called to override this.
236
+ That must be explicit in each instance.
237
+
238
+ Args:
239
+ producer (string): Name of the producer
240
+ application (BaseApplication): Application defining the signal. Optional
241
+ """
242
+ if isinstance(producer, set):
243
+ if self._producer is not None and producer - self._producer:
244
+ raise MultipleProducersError(self, self._producer, producer)
245
+ self.producer = producer
246
+ else:
247
+ if self._producer is not None \
248
+ and isinstance(producer, str) \
249
+ and producer not in self._producer:
250
+ raise MultipleProducersError(self, self._producer, producer)
251
+ self.producer = {producer}
252
+
253
+ def force_producer(self, producer):
254
+ """Forcefully update add producers of a signal
255
+
256
+ This is needed since we have some signals that are written by multiple model
257
+ Args:
258
+ producers (string): Producer of a signal
259
+ application (BaseApplication): Application defining the signal. Optional
260
+ """
261
+
262
+ self._producer.add(producer)
263
+
264
+ @property
265
+ def properties(self):
266
+ """Properties of a signal
267
+
268
+ Currently not homogenized.
269
+ Therefore we read the properties from the application that defined the signal.
270
+
271
+ Returns:
272
+ properties (dict): properties of a signal
273
+ """
274
+ properties = {}
275
+ for application in self.applications.values():
276
+ LOGGER.debug('Getting properties for %s from %s', self.name, application.name)
277
+ application_properties = application.get_signal_properties(self)
278
+ LOGGER.debug(application_properties)
279
+ for key, value in application_properties.items():
280
+ LOGGER.debug('Looking at %s: %s', key, value)
281
+ if key in properties and value != properties[key]:
282
+ LOGGER.debug('Signal %s already has %s with value %s, ignoring %s from %s',
283
+ self.name, key, properties[key], value, application.name)
284
+ continue
285
+ properties[key] = value
286
+ return properties
287
+
288
+
289
+ class Interface:
290
+ """Interface between two objects"""
291
+ def __repr__(self):
292
+ """String representation for logging and debugging
293
+
294
+ Returns:
295
+ repr (string): Name of the interface, and the length of received and transmitted signals
296
+ """
297
+ return (f"<{self.name}"
298
+ f" a->b:{len(self.get_directional_signals(self.current, self.corresponding))}"
299
+ f" b->a:{len(self.get_directional_signals(self.corresponding, self.current))}>")
300
+
301
+ def debug(self):
302
+ """Debug an interface object to stdout"""
303
+ LOGGER.info('name: %s', self.name)
304
+ for signal in self.get_directional_signals(self.current, self.corresponding):
305
+ LOGGER.info('insignal: %s', signal)
306
+ for signal in self.get_directional_signals(self.corresponding, self.current):
307
+ LOGGER.info('outsignal: %s', signal)
308
+
309
+ def __init__(self, current, corresponding):
310
+ """Create the interface object
311
+
312
+ Args:
313
+ current (BaseApplication): Primary object of an interface
314
+ corresponding (BaseApplication): Secondary object of an interface
315
+ """
316
+ self.name = current.name + '_' + corresponding.name
317
+ self.current = current
318
+ self.corresponding = corresponding
319
+
320
+ @staticmethod
321
+ def get_directional_signals(producer, consumer):
322
+ """Get signals going from producer to consumer
323
+
324
+ Args:
325
+ producer (BaseApplication): producer of the signals
326
+ consumer (BaseApplication): consumer of the signals
327
+ Returns:
328
+ signals (list): Signals sent from producer and received in consumer
329
+ """
330
+ outsignals = {signal.name: signal for signal in producer.outsignals}
331
+ signals = []
332
+ for signal in consumer.insignals:
333
+ if signal.name in outsignals:
334
+ signal.set_producer(outsignals[signal.name].producer)
335
+ signal.add_application(producer)
336
+ signal.add_application(consumer)
337
+ signals.append(signal)
338
+ return signals
339
+
340
+ def get_produced_signals(self, producer_name):
341
+ """Get signals going from producer to consumer
342
+
343
+ This function can be used if you are lacking some objects
344
+
345
+ Args:
346
+ consumer_name (string): name of the consumer of the signals
347
+ Returns:
348
+ signals (list): Signals sent from producer and received in consumer
349
+ """
350
+ if producer_name == self.current.name:
351
+ consumer = self.corresponding
352
+ producer = self.current
353
+ elif producer_name == self.corresponding.name:
354
+ consumer = self.current
355
+ producer = self.corresponding
356
+ else:
357
+ LOGGER.error('%s not in [%s, %s]',
358
+ producer_name,
359
+ self.current.name,
360
+ self.corresponding.name)
361
+ return self.get_directional_signals(producer, consumer)
362
+
363
+ def get_consumed_signals(self, consumer_name):
364
+ """Get signals going from producer to consumer
365
+
366
+ This function can be used if you are lacking some objects
367
+
368
+ Args:
369
+ consumer_name (string): name of the consumer of the signals
370
+ Returns:
371
+ signals (list): Signals sent from producer and received in consumer
372
+ """
373
+ if consumer_name == self.current.name:
374
+ consumer = self.current
375
+ producer = self.corresponding
376
+ elif consumer_name == self.corresponding.name:
377
+ consumer = self.corresponding
378
+ producer = self.current
379
+ else:
380
+ LOGGER.error('%s not in [%s, %s]',
381
+ consumer_name,
382
+ self.current.name,
383
+ self.corresponding.name)
384
+ return self.get_directional_signals(producer, consumer)
385
+
386
+
387
+ class Domain:
388
+ """Domain with interacting interfaces"""
389
+ def __repr__(self):
390
+ """String representation for logging and debugging
391
+
392
+ Returns:
393
+ repr (string): Name of the domain, and all clients for that domain
394
+ """
395
+ return f"<{self.name}: {self.clients}>"
396
+
397
+ def __init__(self):
398
+ """Initialize the object"""
399
+ self.name = ''
400
+ self.signals = {}
401
+ self.clients = set()
402
+ self._clients = {}
403
+
404
+ def set_name(self, name):
405
+ """Set the name of the domain
406
+
407
+ Args:
408
+ name (string): Name of the domain
409
+ """
410
+ self.name = name
411
+
412
+ def add_interface(self, interface):
413
+ """Add an interface to a domain
414
+
415
+ Args:
416
+ interface (Interface): Interface object
417
+ """
418
+ self._process_interface(interface)
419
+
420
+ def _process_interface(self, interface):
421
+ """Process interface to add signals to the domain
422
+
423
+ Args:
424
+ interface (Interface): Interface object
425
+ """
426
+ for signal in interface.get_directional_signals(interface.current, interface.corresponding):
427
+ self._process_signal(signal.name, interface.current, interface.corresponding)
428
+ for signal in interface.get_directional_signals(interface.corresponding, interface.current):
429
+ self._process_signal(signal.name, interface.corresponding, interface.current)
430
+
431
+ def _process_signal(self, signal_name, producer, consumer):
432
+ """Process signal to add to the domain
433
+
434
+ Args:
435
+ signal_name (string): Name of the signal
436
+ consumer (BaseApplication): Consumer application of the signal
437
+ producer (BaseApplication): Producer application of the signal
438
+ """
439
+ if signal_name not in self.signals:
440
+ self.signals[signal_name] = Signal(signal_name, producer)
441
+ self.signals[signal_name].consumers = consumer.name
442
+ self.signals[signal_name].add_application(producer)
443
+ self.signals[signal_name].add_application(consumer)
444
+ signal = self.signals[signal_name]
445
+ if producer.name not in self.clients:
446
+ self.clients.add(producer.name)
447
+ self._clients[producer.name] = {'producer': [], 'consumer': []}
448
+ self._clients[producer.name]['producer'].append(signal)
449
+ if consumer.name not in self.clients:
450
+ self.clients.add(producer.name)
451
+ self._clients[producer.name] = {'producer': [], 'consumer': []}
452
+ self._clients[producer.name]['consumer'].append(signal)
453
+ signal.consumers = consumer.name
454
+ try:
455
+ signal.producer = producer.name
456
+ except MultipleProducersError as mpe:
457
+ LOGGER.debug(mpe.message)
458
+
459
+ def create_groups(self):
460
+ """Create groups of signals going from each producer
461
+
462
+ Returns:
463
+ signal_groups (dict): Signal groups
464
+ """
465
+ signal_groups = {}
466
+ for signal in self.signals.values():
467
+ # Producer is always a set, to handle pass-through signals
468
+ for producer in signal.producer - set(signal_groups.keys()):
469
+ signal_groups[producer] = []
470
+ for producer in signal.producer:
471
+ signal_groups[producer].append(signal)
472
+ return signal_groups
473
+
474
+ def create_selective_groups(self, a_names, b_names):
475
+ """Create groups for the a_list communicating with the b_names
476
+
477
+ Returns:
478
+ signal_groups (dict): Signal groups
479
+ """
480
+ signal_groups = {name: {'consumer': [], 'producer': []} for name in a_names}
481
+ for signal in self.signals.values():
482
+ for producer in set(signal.producer):
483
+ if producer in a_names and signal.consumers & b_names:
484
+ signal_groups[producer]['producer'].append(signal)
485
+ for consumer in signal.consumers:
486
+ if consumer in a_names and set(signal.producer) & a_names:
487
+ signal_groups[consumer]['consumer'].append(signal)
488
+ return signal_groups
489
+
490
+ @staticmethod
491
+ def to_yaml(spec, output):
492
+ """Writes spec to yaml file
493
+
494
+ Args:
495
+ spec (dict): data for the yaml
496
+ output (Path): file to write to
497
+ """
498
+ with open(output, 'w', encoding="utf-8") as yaml_file:
499
+ yaml = YAML()
500
+ yaml.dump(spec, yaml_file)