olindgre 2ece01e1d7 Make powertrain-build not overlap with pybuild in site-packages
Change-Id: I7b59f3f04f0f787d35db0b9389f295bf1ad24f56
2024-09-17 10:25:04 +02:00

651 lines
30 KiB
Python

# Copyright 2024 Volvo Car Corporation
# Licensed under Apache 2.0.
# -*- coding: utf-8 -*-
"""Module for merging of a2l-files."""
import json
import os
import re
from string import Template
from powertrain_build.lib.helper_functions import deep_dict_update
from powertrain_build.problem_logger import ProblemLogger
from powertrain_build.a2l_templates import A2lProjectTemplate, A2lSilverTemplate
class A2lMerge(ProblemLogger):
"""Class for merging of a2l-files."""
def __init__(self, prj_cfg, ucfg, a2l_files_unit, a2l_files_gen):
"""Merge a2l-files based on provided project configuration.
Removes symbols not included in the projects unit-config files.
Args:
prj_cfg (obj): Project config.
ucfg (obj): Unit config.
a2l_files_unit (list of str): Files to merge.
a2l_files_gen (list of str): Files to merge.
"""
super().__init__()
self._prj_cfg = prj_cfg
self._unit_cfg = ucfg
self._per_unit_cfg = ucfg.get_per_unit_cfg()
# generate the a2l string
self._blks = {}
self._removed_symbols = []
self.a2l = ""
# ----- Example blocks in a2l (TargetLink) -----
#
# /begin CHARACTERISTIC
# cVc_B_SeriesHev /* Name */
# "Series hybrid" /* LongIdentifier */
# VALUE /* Type */
# 0x00000000 /* address: cVc_B_SeriesHev */
# UBYTE_COL_DIRECT /* Deposit */
# 0 /* MaxDiff */
# Scaling_3 /* Conversion */
# 0 /* LowerLimit */
# 1 /* UpperLimit */
# /end CHARACTERISTIC
#
# Example of Bosch-nvm signal in nvm:
#
# /begin MEASUREMENT
# nvm_list_32._sVcDclVu_D_Markow /* Name */
# "No description given" /* LongIdentifier */
# ULONG /* Datatype */
# VcNvm_1_0_None /* Conversion */
# 1 /* Resolution */
# 0 /* Accuracy */
# 0 /* LowerLimit */
# 4294967295 /* UpperLimit */
# READ_WRITE
# MATRIX_DIM 152 1 1
# ECU_ADDRESS 0x00000000
# /end MEASUREMENT
#
# ----- Example blocks in a2l (Embedded Coder) -----
#
# /begin MEASUREMENT
# /* Name */ sVcAesVe_md_VolmcOffs
# /* Long identifier */ "Volumetric cylinder mass flow offset"
# /* Data type */ FLOAT32_IEEE
# /* Conversion method */ VcAesVe_CM_Float32_g_s
# /* Resolution (Not used) */ 0
# /* Accuracy (Not used) */ 0
# /* Lower limit */ -100.0
# /* Upper limit */ 100.0
# ECU_ADDRESS 0x0000 /* @ECU_Address@sVcAesVe_md_VolmcOffs@ */
# /end MEASUREMENT
#
# /begin CHARACTERISTIC
# /* Name */ cVcAesVe_D_VolmcCmpSel
# /* Long Identifier */ "Select compensation factor characterizing deviation from nominal voleff"
# /* Type */ VALUE
# /* ECU Address */ 0x0000 /* @ECU_Address@cVcAesVe_D_VolmcCmpSel@ */
# /* Record Layout */ Scalar_UBYTE
# /* Maximum Difference */ 0
# /* Conversion Method */ VcAesVe_CM_uint8
# /* Lower Limit */ 1.0
# /* Upper Limit */ 3.0
# /end CHARACTERISTIC
self._block_finder = re.compile(r'(?:\s*\n)*' # Optional blank lines
r'(\s*/begin (\w+)\s*' # begin <something> block
r'\n\s*([\w.]+).*?\n' # label. (Bosch-nvm contains the .)
r'.*?' # block definition
r'/end\s+\2)', # end <something> block. Same something as before
flags=re.M | re.DOTALL)
self._tl_compu_method_parser = re.compile(
r'(?:\s*\n)*(?P<compu_method>'
r'\s*/begin COMPU_METHOD\s*\n'
r'\s*(?P<name>\w*)\s*(/\* Name \*/)?\s*\n' # Name
r'\s*"(?P<ID>.*?)"\s*(/\* LongIdentifier \*/.*?)\s*\n' # Long Identifier
r'\s*(?P<conv_type>[A-Z_]*).*\s*\n' # ConversionType
r'\s*"(?P<disp_format>.*)"\s*(/\* Format \*/)?\s*\n' # Format
r'\s*"(?P<unit>.*?)"\s*(/\* Unit \*/)?\s*\n' # Unit
r'\s*(?P<conversion>.*)\s*\n' # COEFFS
r'(?P<indentation>\s*)/end COMPU_METHOD)', flags=re.M) # No DOTALL, so .* is [^\n]*
# COMPU_METHOD parser that works with files generated by Embedded Coder
self._ec_compu_method_parser = re.compile(
r'(?:\s*\n)*(?P<compu_method>'
r'\s*/begin COMPU_METHOD\s*\n'
r'\s*(/\* Name of CompuMethod\s*\*/)?\s*(?P<name>\w*)\s*\n' # Name
r'\s*(/\* Long identifier\s*\*/)?\s*"(?P<ID>.*?)"\s*\n' # Long Identifier
r'\s*/\* Conversion Type\s*\*/\s*(?P<conv_type>[A-Z_]*)\s*\n' # ConversionType
r'\s*(/\* Format\s*\*/)?\s*"(?P<disp_format>.*?)"\s*\n' # Format
r'\s*(/\* Units\s*\*/)?\s*"(?P<unit>.*?)"\s*\n' # Unit
r'\s*/\* Coefficients\s*\*/\s*(?P<conversion>.*?)\s*\n' # COEFFS
r'(?P<indentation>\s*)/end COMPU_METHOD)', flags=re.M) # No DOTALL, so .* is [^\n]*
self._expr_block_meas_kp_blob_parser = re.compile(
r'\/begin\s+(?P<keyword>\w+)\s+(?P<class>\w+)\s*\n'
r'\s*KP_BLOB\s+(?P<address>0x[0-9a-fA-F]+)\s*\n'
r'\s*\/end\s+\1'
)
self._compu_methods = {}
self._included_compu_methods = []
self._tl_compu_method_template = Template(
'$indentation/begin COMPU_METHOD\n'
'$indentation $name /* Name */\n'
'$indentation "$ID" /* LongIdentifier */\n'
'$indentation $conv_type /* ConversionType */\n'
'$indentation "$disp_format" /* Format */\n'
'$indentation "$unit" /* Unit */\n'
'$indentation $conversion\n'
'$indentation/end COMPU_METHOD'
)
# COMPU_METHOD template that looks similar to COMPU_METHOD generated by Embedded Coder
self._ec_compu_method_template = Template(
'$indentation/begin COMPU_METHOD\n'
'$indentation /* Name of CompuMethod */ $name\n'
'$indentation /* Long identifier */ "$ID"\n'
'$indentation /* Conversion Type */ $conv_type\n'
'$indentation /* Format */ "$disp_format"\n'
'$indentation /* Units */ "$unit"\n'
'$indentation /* Coefficients */ $conversion\n'
'$indentation/end COMPU_METHOD'
)
for filename in a2l_files_unit:
removed_symbols = self._parse_unit(filename)
self._removed_symbols.extend(removed_symbols)
self.debug('Loaded %s', filename)
for filename in a2l_files_gen:
self._parse_gen(filename)
self.debug('Loaded %s', filename)
def _parse_unit(self, filename):
"""Parse the unit a2l-files and apply a filter to only active parameters."""
self.debug('Processing %s', filename)
with open(filename, 'r', encoding="ISO-8859-1") as a2lfp:
a2ld = a2lfp.read()
file_path_parts = os.path.split(filename)
unit = file_path_parts[1].split('.')[0]
base_path = file_path_parts[0]
dcl_match = re.search(r'VcDcl[\w]+Mdl(__[\w]+)', base_path)
if dcl_match is not None and 'Mdl' not in unit:
# Hand coded model names including "__" will lead to this, due to name mismatch of .a2l and .json files.
# E.g. VcDclPtrlMdl__denso:
# 1. config_VcDclPtrlMdl__denso.json vs VcDclPtrlMdl__denso.a2l.
# 1.1. Match: unit in self._per_unit_cfg.
# 2. config_VcDclPtrl__denso.json vs VcDclPtrl.a2l.
# 2.1. No match: unit not in self._per_unit_cfg.
old_unit = unit
unit = unit + dcl_match.group(1)
self.info(
'Found unit %s with .a2l and .json file name mismatch. Using new unit name: %s',
old_unit,
unit
)
if unit in self._per_unit_cfg:
u_conf = self._per_unit_cfg[unit]
code_generator = u_conf['code_generator'] if 'code_generator' in u_conf else 'target_link'
else:
u_conf = {}
code_generator = 'target_link'
if code_generator == 'embedded_coder':
blks = re.findall(r'(?:\s*\n)*(\s*/begin '
r'(?!PROJECT|HEADER|MODULE|MOD_PAR|MOD_COMMON)(\w+)\s*(?:\n\s*)?'
r'(?:/\*\s*[\w ]+\s*\*/\s*)?(\w+)([\[\d+\]]*).*?\n.*?/end\s+\2)',
a2ld, flags=re.M | re.DOTALL)
else:
blks = re.findall(r'(?:\s*\n)*(\s*/begin (?!PROJECT|MODULE)(\w+)[\n\s]*'
r'(\w+(?:\.\w+)?)([\[\d+\]]*).*?\n.*?/end\s+\2)', a2ld,
flags=re.M | re.DOTALL)
compu_method_translators = self._parse_compu_methods(a2ld, unit)
unit_blks = {}
removed_symbols = []
if unit not in self._per_unit_cfg:
# Handcoded a2l without json-files will lead to this.
# Add json files for the handcoded a2l!
# NOTE: Assuming TargetLink
self.debug('%s is not in the units list. Looking for json.', unit)
config_filename = os.path.join(
self._prj_cfg.get_unit_cfg_deliv_dir(),
f'config_{unit}.json')
self.debug('Looking for %s', config_filename)
if os.path.isfile(config_filename):
with open(config_filename, 'r', encoding="utf-8") as config_file:
u_conf = json.load(config_file)
self._handle_config(
code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators
)
else:
self.warning('%s does not have a unit_cfg json, '
'including all a2l-parameters', unit)
for blk_def, type_, label, size in blks:
if type_ == 'COMPU_METHOD':
blk_def, label = self._replace_compu_method(blk_def, label, compu_method_translators)
self.add_block_definition(unit_blks, type_, label, size, blk_def, compu_method_translators)
else:
self._handle_config(
code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators
)
deep_dict_update(self._blks, unit_blks)
return removed_symbols
def _handle_config(self, code_generator, unit, u_conf, blks, unit_blks, removed_symbols, compu_method_translators):
"""Merge all types of ram for the unit."""
ram = u_conf['inports']
ram.update(u_conf['outports'])
ram.update(u_conf['local_vars'])
# TODO: Function the variables and labels needs to be removed from
# the FUNCTION block too
for blk_def, type_, label, size in blks:
remove_excluded_symbol = True
inc = False
if type_ == 'AXIS_PTS':
if label in u_conf['calib_consts']:
inc = True
elif type_ == 'CHARACTERISTIC':
if label in u_conf['calib_consts']:
if label in [axis_label for _, axis_type, axis_label, _ in blks if axis_type == 'AXIS_PTS']:
# AXIS_PTS can be used as CHARACTERISTC but not the other way around.
# If there are duplicates, use the AXIS_PTS.
self.debug('Will not add the block for CHARACTERISTC %s, but will keep it as a symbol,'
' since it exists as AXIS_PTS', label)
remove_excluded_symbol = False
inc = False
else:
inc = self._handle_axis_ptr_ref_config(u_conf, blk_def, unit)
elif type_ == 'MEASUREMENT':
if label in ram:
key = label if size is None else label + size
if label in u_conf['outports']:
# This unit is producing the measurement.
inc = True
elif key in unit_blks.get(type_, {}):
# This unit is not producing it, and it has already been added
inc = False
else:
# This unit is not producing it, but it has not been added
# Could be external signal, etc.
inc = True
elif type_ == 'COMPU_METHOD':
inc = True
blk_def, label = self._replace_compu_method(blk_def, label, compu_method_translators)
else:
inc = True
if inc:
self.add_block_definition(unit_blks, type_, label, size, blk_def, compu_method_translators)
else:
if remove_excluded_symbol:
removed_symbols.append(label + size)
self.debug('Did not include A2L-blk %s%s', label, size)
if not self._unit_cfg.check_if_in_unit_cfg(unit, label):
if type_ != 'COMPU_METHOD':
self.warning('A2l block %s not in config json file for %s', label, unit)
if 'FUNCTION' in unit_blks:
unit_blks['FUNCTION'] = self._remove_symbols_from_func_blks(
code_generator, unit_blks['FUNCTION'], removed_symbols
)
if 'GROUP' in unit_blks:
unit_blks['GROUP'] = self._remove_symbols_from_grp_blks(unit_blks['GROUP'], removed_symbols)
def _handle_axis_ptr_ref_config(self, u_conf, blk, unit):
"""Remove blocks referencing undefined blocks."""
ref_re = re.compile(r'\s*AXIS_PTS_REF\s*([\w]*)')
for axis_ptr_ref in ref_re.findall(blk):
if axis_ptr_ref not in u_conf['calib_consts']:
self.debug('Excluding due to %s missing in config', axis_ptr_ref)
return False
if not self._unit_cfg.check_if_in_unit_cfg(unit, axis_ptr_ref):
self.debug('Excluding due to %s not active in config', axis_ptr_ref)
return False
return True
def add_block_definition(self, unit_blks, type_, label, size, blk_def, compu_method_translators):
"""Add block definition to A2L-file."""
size = '' if size is None else size
blk_def = self._replace_conversions(blk_def, compu_method_translators)
if type_ not in unit_blks:
unit_blks[type_] = {}
unit_blks[type_][label + size] = blk_def
@staticmethod
def _parse_func_blk(code_generator, fnc_blk):
"""Remove the unused symbols from the FUNCTION blocks in the A2L-file.
Parse the FUNCTION block, TL or EC style based on code_generator.
"""
if code_generator == 'target_link':
pattern = r'\s*/begin\s+FUNCTION\s*?\n\s*(\w+).*?\n\s*"(.*?)".*?\n(.*)'
else:
pattern = r'\s*/begin\s+FUNCTION\s*?\n\s*.*\*/\s*(\w+).*?\n\s*.*\*/\s*"(.*?)".*?\n(.*)'
res = re.match(pattern, fnc_blk, flags=re.M | re.DOTALL)
fnc_name = res.group(1)
long_id = res.group(2)
fnc_dict = {
'fnc_name': fnc_name,
'long_id': long_id,
'body': {}
}
fnc_body = res.group(3)
sb_res = re.findall(r'\s*/begin\s+(\w+[\[\d\]]*)\s*\n\s*'
r'(.*?\n)\s*/end \1', fnc_body, flags=re.M | re.DOTALL)
for sb_name, sub_blk in sb_res:
symbols = set(re.findall(r'\s*(\w+(?:\.\w+)?[\[\d\]]*).*?\n', sub_blk, flags=re.M))
fnc_dict['body'][sb_name] = symbols
return fnc_dict
@staticmethod
def _parse_grp_blk(grp_blk):
"""Remove the unused symbols from the GROUP blocks in the A2L-file."""
# parse the GROUP block
res = re.match(r'\s*/begin\s+GROUP\s*?\n\s*.*\*/\s*(\w+).*?\n\s*.*\*/\s*"(.*?)".*?\n(.*)',
grp_blk, flags=re.M | re.DOTALL)
fnc_name = res.group(1)
long_id = res.group(2)
fnc_dict = {
'fnc_name': fnc_name,
'long_id': long_id,
'body': {}
}
fnc_body = res.group(3)
sb_res = re.findall(r'\s*/begin\s+(\w+[\[\d\]]*)\s*\n\s*'
r'(.*?\n)\s*/end \1', fnc_body, flags=re.M | re.DOTALL)
for sb_name, sub_blk in sb_res:
symbols = set(re.findall(r'\s*(\w+(?:\.\w+)?[\[\d\]]*).*?\n', sub_blk, flags=re.M))
fnc_dict['body'][sb_name] = symbols
return fnc_dict
def _recursive_remove(self, a2l_dict, name):
"""Remove symbols from A2L dict (e.g. group or function)."""
if name in a2l_dict:
blk = a2l_dict[name]
if 'SUB_FUNCTION' in blk:
for sub_fnc in blk['SUB_FUNCTION']:
if self._recursive_remove(a2l_dict, sub_fnc):
blk['SUB_FUNCTION'] = blk['SUB_FUNCTION'] - set([sub_fnc])
elif 'SUB_GROUP' in blk:
for sub_grp in blk['SUB_GROUP']:
if self._recursive_remove(a2l_dict, sub_grp):
blk['SUB_GROUP'] = blk['SUB_GROUP'] - set([sub_grp])
empty = True
for key in blk:
if blk[key]:
empty = False
break
if empty:
a2l_dict.pop(name)
return True
return False
def _remove_symbols_from_func_blks(self, code_generator, fnc_blks, removed_symbols):
"""Remove the unused symbols from function blocks.
If the function block is empty, it too will be removed.
first iteration - remove all symbols that have been removed
second iteration - recusively remover all functions without symbols
"""
fnc_dict = {}
for fnc_name, fnc_blk in fnc_blks.items():
fnc_dict[fnc_name] = {}
u_fnc_bdy = self._parse_func_blk(code_generator, fnc_blk)['body']
sub_blk_types = set(u_fnc_bdy.keys()) - set(['SUB_FUNCTION'])
for type_ in list(sub_blk_types):
fnc_dict[fnc_name][type_] = u_fnc_bdy[type_] - set(removed_symbols)
if 'SUB_FUNCTION' in u_fnc_bdy:
fnc_dict[fnc_name]['SUB_FUNCTION'] = u_fnc_bdy['SUB_FUNCTION']
# second iteration - remove empty FUNCTION blocks
# TODO: Add functionality which parses the the function tree structures
# And the run recursive remove on all tree roots.
for fnc_name in fnc_blks.keys():
self._recursive_remove(fnc_dict, fnc_name)
# generate new function blocks
new_fnc_blks = {}
for fnc_name, fnc_data in fnc_dict.items():
fnc_blk = f' /begin FUNCTION\n {fnc_name}\t/* Name */\n'
fnc_blk += " \"\"\t/* LongIdentifier */\n"
for sub_sec in sorted(fnc_data.keys()):
sub_sec_data = fnc_data[sub_sec]
if sub_sec_data:
fnc_blk += f" /begin {sub_sec}\n"
for param in sorted(sub_sec_data):
fnc_blk += f" {param}\t/* Identifier */\n"
fnc_blk += f" /end {sub_sec}\n"
fnc_blk += " /end FUNCTION"
new_fnc_blks[fnc_name] = fnc_blk
return new_fnc_blks
def _remove_symbols_from_grp_blks(self, grp_blks, removed_symbols):
"""Remove the unused symbols from group blocks.
If the group block is empty, it too will be removed.
first iteration - remove all symbols that have been removed
second iteration - recusively remover all groups without symbols
"""
grp_dict = {}
for grp_name, grp_blk in grp_blks.items():
grp_dict[grp_name] = {}
u_grp_bdy = self._parse_grp_blk(grp_blk)['body']
sub_blk_types = set(u_grp_bdy.keys()) - set(['SUB_GROUP'])
for type_ in list(sub_blk_types):
grp_dict[grp_name][type_] = u_grp_bdy[type_] - set(removed_symbols)
if 'SUB_GROUP' in u_grp_bdy:
grp_dict[grp_name]['SUB_GROUP'] = u_grp_bdy['SUB_GROUP']
# second iteration - remove empty GROUP blocks
# TODO: Add functionality which parses the the group tree structures
# And the run recursive remove on all tree roots.
for grp_name in grp_blks.keys():
self._recursive_remove(grp_dict, grp_name)
# generate new group blocks
new_grp_blks = {}
for grp_name, grp_data in grp_dict.items():
grp_blk = f" /begin GROUP \n /* Name */ {grp_name}\n"
grp_blk += " /* Long identifier */ \"\"\n"
for sub_sec in sorted(grp_data.keys()):
sub_sec_data = grp_data[sub_sec]
if sub_sec_data:
grp_blk += f" /begin {sub_sec}\n"
for param in sorted(sub_sec_data):
grp_blk += f" {param}\n"
grp_blk += f" /end {sub_sec}\n"
grp_blk += " /end GROUP"
new_grp_blks[grp_name] = grp_blk
return new_grp_blks
def _parse_gen(self, filename):
"""Parse the generated a2l-files, without filter."""
self.debug('parsing gen a2l: %s', filename)
with open(filename, 'r', encoding="utf-8") as a2lfp:
a2ld = a2lfp.read()
for blk_def, type_, label in self._block_finder.findall(a2ld):
self._blks.setdefault(type_, {}).setdefault(label, blk_def)
@staticmethod
def _replace_compu_method(blk_def, label, compu_method_translators):
"""Replace the compu method block and label."""
for translator in compu_method_translators:
if translator['old_compu_method'] == blk_def:
return translator['new_compu_method'], translator['new_name']
return blk_def, label
def _store_compu_method(self, ID, conv_type, disp_format, unit, conversion, indentation, u_conf):
"""Stash compu methods that exists in the resulting a2l."""
key = (ID, conv_type, disp_format, unit, conversion)
if key in self._compu_methods:
new_name = self._compu_methods[key]['name']
new_compu_method = self._compu_methods[key]['method']
else:
new_name = 'Scaling_' + str(len(self._compu_methods))
if 'code_generator' in u_conf and u_conf['code_generator'] == 'embedded_coder':
new_compu_method = self._ec_compu_method_template.substitute(
name=new_name,
ID=ID,
conv_type=conv_type,
disp_format=disp_format,
unit=unit,
conversion=conversion,
indentation=indentation
)
else:
new_compu_method = self._tl_compu_method_template.substitute(
name=new_name,
ID=ID,
conv_type=conv_type,
disp_format=disp_format,
unit=unit,
conversion=conversion,
indentation=indentation
)
self._compu_methods.update({key: {'name': new_name,
'method': new_compu_method}})
return new_name, new_compu_method
@staticmethod
def _replace_conversions(blk_def, compu_method_translators):
"""Replace conversion identifiers in a2l block."""
for translator in compu_method_translators:
# The following check is faster than running the regex on the block.
# It DOES give false positives, which is why the regex is used for substitution
# and we do not immidiately return after one positive
if translator['old_name'] in blk_def:
blk_def = translator['regex'].sub(translator['replacement'], blk_def)
return blk_def
def _parse_compu_methods(self, blk_def, unit):
"""Replace compu methods to not overwrite any of them."""
compu_method_translators = [] # Translators for one processed a2l file. Needs to be reset between files
u_conf = self._per_unit_cfg[unit] if unit in self._per_unit_cfg else {}
if 'code_generator' in u_conf and u_conf['code_generator'] == 'embedded_coder':
for match in self._ec_compu_method_parser.finditer(blk_def):
new_name, new_compu_method = self._store_compu_method(
match['ID'],
match['conv_type'],
match['disp_format'],
match['unit'],
match['conversion'],
match['indentation'],
u_conf
)
compu_method_translators.append(
{
'new_name': new_name,
'old_name': match['name'],
'regex': re.compile(
r'(\s*)' # beginning
r'\s*(/\* Conversion [Mm]ethod\s*\*/\s*)' # optional comment
r'\b{name}\b' # word
r'('
r'\s*\n' # newline
r')'.format(name=match['name']) # end of end-match
),
'replacement': r'\1\2{name}\3'.format(name=new_name),
'old_compu_method': match['compu_method'],
'new_compu_method': new_compu_method
}
)
else:
for match in self._tl_compu_method_parser.finditer(blk_def):
new_name, new_compu_method = self._store_compu_method(
match['ID'],
match['conv_type'],
match['disp_format'],
match['unit'],
match['conversion'],
match['indentation'],
u_conf
)
compu_method_translators.append(
{
'new_name': new_name,
'old_name': match['name'],
'regex': re.compile(
r'(\s*)' # beginning
r'\b{name}\b' # word
r'(' # start of end-match
r'\s*(/\* Conversion \*/)?' # optional comment
r'\s*\n' # newline
r')'.format(name=match['name']) # end of end-match
),
'replacement': r'\1{name}\2'.format(name=new_name),
'old_compu_method': match['compu_method'],
'new_compu_method': new_compu_method
}
)
return compu_method_translators
def _patch_kp_blob(self, block):
"""Return updated measurement block text.
Args:
block (str): A2L text block
Returns:
a2l_text (str): A2L text without KP_BLOB.
"""
ecu_address = '0x00000000'
for match in self._expr_block_meas_kp_blob_parser.finditer(block):
start, end = match.span()
block = f'{block[:start]}ECU_ADDRESS {ecu_address}{block[end:]}'
return block
def merge(self, f_name, complete_a2l=False, silver_a2l=False):
"""Write merged a2l-file.
Args:
f_name (str): Output filename.
"""
a2l = ''
a2l_config = self._prj_cfg.get_a2l_cfg()
for _, data in self._blks.items():
for _, blk in data.items():
if not a2l_config['allow_kp_blob']:
blk = self._patch_kp_blob(blk)
a2l += blk + '\n\n'
if complete_a2l:
events = []
time_unit_10ms = '0x07'
rasters = self._prj_cfg.get_units_raster_cfg()
for xcp_id, evt_data in enumerate(rasters['SampleTimes'].items(), 1):
events.append({
'time_cycle': '0x%02X' % int(evt_data[1] * 100),
'time_unit': time_unit_10ms,
'name': evt_data[0],
'channel_id': '0x%04X' % xcp_id
})
a2l_template = A2lProjectTemplate(
a2l,
a2l_config['asap2_version'],
a2l_config['name'],
events,
a2l_config['ip_address'],
a2l_config['ip_port']
)
a2l = a2l_template.render()
elif silver_a2l:
a2l_template = A2lSilverTemplate(a2l)
a2l = a2l_template.render()
self.a2l = a2l
with open(f_name, 'w', encoding="ISO-8859-1") as ma2l:
ma2l.write(a2l)
self.info('Written the merged A2L-file %s', f_name)
def get_characteristic_axis_data(self):
"""Get characteristic map axis data from merged a2l-file."""
axis_data = {}
for blk_def, type_, label in self._block_finder.findall(self.a2l):
if type_ == "CHARACTERISTIC":
axes = re.findall('AXIS_PTS_REF (.*)', blk_def)
if label in axis_data:
self.critical("Multiple CHARACTERISTIC for %s in merged a2l.", label)
axis_data[label] = {'axes': axes}
return axis_data