From d1bf4701f5bf2c1aa519453f34b4b301c4625dd5 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Thu, 25 Apr 2024 09:15:37 +0200 Subject: [PATCH 1/3] StandardLengthType: only bail out when encountering compressed masks when in strict mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit as usual, we will soldier on in non-strict mode, but the results will be undefined... Signed-off-by: Andreas Lauser Signed-off-by: Katja Köhler --- odxtools/standardlengthtype.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/odxtools/standardlengthtype.py b/odxtools/standardlengthtype.py index beb42e3c..5f39b08c 100644 --- a/odxtools/standardlengthtype.py +++ b/odxtools/standardlengthtype.py @@ -34,7 +34,9 @@ def __apply_mask(self, internal_value: AtomicOdxType) -> AtomicOdxType: if self.bit_mask is None: return internal_value if self.is_condensed_raw is True: - raise NotImplementedError("Serialization of condensed bit mask is not supported") + odxraise("Serialization of condensed bit mask is not supported", + NotImplementedError) + return if isinstance(internal_value, int): return internal_value & self.bit_mask if isinstance(internal_value, bytes): From 96262689e9b00621649f1fd40a2896713b3c5728 Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Thu, 25 Apr 2024 09:15:52 +0200 Subject: [PATCH 2/3] `EncodeState`: add an `emplace_atomic_value()` method analogous to `DecodeState`'s `extract_atomic_value()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit this method converts an atomic value to bytes, emplaces the result at the correct location, and updates the location where the `EncodeState` object points to. Signed-off-by: Andreas Lauser Signed-off-by: Katja Köhler --- examples/somersault.pdx | Bin 25068 -> 25058 bytes odxtools/decodestate.py | 14 +--- odxtools/diagcodedtype.py | 95 +------------------------ odxtools/dynamiclengthfield.py | 2 +- odxtools/encodestate.py | 114 +++++++++++++++++++++++++++--- odxtools/leadinglengthinfotype.py | 8 +-- odxtools/minmaxlengthtype.py | 35 +++++---- odxtools/odxtypes.py | 17 +++++ odxtools/paramlengthinfotype.py | 5 +- odxtools/standardlengthtype.py | 7 +- odxtools/staticfield.py | 4 +- 11 files changed, 152 insertions(+), 149 deletions(-) diff --git a/examples/somersault.pdx b/examples/somersault.pdx index 92419f8d5a531db0350049853473151187d4691b..61e123984f4bd3bfdbab8f8875e703d10bab895a 100644 GIT binary patch delta 23740 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 N)RYWK*5qF^{{`=#r(pm9 delta 23848 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 DctType: def is_highlow_byte_order(self) -> bool: return self.is_highlow_byte_order_raw in [None, True] - @staticmethod - def _encode_internal_value( - *, - internal_value: AtomicOdxType, - bit_position: int, - bit_length: int, - base_data_type: DataType, - is_highlow_byte_order: bool, - ) -> bytes: - """Convert the internal_value to bytes and emplace this into the PDU""" - # Check that bytes and strings actually fit into the bit length - if base_data_type == DataType.A_BYTEFIELD: - if isinstance(internal_value, bytearray): - internal_value = bytes(internal_value) - if not isinstance(internal_value, bytes): - odxraise() - if 8 * len(internal_value) > bit_length: - raise EncodeError(f"The bytefield {internal_value.hex()} is too large " - f"({len(internal_value)} bytes)." - f" The maximum length is {bit_length//8}.") - if base_data_type == DataType.A_ASCIISTRING: - if not isinstance(internal_value, str): - odxraise() - - # The spec says ASCII, meaning only byte values 0-127. - # But in practice, vendors use iso-8859-1, aka latin-1 - # reason being iso-8859-1 never fails since it has a valid - # character mapping for every possible byte sequence. - internal_value = internal_value.encode("iso-8859-1") - - if 8 * len(internal_value) > bit_length: - raise EncodeError(f"The string {repr(internal_value)} is too large." - f" The maximum number of characters is {bit_length//8}.") - elif base_data_type == DataType.A_UTF8STRING: - if not isinstance(internal_value, str): - odxraise() - - internal_value = internal_value.encode("utf-8") - - if 8 * len(internal_value) > bit_length: - raise EncodeError(f"The string {repr(internal_value)} is too large." - f" The maximum number of bytes is {bit_length//8}.") - - elif base_data_type == DataType.A_UNICODE2STRING: - if not isinstance(internal_value, str): - odxraise() - - text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le" - internal_value = internal_value.encode(text_encoding) - - if 8 * len(internal_value) > bit_length: - raise EncodeError(f"The string {repr(internal_value)} is too large." - f" The maximum number of characters is {bit_length//16}.") - - # If the bit length is zero, return empty bytes - if bit_length == 0: - if (base_data_type.value in [ - DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64 - ] and base_data_type.value != 0): - odxraise( - f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.", - EncodeError) - return b'' - - char = ODX_TYPE_TO_FORMAT_LETTER[base_data_type] - padding = (8 - ((bit_length + bit_position) % 8)) % 8 - odxassert((0 <= padding and padding < 8 and (padding + bit_length + bit_position) % 8 == 0), - f"Incorrect padding {padding}") - left_pad = f"p{padding}" if padding > 0 else "" - - # actually encode the value - coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value) - - # apply byte order for numeric objects - if not is_highlow_byte_order and base_data_type in [ - DataType.A_INT32, - DataType.A_UINT32, - DataType.A_FLOAT32, - DataType.A_FLOAT64, - ]: - coded = coded[::-1] - - return cast(bytes, coded) - def _minimal_byte_length_of(self, internal_value: Union[bytes, str]) -> int: """Helper method to get the minimal byte length. (needed for LeadingLength- and MinMaxLengthType) diff --git a/odxtools/dynamiclengthfield.py b/odxtools/dynamiclengthfield.py index acd5f924..68c793c0 100644 --- a/odxtools/dynamiclengthfield.py +++ b/odxtools/dynamiclengthfield.py @@ -82,7 +82,7 @@ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeSt # ensure the correct message size if the field is empty if len(physical_value) == 0: - encode_state.emplace_bytes(b"", "") + encode_state.emplace_bytes(b"") # move cursor and origin positions encode_state.origin_byte_position = orig_origin diff --git a/odxtools/encodestate.py b/odxtools/encodestate.py index 69caedd8..1ba58d21 100644 --- a/odxtools/encodestate.py +++ b/odxtools/encodestate.py @@ -3,7 +3,13 @@ from dataclasses import dataclass, field from typing import Dict, Optional -from .exceptions import OdxWarning +from .exceptions import EncodeError, OdxWarning, odxassert, odxraise +from .odxtypes import AtomicOdxType, DataType + +try: + import bitstruct.c as bitstruct +except ImportError: + import bitstruct @dataclass @@ -47,7 +53,92 @@ class EncodeState: #: (needed for MinMaxLengthType, EndOfPduField, etc.) is_end_of_pdu: bool = True - def emplace_bytes(self, new_data: bytes, param_name: Optional[str] = None) -> None: + def emplace_atomic_value( + self, + *, + internal_value: AtomicOdxType, + bit_length: int, + base_data_type: DataType, + is_highlow_byte_order: bool, + ) -> None: + """Convert the internal_value to bytes and emplace this into the PDU""" + # Check that bytes and strings actually fit into the bit length + if base_data_type == DataType.A_BYTEFIELD: + if isinstance(internal_value, bytearray): + internal_value = bytes(internal_value) + if not isinstance(internal_value, bytes): + odxraise() + if 8 * len(internal_value) > bit_length: + raise EncodeError(f"The bytefield {internal_value.hex()} is too large " + f"({len(internal_value)} bytes)." + f" The maximum length is {bit_length//8}.") + if base_data_type == DataType.A_ASCIISTRING: + if not isinstance(internal_value, str): + odxraise() + + # The spec says ASCII, meaning only byte values 0-127. + # But in practice, vendors use iso-8859-1, aka latin-1 + # reason being iso-8859-1 never fails since it has a valid + # character mapping for every possible byte sequence. + internal_value = internal_value.encode("iso-8859-1") + + if 8 * len(internal_value) > bit_length: + raise EncodeError(f"The string {repr(internal_value)} is too large." + f" The maximum number of characters is {bit_length//8}.") + elif base_data_type == DataType.A_UTF8STRING: + if not isinstance(internal_value, str): + odxraise() + + internal_value = internal_value.encode("utf-8") + + if 8 * len(internal_value) > bit_length: + raise EncodeError(f"The string {repr(internal_value)} is too large." + f" The maximum number of bytes is {bit_length//8}.") + + elif base_data_type == DataType.A_UNICODE2STRING: + if not isinstance(internal_value, str): + odxraise() + + text_encoding = "utf-16-be" if is_highlow_byte_order else "utf-16-le" + internal_value = internal_value.encode(text_encoding) + + if 8 * len(internal_value) > bit_length: + raise EncodeError(f"The string {repr(internal_value)} is too large." + f" The maximum number of characters is {bit_length//16}.") + + # If the bit length is zero, return empty bytes + if bit_length == 0: + if (base_data_type.value in [ + DataType.A_INT32, DataType.A_UINT32, DataType.A_FLOAT32, DataType.A_FLOAT64 + ] and base_data_type.value != 0): + odxraise( + f"The number {repr(internal_value)} cannot be encoded into {bit_length} bits.", + EncodeError) + self.emplace_bytes(b'') + return + + char = base_data_type.bitstruct_format_letter + padding = (8 - ((bit_length + self.cursor_bit_position) % 8)) % 8 + odxassert((0 <= padding and padding < 8 and + (padding + bit_length + self.cursor_bit_position) % 8 == 0), + f"Incorrect padding {padding}") + left_pad = f"p{padding}" if padding > 0 else "" + + # actually encode the value + coded = bitstruct.pack(f"{left_pad}{char}{bit_length}", internal_value) + + # apply byte order for numeric objects + if not is_highlow_byte_order and base_data_type in [ + DataType.A_INT32, + DataType.A_UINT32, + DataType.A_FLOAT32, + DataType.A_FLOAT64, + ]: + coded = coded[::-1] + + self.emplace_bytes(coded) + + def emplace_bytes(self, new_data: bytes, obj_name: Optional[str] = None) -> None: pos = self.cursor_byte_position # Make blob longer if necessary @@ -60,12 +151,19 @@ def emplace_bytes(self, new_data: bytes, param_name: Optional[str] = None) -> No # the value to be inserted is bitwise "disjoint" from the # value which is already in the PDU... if self.coded_message[pos + i] & new_data[i] != 0: - param_name = "" if param_name is None else param_name - warnings.warn( - f"Object '{param_name}' overlaps with another parameter (bits are already set)", - OdxWarning, - stacklevel=1, - ) + if obj_name is not None: + warnings.warn( + f"'{obj_name}' overlaps with another object (bits to be set are already set)", + OdxWarning, + stacklevel=1, + ) + else: + warnings.warn( + "Object overlap (bits to be set are already set)", + OdxWarning, + stacklevel=1, + ) + self.coded_message[pos + i] |= new_data[i] self.cursor_byte_position += len(new_data) diff --git a/odxtools/leadinglengthinfotype.py b/odxtools/leadinglengthinfotype.py index a52f7fc8..70d1b3ec 100644 --- a/odxtools/leadinglengthinfotype.py +++ b/odxtools/leadinglengthinfotype.py @@ -55,24 +55,20 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta byte_length = self._minimal_byte_length_of(internal_value) - length_bytes = self._encode_internal_value( + encode_state.emplace_atomic_value( internal_value=byte_length, - bit_position=encode_state.cursor_bit_position, bit_length=self.bit_length, base_data_type=DataType.A_UINT32, is_highlow_byte_order=self.is_highlow_byte_order, ) - value_bytes = self._encode_internal_value( + encode_state.emplace_atomic_value( internal_value=internal_value, - bit_position=0, bit_length=8 * byte_length, base_data_type=self.base_data_type, is_highlow_byte_order=self.is_highlow_byte_order, ) - encode_state.emplace_bytes(length_bytes + value_bytes, "") - @override def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: diff --git a/odxtools/minmaxlengthtype.py b/odxtools/minmaxlengthtype.py index 10be50c9..39027c9e 100644 --- a/odxtools/minmaxlengthtype.py +++ b/odxtools/minmaxlengthtype.py @@ -65,14 +65,14 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta else: data_length = len(internal_value) - value_bytes = bytearray( - self._encode_internal_value( - internal_value=internal_value, - bit_position=0, - bit_length=8 * data_length, - base_data_type=self.base_data_type, - is_highlow_byte_order=self.is_highlow_byte_order, - )) + orig_cursor = encode_state.cursor_byte_position + encode_state.emplace_atomic_value( + internal_value=internal_value, + bit_length=8 * data_length, + base_data_type=self.base_data_type, + is_highlow_byte_order=self.is_highlow_byte_order, + ) + value_len = encode_state.cursor_byte_position - orig_cursor # TODO: ensure that the termination delimiter is not # encountered within the encoded value. @@ -80,8 +80,8 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta odxassert( self.termination != "END-OF-PDU" or encode_state.is_end_of_pdu, "Encountered a MIN-MAX-LENGTH type with END-OF-PDU termination " - "which is not at the end of the PDU") - if encode_state.is_end_of_pdu or len(value_bytes) == self.max_length: + "which is not located at the end of the PDU") + if encode_state.is_end_of_pdu or value_len == self.max_length: # All termination types may be ended by the end of the PDU # or once reaching the maximum length. In this case, we # must not add the termination sequence @@ -91,25 +91,24 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta # ensure that we don't try to encode an odd-length # value when using a two-byte terminator - odxassert(len(value_bytes) % len(termination_sequence) == 0) + odxassert(value_len % len(termination_sequence) == 0) - value_bytes.extend(termination_sequence) + value_len += len(termination_sequence) + encode_state.emplace_bytes(termination_sequence) - if len(value_bytes) < self.min_length: + if value_len < self.min_length: odxraise( f"Encoded value for MinMaxLengthType " f"must be at least {self.min_length} bytes long. " - f"(Is: {len(value_bytes)} bytes.)", EncodeError) + f"(Is: {value_len} bytes.)", EncodeError) return - elif self.max_length is not None and len(value_bytes) > self.max_length: + elif self.max_length is not None and value_len > self.max_length: odxraise( f"Encoded value for MinMaxLengthType " f"must not be longer than {self.max_length} bytes. " - f"(Is: {len(value_bytes)} bytes.)", EncodeError) + f"(Is: {value_len} bytes.)", EncodeError) return - encode_state.emplace_bytes(value_bytes, "") - def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: odxassert(decode_state.cursor_bit_position == 0, "No bit position can be specified for MIN-MAX-LENGTH-TYPE values.") diff --git a/odxtools/odxtypes.py b/odxtools/odxtypes.py index a79dd360..a8baad6c 100644 --- a/odxtools/odxtypes.py +++ b/odxtools/odxtypes.py @@ -154,6 +154,19 @@ def compare_odx_values(a: AtomicOdxType, b: AtomicOdxType) -> int: f"and {type(b).__name__}") +# format specifiers for the data type using the bitstruct module +_BITSTRUCT_FORMAT_LETTER_MAP__ = { + "A_INT32": "s", + "A_UINT32": "u", + "A_FLOAT32": "f", + "A_FLOAT64": "f", + "A_BYTEFIELD": "r", + "A_UNICODE2STRING": "r", # UTF-16 strings must be converted explicitly + "A_ASCIISTRING": "r", + "A_UTF8STRING": "r", +} + + class DataType(Enum): """Types for the physical and internal value. @@ -181,6 +194,10 @@ class DataType(Enum): def python_type(self) -> Type[AtomicOdxType]: return _ODX_TYPE_TO_PYTHON_TYPE[self.value] + @property + def bitstruct_format_letter(self) -> str: + return _BITSTRUCT_FORMAT_LETTER_MAP__[self.value] + def from_string(self, value: str) -> AtomicOdxType: return _PARSE_ODX_TYPE[self.value](value) diff --git a/odxtools/paramlengthinfotype.py b/odxtools/paramlengthinfotype.py index 67ead7a8..c7e4c1cd 100644 --- a/odxtools/paramlengthinfotype.py +++ b/odxtools/paramlengthinfotype.py @@ -79,16 +79,13 @@ def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeSta encode_state.length_keys[self.length_key.short_name] = bit_length - raw_data = self._encode_internal_value( + encode_state.emplace_atomic_value( internal_value=internal_value, - bit_position=encode_state.cursor_bit_position, bit_length=bit_length, base_data_type=self.base_data_type, is_highlow_byte_order=self.is_highlow_byte_order, ) - encode_state.emplace_bytes(raw_data, "") - def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: # First, we need to find a length key with matching ID. if self.length_key.short_name not in decode_state.length_keys: diff --git a/odxtools/standardlengthtype.py b/odxtools/standardlengthtype.py index 5f39b08c..273b1820 100644 --- a/odxtools/standardlengthtype.py +++ b/odxtools/standardlengthtype.py @@ -34,8 +34,7 @@ def __apply_mask(self, internal_value: AtomicOdxType) -> AtomicOdxType: if self.bit_mask is None: return internal_value if self.is_condensed_raw is True: - odxraise("Serialization of condensed bit mask is not supported", - NotImplementedError) + odxraise("Serialization of condensed bit mask is not supported", NotImplementedError) return if isinstance(internal_value, int): return internal_value & self.bit_mask @@ -52,14 +51,12 @@ def get_static_bit_length(self) -> Optional[int]: @override def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None: - raw_data = self._encode_internal_value( + encode_state.emplace_atomic_value( internal_value=self.__apply_mask(internal_value), - bit_position=encode_state.cursor_bit_position, bit_length=self.bit_length, base_data_type=self.base_data_type, is_highlow_byte_order=self.is_highlow_byte_order, ) - encode_state.emplace_bytes(raw_data, "") @override def decode_from_pdu(self, decode_state: DecodeState) -> AtomicOdxType: diff --git a/odxtools/staticfield.py b/odxtools/staticfield.py index ae028770..a0c492bd 100644 --- a/odxtools/staticfield.py +++ b/odxtools/staticfield.py @@ -72,8 +72,8 @@ def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeSt encode_state.cursor_byte_position = pos_before + self.item_byte_size elif pos_after - pos_before < self.item_byte_size: # add some padding bytes - encode_state.emplace_bytes( - b'\x00' * (self.item_byte_size - (pos_after - pos_before)), "") + encode_state.emplace_bytes(b'\x00' * (self.item_byte_size - + (pos_after - pos_before))) @override def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue: From 5a4d8b8d4247bda304a52447f1029a50fe9f832d Mon Sep 17 00:00:00 2001 From: Andreas Lauser Date: Thu, 25 Apr 2024 09:16:22 +0200 Subject: [PATCH 3/3] More fixes for `NrcConstParameter` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since these parameters usually overlap with a value parameter (the specification gives the impression that they always do), let's pretend that NRC-CONST parameters are not required to be specified and are also not settable. Signed-off-by: Andreas Lauser Signed-off-by: Katja Köhler --- odxtools/parameters/nrcconstparameter.py | 38 ++++++++++++++++-------- tests/test_encoding.py | 36 ++++++++++++++++++++-- tests/test_somersault.py | 3 +- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/odxtools/parameters/nrcconstparameter.py b/odxtools/parameters/nrcconstparameter.py index 487c75f0..3419dbd3 100644 --- a/odxtools/parameters/nrcconstparameter.py +++ b/odxtools/parameters/nrcconstparameter.py @@ -22,13 +22,19 @@ @dataclass class NrcConstParameter(Parameter): - """A param of type NRC-CONST defines a set of values to be matched. + """A param of type NRC-CONST defines a set of values to be matched for a negative response to apply. - An NRC-CONST can only be used in a negative response. - Its encoding behaviour is similar to a VALUE parameter with a TEXTTABLE. - However, an NRC-CONST is used for matching a response (similar to a CODED-CONST). + The behaviour of NRC-CONST parameters is similar to CODED-CONST + parameters in that they allow to specify which coding objects + apply to a binary string, but in contrast to CODED-CONST + parameters they allow to specify multiple values. Thus, the value + of a CODED-CONST parameter is usually set using an overlapping + VALUE parameter. Since NRC-CONST parameters can only be specified + for negative responses, they can thus be regarded as a multiplexer + mechanism that is specific to negative responses. See ASAM MCD-2 D (ODX), p. 77-79. + """ diag_coded_type: DiagCodedType @@ -83,12 +89,12 @@ def internal_data_type(self) -> DataType: @property @override def is_required(self) -> bool: - return len(self.coded_values) > 1 + return False @property @override def is_settable(self) -> bool: - return True + return False @override def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue], @@ -103,12 +109,20 @@ def _encode_positioned_into_pdu(self, physical_value: Optional[ParameterValue], else: coded_value = physical_value else: - # If the user does not select a value, just select - # any. (This branch should only be taken if there is only - # one possible coded value because if there are more, - # specifying a parameter value is mandatory, - # cf. the `.is_required` property.) - coded_value = self.coded_values[0] + # If the user did not select a value, the value of the + # this parameter is set by another parameter which + # overlaps with it. We thus just move the cursor. + bit_pos = encode_state.cursor_bit_position + bit_len = self.diag_coded_type.get_static_bit_length() + + if bit_len is None: + odxraise("The diag coded type of NRC-CONST parameters must " + "exhibit a static size") + return + + encode_state.cursor_byte_position += (bit_pos + bit_len + 7) // 8 + encode_state.cursor_bit_position = 0 + return self.diag_coded_type.encode_into_pdu(cast(AtomicOdxType, coded_value), encode_state) diff --git a/tests/test_encoding.py b/tests/test_encoding.py index 8eeb3b9b..8eebe829 100644 --- a/tests/test_encoding.py +++ b/tests/test_encoding.py @@ -190,6 +190,23 @@ def test_encode_nrc_const(self) -> None: is_highlow_byte_order_raw=None, is_condensed_raw=None, ) + dop = DataObjectProperty( + odx_id=OdxLinkId("dop.id", doc_frags), + short_name="dop_sn", + long_name="example dop", + description=None, + admin_data=None, + diag_coded_type=diag_coded_type, + physical_type=PhysicalType(DataType.A_UINT32, display_radix=None, precision=None), + compu_method=IdenticalCompuMethod( + internal_type=DataType.A_UINT32, physical_type=DataType.A_UINT32), + unit_ref=None, + sdgs=[], + internal_constr=None, + physical_constr=None, + ) + odxlinks = OdxLinkDatabase() + odxlinks.update(dop._build_odxlinks()) param1 = CodedConstParameter( short_name="param1", long_name=None, @@ -212,6 +229,19 @@ def test_encode_nrc_const(self) -> None: bit_position=None, sdgs=[], ) + param3 = ValueParameter( + short_name="param3", + long_name=None, + description=None, + semantic=None, + dop_ref=OdxLinkRef.from_id(dop.odx_id), + dop_snref=None, + physical_default_value_raw=None, + byte_position=1, + bit_position=None, + sdgs=[], + ) + param3._resolve_odxlinks(odxlinks) resp = Response( odx_id=OdxLinkId("response_id", doc_frags), short_name="response_sn", @@ -220,13 +250,13 @@ def test_encode_nrc_const(self) -> None: admin_data=None, sdgs=[], response_type=ResponseType.POSITIVE, - parameters=NamedItemList([param1, param2]), + parameters=NamedItemList([param1, param2, param3]), byte_size=None, ) with self.assertRaises(EncodeError): - resp.encode() # "No value for required parameter param2 specified" - self.assertEqual(resp.encode(param2=0xAB), bytearray([0x12, 0xAB])) + resp.encode() # "No value for required parameter param3 specified" + self.assertEqual(resp.encode(param3=0xAB), bytearray([0x12, 0xAB])) self.assertRaises(EncodeError, resp.encode, param2=0xEF) def test_encode_overlapping(self) -> None: diff --git a/tests/test_somersault.py b/tests/test_somersault.py index 085fcb11..7c79f197 100644 --- a/tests/test_somersault.py +++ b/tests/test_somersault.py @@ -322,10 +322,9 @@ def test_free_param_info(self) -> None: expected_output = ("forward_soberness_check: uint\n" "num_flips: uint\n" "sault_time: uint\n" - "reason: NRC_const; choices = [0, 1, 2]\n" "flips_successfully_done: uint\n") actual_output = stdout.getvalue() - self.assertEqual(actual_output, expected_output) + self.assertEqual(expected_output, actual_output) def test_decode_response(self) -> None: ecu = odxdb.ecus.somersault_lazy