Skip to content

Commit 3176f44

Browse files
authored
Merge pull request #428 from andlaus/ecu_config
Implement the ECU-CONFIG category
2 parents 7efabe8 + aa5e9ec commit 3176f44

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2095
-110
lines changed

examples/somersaultlazy.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def create_isotp_socket(channel: str | None, rxid: int, txid: int) -> isotp.tpso
6565
return result_socket
6666

6767

68-
async def ecu_send(isotp_socket: isotp.tpsock.socket | None, payload: bytes) -> None:
68+
async def ecu_send(isotp_socket: isotp.tpsock.socket | None, payload: bytes | bytearray) -> None:
6969
"""
7070
ECU sends a message, either in "sterile" or in "live" mode.
7171
"""
@@ -77,7 +77,7 @@ async def ecu_send(isotp_socket: isotp.tpsock.socket | None, payload: bytes) ->
7777
assert sterile_rx_tester_event is not None
7878

7979
sterile_rx_tester_event.clear()
80-
sterile_rx_tester.append(payload)
80+
sterile_rx_tester.append(bytes(payload))
8181
sterile_rx_tester_event.set()
8282
else:
8383
assert isotp_socket is not None
@@ -112,7 +112,7 @@ async def ecu_recv(isotp_socket: isotp.tpsock.socket | None) -> bytes:
112112
return await loop.sock_recv(cast(socket, isotp_socket), 4095)
113113

114114

115-
async def tester_send(isotp_socket: isotp.tpsock.socket | None, payload: bytes) -> None:
115+
async def tester_send(isotp_socket: isotp.tpsock.socket | None, payload: bytes | bytearray) -> None:
116116
"""
117117
Tester sends a message, either in "sterile" or in "live" mode.
118118
"""
@@ -123,7 +123,7 @@ async def tester_send(isotp_socket: isotp.tpsock.socket | None, payload: bytes)
123123
assert isotp_socket is None
124124
assert sterile_rx_ecu_event is not None
125125

126-
sterile_rx_ecu.append(payload)
126+
sterile_rx_ecu.append(bytes(payload))
127127
sterile_rx_ecu_event.set()
128128
else:
129129
assert isotp_socket is not None
@@ -360,7 +360,7 @@ async def _auto_close_session_task(self) -> None:
360360

361361

362362
async def tester_await_response(isotp_socket: isotp.tpsock.socket | None,
363-
raw_message: bytes,
363+
raw_message: bytes | bytearray,
364364
timeout: float = 0.5) -> bytes | ParameterValueDict:
365365
# await the answer from the server (be aware that the maximum
366366
# length of ISO-TP telegrams over the CAN bus is 4095 bytes)

odxtools/cli/browse.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,10 @@ def encode_message_from_string_values(
255255
print(f"The value specified for parameter {inner_param_sn} is not a string")
256256
continue
257257

258-
typed_dict[inner_param_sn] = _convert_string_to_odx_type(
259-
inner_param_value, inner_param.physical_type.base_data_type)
258+
if isinstance(inner_param,
259+
ParameterWithDOP) and inner_param.physical_type is not None:
260+
typed_dict[inner_param_sn] = _convert_string_to_odx_type(
261+
inner_param_value, inner_param.physical_type.base_data_type)
260262
parameter_values[parameter.short_name] = typed_dict
261263
else:
262264
if not isinstance(parameter_value, str):

odxtools/cli/compare.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,18 +425,18 @@ def compare_diagnostic_layers(self, dl1: DiagLayer, dl2: DiagLayer) -> ServiceDi
425425
changed_parameters_of_service=services_with_param_changes)
426426
dl1_service_names = [service.short_name for service in dl1.services]
427427

428-
dl1_request_prefixes: list[bytes | None] = [
428+
dl1_request_prefixes: list[bytes | bytearray | None] = [
429429
None if s.request is None else s.request.coded_const_prefix() for s in dl1.services
430430
]
431-
dl2_request_prefixes: list[bytes | None] = [
431+
dl2_request_prefixes: list[bytes | bytearray | None] = [
432432
None if s.request is None else s.request.coded_const_prefix() for s in dl2.services
433433
]
434434

435435
# compare diagnostic services
436436
for service1 in dl1.services:
437437

438438
# check for added diagnostic services
439-
rq_prefix: bytes
439+
rq_prefix: bytes | bytearray
440440
if service1.request is not None:
441441
rq_prefix = service1.request.coded_const_prefix()
442442

odxtools/compositecodec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def composite_codec_get_free_parameters(codec: CompositeCodec) -> list[Parameter
7979

8080

8181
def composite_codec_get_coded_const_prefix(codec: CompositeCodec,
82-
request_prefix: bytes = b'') -> bytes:
82+
request_prefix: bytes = b'') -> bytearray:
8383
encode_state = EncodeState(coded_message=bytearray(), triggering_request=request_prefix)
8484

8585
for param in codec.parameters:

odxtools/configdata.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass, field
3+
from typing import Any
4+
from xml.etree import ElementTree
5+
6+
from .configrecord import ConfigRecord
7+
from .element import NamedElement
8+
from .nameditemlist import NamedItemList
9+
from .odxdoccontext import OdxDocContext
10+
from .odxlink import OdxLinkDatabase, OdxLinkId
11+
from .snrefcontext import SnRefContext
12+
from .specialdatagroup import SpecialDataGroup
13+
from .utils import dataclass_fields_asdict
14+
from .validbasevariant import ValidBaseVariant
15+
16+
17+
@dataclass(kw_only=True)
18+
class ConfigData(NamedElement):
19+
"""This class represents a CONFIG-DATA."""
20+
valid_base_variants: list[ValidBaseVariant]
21+
config_records: NamedItemList[ConfigRecord]
22+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
23+
24+
@staticmethod
25+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigData":
26+
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
27+
28+
valid_base_variants = [
29+
ValidBaseVariant.from_et(vbv_elem, context)
30+
for vbv_elem in et_element.iterfind("VALID-BASE-VARIANTS/VALID-BASE-VARIANT")
31+
]
32+
config_records = NamedItemList([
33+
ConfigRecord.from_et(cr_elem, context)
34+
for cr_elem in et_element.iterfind("CONFIG-RECORDS/CONFIG-RECORD")
35+
])
36+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
37+
38+
return ConfigData(
39+
valid_base_variants=valid_base_variants,
40+
config_records=config_records,
41+
sdgs=sdgs,
42+
**kwargs)
43+
44+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
45+
result = {}
46+
47+
for valid_base_variant in self.valid_base_variants:
48+
result.update(valid_base_variant._build_odxlinks())
49+
for config_record in self.config_records:
50+
result.update(config_record._build_odxlinks())
51+
for sdg in self.sdgs:
52+
result.update(sdg._build_odxlinks())
53+
54+
return result
55+
56+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
57+
for valid_base_variant in self.valid_base_variants:
58+
valid_base_variant._resolve_odxlinks(odxlinks)
59+
for config_record in self.config_records:
60+
config_record._resolve_odxlinks(odxlinks)
61+
for sdg in self.sdgs:
62+
sdg._resolve_odxlinks(odxlinks)
63+
64+
def _resolve_snrefs(self, context: SnRefContext) -> None:
65+
for valid_base_variant in self.valid_base_variants:
66+
valid_base_variant._resolve_snrefs(context)
67+
for config_record in self.config_records:
68+
config_record._resolve_snrefs(context)
69+
for sdg in self.sdgs:
70+
sdg._resolve_snrefs(context)

odxtools/configdatadictionaryspec.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass, field
3+
from typing import Any
4+
from xml.etree import ElementTree
5+
6+
from .dataobjectproperty import DataObjectProperty
7+
from .nameditemlist import NamedItemList
8+
from .odxdoccontext import OdxDocContext
9+
from .odxlink import OdxLinkDatabase, OdxLinkId
10+
from .snrefcontext import SnRefContext
11+
from .unitspec import UnitSpec
12+
13+
14+
@dataclass(kw_only=True)
15+
class ConfigDataDictionarySpec:
16+
data_object_props: NamedItemList[DataObjectProperty] = field(default_factory=NamedItemList)
17+
unit_spec: UnitSpec | None = None
18+
19+
@staticmethod
20+
def from_et(et_element: ElementTree.Element,
21+
context: OdxDocContext) -> "ConfigDataDictionarySpec":
22+
data_object_props = NamedItemList([
23+
DataObjectProperty.from_et(dop_element, context)
24+
for dop_element in et_element.iterfind("DATA-OBJECT-PROPS/DATA-OBJECT-PROP")
25+
])
26+
27+
if (spec_elem := et_element.find("UNIT-SPEC")) is not None:
28+
unit_spec = UnitSpec.from_et(spec_elem, context)
29+
else:
30+
unit_spec = None
31+
32+
return ConfigDataDictionarySpec(
33+
data_object_props=data_object_props,
34+
unit_spec=unit_spec,
35+
)
36+
37+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
38+
odxlinks = {}
39+
40+
for data_object_prop in self.data_object_props:
41+
odxlinks.update(data_object_prop._build_odxlinks())
42+
if self.unit_spec is not None:
43+
odxlinks.update(self.unit_spec._build_odxlinks())
44+
45+
return odxlinks
46+
47+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
48+
for data_object_prop in self.data_object_props:
49+
data_object_prop._resolve_odxlinks(odxlinks)
50+
if self.unit_spec is not None:
51+
self.unit_spec._resolve_odxlinks(odxlinks)
52+
53+
def _resolve_snrefs(self, context: SnRefContext) -> None:
54+
for data_object_prop in self.data_object_props:
55+
data_object_prop._resolve_snrefs(context)
56+
if self.unit_spec is not None:
57+
self.unit_spec._resolve_snrefs(context)

odxtools/configiditem.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass
3+
from xml.etree import ElementTree
4+
5+
from .configitem import ConfigItem
6+
from .odxdoccontext import OdxDocContext
7+
from .utils import dataclass_fields_asdict
8+
9+
10+
@dataclass(kw_only=True)
11+
class ConfigIdItem(ConfigItem):
12+
"""This class represents a CONFIG-ID-ITEM."""
13+
14+
@staticmethod
15+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigIdItem":
16+
kwargs = dataclass_fields_asdict(ConfigItem.from_et(et_element, context))
17+
18+
return ConfigIdItem(**kwargs)

odxtools/configitem.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# SPDX-License-Identifier: MIT
2+
from dataclasses import dataclass, field
3+
from typing import Any
4+
from xml.etree import ElementTree
5+
6+
from .dopbase import DopBase
7+
from .element import NamedElement
8+
from .exceptions import odxrequire
9+
from .odxdoccontext import OdxDocContext
10+
from .odxlink import OdxLinkDatabase, OdxLinkId, OdxLinkRef, resolve_snref
11+
from .snrefcontext import SnRefContext
12+
from .specialdatagroup import SpecialDataGroup
13+
from .utils import dataclass_fields_asdict
14+
15+
16+
@dataclass(kw_only=True)
17+
class ConfigItem(NamedElement):
18+
"""This class represents a CONFIG-ITEM.
19+
20+
CONFIG-ITEM is the base class for CONFIG-ID-ITEM, DATA-ID-ITEM,
21+
OPTION-ITEM, and SYSTEM-ITEM.
22+
"""
23+
byte_position: int | None = None
24+
bit_position: int | None = None
25+
26+
# according to the spec exactly one of the following two
27+
# attributes is not None...x
28+
data_object_prop_ref: OdxLinkRef | None = None
29+
data_object_prop_snref: str | None = None
30+
sdgs: list[SpecialDataGroup] = field(default_factory=list)
31+
32+
@property
33+
def data_object_prop(self) -> DopBase:
34+
return self._data_object_prop
35+
36+
@staticmethod
37+
def from_et(et_element: ElementTree.Element, context: OdxDocContext) -> "ConfigItem":
38+
kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, context))
39+
40+
byte_position = None
41+
if (byte_pos_elem := et_element.findtext("BYTE-POSITION")) is not None:
42+
byte_position = int(byte_pos_elem)
43+
44+
bit_position = None
45+
if (bit_pos_elem := et_element.findtext("BIT-POSITION")) is not None:
46+
bit_position = int(bit_pos_elem)
47+
48+
data_object_prop_ref = OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), context)
49+
data_object_prop_snref = None
50+
if (data_object_prop_snref_elem := et_element.find("DATA-OBJECT-PROP-SNREF")) is not None:
51+
data_object_prop_snref = odxrequire(
52+
data_object_prop_snref_elem.attrib.get("SHORT-NAME"))
53+
sdgs = [SpecialDataGroup.from_et(sdge, context) for sdge in et_element.iterfind("SDGS/SDG")]
54+
55+
return ConfigItem(
56+
byte_position=byte_position,
57+
bit_position=bit_position,
58+
data_object_prop_ref=data_object_prop_ref,
59+
data_object_prop_snref=data_object_prop_snref,
60+
sdgs=sdgs,
61+
**kwargs)
62+
63+
def _build_odxlinks(self) -> dict[OdxLinkId, Any]:
64+
result = {}
65+
66+
for sdg in self.sdgs:
67+
result.update(sdg._build_odxlinks())
68+
69+
return result
70+
71+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
72+
if self.data_object_prop_ref is not None:
73+
self._data_object_prop = odxlinks.resolve(self.data_object_prop_ref, DopBase)
74+
75+
for sdg in self.sdgs:
76+
sdg._resolve_odxlinks(odxlinks)
77+
78+
def _resolve_snrefs(self, context: SnRefContext) -> None:
79+
if self.data_object_prop_snref is not None:
80+
ddds = odxrequire(context.diag_layer).diag_data_dictionary_spec
81+
self._data_object_prop = resolve_snref(self.data_object_prop_snref,
82+
ddds.all_data_object_properties, DopBase)
83+
84+
for sdg in self.sdgs:
85+
sdg._resolve_snrefs(context)

0 commit comments

Comments
 (0)