[go: nahoru, domu]

Skip to content

Commit

Permalink
implement multi-lingual message and signal descriptions
Browse files Browse the repository at this point in the history
note that for now the default is to assume that comments which don't
explicitly specify a language are in English. An alternative would be
to check if the dictionary containing the comments contains exactly a
single element and use that instead. The problem with this approach is
that comments for different languages might get mixed up more easily:
Assume a supplier based in China who documents its signals only in
Chinese and a OEM based in Brazil who likes to document its stuff
using Portuguese...
  • Loading branch information
andlaus committed Nov 30, 2020
1 parent 173ba03 commit bacf8b8
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 46 deletions.
54 changes: 28 additions & 26 deletions cantools/database/can/formats/arxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _load_message(self, can_frame_triggering):
frame_id = self._load_message_frame_id(can_frame_triggering)
length = self._load_message_length(can_frame)
is_extended_frame = self._load_message_is_extended_frame(can_frame_triggering)
comment = self._load_message_comment(can_frame)
comments = self._load_message_comments(can_frame)

# ToDo: senders

Expand Down Expand Up @@ -181,13 +181,13 @@ def _load_message(self, can_frame_triggering):
send_type=None,
cycle_time=cycle_time,
signals=signals,
comment=comment,
comment=comments,
bus_name=None,
strict=self._strict)

def _load_message_name(self, can_frame_triggering):
return self._get_unique_arxml_child(can_frame_triggering,
"SHORT-NAME").text
'SHORT-NAME').text

def _load_message_frame_id(self, can_frame_triggering):
return int(self._get_unique_arxml_child(can_frame_triggering,
Expand All @@ -205,15 +205,16 @@ def _load_message_is_extended_frame(self, can_frame_triggering):
return False if can_addressing_mode is None \
else can_addressing_mode.text == 'EXTENDED'

def _load_message_comment(self, can_frame):
# This extracts a single language. Support for multi languages
# will be implemented in the near future.
l_2 = self._get_unique_arxml_child(can_frame, ['DESC', 'L-2'])
def _load_message_comments(self, can_frame):
result = {}

if l_2 is not None:
return l_2.text
else:
for l_2 in self._get_arxml_children(can_frame, ['DESC', '*L-2']):
lang = l_2.attrib.get('L', 'EN')
result[lang] = l_2.text

if len(result) == 0:
return None
return result

def _load_signal(self, i_signal_to_i_pdu_mapping):
"""Load given signal and return a signal object.
Expand All @@ -227,7 +228,7 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):
offset = 0
unit = None
choices = None
comment = None
comments = None
receivers = []
decimal = SignalDecimal(Decimal(factor), Decimal(offset))

Expand All @@ -248,7 +249,7 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):
if system_signal is not None:
# Unit and comment.
unit = self._load_signal_unit(system_signal)
comment = self._load_signal_comment(system_signal)
comments = self._load_signal_comments(system_signal)

# Minimum, maximum, factor, offset and choices.
minimum, maximum, factor, offset, choices = \
Expand All @@ -271,7 +272,7 @@ def _load_signal(self, i_signal_to_i_pdu_mapping):
maximum=maximum,
unit=unit,
choices=choices,
comment=comment,
comment=comments,
is_float=is_float,
decimal=decimal)

Expand Down Expand Up @@ -309,15 +310,16 @@ def _load_signal_unit(self, system_signal):

return None if res is None else res.text

def _load_signal_comment(self, system_signal):
# This extracts a single language. Support for multi languages
# will be implemented in the near future.
l_2 = self._get_unique_arxml_child(system_signal, ['DESC', 'L-2'])
def _load_signal_comments(self, system_signal):
result = {}

if l_2 is not None:
return l_2.text
else:
for l_2 in self._get_arxml_children(system_signal, ['DESC', '*L-2']):
lang = l_2.attrib.get('L', 'EN')
result[lang] = l_2.text

if len(result) == 0:
return None
return result

def _load_minimum(self, minimum, decimal):
if minimum is not None:
Expand Down Expand Up @@ -768,7 +770,7 @@ def load_message(self, com_i_pdu):
# Default values.
interval = None
senders = []
comment = None
comments = None

# Name, frame id, length and is_extended_frame.
name = com_i_pdu.find(SHORT_NAME_XPATH, NAMESPACES).text
Expand Down Expand Up @@ -814,7 +816,7 @@ def load_message(self, com_i_pdu):

return None

# ToDo: interval, senders, comment
# ToDo: interval, senders, comments

# Find all signals in this message.
signals = []
Expand Down Expand Up @@ -842,7 +844,7 @@ def load_message(self, com_i_pdu):
send_type=None,
cycle_time=interval,
signals=signals,
comment=comment,
comment=comments,
bus_name=None,
strict=self.strict)

Expand Down Expand Up @@ -896,7 +898,7 @@ def load_signal(self, xpath):
offset = 0
unit = None
choices = None
comment = None
comments = None
receivers = []
decimal = SignalDecimal(Decimal(factor), Decimal(offset))

Expand Down Expand Up @@ -934,7 +936,7 @@ def load_signal(self, xpath):
return None

# ToDo: minimum, maximum, factor, offset, unit, choices,
# comment and receivers.
# comments and receivers.

return Signal(name=name,
start=bit_position,
Expand All @@ -948,7 +950,7 @@ def load_signal(self, xpath):
maximum=maximum,
unit=unit,
choices=choices,
comment=comment,
comment=comments,
is_float=is_float,
decimal=decimal)

Expand Down
38 changes: 34 additions & 4 deletions cantools/database/can/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,19 @@ def __init__(self,
self._length = length
self._signals = signals
self._signals.sort(key=start_bit)
self._comment = comment

# if the 'comment' argument is a string, we assume that is an
# english comment. this is slightly hacky because the
# function's behavior depends on the type of the passed
# argument, but it is quite convenient...
if isinstance(comment, str):
# use the first comment in the dictionary as "The" comment
self._comments = { None: comment }
else:
# assume that we have either no comment at all or a
# multi-lingual dictionary
self._comments = comment

self._senders = senders if senders else []
self._send_type = send_type
self._cycle_time = cycle_time
Expand Down Expand Up @@ -220,13 +232,31 @@ def signal_groups(self, value):
def comment(self):
"""The message comment, or ``None`` if unavailable.
Note that we implicitly try to return the comment's language
to be English comment if multiple languages were specified.
"""
if self._comments is None:
return None
elif self._comments.get(None) is not None:
return self._comments.get(None)

return self._comment
return self._comments.get('EN', None)

@property
def comments(self):
"""The dictionary with the descriptions of the message in multiple languages. ``None`` if unavailable.
"""
return self._comments

@comment.setter
def comment(self, value):
self._comment = value
self._comments = { None: value }

@comments.setter
def comments(self, value):
self._comments = value

@property
def senders(self):
Expand Down Expand Up @@ -910,4 +940,4 @@ def __repr__(self):
self._frame_id,
self._is_extended_frame,
self._length,
"'" + self._comment + "'" if self._comment is not None else None)
f"{self._comments}" if self._comments else None)
39 changes: 35 additions & 4 deletions cantools/database/can/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,19 @@ def __init__(self,
self._unit = unit
self._choices = choices
self._dbc = dbc_specifics
self._comment = comment

# if the 'comment' argument is a string, we assume that is an
# english comment. this is slightly hacky because the
# function's behavior depends on the type of the passed
# argument, but it is quite convenient...
if isinstance(comment, str):
# use the first comment in the dictionary as "The" comment
self._comments = { None: comment }
else:
# assume that we have either no comment at all or a
# multi-lingual dictionary
self._comments = comment

self._receivers = [] if receivers is None else receivers
self._is_multiplexer = is_multiplexer
self._multiplexer_ids = multiplexer_ids
Expand Down Expand Up @@ -334,13 +346,32 @@ def dbc(self, value):
def comment(self):
"""The signal comment, or ``None`` if unavailable.
Note that we implicitly try to return the comment's language
to be English comment if multiple languages were specified.
"""
if self._comments is None:
return None
elif self._comments.get(None) is not None:
return self._comments.get(None)

return self._comments.get('EN', None)

return self._comment
@property
def comments(self):
"""The dictionary with the descriptions of the signal in multiple
languages. ``None`` if unavailable.
"""
return self._comments

@comment.setter
def comment(self, value):
self._comment = value
self._comments = { None: value }

@comments.setter
def comments(self, value):
self._comments = value

@property
def receivers(self):
Expand Down Expand Up @@ -431,4 +462,4 @@ def __repr__(self):
self._multiplexer_ids,
choices,
self._spn,
"'" + self._comment + "'" if self._comment is not None else None)
f"{self._comments}" if self._comments else None)
9 changes: 8 additions & 1 deletion tests/files/arxml/system-4.2.arxml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@
<ELEMENTS>
<CAN-FRAME>
<SHORT-NAME>Message1</SHORT-NAME>
<DESC><L-2 L="FOR-ALL">Comment1</L-2></DESC>
<DESC>
<L-2 L="EN">Comment1</L-2>
<L-2 L="DE">Kommentar1</L-2>
</DESC>
<FRAME-LENGTH>6</FRAME-LENGTH>
<PDU-TO-FRAME-MAPPINGS>
<PDU-TO-FRAME-MAPPING>
Expand Down Expand Up @@ -289,6 +292,10 @@
<ELEMENTS>
<SYSTEM-SIGNAL>
<SHORT-NAME>Signal1</SHORT-NAME>
<DESC>
<L-2 L="EN">Signal comment!</L-2>
<L-2 L="DE">Signalkommentar!</L-2>
</DESC>
<PHYSICAL-PROPS>
<SW-DATA-DEF-PROPS-VARIANTS>
<SW-DATA-DEF-PROPS-CONDITIONAL>
Expand Down
24 changes: 13 additions & 11 deletions tests/test_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def test_vehicle(self):
"message('RT_SB_INS_Vel_Body_Axes', 0x9588322, True, 8, None)")
self.assertEqual(repr(db.messages[0].signals[0]),
"signal('Validity_INS_Vel_Forwards', 0, 1, 'little_endian', "
"False, 0, 1, 0, 0, 1, 'None', False, None, None, None, 'Valid when "
"bit is set, invalid when bit is clear.')")
"False, 0, 1, 0, 0, 1, 'None', False, None, None, None, {None: 'Valid when "
"bit is set, invalid when bit is clear.'})")
self.assertEqual(db.messages[0].signals[0].initial, 0)
self.assertEqual(db.messages[0].signals[0].receivers, [])
self.assertEqual(db.messages[0].cycle_time, None)
Expand All @@ -83,8 +83,8 @@ def test_dbc_signal_initial_value(self):
self.assertEqual(
repr(signal),
"signal('Validity_Accel_Longitudinal', 0, 1, 'little_endian', False, "
"None, 1, 0, None, None, 'None', False, None, None, None, 'Valid when bit is "
"set, invalid when bit is clear.')")
"None, 1, 0, None, None, 'None', False, None, None, None, {None: 'Valid when bit is "
"set, invalid when bit is clear.'})")

signal = message.get_signal_by_name('Validity_Accel_Lateral')
self.assertEqual(signal.initial , 1)
Expand All @@ -100,9 +100,9 @@ def test_dbc_signal_initial_value(self):
self.assertEqual(
repr(signal),
"signal('Accel_Longitudinal', 16, 16, 'little_endian', True, 32767, "
"0.001, 0, -65, 65, 'g', False, None, None, None, 'Longitudinal "
"0.001, 0, -65, 65, 'g', False, None, None, None, {None: 'Longitudinal "
"acceleration. This is positive when the vehicle accelerates in a "
"forwards direction.')")
"forwards direction.'})")

signal = message.get_signal_by_name('Accel_Lateral')
self.assertEqual(signal.initial , -30000)
Expand Down Expand Up @@ -204,11 +204,11 @@ def test_foobar(self):
"node('FIE', None)\n"
"node('FUM', None)\n"
"\n"
"message('Foo', 0x12330, True, 8, 'Foo.')\n"
"message('Foo', 0x12330, True, 8, {None: 'Foo.'})\n"
" signal('Foo', 0, 12, 'big_endian', True, None, 0.01, "
"250, 229.53, 270.47, 'degK', False, None, None, None, None)\n"
" signal('Bar', 24, 32, 'big_endian', True, None, 0.1, "
"0, 0, 5, 'm', False, None, None, None, 'Bar.')\n"
"0, 0, 5, 'm', False, None, None, None, {None: 'Bar.'})\n"
"\n"
"message('Fum', 0x12331, True, 5, None)\n"
" signal('Fum', 0, 12, 'little_endian', True, None, 1, 0, 0, 10, "
Expand Down Expand Up @@ -4320,7 +4320,8 @@ def test_system_arxml(self):
self.assertEqual(message_1.send_type, None)
self.assertEqual(message_1.cycle_time, None)
self.assertEqual(len(message_1.signals), 3)
self.assertEqual(message_1.comment, 'Comment1')
self.assertEqual(message_1.comments["DE"], 'Kommentar1')
self.assertEqual(message_1.comments["EN"], 'Comment1')
self.assertEqual(message_1.bus_name, None)

signal_1 = message_1.signals[0]
Expand Down Expand Up @@ -4363,7 +4364,8 @@ def test_system_arxml(self):
self.assertEqual(signal_2.decimal.maximum, 4.0)
self.assertEqual(signal_2.unit, 'm')
self.assertEqual(signal_2.choices, None)
self.assertEqual(signal_2.comment, None)
self.assertEqual(signal_2.comments["EN"],'Signal comment!' )
self.assertEqual(signal_2.comments["DE"],'Signalkommentar!' )
self.assertEqual(signal_2.is_multiplexer, False)
self.assertEqual(signal_2.multiplexer_ids, None)

Expand Down Expand Up @@ -4441,7 +4443,7 @@ def test_system_arxml(self):
self.assertEqual(signal_2.decimal.maximum, None)
self.assertEqual(signal_2.unit, None)
self.assertEqual(signal_2.choices, None)
self.assertEqual(signal_2.comment, 'Signal comment!')
self.assertEqual(signal_2.comments["FOR-ALL"], 'Signal comment!')
self.assertEqual(signal_2.is_multiplexer, False)
self.assertEqual(signal_2.multiplexer_ids, None)

Expand Down

0 comments on commit bacf8b8

Please sign in to comment.