Skip to content

Commit

Permalink
Merge pull request #298 from andlaus/dynamic_endmarker_fields
Browse files Browse the repository at this point in the history
Implement dynamic endmarker fields
  • Loading branch information
andlaus authored May 6, 2024
2 parents 723be6a + 0c486ab commit 4969472
Show file tree
Hide file tree
Showing 12 changed files with 575 additions and 17 deletions.
2 changes: 2 additions & 0 deletions examples/somersaultecu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,7 @@ class SomersaultSID(IntEnum):
static_fields=NamedItemList(),
end_of_pdu_fields=NamedItemList(),
dynamic_length_fields=NamedItemList(),
dynamic_endmarker_fields=NamedItemList(),
sdgs=[],
)

Expand Down Expand Up @@ -2361,6 +2362,7 @@ class SomersaultSID(IntEnum):
structures=NamedItemList(),
end_of_pdu_fields=NamedItemList(),
dynamic_length_fields=NamedItemList(),
dynamic_endmarker_fields=NamedItemList(),
tables=NamedItemList(),
env_data_descs=NamedItemList(),
env_datas=NamedItemList(),
Expand Down
28 changes: 14 additions & 14 deletions odxtools/diagdatadictionaryspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .dataobjectproperty import DataObjectProperty
from .dopbase import DopBase
from .dtcdop import DtcDop
from .dynamicendmarkerfield import DynamicEndmarkerField
from .dynamiclengthfield import DynamicLengthField
from .endofpdufield import EndOfPduField
from .environmentdata import EnvironmentData
Expand Down Expand Up @@ -37,7 +38,7 @@ class DiagDataDictionarySpec:
structures: NamedItemList[BasicStructure]
static_fields: NamedItemList[StaticField]
dynamic_length_fields: NamedItemList[DynamicLengthField]
#dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
dynamic_endmarker_fields: NamedItemList[DynamicEndmarkerField]
end_of_pdu_fields: NamedItemList[EndOfPduField]
muxs: NamedItemList[Multiplexer]
env_datas: NamedItemList[EnvironmentData]
Expand All @@ -54,7 +55,7 @@ def __post_init__(self) -> None:
self.structures,
self.static_fields,
self.dynamic_length_fields,
#self.dynamic_endmarker_fields,
self.dynamic_endmarker_fields,
self.end_of_pdu_fields,
self.muxs,
self.env_datas,
Expand Down Expand Up @@ -99,11 +100,10 @@ def from_et(et_element: ElementTree.Element,
for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
]

# TODO: dynamic endmarker fields
#dynamic_endmarker_fields = [
# DynamicEndmarkerField.from_et(dl_element, doc_frags)
# for dl_element in et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
#]
dynamic_endmarker_fields = [
DynamicEndmarkerField.from_et(dl_element, doc_frags) for dl_element in
et_element.iterfind("DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD")
]

end_of_pdu_fields = [
EndOfPduField.from_et(eofp_element, doc_frags)
Expand Down Expand Up @@ -145,7 +145,7 @@ def from_et(et_element: ElementTree.Element,
structures=NamedItemList(structures),
static_fields=NamedItemList(static_fields),
dynamic_length_fields=NamedItemList(dynamic_length_fields),
#dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
dynamic_endmarker_fields=NamedItemList(dynamic_endmarker_fields),
end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
muxs=NamedItemList(muxs),
env_datas=NamedItemList(env_datas),
Expand All @@ -172,8 +172,8 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
odxlinks.update(static_field._build_odxlinks())
for dynamic_length_field in self.dynamic_length_fields:
odxlinks.update(dynamic_length_field._build_odxlinks())
#for dynamic_endmarker_field in self.dynamic_endmarker_fields:
# odxlinks.update(dynamic_endmarker_field._build_odxlinks())
for dynamic_endmarker_field in self.dynamic_endmarker_fields:
odxlinks.update(dynamic_endmarker_field._build_odxlinks())
for end_of_pdu_field in self.end_of_pdu_fields:
odxlinks.update(end_of_pdu_field._build_odxlinks())
for mux in self.muxs:
Expand Down Expand Up @@ -204,8 +204,8 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
static_field._resolve_odxlinks(odxlinks)
for dynamic_length_field in self.dynamic_length_fields:
dynamic_length_field._resolve_odxlinks(odxlinks)
#for dynamic_endmarker_field in self.dynamic_endmarker_fields:
# dynamic_endmarker_field._resolve_odxlinks(odxlinks)
for dynamic_endmarker_field in self.dynamic_endmarker_fields:
dynamic_endmarker_field._resolve_odxlinks(odxlinks)
for end_of_pdu_field in self.end_of_pdu_fields:
end_of_pdu_field._resolve_odxlinks(odxlinks)
for mux in self.muxs:
Expand Down Expand Up @@ -234,8 +234,8 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
static_field._resolve_snrefs(diag_layer)
for dynamic_length_field in self.dynamic_length_fields:
dynamic_length_field._resolve_snrefs(diag_layer)
#for dynamic_endmarker_field in self.dynamic_endmarker_fields:
# dynamic_endmarker_field._resolve_snrefs(diag_layer)
for dynamic_endmarker_field in self.dynamic_endmarker_fields:
dynamic_endmarker_field._resolve_snrefs(diag_layer)
for end_of_pdu_field in self.end_of_pdu_fields:
end_of_pdu_field._resolve_snrefs(diag_layer)
for mux in self.muxs:
Expand Down
5 changes: 5 additions & 0 deletions odxtools/diaglayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
lambda ddd_spec: ddd_spec.end_of_pdu_fields,
lambda parent_ref: parent_ref.not_inherited_dops,
)
dynamic_endmarker_fields = self._compute_available_ddd_spec_items(
lambda ddd_spec: ddd_spec.dynamic_endmarker_fields,
lambda parent_ref: parent_ref.not_inherited_dops,
)
dynamic_length_fields = self._compute_available_ddd_spec_items(
lambda ddd_spec: ddd_spec.dynamic_length_fields,
lambda parent_ref: parent_ref.not_inherited_dops,
Expand Down Expand Up @@ -246,6 +250,7 @@ def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
structures=structures,
static_fields=NamedItemList(),
end_of_pdu_fields=end_of_pdu_fields,
dynamic_endmarker_fields=dynamic_endmarker_fields,
dynamic_length_fields=dynamic_length_fields,
tables=tables,
env_data_descs=env_data_descs,
Expand Down
119 changes: 119 additions & 0 deletions odxtools/dynamicendmarkerfield.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Dict, List
from xml.etree import ElementTree

from typing_extensions import override

from .dataobjectproperty import DataObjectProperty
from .decodestate import DecodeState
from .dynenddopref import DynEndDopRef
from .encodestate import EncodeState
from .exceptions import DecodeError, EncodeError, odxassert, odxraise, odxrequire
from .field import Field
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .odxtypes import AtomicOdxType, ParameterValue
from .utils import dataclass_fields_asdict

if TYPE_CHECKING:
from .diaglayer import DiagLayer


@dataclass
class DynamicEndmarkerField(Field):
"""Array of a structure with variable length determined by a termination sequence"""

dyn_end_dop_ref: DynEndDopRef

@staticmethod
def from_et(et_element: ElementTree.Element,
doc_frags: List[OdxDocFragment]) -> "DynamicEndmarkerField":
kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))

dyn_end_dop_ref = DynEndDopRef.from_et(
odxrequire(et_element.find("DYN-END-DOP-REF")), doc_frags)

return DynamicEndmarkerField(dyn_end_dop_ref=dyn_end_dop_ref, **kwargs)

def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
odxlinks = super()._build_odxlinks()
return odxlinks

def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
super()._resolve_odxlinks(odxlinks)

self._dyn_end_dop = odxlinks.resolve(self.dyn_end_dop_ref, DataObjectProperty)

tv_string = self.dyn_end_dop_ref.termination_value_raw
tv_physical = self._dyn_end_dop.diag_coded_type.base_data_type.from_string(tv_string)

self._termination_value = tv_physical

def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
super()._resolve_snrefs(diag_layer)

@property
def dyn_end_dop(self) -> DataObjectProperty:
return self._dyn_end_dop

@property
def termination_value(self) -> AtomicOdxType:
return self._termination_value

@override
def encode_into_pdu(self, physical_value: ParameterValue, encode_state: EncodeState) -> None:

odxassert(encode_state.cursor_bit_position == 0,
"No bit position can be specified for dynamic endmarker fields!")
if not isinstance(physical_value, (tuple, list)):
odxraise(
f"Expected a sequence of values for dynamic endmarker field {self.short_name}, "
f"got {type(physical_value).__name__}", EncodeError)
return

orig_is_end_of_pdu = encode_state.is_end_of_pdu
encode_state.is_end_of_pdu = False
for i, item in enumerate(physical_value):
if i == len(physical_value) - 1:
encode_state.is_end_of_pdu = orig_is_end_of_pdu

self.structure.encode_into_pdu(item, encode_state)
encode_state.is_end_of_pdu = orig_is_end_of_pdu

if not encode_state.is_end_of_pdu:
# only add an endmarker if we are not at the end of the PDU
self.dyn_end_dop.encode_into_pdu(self.termination_value, encode_state)

@override
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:

odxassert(decode_state.cursor_bit_position == 0,
"No bit position can be specified for dynamic endmarker fields!")

orig_origin = decode_state.origin_byte_position
orig_cursor = decode_state.cursor_byte_position
decode_state.origin_byte_position = decode_state.cursor_byte_position

result: List[ParameterValue] = []
while True:
# check if we're at the end of the PDU
if decode_state.cursor_byte_position == len(decode_state.coded_message):
break

# check if the cursor currently points to a termination
# value
tmp_cursor = decode_state.cursor_byte_position
try:
tv_candidate = self.dyn_end_dop.decode_from_pdu(decode_state)
if tv_candidate == self.termination_value:
break
except DecodeError:
pass
decode_state.cursor_byte_position = tmp_cursor

result.append(self.structure.decode_from_pdu(decode_state))

decode_state.origin_byte_position = orig_origin
decode_state.cursor_byte_position = max(orig_cursor, decode_state.cursor_byte_position)

return result
38 changes: 38 additions & 0 deletions odxtools/dynenddopref.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: MIT
from dataclasses import dataclass
from typing import List, Optional, overload
from xml.etree import ElementTree

from .exceptions import odxraise, odxrequire
from .odxlink import OdxDocFragment, OdxLinkRef
from .utils import dataclass_fields_asdict


@dataclass
class DynEndDopRef(OdxLinkRef):
termination_value_raw: str

@staticmethod
@overload
def from_et(et_element: None, source_doc_frags: List[OdxDocFragment]) -> None:
...

@staticmethod
@overload
def from_et(et_element: ElementTree.Element,
source_doc_frags: List[OdxDocFragment]) -> "DynEndDopRef":
...

@staticmethod
def from_et(et_element: Optional[ElementTree.Element],
source_doc_frags: List[OdxDocFragment]) -> Optional["DynEndDopRef"]:

if et_element is None:
odxraise("Mandatory DYN-END-DOP-REF tag is missing")
return None

kwargs = dataclass_fields_asdict(OdxLinkRef.from_et(et_element, source_doc_frags))

termination_value_raw = odxrequire(et_element.findtext("TERMINATION-VALUE"))

return DynEndDopRef(termination_value_raw=termination_value_raw, **kwargs)
6 changes: 3 additions & 3 deletions odxtools/encodestate.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SPDX-License-Identifier: MIT
import warnings
from dataclasses import dataclass, field
from typing import Dict, Optional
from typing import Dict, Optional, SupportsBytes

from .exceptions import EncodeError, OdxWarning, odxassert, odxraise
from .odxtypes import AtomicOdxType, DataType
Expand Down Expand Up @@ -83,13 +83,13 @@ def emplace_atomic_value(

# Check that bytes and strings actually fit into the bit length
if base_data_type == DataType.A_BYTEFIELD:
if not isinstance(internal_value, bytes):
if not isinstance(internal_value, (bytes, bytearray, SupportsBytes)):
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}.")
raw_value = internal_value
raw_value = bytes(internal_value)
elif base_data_type == DataType.A_ASCIISTRING:
if not isinstance(internal_value, str):
odxraise()
Expand Down
16 changes: 16 additions & 0 deletions odxtools/templates/macros/printDynamicEndmarkerField.xml.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{#- -*- mode: sgml; tab-width: 1; indent-tabs-mode: nil -*-
#
# SPDX-License-Identifier: MIT
-#}

{%- import('macros/printElementId.xml.jinja2') as peid %}

{%- macro printStaticField(demf) -%}
<DYNAMIC-ENDMARKER-FIELD ID="{{demf.odx_id.local_id}}">
{{ peid.printElementIdSubtags(demf)|indent(1) }}
<BASIC-STRUCTURE-REF ID-REF="{{demf.structure_ref.ref_id}}" />
<DYN-END-DOP-REF ID-REF="{{demf.dyn_end_dop_ref.ref_id}}">
<TERMINATION-VALUE>{{demf.dyn_end_dop_ref.termination_value_raw}}</TERMINATION-VALUE>
</DYN-END-DOP-REF>
</DYNAMIC-ENDMARKER-FIELD>
{%- endmacro -%}
8 changes: 8 additions & 0 deletions odxtools/templates/macros/printVariant.xml.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
{%- import('macros/printEndOfPdu.xml.jinja2') as peopdu %}
{%- import('macros/printStaticField.xml.jinja2') as psf %}
{%- import('macros/printDynamicLengthField.xml.jinja2') as pdlf %}
{%- import('macros/printDynamicEndmarkerField.xml.jinja2') as pdemf %}
{%- import('macros/printMux.xml.jinja2') as pm %}
{%- import('macros/printEnvData.xml.jinja2') as ped %}
{%- import('macros/printEnvDataDesc.xml.jinja2') as pedd %}
Expand Down Expand Up @@ -82,6 +83,13 @@
{%- endfor %}
</DYNAMIC-LENGTH-FIELDS>
{%- endif %}
{%- if ddds.dynamic_endmarker_fields %}
<DYNAMIC-ENDMARKER-FIELDS>
{%- for demf in ddds.dynamic_endmarker_fields %}
{{ pdemf.printDynamicEndmarkerField(demf)|indent(3) }}
{%- endfor %}
</DYNAMIC-ENDMARKER-FIELDS>
{%- endif %}
{%- if ddds.end_of_pdu_fields %}
<END-OF-PDU-FIELDS>
{%- for eopdu in ddds.end_of_pdu_fields %}
Expand Down
Loading

0 comments on commit 4969472

Please sign in to comment.