Skip to content

Commit

Permalink
implement static fields
Browse files Browse the repository at this point in the history
this includes parsing/writing, en-/decoding and a unit test...

Signed-off-by: Andreas Lauser <[email protected]>
Signed-off-by: Michael Hahn <[email protected]>
  • Loading branch information
andlaus committed Mar 1, 2024
1 parent abdf3c8 commit 7c5d0f8
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 20 deletions.
2 changes: 2 additions & 0 deletions examples/somersaultecu.py
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,7 @@ class SomersaultSID(IntEnum):
env_data_descs=NamedItemList(somersault_env_data_descs.values()),
dtc_dops=NamedItemList(),
structures=NamedItemList(),
static_fields=NamedItemList(),
end_of_pdu_fields=NamedItemList(),
dynamic_length_fields=NamedItemList(),
sdgs=[],
Expand Down Expand Up @@ -2327,6 +2328,7 @@ class SomersaultSID(IntEnum):
diag_data_dictionary_spec=DiagDataDictionarySpec(
dtc_dops=NamedItemList(),
data_object_props=NamedItemList(),
static_fields=NamedItemList(),
structures=NamedItemList(),
end_of_pdu_fields=NamedItemList(),
dynamic_length_fields=NamedItemList(),
Expand Down
29 changes: 15 additions & 14 deletions odxtools/diagdatadictionaryspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
from .environmentdata import EnvironmentData
from .environmentdatadescription import EnvironmentDataDescription
from .exceptions import odxraise
from .globals import logger
from .multiplexer import Multiplexer
from .nameditemlist import NamedItemList
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .specialdatagroup import SpecialDataGroup
from .staticfield import StaticField
from .structure import Structure
from .table import Table
from .unitspec import UnitSpec
Expand All @@ -33,6 +33,7 @@ class DiagDataDictionarySpec:
data_object_props: NamedItemList[DataObjectProperty]
structures: NamedItemList[BasicStructure]
end_of_pdu_fields: NamedItemList[EndOfPduField]
static_fields: NamedItemList[StaticField]
dynamic_length_fields: NamedItemList[DynamicLengthField]
tables: NamedItemList[Table]
env_data_descs: NamedItemList[EnvironmentDataDescription]
Expand All @@ -48,6 +49,7 @@ def __post_init__(self) -> None:
self.data_object_props,
self.structures,
self.end_of_pdu_fields,
self.static_fields,
self.dynamic_length_fields,
self.env_data_descs,
self.env_datas,
Expand All @@ -73,6 +75,11 @@ def from_et(et_element: ElementTree.Element,
for eofp_element in et_element.iterfind("END-OF-PDU-FIELDS/END-OF-PDU-FIELD")
]

static_fields = [
StaticField.from_et(dl_element, doc_frags)
for dl_element in et_element.iterfind("STATIC-FIELDS/STATIC-FIELD")
]

dynamic_length_fields = [
DynamicLengthField.from_et(dl_element, doc_frags)
for dl_element in et_element.iterfind("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD")
Expand Down Expand Up @@ -115,25 +122,13 @@ def from_et(et_element: ElementTree.Element,
else:
unit_spec = None

# TODO: Parse different specs.. Which of them are needed?
for (path, name) in [
("STATIC-FIELDS", "static fields"),
("DYNAMIC-LENGTH-FIELDS/DYNAMIC-LENGTH-FIELD", "dynamic length fields"),
(
"DYNAMIC-ENDMARKER-FIELDS/DYNAMIC-ENDMARKER-FIELD",
"dynamic endmarker fields",
),
]:
num = len(list(et_element.iterfind(path)))
if num > 0:
logger.info(f"Not implemented: Did not parse {num} {name}.")

sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)

return DiagDataDictionarySpec(
data_object_props=NamedItemList(data_object_props),
structures=NamedItemList(structures),
end_of_pdu_fields=NamedItemList(end_of_pdu_fields),
static_fields=NamedItemList(static_fields),
dynamic_length_fields=NamedItemList(dynamic_length_fields),
dtc_dops=NamedItemList(dtc_dops),
unit_spec=unit_spec,
Expand Down Expand Up @@ -162,6 +157,8 @@ def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
odxlinks.update(sdg._build_odxlinks())
for structure in self.structures:
odxlinks.update(structure._build_odxlinks())
for static_field in self.static_fields:
odxlinks.update(static_field._build_odxlinks())
for dynamic_length_field in self.dynamic_length_fields:
odxlinks.update(dynamic_length_field._build_odxlinks())
for end_of_pdu_field in self.end_of_pdu_fields:
Expand All @@ -179,6 +176,8 @@ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
data_object_prop._resolve_odxlinks(odxlinks)
for dtc_dop in self.dtc_dops:
dtc_dop._resolve_odxlinks(odxlinks)
for static_field in self.static_fields:
static_field._resolve_odxlinks(odxlinks)
for dynamic_length_field in self.dynamic_length_fields:
dynamic_length_field._resolve_odxlinks(odxlinks)
for end_of_pdu_field in self.end_of_pdu_fields:
Expand All @@ -204,6 +203,8 @@ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
data_object_prop._resolve_snrefs(diag_layer)
for dtc_dop in self.dtc_dops:
dtc_dop._resolve_snrefs(diag_layer)
for static_field in self.static_fields:
static_field._resolve_snrefs(diag_layer)
for dynamic_length_field in self.dynamic_length_fields:
dynamic_length_field._resolve_snrefs(diag_layer)
for end_of_pdu_field in self.end_of_pdu_fields:
Expand Down
1 change: 1 addition & 0 deletions odxtools/diaglayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ def _finalize_init(self, odxlinks: OdxLinkDatabase) -> None:
data_object_props=dops,
dtc_dops=dtc_dops,
structures=structures,
static_fields=NamedItemList(),
end_of_pdu_fields=end_of_pdu_fields,
dynamic_length_fields=dynamic_length_fields,
tables=tables,
Expand Down
10 changes: 5 additions & 5 deletions odxtools/dynamiclengthfield.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
decode_state.cursor_bit_position = det_num_items.bit_position or 0

n = det_num_items.dop.decode_from_pdu(decode_state)
result: List[ParameterValue] = []

if not isinstance(n, int):
odxraise(f"Number of items specified by a dynamic length field {self.short_name} "
Expand All @@ -107,11 +108,10 @@ def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
odxraise(
f"Number of items specified by a dynamic length field {self.short_name} "
f"must be positive (is: {n})", DecodeError)
else:
decode_state.cursor_byte_position = decode_state.origin_byte_position + self.offset
result: List[ParameterValue] = []
for _ in range(n):
result.append(self.structure.decode_from_pdu(decode_state))

decode_state.cursor_byte_position = decode_state.origin_byte_position + self.offset
for _ in range(n):
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)
Expand Down
101 changes: 101 additions & 0 deletions odxtools/staticfield.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# 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 .decodestate import DecodeState
from .encodestate import EncodeState
from .exceptions import odxassert, odxraise, odxrequire
from .field import Field
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
from .odxtypes import ParameterValue
from .utils import dataclass_fields_asdict

if TYPE_CHECKING:
from .diaglayer import DiagLayer


@dataclass
class StaticField(Field):
"""Array of a fixed number of structure objects"""
fixed_number_of_items: int
item_byte_size: int

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

fixed_number_of_items = int(odxrequire(et_element.findtext('FIXED-NUMBER-OF-ITEMS')))
item_byte_size = int(odxrequire(et_element.findtext('ITEM-BYTE-SIZE')))

return StaticField(
fixed_number_of_items=fixed_number_of_items, item_byte_size=item_byte_size, **kwargs)

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

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

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

@override
def convert_physical_to_bytes(
self,
physical_value: ParameterValue,
encode_state: EncodeState,
bit_position: int = 0,
) -> bytes:
if not isinstance(physical_value,
(tuple, list)) or len(physical_value) != self.fixed_number_of_items:
odxraise(f"Value for static field '{self.short_name}' "
f"must be a list of size {self.fixed_number_of_items}")

result = bytearray()
for val in physical_value:
if not isinstance(val, dict):
odxraise(f"The individual parameter values for static field '{self.short_name}' "
f"must be dictionaries for structure '{self.structure.short_name}'")

data = self.structure.convert_physical_to_bytes(val, encode_state)

if len(data) > self.item_byte_size:
odxraise(f"Insufficient item byte size for static field {self.short_name}: "
f"Is {self.item_byte_size} bytes, but need at least {len(data)} bytes")
data = data[:self.item_byte_size]
elif len(data) < self.item_byte_size:
# add some padding bytes
data = data.ljust(self.item_byte_size, b'\x00')

result += data

return result

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

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

result: List[ParameterValue] = []
for _ in range(self.fixed_number_of_items):
orig_cursor = decode_state.cursor_byte_position

if decode_state.cursor_byte_position - orig_cursor > self.item_byte_size:
odxraise(f"Insufficient item byte size for static field {self.short_name}: "
f"Is {self.item_byte_size} bytes, but need at least "
f"{decode_state.cursor_byte_position - orig_cursor} bytes")

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

decode_state.cursor_byte_position = orig_cursor + self.item_byte_size

return result
15 changes: 15 additions & 0 deletions odxtools/templates/macros/printStaticField.xml.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{#- -*- mode: sgml; tab-width: 1; indent-tabs-mode: nil -*-
#
# SPDX-License-Identifier: MIT
-#}

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

{%- macro printStaticField(sf) -%}
<STATIC-FIELD ID="{{sf.odx_id.local_id}}">
{{ peid.printElementIdSubtags(sf)|indent(1) }}
<BASIC-STRUCTURE-REF ID-REF="{{sf.structure_ref.ref_id}}" />
<FIXED-NUMBER-OF-ITEMS>{{sf.fixed_number_of_items}}</FIXED-NUMBER-OF-ITEMS>
<ITEM-BYTE-SIZE>{{sf.item_byte_size}}</ITEM-BYTE-SIZE>
</STATIC-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 @@ -9,6 +9,7 @@
{%- import('macros/printFunctionalClass.xml.jinja2') as pfc %}
{%- import('macros/printStructure.xml.jinja2') as pst %}
{%- import('macros/printEndOfPdu.xml.jinja2') as peopdu %}
{%- import('macros/printStaticField.xml.jinja2') as psf %}
{%- import('macros/printDynamicLengthField.xml.jinja2') as pdlf %}
{%- import('macros/printMux.xml.jinja2') as pm %}
{%- import('macros/printEnvData.xml.jinja2') as ped %}
Expand Down Expand Up @@ -67,6 +68,13 @@
{%- endfor %}
</STRUCTURES>
{%- endif %}
{%- if ddds.static_fields %}
<STATIC-FIELDS>
{%- for sf in ddds.static_fields %}
{{ psf.printStaticField(sf)|indent(3) }}
{%- endfor %}
</STATIC-FIELDS>
{%- endif %}
{%- if ddds.dynamic_length_fields %}
<DYNAMIC-LENGTH-FIELDS>
{%- for dlf in ddds.dynamic_length_fields %}
Expand Down
Loading

0 comments on commit 7c5d0f8

Please sign in to comment.