Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arxml refactoring #571

Merged
merged 2 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,13 @@ this will remove signales ``mySignal`` and ``mySignal2`` in ``source.dbc`` and s
this will load ``source.dbc`` and rename signals ``mySignal`` in ``myNewSignal`` and ``mySignal2`` in ``myNewSignal2``.
The result is stored in ``target.dlc``.

::

$ canconvert --signalNameFromAttrib=CompuMethodName source.ARXML target.dbc

this will load ``source.arxml`` and create ``target.dlc`` while all signals are named from CompuMethodName in ARXML.
Also known values from ARXML are SysSignalName and ISignalName

**canFD:**

::
Expand Down Expand Up @@ -361,6 +368,11 @@ ____________________

rename Signal form databases. (comma separated list) Syntax: --renameSignal=myOldSignal:myNewSignal,mySecondSignal:mySecondNewSignal

--signalNameFromAttrib=ATTRIBUTENAME

change signal_name to given signal attribute Syntax: --signalNameFromAttrib=SysSignalName
Example --signalNameFromAttrib SysSignalName
ARXML known Attributes: SysSignalName, ISignalName, CompuMethodName


* dbc:
Expand Down Expand Up @@ -407,10 +419,10 @@ ____________________

Excel format for startbit of motorola coded signals. Valid values: msb, lsb, msbreverse default msbreverse. [more about starbits...](https://github.com/ebroecker/canmatrix/wiki/signal-Byteorder)

* csv:
* csv/xls/xlsx:

--csvAdditionalSignalAttributes
append additional signal-collums to csv, example:
--additionalSignalAttributes//--additionalFrameAttributes
append additional signal/frame-colums to csv, example:
is_signed,attributes["GenSigStartValue"]


Expand Down
5 changes: 5 additions & 0 deletions src/canmatrix/cli/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def get_formats():
@click.option('--renameEcu', 'renameEcu', help="rename Ecu form databases. (comma separated list)\nSyntax: --renameEcu=myOldEcu:myNewEcu,mySecondEcu:mySecondNewEcu")
@click.option('--deleteSignal', 'deleteSignal', help="delete Signal form databases. (comma separated list)\nSyntax: --deleteSignal=mySignal1,mySecondSignal")
@click.option('--renameSignal', 'renameSignal', help="rename Signal form databases. (comma separated list)\nSyntax: --renameSignal=myOldSignal:myNewSignal,mySecondSignal:mySecondNewSignal")
@click.option('--signalNameFromAttrib', 'signalNameFromAttrib', help="change signal_name to given signal attribute\n\
Example --signalNameFromAttrib SysSignalName\nARXML known Attributes: SysSignalName, ISignalName, CompuMethodName", default=None)
@click.option('--deleteZeroSignals/--no-deleteZeroSignals', 'deleteZeroSignals', default=False, help="delete zero length signals (signals with 0 bit length) from matrix\ndefault False")
@click.option('--deleteSignalAttributes', 'deleteSignalAttributes', help="delete attributes from all signals\nExample --deleteSignalAttributes GenMsgSomeVar,CycleTime")
@click.option('--deleteFrame', 'deleteFrame', help="delete Frame form databases. (comma separated list)\nSyntax: --deleteFrame=myFrame1,mySecondFrame")
Expand All @@ -75,6 +77,9 @@ def get_formats():
@click.option('--frames', help="Copy only given Frames (comma separated list) to target matrix")
@click.option('--signals', help="Copy only given Signals (comma separated list) to target matrix just as 'free' signals without containing frame")
@click.option('--merge', help="merge additional can databases.\nSyntax: --merge filename[:ecu=SOMEECU][:frame=FRAME1][:frame=FRAME2],filename2")



# arxml switches
@click.option('--arxmlIgnoreClusterInfo/--no-arxmlIgnoreClusterInfo', 'arxmlIgnoreClusterInfo', default=False, help="Ignore any can cluster info from arxml; Import all frames in one matrix\ndefault False")
@click.option('--arxmlUseXpath/--no-arxmlUseXpath', 'arxmlUseXpath', default=False, help="Use experimental Xpath-Implementation for resolving AR-Paths; \ndefault False")
Expand Down
4 changes: 4 additions & 0 deletions src/canmatrix/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,10 @@ def convert(infile, out_file_name, **options): # type: (str, str, **str) -> Non
if 'recalcDLC' in options and options['recalcDLC']:
db.recalc_dlc(options['recalcDLC'])

if options.get('signalNameFromAttrib') is not None:
for signal in [b for a in db for b in a.signals]:
signal.name = signal.attributes.get(options.get('signalNameFromAttrib'), signal.name)

logger.info(name)
logger.info("%d Frames found" % (db.frames.__len__()))

Expand Down
97 changes: 27 additions & 70 deletions src/canmatrix/formats/arxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,8 @@
clusterImporter = 1


class ArTree(object):
def __init__(self, name="", ref=None): # type: (str, lxml.etree._Element) -> None
self._name = name
self._ref = ref
self._array = [] # type: typing.List[ArTree]
self._names = {} # type: typing.Dict[str, ArTree]

def append_child(self, name, child): # type: (str, typing.Any) -> ArTree
"""Append new child and return it."""
temp = ArTree(name, child)
self._array.append(temp)
self._names[name] = temp
return temp

def get_child_by_name(self, name): # type: (str) -> typing.Union[ArTree, None]
if name in self._names:
return self._names[name]
return None

@property
def ref(self): # type: () -> lxml.etree._Element
return self._ref


# for typing only
_Element = lxml.etree._Element
_DocRoot = typing.Union[_Element, ArTree]
_MultiplexId = typing.Union[str, int, None]
_FloatFactory = typing.Callable[[typing.Any], typing.Any]

Expand All @@ -81,6 +56,25 @@ def __init__(self, use_ar_xpath):
self.xml_element_cache = dict() # type: typing.Dict[str, _Element]
self.use_ar_xpath = use_ar_xpath
self.xml_element_cache = {}
self.path_cache = {}

def fill_caches(self, start_element=None, ar_path=""):
if start_element is None:
start_element = self.root
self.path_cache = {}
if start_element.tag == self.ns + "SHORT-NAME":
return start_element.text
for sub_element in start_element:
text = sub_element.text
if text is not None and len(text) > 0 and text.startswith('/'):
if text not in self.path_cache:
self.path_cache[text] = []
self.path_cache[text].append(sub_element)
new_ar_path = self.fill_caches(sub_element, ar_path)
if new_ar_path != "":
ar_path += '/' + new_ar_path
self.xml_element_cache[ar_path] = start_element
return ''

def open(self, filename):
self.tree = lxml.etree.parse(filename)
Expand All @@ -89,6 +83,8 @@ def open(self, filename):

self.ns = "{" + self.tree.xpath('namespace-uri(.)') + "}" # type: str
self.nsp = self.tree.xpath('namespace-uri(.)')
if not self.use_ar_xpath:
self.fill_caches()

def findall(self, xpath, start_element=None):
if start_element is None:
Expand Down Expand Up @@ -128,9 +124,7 @@ def get_short_name_path(self, shortname_path):
self.xml_element_cache[shortname_path] = temp[0]
return temp[0]
return None
else:
return self.get_shortname_path_from_artree(shortname_path)
return elems
return None

def get_short_name(self, element):
# type: (_Element, str) -> str
Expand Down Expand Up @@ -162,17 +156,6 @@ def get_all_sub_by_name(self, start_element, name):
def get_sub_by_name(self, start_element, name):
return self.find(name, start_element)

def fill_tree_from_xml(self, tag, tree_point):
# type: (_Element, ArTree, str) -> None
"""Parse the xml tree into ArTree objects."""
for child in tag: # type: _Element
name_elem = child.find('./' + self.ns + 'SHORT-NAME')
# long_name = child.find('./' + namespace + 'LONG-NAME')
if name_elem is not None and child is not None:
self.fill_tree_from_xml(child, tree_point.append_child(name_elem.text, child))
if name_elem is None and child is not None:
self.fill_tree_from_xml(child, tree_point)

def find_children_by_path(self, from_element, path):
# type: (_Element, str, _DocRoot, str) -> typing.Sequence[_Element]
path_elements = path.split('/')
Expand All @@ -190,17 +173,6 @@ def get_element_name(self, parent):
return name.text
return ""

def get_shortname_path_from_artree(self, path):
# type: (ArTree, str) -> typing.Optional[_Element]
"""Get element from ArTree by path."""
ptr = self.ar_tree
for name in path.split('/'):
if ptr is None:
return None
if name.strip():
ptr = ptr.get_child_by_name(name)
return ptr.ref if ptr else None

def get_child(self, parent, tag_name):
# type: (_Element, str, _DocRoot, str) -> typing.Optional[_Element]
"""Get first sub-child or referenced sub-child with given name."""
Expand All @@ -227,19 +199,6 @@ def get_children(self, parent, tag_name):
raise "use follow_all_ref!"
return ret

def build_ar_tree(self):
top_level_packages = self.find('TOP-LEVEL-PACKAGES')

if top_level_packages is None:
# no "TOP-LEVEL-PACKAGES found, try root
top_level_packages = self.root

if self.use_ar_xpath:
search_point = top_level_packages # type: typing.Union[_Element, ArTree]
else:
self.ar_tree = ArTree()
self.fill_tree_from_xml(top_level_packages, self.ar_tree)

def get_element_desc(self, element):
# type: (_Element, _DocRoot, str) -> str
"""Get element description from XML."""
Expand All @@ -264,7 +223,6 @@ def get_ecu_instance(self, element):
else:
return None


def create_sub_element(parent, element_name, text=None, dest=None):
# type: (_Element, str, typing.Optional[str]) -> _Element
sn = lxml.etree.SubElement(parent, element_name)
Expand All @@ -274,7 +232,6 @@ def create_sub_element(parent, element_name, text=None, dest=None):
sn.set("DEST", dest)
return sn


def get_base_type_of_signal(signal):
# type: (canmatrix.Signal) -> typing.Tuple[str, int]
"""Get signal arxml-type and size based on the Signal properties."""
Expand Down Expand Up @@ -1274,7 +1231,7 @@ def get_signals(signal_array, frame, ea, multiplex_id, float_factory, bit_offset
if isignal_name is not None and isignal_name:
new_signal.add_attribute("ISignalName", isignal_name)
if system_signal_name is not None and system_signal_name:
new_signal.add_attribute("ShortName", system_signal_name)
new_signal.add_attribute("SysSignalName", system_signal_name)
existing_signal = frame.signal_by_name(new_signal.name)
if existing_signal is None:
frame.add_signal(new_signal)
Expand All @@ -1298,7 +1255,7 @@ def get_frame_from_multiplexed_ipdu(pdu, target_frame, multiplex_translation, ea
multiplexor.initial_value = 0
target_frame.add_signal(multiplexor)
static_part = ea.get_child(pdu, "STATIC-PART")
ipdu = ea.get_child(static_part, "I-PDU")
ipdu = ea.follow_ref(static_part, "I-PDU-REF")
if ipdu is not None:
pdu_sig_mappings = ea.get_child(ipdu, "SIGNAL-TO-PDU-MAPPINGS")
pdu_sig_mapping = ea.get_children(pdu_sig_mappings, "I-SIGNAL-TO-I-PDU-MAPPING")
Expand Down Expand Up @@ -1938,10 +1895,8 @@ def load(file, **options):
ea = Earxml(use_ar_xpath)
ea.open(file)

logger.debug("Build arTree ...")

ea.build_ar_tree()
com_module = ea.get_short_name_path("ActiveEcuC/Com")
com_module = ea.get_short_name_path("/ActiveEcuC/Com")

if com_module is not None and len(com_module) > 0:
logger.info("seems to be a ECUC arxml. Very limited support for extracting canmatrix.")
Expand All @@ -1962,4 +1917,6 @@ def load(file, **options):

result.update(decode_can_helper(ea, float_factory, ignore_cluster_info))



return result
4 changes: 2 additions & 2 deletions src/canmatrix/tests/test_arxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ def test_get_signals_from_container_i_pdu():
assert matrix["New_CanCluster"].frames[0].signals[1].name == 'Header_DLC'
assert matrix["New_CanCluster"].frames[0].signals[2].name == 'PDU_Contained_1_Signal1'
assert matrix["New_CanCluster"].frames[0].signalGroups[0].signals[0].name == 'PDU_Contained_1_Signal1'
assert matrix["New_CanCluster"].frames[0].signals[2].attributes["ShortName"] == 'PDU_Contained_1_Signal1_905db81da40081cb'
assert matrix["New_CanCluster"].frames[0].signalGroups[0].signals[0].attributes["ShortName"] == 'PDU_Contained_1_Signal1_905db81da40081cb'
assert matrix["New_CanCluster"].frames[0].signals[2].attributes["SysSignalName"] == 'PDU_Contained_1_Signal1_905db81da40081cb'
assert matrix["New_CanCluster"].frames[0].signalGroups[0].signals[0].attributes["SysSignalName"] == 'PDU_Contained_1_Signal1_905db81da40081cb'

def test_get_signals_from_secured_pdu():
here = Path(__file__).parent
Expand Down