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,677 +1,677 @@
1
- # Copyright 2024 Volvo Car Corporation
2
- # Licensed under Apache 2.0.
3
-
4
- """Python module used for reading device proxy arxml:s"""
5
- from ruamel.yaml import YAML
6
- import enum
7
- from powertrain_build.interface.base import BaseApplication, Signal
8
- from powertrain_build.lib import logger
9
-
10
- LOGGER = logger.create_logger("device_proxy")
11
-
12
-
13
- class MissingDevice(Exception):
14
- """Exception to raise when device is missing"""
15
- def __init__(self, dp):
16
- self.message = f"Device proxy {dp} missing from deviceDomains.json"
17
-
18
-
19
- class BadYamlFormat(Exception):
20
- """Exception to raise when in/out signal is not defined."""
21
- def __init__(self, message):
22
- self.message = message
23
-
24
-
25
- class DPAL(BaseApplication):
26
- """Device Proxy abstraction layer"""
27
-
28
- dp_position = enum.Enum(
29
- "Position",
30
- names=[
31
- "domain",
32
- "property",
33
- "variable_type",
34
- "property_interface_type",
35
- "property_manifest_type",
36
- "offset",
37
- "factor",
38
- "default",
39
- "length",
40
- "min",
41
- "max",
42
- "enum",
43
- "init",
44
- "description",
45
- "unit",
46
- "group",
47
- "strategy",
48
- "debug",
49
- "dependability",
50
- "port_name"
51
- ],
52
- )
53
-
54
- def __repr__(self):
55
- """String representation of DPAL"""
56
- return (
57
- f"<DPAL {self.name}"
58
- f" app_side insignals: {len(self.signal_names['other']['insignals'])}"
59
- f" app_side outsignals: {len(self.signal_names['other']['outsignals'])}>"
60
- )
61
-
62
- def __init__(self, base_application):
63
- """Create the interface object
64
-
65
- Args:
66
- base_application (BaseApplication): Primary object of an interface
67
- Usually a raster, but can be an application or a model too.
68
- """
69
- self.name = ""
70
- self.dp_translations = {}
71
- # We do not care about domain when looking from a project perspective,
72
- # we only care when generating manifests for csp.
73
- self.domain_filter = None
74
- self.signal_names = {
75
- "dp": {"insignals": set(), "outsignals": set()},
76
- "other": {"insignals": set(), "outsignals": set()},
77
- }
78
- self.e2e_sts_signals = set()
79
- self.base_application = base_application
80
- self.translations_files = []
81
- self.device_domain = base_application.get_domain_mapping()
82
- self.signal_primitives_list = []
83
-
84
- def clear_signal_names(self):
85
- """Clear signal names
86
-
87
- Clears defined signal names (but not signal properties).
88
- """
89
- self.signal_names = {
90
- "dp": {"insignals": set(), "outsignals": set()},
91
- "other": {"insignals": set(), "outsignals": set()},
92
- }
93
-
94
- def add_signals(self, signals, signal_type="insignal", properties=[]):
95
- """Add signal names and properties
96
-
97
- Args:
98
- signals (list(Signals)): Signals to use
99
- signal_type (str): 'insignals' or 'outsignals'
100
- properties (list(str)): signal definition properties, default = []
101
- """
102
- opposite = {"insignals": "outsignals", "outsignals": "insignals"}
103
- dp_type = opposite[signal_type]
104
- for signal in signals:
105
- LOGGER.debug("Adding signal: %s", signal)
106
- temp_set = set()
107
- for translation in self.dp_translations.get(signal.name, []):
108
- temp_list = list(translation)
109
- domain = translation[self.dp_position.domain.value]
110
- group = translation[self.dp_position.group.value]
111
- dp_signal = translation[self.dp_position.property.value]
112
- self.check_signal_property(domain, group, dp_signal, signal_type)
113
- self.signal_names["dp"][dp_type].add(dp_signal)
114
- for enum_property in properties:
115
- LOGGER.debug("Modifying property: %s", enum_property)
116
- value = signal.properties[enum_property["source"]]
117
- if value == "-":
118
- value = enum_property["default"]
119
- temp_list[
120
- self.dp_position[enum_property["destination"]].value
121
- ] = value
122
- temp_set.add(tuple(temp_list))
123
- self.dp_translations[signal.name] = temp_set
124
- self.signal_names["other"][signal_type].add(signal.name)
125
- for e2e_sts_signal_name in self.e2e_sts_signals:
126
- if e2e_sts_signal_name not in self.signal_names["other"]["insignals"]:
127
- LOGGER.warning("E2E check signal %s not used in any model.", e2e_sts_signal_name)
128
- self.signal_names["other"][signal_type].add(e2e_sts_signal_name)
129
- self.check_groups()
130
- LOGGER.debug("Registered signal names: %s", self.signal_names)
131
-
132
- def check_signal_property(self, domain, group, property_name, signal_type):
133
- """Check if we have only one signal written for the same property.
134
-
135
- Args:
136
- domain (str): signal domain
137
- group (str): signal group
138
- property_name (str): signal property
139
- signal_type (str): 'insignals' or 'outsignals'
140
- """
141
- primitive_value = ""
142
- for value in [domain, group, property_name]:
143
- if value:
144
- if primitive_value == "":
145
- primitive_value = value
146
- else:
147
- primitive_value = primitive_value + '.' + value
148
- if primitive_value == "":
149
- raise Exception("The primitive does not contain any value!")
150
- directional_primitive = f"{primitive_value}.{signal_type}"
151
- self.check_property(directional_primitive, signal_type)
152
-
153
- def check_property(self, property_spec, signal_type):
154
- """Check if we have only one signal written for the same property.
155
-
156
- Args:
157
- property_spec (str): property specification
158
- signal_type (str): 'insignals' or 'outsignals'
159
- """
160
- if property_spec in self.signal_primitives_list:
161
- error_msg = (f"You can't write {property_spec} as "
162
- f"{signal_type} since this primitive has been used."
163
- " Run model_yaml_verification to identify exact models.")
164
- raise Exception(error_msg)
165
- self.signal_primitives_list.append(property_spec)
166
-
167
- def check_groups(self):
168
- """Check and crash if signal group contains both produces and consumes signals."""
169
- groups = {}
170
- for signal_name, signal_specs in self.dp_translations.items():
171
- if signal_name in self.signal_names["other"]['insignals']:
172
- consumed = True
173
- elif signal_name in self.signal_names["other"]['outsignals']:
174
- consumed = False
175
- else:
176
- continue
177
- for signal_spec in signal_specs:
178
- group = signal_spec[self.dp_position.group.value]
179
- if group is None:
180
- continue
181
- domain = signal_spec[self.dp_position.domain.value]
182
- key = (domain, group)
183
- if key not in groups:
184
- groups[key] = {"consumed": consumed,
185
- "signals": set()}
186
- groups[key]["signals"].add(signal_name)
187
- assert consumed == groups[key]["consumed"], \
188
- f"Signal group {group} for {domain} contains both consumed and produced signals"
189
-
190
- @staticmethod
191
- def read_translation(translation_file):
192
- """Read specification of the format:
193
-
194
- service:
195
- interface:
196
- properties:
197
- - endpoint_name:
198
- - signal: name
199
- property: name
200
- - signal: name
201
- property: name
202
- hal:
203
- hal_name:
204
- - primitive_endpoint:
205
- - insignal: name
206
- hal_name:
207
- - struct_endpoint:
208
- - insignal: name1
209
- property: member1
210
- - insignal: name2
211
- property: member2
212
- ecm:
213
- - signal: name
214
- signals:
215
- tvrl:
216
- - signal: name
217
- property: can_name
218
- offset: offset
219
- factor: scaling
220
-
221
- Args:
222
- translation_file (Path): file with specs
223
-
224
- Returns:
225
- yaml_data (dict): Loaded YAML data as dict, empty if not found
226
- """
227
- if not translation_file.is_file():
228
- LOGGER.warning("No file found for %s", translation_file)
229
- return {}
230
- with open(translation_file, encoding="utf-8") as translation:
231
- yaml = YAML(typ='safe', pure=True)
232
- raw = yaml.load(translation)
233
- return raw
234
-
235
- def parse_group_definitions(self, signal_groups):
236
- """Parse group definitions.
237
-
238
- Args:
239
- signal_groups (dict): parsed yaml file.
240
- """
241
- for dp_name, group_definitions in signal_groups.items():
242
- for group in group_definitions:
243
- port_name = None
244
- if 'portname' in group:
245
- port_name = group.pop('portname')
246
- for group_name, signals in group.items():
247
- self.parse_signal_definitions({dp_name: signals}, group_name, port_name)
248
-
249
- def parse_signal_definitions(self, signals_definition, group=None, port_name=None):
250
- """Parse signal definitions.
251
-
252
- Args:
253
- signals_definition (dict): parsed yaml file.
254
- group (str): Name of signal group, if signal belongs to a group.
255
- port_name (str): Name of signal port, if there is one.
256
- """
257
- enumerations = self.base_application.enumerations
258
- for dp_name, dp_specification in signals_definition.items():
259
- for specification in dp_specification:
260
- in_out_signal = [key for key in specification.keys() if 'signal' in key]
261
- base_signal = None
262
- signal_name = None
263
- if "in" in in_out_signal[0]:
264
- for signal in self.base_application.insignals:
265
- if signal.name == specification["insignal"]:
266
- base_signal = signal
267
- signal_name = signal.name
268
- elif "out" in in_out_signal[0]:
269
- for signal in self.base_application.outsignals:
270
- if signal.name == specification["outsignal"]:
271
- base_signal = signal
272
- signal_name = signal.name
273
- else:
274
- raise BadYamlFormat(f"in/out signal for {dp_name} is missing.")
275
- if base_signal is None:
276
- continue
277
- base_properties = self.base_application.get_signal_properties(
278
- base_signal
279
- )
280
- if base_properties["type"] in enumerations:
281
- underlying_data_type = enumerations[base_properties['type']]['underlying_data_type']
282
- interface_type = underlying_data_type
283
- manifest_type = underlying_data_type
284
- if 'init' not in specification:
285
- if enumerations[base_properties['type']]['default_value'] is not None:
286
- init_value = enumerations[base_properties['type']]['default_value']
287
- else:
288
- LOGGER.warning('Initializing enumeration %s to "zero".', base_properties['type'])
289
- init_value = [
290
- k for k, v in enumerations[base_properties['type']]['members'].items() if v == 0
291
- ][0]
292
- else:
293
- init_value = specification.get("init", 0)
294
- else:
295
- interface_type = base_properties["type"]
296
- manifest_type = base_properties["type"]
297
- init_value = specification.get("init", 0)
298
-
299
- if "out" in in_out_signal[0] and "strategy" in specification:
300
- LOGGER.warning('Cannot set read strategy for outsignal %s, using "Always".', signal_name)
301
- strategy = "Always"
302
- else:
303
- strategy = specification.get("strategy", "Always")
304
- if strategy not in self.read_strategies:
305
- LOGGER.warning('Invalid strategy %s, using "Always" instead.', strategy)
306
- strategy = "Always"
307
-
308
- if group is not None and specification.get("portname", None) is not None:
309
- raise BadYamlFormat(f"Port name should be on group level not signal level: {dp_name}")
310
- port_name_tmp = port_name if port_name is not None else specification.get("portname", None)
311
-
312
- is_safe_signal = specification.get("dependability", False)
313
-
314
- if signal_name not in self.dp_translations:
315
- self.dp_translations[signal_name] = set()
316
- domain = self._get_domain(dp_name)
317
- self.dp_translations[signal_name].add(
318
- (
319
- "enum_0", # read from this tuple using the dp_position enum. Enum starts at 1 though.
320
- domain,
321
- specification["property"],
322
- specification.get("type"),
323
- interface_type,
324
- manifest_type,
325
- specification.get("offset"),
326
- specification.get("factor"),
327
- specification.get("default"),
328
- specification.get("length"),
329
- specification.get("min"),
330
- specification.get("max"),
331
- specification.get("enum"),
332
- init_value,
333
- specification.get("description"),
334
- specification.get("unit"),
335
- group,
336
- strategy,
337
- specification.get("debug", False),
338
- is_safe_signal,
339
- port_name_tmp
340
- )
341
- )
342
-
343
- enable_e2e_sts = self.base_application.pybuild['build_cfg'].get_enable_end_to_end_status_signals()
344
- if enable_e2e_sts and is_safe_signal and group is not None:
345
- e2e_sts_property = f"{group}E2eSts"
346
- e2e_sts_signal_name = f"sVc{domain}_D_{e2e_sts_property}"
347
-
348
- if signal_name == e2e_sts_signal_name:
349
- raise BadYamlFormat(f"Don't put E2E status signals ({signal_name}) in yaml interface files.")
350
-
351
- if e2e_sts_signal_name not in self.dp_translations:
352
- self.dp_translations[e2e_sts_signal_name] = set()
353
- self.dp_translations[e2e_sts_signal_name].add(
354
- (
355
- "enum_0", # read from this tuple using the dp_position enum. Enum starts at 1 though.
356
- domain,
357
- e2e_sts_property,
358
- "UInt8",
359
- "UInt8",
360
- "UInt8",
361
- 0,
362
- 1,
363
- None,
364
- None,
365
- 0,
366
- 255,
367
- None,
368
- 255,
369
- f"E2E status code for E2E protected signal (group) {signal_name}.",
370
- None,
371
- group,
372
- strategy,
373
- False,
374
- is_safe_signal,
375
- port_name_tmp
376
- )
377
- )
378
- self.e2e_sts_signals.add(e2e_sts_signal_name)
379
-
380
- def parse_definition(self, definition):
381
- """Parses all definition files
382
-
383
- Args:
384
- definition (list(Path)): Definition files
385
- """
386
-
387
- for translation in definition:
388
- raw = self.read_translation(translation)
389
- self.parse_signal_definitions(raw.get("signals", {}))
390
- self.parse_group_definitions(raw.get("signal_groups", {}))
391
-
392
- def get_signal_properties(self, signal):
393
- """Get signal properties for signal
394
-
395
- Calls self.base_application to get signal properties
396
-
397
- Args:
398
- signal (Signal): Signal to get properties for
399
- """
400
- self.base_application.get_signal_properties(signal)
401
-
402
- def _get_signals(self):
403
- """Read signals"""
404
- self.parse_definition(self.translations_files)
405
-
406
- def _get_domain(self, device_proxy):
407
- """Get domain for device proxy
408
-
409
- Args:
410
- device_proxy (str): Name of device proxy
411
- Returns:
412
- domain (str): Name of the domain
413
- """
414
- if device_proxy not in self.device_domain:
415
- raise MissingDevice(device_proxy)
416
- return self.device_domain[device_proxy]
417
-
418
- def _allow_domain(self, domain):
419
- """Check if device proxy is in current domain_filter
420
-
421
- If there is no filter, the device is seen as part of the filter
422
-
423
- Args:
424
- domain (str): Name of the domain
425
- Returns:
426
- filtered (bool): True if device is not filtered away
427
- """
428
- return self.domain_filter is None or domain in self.domain_filter
429
-
430
- def get_signals(self, signal_type="insignals"):
431
- """Get signals to and from a dp abstraction
432
-
433
- If it is set to False, we look at the application side.
434
-
435
- Args:
436
- signal_type (str): insignals or outsignals
437
- Returns:
438
- signals (list): Signals in the interface
439
- """
440
- signal_names = self.signal_names["other"][signal_type]
441
-
442
- signals = []
443
- for name in self._allowed_names(signal_names):
444
- signals.append(Signal(name, self))
445
- return signals
446
-
447
- @property
448
- def insignals(self):
449
- """ Signals going to the device proxy. """
450
- return self.get_signals("insignals")
451
-
452
- @property
453
- def outsignals(self):
454
- """ Signals sent from the device proxy. """
455
- return self.get_signals("outsignals")
456
-
457
- def dp_spec_to_dict(self, signal_spec, signal_name):
458
- """Convert signal specification to dict.
459
-
460
- Args:
461
- signal_spec (tuple): Signal specification
462
- signal_name (str): Signal name
463
- Returns:
464
- signal_spec (dict): Signal specification
465
- """
466
- return {
467
- "variable": signal_name,
468
- "variable_type": signal_spec[self.dp_position.variable_type.value],
469
- "property_type": signal_spec[self.dp_position.property_interface_type.value],
470
- "domain": signal_spec[self.dp_position.domain.value],
471
- "default": signal_spec[self.dp_position.default.value],
472
- "length": signal_spec[self.dp_position.length.value],
473
- "property": signal_spec[self.dp_position.property.value],
474
- "offset": signal_spec[self.dp_position.offset.value],
475
- "factor": signal_spec[self.dp_position.factor.value],
476
- "range": {
477
- "min": signal_spec[self.dp_position.min.value],
478
- "max": signal_spec[self.dp_position.max.value],
479
- },
480
- "init": signal_spec[self.dp_position.init.value],
481
- "description": signal_spec[self.dp_position.description.value],
482
- "unit": signal_spec[self.dp_position.unit.value],
483
- "group": signal_spec[self.dp_position.group.value],
484
- "strategy": signal_spec[self.dp_position.strategy.value],
485
- "debug": signal_spec[self.dp_position.debug.value],
486
- "dependability": signal_spec[self.dp_position.dependability.value],
487
- "port_name": signal_spec[self.dp_position.port_name.value]
488
- }
489
-
490
- @classmethod
491
- def dp_spec_for_manifest(cls, signal_spec):
492
- """Convert signal specification to dict for a signal manifest.
493
-
494
- Args:
495
- signal_spec (tuple): Signal specification
496
- Returns:
497
- signal_spec (dict): Signal specification
498
- """
499
- spec = {
500
- "name": signal_spec[cls.dp_position.property.value],
501
- "type": signal_spec[cls.dp_position.property_manifest_type.value],
502
- }
503
- for key, value in {
504
- "default": cls.dp_position.default.value,
505
- "length": cls.dp_position.length.value,
506
- "enum": cls.dp_position.enum.value,
507
- "description": cls.dp_position.description.value,
508
- "unit": cls.dp_position.unit.value,
509
- }.items():
510
- if signal_spec[value] is not None:
511
- spec[key] = signal_spec[value]
512
- if (
513
- signal_spec[cls.dp_position.min.value] is not None
514
- and signal_spec[cls.dp_position.max.value] is not None
515
- and cls.dp_position.enum.value is not None
516
- ):
517
- spec["range"] = {
518
- "min": signal_spec[cls.dp_position.min.value],
519
- "max": signal_spec[cls.dp_position.max.value],
520
- }
521
- return spec
522
-
523
- def to_dict(self):
524
- """Method to generate dict to be saved as yaml
525
-
526
- Returns:
527
- spec (dict): Signalling specification
528
- """
529
- spec = {"consumer": [], "producer": []}
530
- for signal_name, signal_spec in self._allowed_names_and_specifications(
531
- self.signal_names["other"]["insignals"]):
532
- spec['consumer'].append(
533
- self.dp_spec_to_dict(signal_spec, signal_name)
534
- )
535
- for signal_name, signal_spec in self._allowed_names_and_specifications(
536
- self.signal_names["other"]["outsignals"]):
537
- spec['producer'].append(
538
- self.dp_spec_to_dict(signal_spec, signal_name)
539
- )
540
- return spec
541
-
542
- def to_manifest(self, client_name):
543
- """Method to generate dict to be saved as yaml
544
- Args:
545
- client_name (str): Name of the client in signal comm
546
- Returns:
547
- spec (dict): Signal manifest for using a Device proxy
548
- """
549
- manifest = {"name": client_name}
550
- manifest["consumes"] = self.insignals_dp_manifest(client_name)
551
- manifest["produces"] = self.outsignals_dp_manifest(client_name)
552
- manifest = self.cleanup_dp_manifest(manifest)
553
- if "consumes" not in manifest and "produces" not in manifest:
554
- return None
555
- return {"signal_info": {"version": 0.2, "clients": [manifest]}}
556
-
557
- def _generator(self, signal_names, unique_names=False):
558
- """Iterate over signals for allowed devices
559
-
560
- If unique_names is True, the iterator does not yield the same signal twice
561
- if unique_names is False, it yields each allowed signal spec with the signal name
562
-
563
- Args:
564
- signal_names (list): allowed signals
565
- Yields:
566
- name (str): Name of the signal
567
- specification (dict): signal specification for allowed device
568
- """
569
- for signal_name, specifications in (
570
- (name, spec) for name, spec in self.dp_translations.items()
571
- if name in signal_names):
572
- for specification in (
573
- spec for spec in specifications
574
- if self._allow_domain(spec[self.dp_position.domain.value])):
575
- if unique_names:
576
- yield signal_name, specification
577
- break
578
- yield signal_name, specification
579
-
580
- def _allowed_names(self, signal_names):
581
- """ Iterate over signal names for allowed devices
582
-
583
- Args:
584
- signal_names (list): allowed signals
585
- Yields:
586
- name (str): Signal name
587
- """
588
- for name, _ in self._generator(signal_names, unique_names=True):
589
- yield name
590
-
591
- def _allowed_specifications(self, signal_names):
592
- """ Iterate over signal specifications for allowed devices
593
-
594
- Args:
595
- signal_names (list): allowed signals
596
- Yields:
597
- specification (dict): Specification for a signal for an allowed device
598
- """
599
- for _, spec in self._generator(signal_names, unique_names=False):
600
- yield spec
601
-
602
- def _allowed_names_and_specifications(self, signal_names):
603
- """ Iterate over signal specifications for allowed devices
604
-
605
- Args:
606
- signal_names (list): allowed signals
607
- Yields:
608
- name (str): Signal name
609
- specification (dict): Specification for the signal for an allowed device
610
- """
611
- for name, spec in self._generator(signal_names, unique_names=False):
612
- yield name, spec
613
-
614
- def insignals_dp_manifest(self, client_name):
615
- """ Create consumes part of manifest for reading signals from device proxies
616
- Args:
617
- client_name (str): Name of the client in signal comm
618
- """
619
- consumes = [{"name": client_name, "signal_groups": []}]
620
- signal_names = self.signal_names["other"]["insignals"]
621
- consumed_groups = set()
622
- for signal_spec in self._allowed_specifications(signal_names):
623
- group = signal_spec[self.dp_position.group.value]
624
- if group is not None:
625
- consumed_groups.add(group)
626
- else:
627
- consumes[0]["signal_groups"].append(
628
- {"name": signal_spec[self.dp_position.property.value]}
629
- )
630
- for group in consumed_groups:
631
- consumes[0]["signal_groups"].append(
632
- {"name": group}
633
- )
634
- return consumes
635
-
636
- def outsignals_dp_manifest(self, client_name):
637
- """ Update manifests for writing signals to device proxies
638
- Args:
639
- client_name (str): Name of the client in signal comm
640
- """
641
- produces = [{"name": client_name, "signals": [], "signal_groups": []}]
642
- signal_names = self.signal_names["other"]["outsignals"]
643
- group_signals = {}
644
- for signal_spec in self._allowed_specifications(signal_names):
645
- group = signal_spec[self.dp_position.group.value]
646
- if group is not None:
647
- if group not in group_signals:
648
- group_signals[group] = []
649
- group_signals[group].append(
650
- self.dp_spec_for_manifest(signal_spec)
651
- )
652
- else:
653
- produces[0]["signals"].append(
654
- self.dp_spec_for_manifest(signal_spec)
655
- )
656
- for group_name, signals in group_signals.items():
657
- produces[0]["signal_groups"].append(
658
- {"name": group_name,
659
- "signals": list(signals)}
660
- )
661
- return produces
662
-
663
- @staticmethod
664
- def cleanup_dp_manifest(manifest):
665
- """ Remove empty device proxies.
666
- Args:
667
- manifest (dict): Device proxy configurations
668
- """
669
- if not manifest["produces"][0]["signal_groups"]:
670
- del manifest["produces"][0]["signal_groups"]
671
- if not manifest["produces"][0]["signals"]:
672
- del manifest["produces"][0]["signals"]
673
- if list(manifest["produces"][0].keys()) == ["name"]:
674
- del manifest["produces"]
675
- if not manifest["consumes"][0]["signal_groups"]:
676
- del manifest["consumes"]
677
- return manifest
1
+ # Copyright 2024 Volvo Car Corporation
2
+ # Licensed under Apache 2.0.
3
+
4
+ """Python module used for reading device proxy arxml:s"""
5
+ from ruamel.yaml import YAML
6
+ import enum
7
+ from powertrain_build.interface.base import BaseApplication, Signal
8
+ from powertrain_build.lib import logger
9
+
10
+ LOGGER = logger.create_logger("device_proxy")
11
+
12
+
13
+ class MissingDevice(Exception):
14
+ """Exception to raise when device is missing"""
15
+ def __init__(self, dp):
16
+ self.message = f"Device proxy {dp} missing from deviceDomains.json"
17
+
18
+
19
+ class BadYamlFormat(Exception):
20
+ """Exception to raise when in/out signal is not defined."""
21
+ def __init__(self, message):
22
+ self.message = message
23
+
24
+
25
+ class DPAL(BaseApplication):
26
+ """Device Proxy abstraction layer"""
27
+
28
+ dp_position = enum.Enum(
29
+ "Position",
30
+ names=[
31
+ "domain",
32
+ "property",
33
+ "variable_type",
34
+ "property_interface_type",
35
+ "property_manifest_type",
36
+ "offset",
37
+ "factor",
38
+ "default",
39
+ "length",
40
+ "min",
41
+ "max",
42
+ "enum",
43
+ "init",
44
+ "description",
45
+ "unit",
46
+ "group",
47
+ "strategy",
48
+ "debug",
49
+ "dependability",
50
+ "port_name"
51
+ ],
52
+ )
53
+
54
+ def __repr__(self):
55
+ """String representation of DPAL"""
56
+ return (
57
+ f"<DPAL {self.name}"
58
+ f" app_side insignals: {len(self.signal_names['other']['insignals'])}"
59
+ f" app_side outsignals: {len(self.signal_names['other']['outsignals'])}>"
60
+ )
61
+
62
+ def __init__(self, base_application):
63
+ """Create the interface object
64
+
65
+ Args:
66
+ base_application (BaseApplication): Primary object of an interface
67
+ Usually a raster, but can be an application or a model too.
68
+ """
69
+ self.name = ""
70
+ self.dp_translations = {}
71
+ # We do not care about domain when looking from a project perspective,
72
+ # we only care when generating manifests for csp.
73
+ self.domain_filter = None
74
+ self.signal_names = {
75
+ "dp": {"insignals": set(), "outsignals": set()},
76
+ "other": {"insignals": set(), "outsignals": set()},
77
+ }
78
+ self.e2e_sts_signals = set()
79
+ self.base_application = base_application
80
+ self.translations_files = []
81
+ self.device_domain = base_application.get_domain_mapping()
82
+ self.signal_primitives_list = []
83
+
84
+ def clear_signal_names(self):
85
+ """Clear signal names
86
+
87
+ Clears defined signal names (but not signal properties).
88
+ """
89
+ self.signal_names = {
90
+ "dp": {"insignals": set(), "outsignals": set()},
91
+ "other": {"insignals": set(), "outsignals": set()},
92
+ }
93
+
94
+ def add_signals(self, signals, signal_type="insignal", properties=[]):
95
+ """Add signal names and properties
96
+
97
+ Args:
98
+ signals (list(Signals)): Signals to use
99
+ signal_type (str): 'insignals' or 'outsignals'
100
+ properties (list(str)): signal definition properties, default = []
101
+ """
102
+ opposite = {"insignals": "outsignals", "outsignals": "insignals"}
103
+ dp_type = opposite[signal_type]
104
+ for signal in signals:
105
+ LOGGER.debug("Adding signal: %s", signal)
106
+ temp_set = set()
107
+ for translation in self.dp_translations.get(signal.name, []):
108
+ temp_list = list(translation)
109
+ domain = translation[self.dp_position.domain.value]
110
+ group = translation[self.dp_position.group.value]
111
+ dp_signal = translation[self.dp_position.property.value]
112
+ self.check_signal_property(domain, group, dp_signal, signal_type)
113
+ self.signal_names["dp"][dp_type].add(dp_signal)
114
+ for enum_property in properties:
115
+ LOGGER.debug("Modifying property: %s", enum_property)
116
+ value = signal.properties[enum_property["source"]]
117
+ if value == "-":
118
+ value = enum_property["default"]
119
+ temp_list[
120
+ self.dp_position[enum_property["destination"]].value
121
+ ] = value
122
+ temp_set.add(tuple(temp_list))
123
+ self.dp_translations[signal.name] = temp_set
124
+ self.signal_names["other"][signal_type].add(signal.name)
125
+ for e2e_sts_signal_name in self.e2e_sts_signals:
126
+ if e2e_sts_signal_name not in self.signal_names["other"]["insignals"]:
127
+ LOGGER.warning("E2E check signal %s not used in any model.", e2e_sts_signal_name)
128
+ self.signal_names["other"][signal_type].add(e2e_sts_signal_name)
129
+ self.check_groups()
130
+ LOGGER.debug("Registered signal names: %s", self.signal_names)
131
+
132
+ def check_signal_property(self, domain, group, property_name, signal_type):
133
+ """Check if we have only one signal written for the same property.
134
+
135
+ Args:
136
+ domain (str): signal domain
137
+ group (str): signal group
138
+ property_name (str): signal property
139
+ signal_type (str): 'insignals' or 'outsignals'
140
+ """
141
+ primitive_value = ""
142
+ for value in [domain, group, property_name]:
143
+ if value:
144
+ if primitive_value == "":
145
+ primitive_value = value
146
+ else:
147
+ primitive_value = primitive_value + '.' + value
148
+ if primitive_value == "":
149
+ raise Exception("The primitive does not contain any value!")
150
+ directional_primitive = f"{primitive_value}.{signal_type}"
151
+ self.check_property(directional_primitive, signal_type)
152
+
153
+ def check_property(self, property_spec, signal_type):
154
+ """Check if we have only one signal written for the same property.
155
+
156
+ Args:
157
+ property_spec (str): property specification
158
+ signal_type (str): 'insignals' or 'outsignals'
159
+ """
160
+ if property_spec in self.signal_primitives_list:
161
+ error_msg = (f"You can't write {property_spec} as "
162
+ f"{signal_type} since this primitive has been used."
163
+ " Run model_yaml_verification to identify exact models.")
164
+ raise Exception(error_msg)
165
+ self.signal_primitives_list.append(property_spec)
166
+
167
+ def check_groups(self):
168
+ """Check and crash if signal group contains both produces and consumes signals."""
169
+ groups = {}
170
+ for signal_name, signal_specs in self.dp_translations.items():
171
+ if signal_name in self.signal_names["other"]['insignals']:
172
+ consumed = True
173
+ elif signal_name in self.signal_names["other"]['outsignals']:
174
+ consumed = False
175
+ else:
176
+ continue
177
+ for signal_spec in signal_specs:
178
+ group = signal_spec[self.dp_position.group.value]
179
+ if group is None:
180
+ continue
181
+ domain = signal_spec[self.dp_position.domain.value]
182
+ key = (domain, group)
183
+ if key not in groups:
184
+ groups[key] = {"consumed": consumed,
185
+ "signals": set()}
186
+ groups[key]["signals"].add(signal_name)
187
+ assert consumed == groups[key]["consumed"], \
188
+ f"Signal group {group} for {domain} contains both consumed and produced signals"
189
+
190
+ @staticmethod
191
+ def read_translation(translation_file):
192
+ """Read specification of the format:
193
+
194
+ service:
195
+ interface:
196
+ properties:
197
+ - endpoint_name:
198
+ - signal: name
199
+ property: name
200
+ - signal: name
201
+ property: name
202
+ hal:
203
+ hal_name:
204
+ - primitive_endpoint:
205
+ - insignal: name
206
+ hal_name:
207
+ - struct_endpoint:
208
+ - insignal: name1
209
+ property: member1
210
+ - insignal: name2
211
+ property: member2
212
+ ecm:
213
+ - signal: name
214
+ signals:
215
+ tvrl:
216
+ - signal: name
217
+ property: can_name
218
+ offset: offset
219
+ factor: scaling
220
+
221
+ Args:
222
+ translation_file (Path): file with specs
223
+
224
+ Returns:
225
+ yaml_data (dict): Loaded YAML data as dict, empty if not found
226
+ """
227
+ if not translation_file.is_file():
228
+ LOGGER.warning("No file found for %s", translation_file)
229
+ return {}
230
+ with open(translation_file, encoding="utf-8") as translation:
231
+ yaml = YAML(typ='safe', pure=True)
232
+ raw = yaml.load(translation)
233
+ return raw
234
+
235
+ def parse_group_definitions(self, signal_groups):
236
+ """Parse group definitions.
237
+
238
+ Args:
239
+ signal_groups (dict): parsed yaml file.
240
+ """
241
+ for dp_name, group_definitions in signal_groups.items():
242
+ for group in group_definitions:
243
+ port_name = None
244
+ if 'portname' in group:
245
+ port_name = group.pop('portname')
246
+ for group_name, signals in group.items():
247
+ self.parse_signal_definitions({dp_name: signals}, group_name, port_name)
248
+
249
+ def parse_signal_definitions(self, signals_definition, group=None, port_name=None):
250
+ """Parse signal definitions.
251
+
252
+ Args:
253
+ signals_definition (dict): parsed yaml file.
254
+ group (str): Name of signal group, if signal belongs to a group.
255
+ port_name (str): Name of signal port, if there is one.
256
+ """
257
+ enumerations = self.base_application.enumerations
258
+ for dp_name, dp_specification in signals_definition.items():
259
+ for specification in dp_specification:
260
+ in_out_signal = [key for key in specification.keys() if 'signal' in key]
261
+ base_signal = None
262
+ signal_name = None
263
+ if "in" in in_out_signal[0]:
264
+ for signal in self.base_application.insignals:
265
+ if signal.name == specification["insignal"]:
266
+ base_signal = signal
267
+ signal_name = signal.name
268
+ elif "out" in in_out_signal[0]:
269
+ for signal in self.base_application.outsignals:
270
+ if signal.name == specification["outsignal"]:
271
+ base_signal = signal
272
+ signal_name = signal.name
273
+ else:
274
+ raise BadYamlFormat(f"in/out signal for {dp_name} is missing.")
275
+ if base_signal is None:
276
+ continue
277
+ base_properties = self.base_application.get_signal_properties(
278
+ base_signal
279
+ )
280
+ if base_properties["type"] in enumerations:
281
+ underlying_data_type = enumerations[base_properties['type']]['underlying_data_type']
282
+ interface_type = underlying_data_type
283
+ manifest_type = underlying_data_type
284
+ if 'init' not in specification:
285
+ if enumerations[base_properties['type']]['default_value'] is not None:
286
+ init_value = enumerations[base_properties['type']]['default_value']
287
+ else:
288
+ LOGGER.warning('Initializing enumeration %s to "zero".', base_properties['type'])
289
+ init_value = [
290
+ k for k, v in enumerations[base_properties['type']]['members'].items() if v == 0
291
+ ][0]
292
+ else:
293
+ init_value = specification.get("init", 0)
294
+ else:
295
+ interface_type = base_properties["type"]
296
+ manifest_type = base_properties["type"]
297
+ init_value = specification.get("init", 0)
298
+
299
+ if "out" in in_out_signal[0] and "strategy" in specification:
300
+ LOGGER.warning('Cannot set read strategy for outsignal %s, using "Always".', signal_name)
301
+ strategy = "Always"
302
+ else:
303
+ strategy = specification.get("strategy", "Always")
304
+ if strategy not in self.read_strategies:
305
+ LOGGER.warning('Invalid strategy %s, using "Always" instead.', strategy)
306
+ strategy = "Always"
307
+
308
+ if group is not None and specification.get("portname", None) is not None:
309
+ raise BadYamlFormat(f"Port name should be on group level not signal level: {dp_name}")
310
+ port_name_tmp = port_name if port_name is not None else specification.get("portname", None)
311
+
312
+ is_safe_signal = specification.get("dependability", False)
313
+
314
+ if signal_name not in self.dp_translations:
315
+ self.dp_translations[signal_name] = set()
316
+ domain = self._get_domain(dp_name)
317
+ self.dp_translations[signal_name].add(
318
+ (
319
+ "enum_0", # read from this tuple using the dp_position enum. Enum starts at 1 though.
320
+ domain,
321
+ specification["property"],
322
+ specification.get("type"),
323
+ interface_type,
324
+ manifest_type,
325
+ specification.get("offset"),
326
+ specification.get("factor"),
327
+ specification.get("default"),
328
+ specification.get("length"),
329
+ specification.get("min"),
330
+ specification.get("max"),
331
+ specification.get("enum"),
332
+ init_value,
333
+ specification.get("description"),
334
+ specification.get("unit"),
335
+ group,
336
+ strategy,
337
+ specification.get("debug", False),
338
+ is_safe_signal,
339
+ port_name_tmp
340
+ )
341
+ )
342
+
343
+ enable_e2e_sts = self.base_application.pybuild['build_cfg'].get_enable_end_to_end_status_signals()
344
+ if enable_e2e_sts and is_safe_signal and group is not None:
345
+ e2e_sts_property = f"{group}E2eSts"
346
+ e2e_sts_signal_name = f"sVc{domain}_D_{e2e_sts_property}"
347
+
348
+ if signal_name == e2e_sts_signal_name:
349
+ raise BadYamlFormat(f"Don't put E2E status signals ({signal_name}) in yaml interface files.")
350
+
351
+ if e2e_sts_signal_name not in self.dp_translations:
352
+ self.dp_translations[e2e_sts_signal_name] = set()
353
+ self.dp_translations[e2e_sts_signal_name].add(
354
+ (
355
+ "enum_0", # read from this tuple using the dp_position enum. Enum starts at 1 though.
356
+ domain,
357
+ e2e_sts_property,
358
+ "UInt8",
359
+ "UInt8",
360
+ "UInt8",
361
+ 0,
362
+ 1,
363
+ None,
364
+ None,
365
+ 0,
366
+ 255,
367
+ None,
368
+ 255,
369
+ f"E2E status code for E2E protected signal (group) {signal_name}.",
370
+ None,
371
+ group,
372
+ strategy,
373
+ False,
374
+ is_safe_signal,
375
+ port_name_tmp
376
+ )
377
+ )
378
+ self.e2e_sts_signals.add(e2e_sts_signal_name)
379
+
380
+ def parse_definition(self, definition):
381
+ """Parses all definition files
382
+
383
+ Args:
384
+ definition (list(Path)): Definition files
385
+ """
386
+
387
+ for translation in definition:
388
+ raw = self.read_translation(translation)
389
+ self.parse_signal_definitions(raw.get("signals", {}))
390
+ self.parse_group_definitions(raw.get("signal_groups", {}))
391
+
392
+ def get_signal_properties(self, signal):
393
+ """Get signal properties for signal
394
+
395
+ Calls self.base_application to get signal properties
396
+
397
+ Args:
398
+ signal (Signal): Signal to get properties for
399
+ """
400
+ self.base_application.get_signal_properties(signal)
401
+
402
+ def _get_signals(self):
403
+ """Read signals"""
404
+ self.parse_definition(self.translations_files)
405
+
406
+ def _get_domain(self, device_proxy):
407
+ """Get domain for device proxy
408
+
409
+ Args:
410
+ device_proxy (str): Name of device proxy
411
+ Returns:
412
+ domain (str): Name of the domain
413
+ """
414
+ if device_proxy not in self.device_domain:
415
+ raise MissingDevice(device_proxy)
416
+ return self.device_domain[device_proxy]
417
+
418
+ def _allow_domain(self, domain):
419
+ """Check if device proxy is in current domain_filter
420
+
421
+ If there is no filter, the device is seen as part of the filter
422
+
423
+ Args:
424
+ domain (str): Name of the domain
425
+ Returns:
426
+ filtered (bool): True if device is not filtered away
427
+ """
428
+ return self.domain_filter is None or domain in self.domain_filter
429
+
430
+ def get_signals(self, signal_type="insignals"):
431
+ """Get signals to and from a dp abstraction
432
+
433
+ If it is set to False, we look at the application side.
434
+
435
+ Args:
436
+ signal_type (str): insignals or outsignals
437
+ Returns:
438
+ signals (list): Signals in the interface
439
+ """
440
+ signal_names = self.signal_names["other"][signal_type]
441
+
442
+ signals = []
443
+ for name in self._allowed_names(signal_names):
444
+ signals.append(Signal(name, self))
445
+ return signals
446
+
447
+ @property
448
+ def insignals(self):
449
+ """ Signals going to the device proxy. """
450
+ return self.get_signals("insignals")
451
+
452
+ @property
453
+ def outsignals(self):
454
+ """ Signals sent from the device proxy. """
455
+ return self.get_signals("outsignals")
456
+
457
+ def dp_spec_to_dict(self, signal_spec, signal_name):
458
+ """Convert signal specification to dict.
459
+
460
+ Args:
461
+ signal_spec (tuple): Signal specification
462
+ signal_name (str): Signal name
463
+ Returns:
464
+ signal_spec (dict): Signal specification
465
+ """
466
+ return {
467
+ "variable": signal_name,
468
+ "variable_type": signal_spec[self.dp_position.variable_type.value],
469
+ "property_type": signal_spec[self.dp_position.property_interface_type.value],
470
+ "domain": signal_spec[self.dp_position.domain.value],
471
+ "default": signal_spec[self.dp_position.default.value],
472
+ "length": signal_spec[self.dp_position.length.value],
473
+ "property": signal_spec[self.dp_position.property.value],
474
+ "offset": signal_spec[self.dp_position.offset.value],
475
+ "factor": signal_spec[self.dp_position.factor.value],
476
+ "range": {
477
+ "min": signal_spec[self.dp_position.min.value],
478
+ "max": signal_spec[self.dp_position.max.value],
479
+ },
480
+ "init": signal_spec[self.dp_position.init.value],
481
+ "description": signal_spec[self.dp_position.description.value],
482
+ "unit": signal_spec[self.dp_position.unit.value],
483
+ "group": signal_spec[self.dp_position.group.value],
484
+ "strategy": signal_spec[self.dp_position.strategy.value],
485
+ "debug": signal_spec[self.dp_position.debug.value],
486
+ "dependability": signal_spec[self.dp_position.dependability.value],
487
+ "port_name": signal_spec[self.dp_position.port_name.value]
488
+ }
489
+
490
+ @classmethod
491
+ def dp_spec_for_manifest(cls, signal_spec):
492
+ """Convert signal specification to dict for a signal manifest.
493
+
494
+ Args:
495
+ signal_spec (tuple): Signal specification
496
+ Returns:
497
+ signal_spec (dict): Signal specification
498
+ """
499
+ spec = {
500
+ "name": signal_spec[cls.dp_position.property.value],
501
+ "type": signal_spec[cls.dp_position.property_manifest_type.value],
502
+ }
503
+ for key, value in {
504
+ "default": cls.dp_position.default.value,
505
+ "length": cls.dp_position.length.value,
506
+ "enum": cls.dp_position.enum.value,
507
+ "description": cls.dp_position.description.value,
508
+ "unit": cls.dp_position.unit.value,
509
+ }.items():
510
+ if signal_spec[value] is not None:
511
+ spec[key] = signal_spec[value]
512
+ if (
513
+ signal_spec[cls.dp_position.min.value] is not None
514
+ and signal_spec[cls.dp_position.max.value] is not None
515
+ and cls.dp_position.enum.value is not None
516
+ ):
517
+ spec["range"] = {
518
+ "min": signal_spec[cls.dp_position.min.value],
519
+ "max": signal_spec[cls.dp_position.max.value],
520
+ }
521
+ return spec
522
+
523
+ def to_dict(self):
524
+ """Method to generate dict to be saved as yaml
525
+
526
+ Returns:
527
+ spec (dict): Signalling specification
528
+ """
529
+ spec = {"consumer": [], "producer": []}
530
+ for signal_name, signal_spec in self._allowed_names_and_specifications(
531
+ self.signal_names["other"]["insignals"]):
532
+ spec['consumer'].append(
533
+ self.dp_spec_to_dict(signal_spec, signal_name)
534
+ )
535
+ for signal_name, signal_spec in self._allowed_names_and_specifications(
536
+ self.signal_names["other"]["outsignals"]):
537
+ spec['producer'].append(
538
+ self.dp_spec_to_dict(signal_spec, signal_name)
539
+ )
540
+ return spec
541
+
542
+ def to_manifest(self, client_name):
543
+ """Method to generate dict to be saved as yaml
544
+ Args:
545
+ client_name (str): Name of the client in signal comm
546
+ Returns:
547
+ spec (dict): Signal manifest for using a Device proxy
548
+ """
549
+ manifest = {"name": client_name}
550
+ manifest["consumes"] = self.insignals_dp_manifest(client_name)
551
+ manifest["produces"] = self.outsignals_dp_manifest(client_name)
552
+ manifest = self.cleanup_dp_manifest(manifest)
553
+ if "consumes" not in manifest and "produces" not in manifest:
554
+ return None
555
+ return {"signal_info": {"version": 0.2, "clients": [manifest]}}
556
+
557
+ def _generator(self, signal_names, unique_names=False):
558
+ """Iterate over signals for allowed devices
559
+
560
+ If unique_names is True, the iterator does not yield the same signal twice
561
+ if unique_names is False, it yields each allowed signal spec with the signal name
562
+
563
+ Args:
564
+ signal_names (list): allowed signals
565
+ Yields:
566
+ name (str): Name of the signal
567
+ specification (dict): signal specification for allowed device
568
+ """
569
+ for signal_name, specifications in (
570
+ (name, spec) for name, spec in self.dp_translations.items()
571
+ if name in signal_names):
572
+ for specification in (
573
+ spec for spec in specifications
574
+ if self._allow_domain(spec[self.dp_position.domain.value])):
575
+ if unique_names:
576
+ yield signal_name, specification
577
+ break
578
+ yield signal_name, specification
579
+
580
+ def _allowed_names(self, signal_names):
581
+ """ Iterate over signal names for allowed devices
582
+
583
+ Args:
584
+ signal_names (list): allowed signals
585
+ Yields:
586
+ name (str): Signal name
587
+ """
588
+ for name, _ in self._generator(signal_names, unique_names=True):
589
+ yield name
590
+
591
+ def _allowed_specifications(self, signal_names):
592
+ """ Iterate over signal specifications for allowed devices
593
+
594
+ Args:
595
+ signal_names (list): allowed signals
596
+ Yields:
597
+ specification (dict): Specification for a signal for an allowed device
598
+ """
599
+ for _, spec in self._generator(signal_names, unique_names=False):
600
+ yield spec
601
+
602
+ def _allowed_names_and_specifications(self, signal_names):
603
+ """ Iterate over signal specifications for allowed devices
604
+
605
+ Args:
606
+ signal_names (list): allowed signals
607
+ Yields:
608
+ name (str): Signal name
609
+ specification (dict): Specification for the signal for an allowed device
610
+ """
611
+ for name, spec in self._generator(signal_names, unique_names=False):
612
+ yield name, spec
613
+
614
+ def insignals_dp_manifest(self, client_name):
615
+ """ Create consumes part of manifest for reading signals from device proxies
616
+ Args:
617
+ client_name (str): Name of the client in signal comm
618
+ """
619
+ consumes = [{"name": client_name, "signal_groups": []}]
620
+ signal_names = self.signal_names["other"]["insignals"]
621
+ consumed_groups = set()
622
+ for signal_spec in self._allowed_specifications(signal_names):
623
+ group = signal_spec[self.dp_position.group.value]
624
+ if group is not None:
625
+ consumed_groups.add(group)
626
+ else:
627
+ consumes[0]["signal_groups"].append(
628
+ {"name": signal_spec[self.dp_position.property.value]}
629
+ )
630
+ for group in consumed_groups:
631
+ consumes[0]["signal_groups"].append(
632
+ {"name": group}
633
+ )
634
+ return consumes
635
+
636
+ def outsignals_dp_manifest(self, client_name):
637
+ """ Update manifests for writing signals to device proxies
638
+ Args:
639
+ client_name (str): Name of the client in signal comm
640
+ """
641
+ produces = [{"name": client_name, "signals": [], "signal_groups": []}]
642
+ signal_names = self.signal_names["other"]["outsignals"]
643
+ group_signals = {}
644
+ for signal_spec in self._allowed_specifications(signal_names):
645
+ group = signal_spec[self.dp_position.group.value]
646
+ if group is not None:
647
+ if group not in group_signals:
648
+ group_signals[group] = []
649
+ group_signals[group].append(
650
+ self.dp_spec_for_manifest(signal_spec)
651
+ )
652
+ else:
653
+ produces[0]["signals"].append(
654
+ self.dp_spec_for_manifest(signal_spec)
655
+ )
656
+ for group_name, signals in group_signals.items():
657
+ produces[0]["signal_groups"].append(
658
+ {"name": group_name,
659
+ "signals": list(signals)}
660
+ )
661
+ return produces
662
+
663
+ @staticmethod
664
+ def cleanup_dp_manifest(manifest):
665
+ """ Remove empty device proxies.
666
+ Args:
667
+ manifest (dict): Device proxy configurations
668
+ """
669
+ if not manifest["produces"][0]["signal_groups"]:
670
+ del manifest["produces"][0]["signal_groups"]
671
+ if not manifest["produces"][0]["signals"]:
672
+ del manifest["produces"][0]["signals"]
673
+ if list(manifest["produces"][0].keys()) == ["name"]:
674
+ del manifest["produces"]
675
+ if not manifest["consumes"][0]["signal_groups"]:
676
+ del manifest["consumes"]
677
+ return manifest