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