[go: nahoru, domu]

Skip to content

Commit

Permalink
Merge pull request cantools#242 from Daimler/arxml_signal_length_init…
Browse files Browse the repository at this point in the history
…ial_range

ARXML: fix determining signal lengths their initial value and its range
  • Loading branch information
eerimoq committed Jan 29, 2021
2 parents 4071343 + 57af083 commit 7a1c31a
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 56 deletions.
195 changes: 139 additions & 56 deletions cantools/database/can/formats/arxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,23 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):
"""

i_signal = self._get_i_signal(i_signal_to_i_pdu_mapping)

if i_signal is None:
# No I-SIGNAL found, i.e. this i-signal-to-i-pdu-mapping is
# probably a i-signal group. According to the XSD, I-SIGNAL and
# I-SIGNAL-GROUP-REF are mutually exclusive...
return None

# Get the system signal XML node. This may also be a system signal
# group, in which case we have ignore it if the XSD is to be believed.
# ARXML is great!
system_signal = self._get_unique_arxml_child(i_signal, '&SYSTEM-SIGNAL')
if system_signal is not None and system_signal.tag != f'{{{self.xml_namespace}}}SYSTEM-SIGNAL':
return None

# Default values.
initial = None
minimum = None
maximum = None
factor = 1
Expand All @@ -232,19 +248,14 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):
receivers = []
decimal = SignalDecimal(Decimal(factor), Decimal(offset))

i_signal = self._get_i_signal(i_signal_to_i_pdu_mapping)

if i_signal is None:
# Probably a signal group (I-SIGNAL-GROUP).
return None

# Name, start position, length and byte order.
name = self._load_signal_name(i_signal)
start_position = self._load_signal_start_position(i_signal_to_i_pdu_mapping)
length = self._load_signal_length(i_signal)
length = self._load_signal_length(i_signal, system_signal)
byte_order = self._load_signal_byte_order(i_signal_to_i_pdu_mapping)

system_signal = self._get_system_signal(i_signal)
# Type.
is_signed, is_float = self._load_signal_type(i_signal)

if system_signal is not None:
# Unit and comment.
Expand All @@ -253,10 +264,21 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):

# Minimum, maximum, factor, offset and choices.
minimum, maximum, factor, offset, choices = \
self._load_system_signal(system_signal, decimal)

# Type.
is_signed, is_float = self._load_signal_type(i_signal)
self._load_system_signal(system_signal, decimal, is_float)

# loading constants is way too complicated, so it it is the job of a separate method
initial = self._load_arxml_init_value_string(i_signal)

if initial is not None:
if is_float:
initial = float(initial)
elif initial.strip().lower() == "true":
initial = True
elif initial.strip().lower() == "false":
initial = False
# TODO: strings?
else:
initial = int(initial)

# ToDo: receivers

Expand All @@ -268,6 +290,7 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):
is_signed=is_signed,
scale=factor,
offset=offset,
initial=initial,
minimum=minimum,
maximum=maximum,
unit=unit,
Expand All @@ -284,8 +307,58 @@ def _load_signal_start_position(self, i_signal_to_i_pdu_mapping):
return int(self._get_unique_arxml_child(i_signal_to_i_pdu_mapping,
'START-POSITION').text)

def _load_signal_length(self, i_signal):
return int(self._get_unique_arxml_child(i_signal, 'LENGTH').text)
def _load_signal_length(self, i_signal, system_signal):
i_signal_length = self._get_unique_arxml_child(i_signal, 'LENGTH')

if i_signal_length is not None:
return int(i_signal_length.text)

return None # error?!

def _load_arxml_init_value_string(self, base_elem):
""""Load the initial value of a signal
Supported mechanisms are references to constants and direct
specifcation of the value. Note that this method returns a
string which must be converted into the signal's data type by
the calling code.
"""
if base_elem is None:
return None

value_elem = self._get_unique_arxml_child(base_elem,
[
'INIT-VALUE',
'NUMERICAL-VALUE-SPECIFICATION',
'VALUE'
])
if value_elem is not None:
# initial value is specified directly.
return value_elem.text

const_ref = self._get_unique_arxml_child(base_elem,
[
'INIT-VALUE',
'CONSTANT-REFERENCE',
'CONSTANT-REF'
])
if const_ref is not None:
# initial value is specified via a constant reference
const_spec_elem = self._follow_arxml_reference(base_elem, const_ref.text, const_ref.attrib['DEST'])
if const_spec_elem is None:
return None

value_elem = self._get_unique_arxml_child(const_spec_elem,
[
'VALUE-SPEC',
'NUMERICAL-VALUE-SPECIFICATION',
'VALUE'
])
return None if value_elem is None else value_elem.text

# no initial value specified or specified in a way which we
# don't recognize
return None

def _load_signal_byte_order(self, i_signal_to_i_pdu_mapping):
packing_byte_order = \
Expand Down Expand Up @@ -321,25 +394,13 @@ def _load_signal_comments(self, system_signal):
return None
return result

def _load_minimum(self, minimum, decimal):
if minimum is not None:
decimal.minimum = Decimal(minimum.text)
minimum = float(decimal.minimum)

return minimum

def _load_maximum(self, maximum, decimal):
if maximum is not None:
decimal.maximum = Decimal(maximum.text)
maximum = float(decimal.maximum)

return maximum

def _load_texttable(self, compu_method, decimal):
def _load_texttable(self, compu_method, decimal, is_float):
minimum = None
maximum = None
choices = {}

text_to_num_fn = float if is_float else int

for compu_scale in self._get_arxml_children(compu_method,
[
'&COMPU-INTERNAL-TO-PHYS',
Expand All @@ -350,20 +411,26 @@ def _load_texttable(self, compu_method, decimal):
upper_limit = self._get_unique_arxml_child(compu_scale, 'UPPER-LIMIT')
vt = self._get_unique_arxml_child(compu_scale, ['&COMPU-CONST', 'VT'])

minimum_scale = None if lower_limit is None else text_to_num_fn(lower_limit.text)
maximum_scale = None if upper_limit is None else text_to_num_fn(upper_limit.text)

if minimum is None: minimum = minimum_scale
elif minimum_scale is not None: minimum = min(minimum, minimum_scale)
if maximum is None: maximum = maximum_scale
elif maximum_scale is not None: maximum = max(maximum, maximum_scale)
if vt is not None:
choices[vt.text] = int(lower_limit.text)
else:
minimum = self._load_minimum(lower_limit, decimal)
maximum = self._load_maximum(upper_limit, decimal)

decimal.minimum = minimum
decimal.maximum = maximum
return minimum, maximum, choices

def _load_linear_factor_and_offset(self, compu_scale, decimal):
compu_rational_coeffs = \
self._get_unique_arxml_child(compu_scale, '&COMPU-RATIONAL-COEFFS')

if compu_rational_coeffs is None:
return 1, 0
return None, None

numerators = self._get_arxml_children(compu_rational_coeffs,
['&COMPU-NUMERATOR', '*&V'])
Expand All @@ -387,7 +454,7 @@ def _load_linear_factor_and_offset(self, compu_scale, decimal):

return float(decimal.scale), float(decimal.offset)

def _load_linear(self, compu_method, decimal):
def _load_linear(self, compu_method, decimal, is_float):
compu_scale = self._get_unique_arxml_child(compu_method,
[
'COMPU-INTERNAL-TO-PHYS',
Expand All @@ -398,22 +465,25 @@ def _load_linear(self, compu_method, decimal):
lower_limit = self._get_unique_arxml_child(compu_scale, '&LOWER-LIMIT')
upper_limit = self._get_unique_arxml_child(compu_scale, '&UPPER-LIMIT')

minimum = self._load_minimum(lower_limit, decimal)
maximum = self._load_maximum(upper_limit, decimal)
text_to_num_fn = float if is_float else int
minimum = None if lower_limit is None else text_to_num_fn(lower_limit.text)
maximum = None if upper_limit is None else text_to_num_fn(upper_limit.text)

factor, offset = self._load_linear_factor_and_offset(
compu_scale,
decimal)
factor, offset = self._load_linear_factor_and_offset(compu_scale, decimal)

decimal.minimum = None if minimum is None else Decimal(minimum)
decimal.maximum = None if maximum is None else Decimal(maximum)
return minimum, maximum, factor, offset

def _load_scale_linear_and_texttable(self, compu_method, decimal):
def _load_scale_linear_and_texttable(self, compu_method, decimal, is_float):
minimum = None
maximum = None
factor = 1
offset = 0
choices = {}

text_to_num_fn = float if is_float else int

for compu_scale in self._get_arxml_children(compu_method,
[
'&COMPU-INTERNAL-TO-PHYS',
Expand All @@ -425,18 +495,32 @@ def _load_scale_linear_and_texttable(self, compu_method, decimal):
upper_limit = self._get_unique_arxml_child(compu_scale, 'UPPER-LIMIT')
vt = self._get_unique_arxml_child(compu_scale, ['&COMPU-CONST', 'VT'])

minimum_scale = None if lower_limit is None else text_to_num_fn(lower_limit.text)
maximum_scale = None if upper_limit is None else text_to_num_fn(upper_limit.text)

if minimum is None: minimum = minimum_scale
elif minimum_scale is not None: minimum = min(minimum, minimum_scale)
if maximum is None: maximum = maximum_scale
elif maximum_scale is not None: maximum = max(maximum, maximum_scale)

# TODO: make sure that no conflicting scaling factors and offsets
# are specified. For now, let's just assume that the ARXML file is
# well formed.
factor_scale, offset_scale = self._load_linear_factor_and_offset(compu_scale, decimal)
if factor_scale is not None:
factor = factor_scale
if offset_scale is not None:
offset = offset_scale

if vt is not None:
choices[vt.text] = int(lower_limit.text)
else:
minimum = self._load_minimum(lower_limit, decimal)
maximum = self._load_maximum(upper_limit, decimal)
factor, offset = self._load_linear_factor_and_offset(
compu_scale,
decimal)
assert(minimum_scale is not None and minimum_scale == maximum_scale)
choices[vt.text] = int(minimum_scale)

decimal.minimum = Decimal(minimum)
decimal.maximum = Decimal(maximum)
return minimum, maximum, factor, offset, choices

def _load_system_signal(self, system_signal, decimal):
def _load_system_signal(self, system_signal, decimal, is_float):
minimum = None
maximum = None
factor = 1
Expand All @@ -457,23 +541,23 @@ def _load_system_signal(self, system_signal, decimal):

if category == 'TEXTTABLE':
minimum, maximum, choices = \
self._load_texttable(compu_method, decimal)
self._load_texttable(compu_method, decimal, is_float)
elif category == 'LINEAR':
minimum, maximum, factor, offset = \
self._load_linear(compu_method, decimal)
self._load_linear(compu_method, decimal, is_float)
elif category == 'SCALE_LINEAR_AND_TEXTTABLE':
(minimum,
maximum,
factor,
offset,
choices) = self._load_scale_linear_and_texttable(compu_method,
decimal)
decimal,
is_float)
else:
LOGGER.debug('Compu method category %s is not yet implemented.',
category)

return minimum, maximum, factor, offset, choices

return minimum, maximum, 1 if factor is None else factor, 0 if offset is None else offset, choices

def _load_signal_type(self, i_signal):
is_signed = False
Expand Down Expand Up @@ -518,8 +602,10 @@ def _follow_arxml_reference(self, base_elem, arxml_path, child_tag_name):
is_absolute_path = arxml_path.startswith('/')

if is_absolute_path and arxml_path in self._arxml_reference_cache:
# absolute paths are globally unique and thus can be cached
return self._arxml_reference_cache[arxml_path]

# TODO (?): for relative paths, we need to find the corresponding package tag for each base element!
base_elem = self._root if is_absolute_path else base_elem
if not base_elem:
raise ValueError(
Expand Down Expand Up @@ -674,9 +760,6 @@ def _get_pdu(self, can_frame):
'&PDU'
])

def _get_system_signal(self, i_signal):
return self._get_unique_arxml_child(i_signal, '&SYSTEM-SIGNAL')

def _get_compu_method(self, system_signal):
return self._get_unique_arxml_child(system_signal,
[
Expand Down
24 changes: 24 additions & 0 deletions tests/files/arxml/system-4.2.arxml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
<ELEMENTS>
<I-SIGNAL UUID="52b93c1cec70bd40a590ece0b3893c0d">
<SHORT-NAME>signal1</SHORT-NAME>
<INIT-VALUE>
<NUMERICAL-VALUE-SPECIFICATION>
<VALUE>5</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</INIT-VALUE>
<LENGTH>3</LENGTH>
<SYSTEM-SIGNAL-REF DEST="SYSTEM-SIGNAL">/SystemSignal/Signal1</SYSTEM-SIGNAL-REF>
</I-SIGNAL>
Expand Down Expand Up @@ -167,11 +172,30 @@
</I-SIGNAL>
<I-SIGNAL UUID="7ac5ce20002a1a4a3c4beb72b17bc1b5">
<SHORT-NAME>signal6</SHORT-NAME>
<INIT-VALUE>
<CONSTANT-REFERENCE>
<CONSTANT-REF DEST="CONSTANT-SPECIFICATION">/Constants/BooleanFalse</CONSTANT-REF>
</CONSTANT-REFERENCE>
</INIT-VALUE>
<LENGTH>1</LENGTH>
<SYSTEM-SIGNAL-REF DEST="SYSTEM-SIGNAL">/SystemSignal/Signal6</SYSTEM-SIGNAL-REF>
</I-SIGNAL>
</ELEMENTS>
</AR-PACKAGE>
<AR-PACKAGE UUID="89cb5634c5ae4850b6dd0ba310fbc247">
<SHORT-NAME>Constants</SHORT-NAME>
<ELEMENTS>
<CONSTANT-SPECIFICATION UUID="0778143a6fc7ae0b1bcc9670853fe048">
<SHORT-NAME>BooleanFalse</SHORT-NAME>
<VALUE-SPEC>
<NUMERICAL-VALUE-SPECIFICATION>
<SHORT-LABEL>literal</SHORT-LABEL>
<VALUE>false</VALUE>
</NUMERICAL-VALUE-SPECIFICATION>
</VALUE-SPEC>
</CONSTANT-SPECIFICATION>
</ELEMENTS>
</AR-PACKAGE>
<AR-PACKAGE UUID="bf5ded8d0c2d7bd37cfb17f2525d7208">
<SHORT-NAME>ISignalIPdu</SHORT-NAME>
<ELEMENTS>
Expand Down
Loading

0 comments on commit 7a1c31a

Please sign in to comment.