odxtools 8.3.4__py3-none-any.whl → 9.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odxtools/basicstructure.py +36 -207
- odxtools/cli/_print_utils.py +163 -131
- odxtools/cli/browse.py +94 -79
- odxtools/cli/compare.py +88 -69
- odxtools/cli/list.py +2 -3
- odxtools/codec.py +211 -0
- odxtools/comparamspec.py +16 -54
- odxtools/comparamsubset.py +20 -61
- odxtools/database.py +18 -2
- odxtools/diaglayercontainer.py +14 -40
- odxtools/diaglayers/hierarchyelement.py +0 -10
- odxtools/dopbase.py +5 -3
- odxtools/dtcdop.py +101 -14
- odxtools/inputparam.py +0 -7
- odxtools/leadinglengthinfotype.py +1 -8
- odxtools/message.py +0 -7
- odxtools/minmaxlengthtype.py +4 -4
- odxtools/odxcategory.py +83 -0
- odxtools/outputparam.py +0 -7
- odxtools/parameterinfo.py +12 -12
- odxtools/parameters/lengthkeyparameter.py +1 -2
- odxtools/parameters/parameter.py +6 -4
- odxtools/parameters/tablekeyparameter.py +9 -8
- odxtools/paramlengthinfotype.py +8 -9
- odxtools/request.py +109 -16
- odxtools/response.py +115 -15
- odxtools/specialdatagroup.py +1 -1
- odxtools/templates/comparam-spec.odx-c.xml.jinja2 +4 -21
- odxtools/templates/comparam-subset.odx-cs.xml.jinja2 +4 -22
- odxtools/templates/diag_layer_container.odx-d.xml.jinja2 +4 -25
- odxtools/templates/macros/printDOP.xml.jinja2 +16 -0
- odxtools/templates/macros/printOdxCategory.xml.jinja2 +28 -0
- odxtools/uds.py +0 -8
- odxtools/version.py +2 -2
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/METADATA +7 -8
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/RECORD +40 -37
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/WHEEL +1 -1
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/LICENSE +0 -0
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/entry_points.txt +0 -0
- {odxtools-8.3.4.dist-info → odxtools-9.1.0.dist-info}/top_level.txt +0 -0
odxtools/odxcategory.py
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
2
|
+
from dataclasses import dataclass
|
3
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
4
|
+
from xml.etree import ElementTree
|
5
|
+
|
6
|
+
from .admindata import AdminData
|
7
|
+
from .companydata import CompanyData
|
8
|
+
from .element import IdentifiableElement
|
9
|
+
from .exceptions import odxrequire
|
10
|
+
from .nameditemlist import NamedItemList
|
11
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
12
|
+
from .snrefcontext import SnRefContext
|
13
|
+
from .specialdatagroup import SpecialDataGroup
|
14
|
+
from .utils import dataclass_fields_asdict
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from .database import Database
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass
|
21
|
+
class OdxCategory(IdentifiableElement):
|
22
|
+
"""This is the base class for all top-level container classes in ODX"""
|
23
|
+
|
24
|
+
admin_data: Optional[AdminData]
|
25
|
+
company_datas: NamedItemList[CompanyData]
|
26
|
+
sdgs: List[SpecialDataGroup]
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "OdxCategory":
|
30
|
+
raise Exception("Calling `._from_et()` is not allowed for OdxCategory. "
|
31
|
+
"Use `OdxCategory.category_from_et()`!")
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def category_from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment], *,
|
35
|
+
doc_type: str) -> "OdxCategory":
|
36
|
+
|
37
|
+
short_name = odxrequire(et_element.findtext("SHORT-NAME"))
|
38
|
+
# create the current ODX "document fragment" (description of the
|
39
|
+
# current document for references and IDs)
|
40
|
+
doc_frags = [OdxDocFragment(short_name, doc_type)]
|
41
|
+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
42
|
+
|
43
|
+
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
|
44
|
+
company_datas = NamedItemList([
|
45
|
+
CompanyData.from_et(cde, doc_frags)
|
46
|
+
for cde in et_element.iterfind("COMPANY-DATAS/COMPANY-DATA")
|
47
|
+
])
|
48
|
+
sdgs = [
|
49
|
+
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
50
|
+
]
|
51
|
+
|
52
|
+
return OdxCategory(admin_data=admin_data, company_datas=company_datas, sdgs=sdgs, **kwargs)
|
53
|
+
|
54
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
55
|
+
result = {self.odx_id: self}
|
56
|
+
|
57
|
+
if self.admin_data is not None:
|
58
|
+
result.update(self.admin_data._build_odxlinks())
|
59
|
+
for cd in self.company_datas:
|
60
|
+
result.update(cd._build_odxlinks())
|
61
|
+
for sdg in self.sdgs:
|
62
|
+
result.update(sdg._build_odxlinks())
|
63
|
+
|
64
|
+
return result
|
65
|
+
|
66
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
67
|
+
if self.admin_data is not None:
|
68
|
+
self.admin_data._resolve_odxlinks(odxlinks)
|
69
|
+
for cd in self.company_datas:
|
70
|
+
cd._resolve_odxlinks(odxlinks)
|
71
|
+
for sdg in self.sdgs:
|
72
|
+
sdg._resolve_odxlinks(odxlinks)
|
73
|
+
|
74
|
+
def _finalize_init(self, database: "Database", odxlinks: OdxLinkDatabase) -> None:
|
75
|
+
pass
|
76
|
+
|
77
|
+
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
78
|
+
if self.admin_data is not None:
|
79
|
+
self.admin_data._resolve_snrefs(context)
|
80
|
+
for cd in self.company_datas:
|
81
|
+
cd._resolve_snrefs(context)
|
82
|
+
for sdg in self.sdgs:
|
83
|
+
sdg._resolve_snrefs(context)
|
odxtools/outputparam.py
CHANGED
@@ -3,8 +3,6 @@ from dataclasses import dataclass
|
|
3
3
|
from typing import Any, Dict, List, Optional
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
-
from deprecation import deprecated
|
7
|
-
|
8
6
|
from .dopbase import DopBase
|
9
7
|
from .element import IdentifiableElement
|
10
8
|
from .exceptions import odxrequire
|
@@ -40,8 +38,3 @@ class OutputParam(IdentifiableElement):
|
|
40
38
|
def dop_base(self) -> DopBase:
|
41
39
|
"""The data object property describing this parameter."""
|
42
40
|
return self._dop_base
|
43
|
-
|
44
|
-
@property
|
45
|
-
@deprecated(details="use .dop_base") # type: ignore[misc]
|
46
|
-
def dop(self) -> DopBase:
|
47
|
-
return self._dop_base
|
odxtools/parameterinfo.py
CHANGED
@@ -34,36 +34,36 @@ from .staticfield import StaticField
|
|
34
34
|
|
35
35
|
def _get_linear_segment_info(segment: LinearSegment) -> str:
|
36
36
|
ll = segment.physical_lower_limit
|
37
|
-
ul = segment.physical_upper_limit
|
38
37
|
if ll is None or ll.interval_type == IntervalType.INFINITE:
|
39
38
|
ll_str = "(-inf"
|
40
39
|
else:
|
41
40
|
ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
|
42
41
|
ll_str = f"{ll_delim}{ll._value!r}"
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
ul = segment.physical_upper_limit
|
44
|
+
if ul is None or ul.interval_type == IntervalType.INFINITE:
|
45
|
+
ul_str = "inf)"
|
46
|
+
else:
|
47
|
+
ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
|
48
|
+
ul_str = f"{ul._value!r}{ul_delim}"
|
49
49
|
|
50
50
|
return f"{ll_str}, {ul_str}"
|
51
51
|
|
52
52
|
|
53
53
|
def _get_rat_func_segment_info(segment: RatFuncSegment) -> str:
|
54
54
|
ll = segment.lower_limit
|
55
|
-
ul = segment.upper_limit
|
56
55
|
if ll is None or ll.interval_type == IntervalType.INFINITE:
|
57
56
|
ll_str = "(-inf"
|
58
57
|
else:
|
59
58
|
ll_delim = '(' if ll.interval_type == IntervalType.OPEN else '['
|
60
59
|
ll_str = f"{ll_delim}{ll._value!r}"
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
ul = segment.upper_limit
|
62
|
+
if ul is None or ul.interval_type == IntervalType.INFINITE:
|
63
|
+
ul_str = "inf)"
|
64
|
+
else:
|
65
|
+
ul_delim = ')' if ul.interval_type == IntervalType.OPEN else ']'
|
66
|
+
ul_str = f"{ul._value!r}{ul_delim}"
|
67
67
|
|
68
68
|
return f"{ll_str}, {ul_str}"
|
69
69
|
|
@@ -105,9 +105,8 @@ class LengthKeyParameter(ParameterWithDOP):
|
|
105
105
|
# emplace a value of zero into the encode state, but pretend the bits not to be used
|
106
106
|
n = odxrequire(self.dop.get_static_bit_length()) + encode_state.cursor_bit_position
|
107
107
|
tmp_val = b'\x00' * ((n + 7) // 8)
|
108
|
-
encode_state.emplace_bytes(tmp_val, obj_used_mask=tmp_val)
|
109
|
-
|
110
108
|
encode_state.cursor_bit_position = 0
|
109
|
+
encode_state.emplace_bytes(tmp_val, obj_used_mask=tmp_val)
|
111
110
|
|
112
111
|
def encode_value_into_pdu(self, encode_state: EncodeState) -> None:
|
113
112
|
|
odxtools/parameters/parameter.py
CHANGED
@@ -33,11 +33,13 @@ ParameterType = Literal[
|
|
33
33
|
@dataclass
|
34
34
|
class Parameter(NamedElement):
|
35
35
|
"""This class corresponds to POSITIONABLE-PARAM in the ODX
|
36
|
-
specification
|
36
|
+
specification
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
All parameter classes must adhere to the `Codec` type protocol, so
|
39
|
+
`isinstance(param, Codec)` ought to be true. Be aware that, even
|
40
|
+
though the ODX specification seems to make the distinction of
|
41
|
+
"positionable" and "normal" parameters, it does not define any
|
42
|
+
non-positionable parameter types.
|
41
43
|
|
42
44
|
"""
|
43
45
|
oid: Optional[str]
|
@@ -80,18 +80,23 @@ class TableKeyParameter(Parameter):
|
|
80
80
|
super()._resolve_odxlinks(odxlinks)
|
81
81
|
|
82
82
|
# Either table_ref or table_row_ref will be defined
|
83
|
-
if self.table_ref:
|
83
|
+
if self.table_ref is not None:
|
84
84
|
if TYPE_CHECKING:
|
85
85
|
self._table = odxlinks.resolve(self.table_ref, Table)
|
86
86
|
else:
|
87
87
|
self._table = odxlinks.resolve(self.table_ref)
|
88
88
|
|
89
|
-
if self.table_row_ref:
|
89
|
+
if self.table_row_ref is not None:
|
90
90
|
if TYPE_CHECKING:
|
91
91
|
self._table_row = odxlinks.resolve(self.table_row_ref, TableRow)
|
92
92
|
else:
|
93
93
|
self._table_row = odxlinks.resolve(self.table_row_ref)
|
94
|
-
|
94
|
+
|
95
|
+
if self.table_ref is None and self.table_snref is None:
|
96
|
+
if TYPE_CHECKING:
|
97
|
+
self._table = odxlinks.resolve(self._table_row.table_ref, Table)
|
98
|
+
else:
|
99
|
+
self._table = odxlinks.resolve(self._table_row.table_ref)
|
95
100
|
|
96
101
|
@override
|
97
102
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
@@ -116,11 +121,7 @@ class TableKeyParameter(Parameter):
|
|
116
121
|
|
117
122
|
@property
|
118
123
|
def table(self) -> "Table":
|
119
|
-
|
120
|
-
return self._table
|
121
|
-
if self._table_row is not None:
|
122
|
-
return self._table_row.table
|
123
|
-
odxraise(f'Could not resolve the table of {self.short_name}')
|
124
|
+
return self._table
|
124
125
|
|
125
126
|
@property
|
126
127
|
def table_row(self) -> Optional["TableRow"]:
|
odxtools/paramlengthinfotype.py
CHANGED
@@ -20,9 +20,16 @@ if TYPE_CHECKING:
|
|
20
20
|
|
21
21
|
@dataclass
|
22
22
|
class ParamLengthInfoType(DiagCodedType):
|
23
|
-
|
24
23
|
length_key_ref: OdxLinkRef
|
25
24
|
|
25
|
+
@property
|
26
|
+
def dct_type(self) -> DctType:
|
27
|
+
return "PARAM-LENGTH-INFO-TYPE"
|
28
|
+
|
29
|
+
@property
|
30
|
+
def length_key(self) -> "LengthKeyParameter":
|
31
|
+
return self._length_key
|
32
|
+
|
26
33
|
@staticmethod
|
27
34
|
@override
|
28
35
|
def from_et(et_element: ElementTree.Element,
|
@@ -34,10 +41,6 @@ class ParamLengthInfoType(DiagCodedType):
|
|
34
41
|
|
35
42
|
return ParamLengthInfoType(length_key_ref=length_key_ref, **kwargs)
|
36
43
|
|
37
|
-
@property
|
38
|
-
def dct_type(self) -> DctType:
|
39
|
-
return "PARAM-LENGTH-INFO-TYPE"
|
40
|
-
|
41
44
|
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
42
45
|
return super()._build_odxlinks()
|
43
46
|
|
@@ -54,10 +57,6 @@ class ParamLengthInfoType(DiagCodedType):
|
|
54
57
|
"""Recursively resolve any short-name references"""
|
55
58
|
super()._resolve_snrefs(context)
|
56
59
|
|
57
|
-
@property
|
58
|
-
def length_key(self) -> "LengthKeyParameter":
|
59
|
-
return self._length_key
|
60
|
-
|
61
60
|
@override
|
62
61
|
def encode_into_pdu(self, internal_value: AtomicOdxType, encode_state: EncodeState) -> None:
|
63
62
|
bit_length = encode_state.length_keys.get(self.length_key.short_name)
|
odxtools/request.py
CHANGED
@@ -1,39 +1,132 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
|
-
from typing import List
|
3
|
+
from typing import Any, Dict, List, Optional, cast
|
4
4
|
from xml.etree import ElementTree
|
5
5
|
|
6
|
-
from .
|
6
|
+
from .admindata import AdminData
|
7
|
+
from .codec import (composite_codec_decode_from_pdu, composite_codec_encode_into_pdu,
|
8
|
+
composite_codec_get_coded_const_prefix, composite_codec_get_free_parameters,
|
9
|
+
composite_codec_get_required_parameters, composite_codec_get_static_bit_length)
|
10
|
+
from .decodestate import DecodeState
|
11
|
+
from .element import IdentifiableElement
|
7
12
|
from .encodestate import EncodeState
|
8
|
-
from .
|
9
|
-
from .
|
13
|
+
from .exceptions import odxraise
|
14
|
+
from .nameditemlist import NamedItemList
|
15
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
16
|
+
from .odxtypes import ParameterValue, ParameterValueDict
|
17
|
+
from .parameters.createanyparameter import create_any_parameter_from_et
|
18
|
+
from .parameters.parameter import Parameter
|
10
19
|
from .snrefcontext import SnRefContext
|
20
|
+
from .specialdatagroup import SpecialDataGroup
|
11
21
|
from .utils import dataclass_fields_asdict
|
12
22
|
|
13
23
|
|
14
|
-
# TODO: The spec does not say that requests are basic structures. For
|
15
|
-
# now, we derive from it anyway because it simplifies the en- and
|
16
|
-
# decoding machinery...
|
17
24
|
@dataclass
|
18
|
-
class Request(
|
25
|
+
class Request(IdentifiableElement):
|
26
|
+
"""Represents all information related to an UDS request
|
27
|
+
|
28
|
+
This class implements the `CompositeCodec` interface.
|
29
|
+
"""
|
30
|
+
admin_data: Optional[AdminData]
|
31
|
+
parameters: NamedItemList[Parameter]
|
32
|
+
sdgs: List[SpecialDataGroup]
|
33
|
+
|
34
|
+
@property
|
35
|
+
def required_parameters(self) -> List[Parameter]:
|
36
|
+
return composite_codec_get_required_parameters(self)
|
37
|
+
|
38
|
+
@property
|
39
|
+
def free_parameters(self) -> List[Parameter]:
|
40
|
+
return composite_codec_get_free_parameters(self)
|
19
41
|
|
20
42
|
@staticmethod
|
21
43
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Request":
|
22
|
-
|
23
|
-
kwargs = dataclass_fields_asdict(BasicStructure.from_et(et_element, doc_frags))
|
44
|
+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
24
45
|
|
25
|
-
|
46
|
+
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
|
47
|
+
parameters = NamedItemList([
|
48
|
+
create_any_parameter_from_et(et_parameter, doc_frags)
|
49
|
+
for et_parameter in et_element.iterfind("PARAMS/PARAM")
|
50
|
+
])
|
51
|
+
sdgs = [
|
52
|
+
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
53
|
+
]
|
26
54
|
|
27
|
-
|
28
|
-
encode_state = EncodeState(is_end_of_pdu=True)
|
55
|
+
return Request(admin_data=admin_data, parameters=parameters, sdgs=sdgs, **kwargs)
|
29
56
|
|
30
|
-
|
57
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
58
|
+
result = {self.odx_id: self}
|
31
59
|
|
32
|
-
|
60
|
+
if self.admin_data is not None:
|
61
|
+
result.update(self.admin_data._build_odxlinks())
|
62
|
+
|
63
|
+
for param in self.parameters:
|
64
|
+
result.update(param._build_odxlinks())
|
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.admin_data is not None:
|
73
|
+
self.admin_data._resolve_odxlinks(odxlinks)
|
74
|
+
|
75
|
+
for param in self.parameters:
|
76
|
+
param._resolve_odxlinks(odxlinks)
|
77
|
+
|
78
|
+
for sdg in self.sdgs:
|
79
|
+
sdg._resolve_odxlinks(odxlinks)
|
33
80
|
|
34
81
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
35
82
|
context.request = self
|
83
|
+
context.parameters = self.parameters
|
36
84
|
|
37
|
-
|
85
|
+
if self.admin_data is not None:
|
86
|
+
self.admin_data._resolve_snrefs(context)
|
87
|
+
|
88
|
+
for param in self.parameters:
|
89
|
+
param._resolve_snrefs(context)
|
90
|
+
|
91
|
+
for sdg in self.sdgs:
|
92
|
+
sdg._resolve_snrefs(context)
|
38
93
|
|
39
94
|
context.request = None
|
95
|
+
context.parameters = None
|
96
|
+
|
97
|
+
def get_static_bit_length(self) -> Optional[int]:
|
98
|
+
return composite_codec_get_static_bit_length(self)
|
99
|
+
|
100
|
+
def print_free_parameters_info(self) -> None:
|
101
|
+
"""Print a human readable description of the composite codec's
|
102
|
+
free parameters to `stdout`
|
103
|
+
"""
|
104
|
+
from .parameterinfo import parameter_info
|
105
|
+
|
106
|
+
print(parameter_info(self.free_parameters), end="")
|
107
|
+
|
108
|
+
def encode(self, **kwargs: ParameterValue) -> bytearray:
|
109
|
+
encode_state = EncodeState(is_end_of_pdu=True)
|
110
|
+
|
111
|
+
self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
|
112
|
+
|
113
|
+
return encode_state.coded_message
|
114
|
+
|
115
|
+
def decode(self, message: bytes) -> ParameterValueDict:
|
116
|
+
decode_state = DecodeState(coded_message=message)
|
117
|
+
param_values = self.decode_from_pdu(decode_state)
|
118
|
+
|
119
|
+
if not isinstance(param_values, dict):
|
120
|
+
odxraise("Decoding a request must result in a dictionary")
|
121
|
+
|
122
|
+
return cast(ParameterValueDict, param_values)
|
123
|
+
|
124
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
125
|
+
encode_state: EncodeState) -> None:
|
126
|
+
composite_codec_encode_into_pdu(self, physical_value, encode_state)
|
127
|
+
|
128
|
+
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
129
|
+
return composite_codec_decode_from_pdu(self, decode_state)
|
130
|
+
|
131
|
+
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
|
132
|
+
return composite_codec_get_coded_const_prefix(self, request_prefix)
|
odxtools/response.py
CHANGED
@@ -1,15 +1,24 @@
|
|
1
1
|
# SPDX-License-Identifier: MIT
|
2
2
|
from dataclasses import dataclass
|
3
3
|
from enum import Enum
|
4
|
-
from typing import List, Optional, cast
|
4
|
+
from typing import Any, Dict, List, Optional, cast
|
5
5
|
from xml.etree import ElementTree
|
6
6
|
|
7
|
-
from .
|
7
|
+
from .admindata import AdminData
|
8
|
+
from .codec import (composite_codec_decode_from_pdu, composite_codec_encode_into_pdu,
|
9
|
+
composite_codec_get_coded_const_prefix, composite_codec_get_free_parameters,
|
10
|
+
composite_codec_get_required_parameters, composite_codec_get_static_bit_length)
|
11
|
+
from .decodestate import DecodeState
|
12
|
+
from .element import IdentifiableElement
|
8
13
|
from .encodestate import EncodeState
|
9
14
|
from .exceptions import odxraise
|
10
|
-
from .
|
11
|
-
from .
|
15
|
+
from .nameditemlist import NamedItemList
|
16
|
+
from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId
|
17
|
+
from .odxtypes import ParameterValue, ParameterValueDict
|
18
|
+
from .parameters.createanyparameter import create_any_parameter_from_et
|
19
|
+
from .parameters.parameter import Parameter
|
12
20
|
from .snrefcontext import SnRefContext
|
21
|
+
from .specialdatagroup import SpecialDataGroup
|
13
22
|
from .utils import dataclass_fields_asdict
|
14
23
|
|
15
24
|
|
@@ -19,17 +28,23 @@ class ResponseType(Enum):
|
|
19
28
|
GLOBAL_NEGATIVE = "GLOBAL-NEG-RESPONSE"
|
20
29
|
|
21
30
|
|
22
|
-
# TODO: The spec does not say that responses are basic structures. For
|
23
|
-
# now, we derive from it anyway because it simplifies the en- and
|
24
|
-
# decoding machinery...
|
25
31
|
@dataclass
|
26
|
-
class Response(
|
32
|
+
class Response(IdentifiableElement):
|
33
|
+
"""Represents all information related to an UDS response
|
34
|
+
|
35
|
+
This class implements the `CompositeCodec` interface.
|
36
|
+
"""
|
37
|
+
|
27
38
|
response_type: ResponseType
|
28
39
|
|
40
|
+
admin_data: Optional[AdminData]
|
41
|
+
parameters: NamedItemList[Parameter]
|
42
|
+
sdgs: List[SpecialDataGroup]
|
43
|
+
|
29
44
|
@staticmethod
|
30
45
|
def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Response":
|
31
46
|
"""Reads a response."""
|
32
|
-
kwargs = dataclass_fields_asdict(
|
47
|
+
kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
|
33
48
|
|
34
49
|
try:
|
35
50
|
response_type = ResponseType(et_element.tag)
|
@@ -37,18 +52,103 @@ class Response(BasicStructure):
|
|
37
52
|
response_type = cast(ResponseType, None)
|
38
53
|
odxraise(f"Encountered unknown response type '{et_element.tag}'")
|
39
54
|
|
40
|
-
|
55
|
+
admin_data = AdminData.from_et(et_element.find("ADMIN-DATA"), doc_frags)
|
56
|
+
parameters = NamedItemList([
|
57
|
+
create_any_parameter_from_et(et_parameter, doc_frags)
|
58
|
+
for et_parameter in et_element.iterfind("PARAMS/PARAM")
|
59
|
+
])
|
60
|
+
sdgs = [
|
61
|
+
SpecialDataGroup.from_et(sdge, doc_frags) for sdge in et_element.iterfind("SDGS/SDG")
|
62
|
+
]
|
41
63
|
|
42
|
-
|
43
|
-
|
64
|
+
return Response(
|
65
|
+
response_type=response_type,
|
66
|
+
admin_data=admin_data,
|
67
|
+
parameters=parameters,
|
68
|
+
sdgs=sdgs,
|
69
|
+
**kwargs)
|
44
70
|
|
45
|
-
|
71
|
+
def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
|
72
|
+
result = {self.odx_id: self}
|
46
73
|
|
47
|
-
|
74
|
+
if self.admin_data is not None:
|
75
|
+
result.update(self.admin_data._build_odxlinks())
|
76
|
+
|
77
|
+
for param in self.parameters:
|
78
|
+
result.update(param._build_odxlinks())
|
79
|
+
|
80
|
+
for sdg in self.sdgs:
|
81
|
+
result.update(sdg._build_odxlinks())
|
82
|
+
|
83
|
+
return result
|
84
|
+
|
85
|
+
def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
|
86
|
+
if self.admin_data is not None:
|
87
|
+
self.admin_data._resolve_odxlinks(odxlinks)
|
88
|
+
|
89
|
+
for param in self.parameters:
|
90
|
+
param._resolve_odxlinks(odxlinks)
|
91
|
+
|
92
|
+
for sdg in self.sdgs:
|
93
|
+
sdg._resolve_odxlinks(odxlinks)
|
48
94
|
|
49
95
|
def _resolve_snrefs(self, context: SnRefContext) -> None:
|
50
96
|
context.response = self
|
97
|
+
context.parameters = self.parameters
|
51
98
|
|
52
|
-
|
99
|
+
if self.admin_data is not None:
|
100
|
+
self.admin_data._resolve_snrefs(context)
|
101
|
+
|
102
|
+
for param in self.parameters:
|
103
|
+
param._resolve_snrefs(context)
|
104
|
+
|
105
|
+
for sdg in self.sdgs:
|
106
|
+
sdg._resolve_snrefs(context)
|
53
107
|
|
54
108
|
context.response = None
|
109
|
+
context.parameters = None
|
110
|
+
|
111
|
+
def encode(self, coded_request: Optional[bytes] = None, **kwargs: ParameterValue) -> bytearray:
|
112
|
+
encode_state = EncodeState(triggering_request=coded_request, is_end_of_pdu=True)
|
113
|
+
|
114
|
+
self.encode_into_pdu(physical_value=kwargs, encode_state=encode_state)
|
115
|
+
|
116
|
+
return encode_state.coded_message
|
117
|
+
|
118
|
+
def decode(self, message: bytes) -> ParameterValueDict:
|
119
|
+
decode_state = DecodeState(coded_message=message)
|
120
|
+
param_values = self.decode_from_pdu(decode_state)
|
121
|
+
|
122
|
+
if not isinstance(param_values, dict):
|
123
|
+
odxraise("Decoding a response must result in a dictionary")
|
124
|
+
|
125
|
+
return cast(ParameterValueDict, param_values)
|
126
|
+
|
127
|
+
def encode_into_pdu(self, physical_value: Optional[ParameterValue],
|
128
|
+
encode_state: EncodeState) -> None:
|
129
|
+
composite_codec_encode_into_pdu(self, physical_value, encode_state)
|
130
|
+
|
131
|
+
def decode_from_pdu(self, decode_state: DecodeState) -> ParameterValue:
|
132
|
+
return composite_codec_decode_from_pdu(self, decode_state)
|
133
|
+
|
134
|
+
def get_static_bit_length(self) -> Optional[int]:
|
135
|
+
return composite_codec_get_static_bit_length(self)
|
136
|
+
|
137
|
+
@property
|
138
|
+
def required_parameters(self) -> List[Parameter]:
|
139
|
+
return composite_codec_get_required_parameters(self)
|
140
|
+
|
141
|
+
@property
|
142
|
+
def free_parameters(self) -> List[Parameter]:
|
143
|
+
return composite_codec_get_free_parameters(self)
|
144
|
+
|
145
|
+
def print_free_parameters_info(self) -> None:
|
146
|
+
"""Return a human readable description of the structure's
|
147
|
+
free parameters.
|
148
|
+
"""
|
149
|
+
from .parameterinfo import parameter_info
|
150
|
+
|
151
|
+
print(parameter_info(self.free_parameters), end="")
|
152
|
+
|
153
|
+
def coded_const_prefix(self, request_prefix: bytes = b'') -> bytes:
|
154
|
+
return composite_codec_get_coded_const_prefix(self, request_prefix)
|
odxtools/specialdatagroup.py
CHANGED
@@ -13,8 +13,8 @@ from .specialdatagroupcaption import SpecialDataGroupCaption
|
|
13
13
|
class SpecialDataGroup:
|
14
14
|
sdg_caption: Optional[SpecialDataGroupCaption]
|
15
15
|
sdg_caption_ref: Optional[OdxLinkRef]
|
16
|
-
semantic_info: Optional[str] # the "SI" attribute
|
17
16
|
values: List[Union["SpecialDataGroup", SpecialData]]
|
17
|
+
semantic_info: Optional[str] # the "SI" attribute
|
18
18
|
|
19
19
|
@staticmethod
|
20
20
|
def from_et(et_element: ElementTree.Element,
|
@@ -5,32 +5,15 @@
|
|
5
5
|
# This template writes an .odx-c file for a communication
|
6
6
|
# parameter specification.
|
7
7
|
-#}
|
8
|
-
{%- import('macros/
|
9
|
-
{%- import('macros/printCompanyData.xml.jinja2') as pcd -%}
|
8
|
+
{%- import('macros/printOdxCategory.xml.jinja2') as poc %}
|
10
9
|
{%- import('macros/printProtStack.xml.jinja2') as pps %}
|
11
|
-
{%- import('macros/printDescription.xml.jinja2') as pd %}
|
12
10
|
{#- -#}
|
13
11
|
|
14
12
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
15
|
-
<ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
|
16
13
|
<!-- Written using odxtools {{odxtools_version}} -->
|
17
|
-
|
18
|
-
|
19
|
-
{
|
20
|
-
<LONG-NAME>{{comparam_spec.long_name|e}}</LONG-NAME>
|
21
|
-
{%- endif %}
|
22
|
-
{{pd.printDescription(comparam_spec.description)}}
|
23
|
-
{%- if comparam_spec.admin_data is not none %}
|
24
|
-
{{- pad.printAdminData(comparam_spec.admin_data) | indent(3) }}
|
25
|
-
{%- endif %}
|
26
|
-
{%- if comparam_spec.company_datas %}
|
27
|
-
<COMPANY-DATAS>
|
28
|
-
{%- for cd in comparam_spec.company_datas %}
|
29
|
-
{{- pcd.printCompanyData(cd) | indent(5) -}}
|
30
|
-
{%- endfor %}
|
31
|
-
</COMPANY-DATAS>
|
32
|
-
{%- endif %}
|
33
|
-
|
14
|
+
<ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
|
15
|
+
<COMPARAM-SPEC {{- poc.printOdxCategoryAttribs(comparam_spec) }}>
|
16
|
+
{{- poc.printOdxCategorySubtags(comparam_spec)|indent(3) }}
|
34
17
|
{%- if comparam_spec.prot_stacks %}
|
35
18
|
<PROT-STACKS>
|
36
19
|
{%- for ps in comparam_spec.prot_stacks %}
|
@@ -5,36 +5,18 @@
|
|
5
5
|
# This template writes an .odx-cs file for a communication
|
6
6
|
# parameter subset.
|
7
7
|
-#}
|
8
|
+
{%- import('macros/printOdxCategory.xml.jinja2') as poc %}
|
8
9
|
{%- import('macros/printComparam.xml.jinja2') as pcp -%}
|
9
|
-
{%- import('macros/printAdminData.xml.jinja2') as pad -%}
|
10
|
-
{%- import('macros/printCompanyData.xml.jinja2') as pcd -%}
|
11
10
|
{%- import('macros/printDOP.xml.jinja2') as pdop %}
|
12
11
|
{%- import('macros/printUnitSpec.xml.jinja2') as pus %}
|
13
|
-
{%- import('macros/printSpecialData.xml.jinja2') as psd %}
|
14
12
|
{%- import('macros/printDescription.xml.jinja2') as pd %}
|
15
13
|
{#- -#}
|
16
14
|
|
17
15
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
18
|
-
<ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
|
19
16
|
<!-- Written using odxtools {{odxtools_version}} -->
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
{%- if comparam_subset.long_name is not none %}
|
24
|
-
<LONG-NAME>{{comparam_subset.long_name|e}}</LONG-NAME>
|
25
|
-
{%- endif %}
|
26
|
-
{{pd.printDescription(comparam_subset.description)}}
|
27
|
-
{%- if comparam_subset.admin_data is not none %}
|
28
|
-
{{- pad.printAdminData(comparam_subset.admin_data) | indent(3) }}
|
29
|
-
{%- endif %}
|
30
|
-
{%- if comparam_subset.company_datas %}
|
31
|
-
<COMPANY-DATAS>
|
32
|
-
{%- for cd in comparam_subset.company_datas %}
|
33
|
-
{{- pcd.printCompanyData(cd) | indent(5, first=True) }}
|
34
|
-
{%- endfor %}
|
35
|
-
</COMPANY-DATAS>
|
36
|
-
{%- endif %}
|
37
|
-
{{- psd.printSpecialDataGroups(comparam_subset.sdgs)|indent(3, first=True) }}
|
17
|
+
<ODX MODEL-VERSION="2.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="odx.xsd">
|
18
|
+
<COMPARAM-SUBSET {{- poc.printOdxCategoryAttribs(comparam_subset) }} {{make_xml_attrib("CATEGORY", comparam_subset.category)}}>
|
19
|
+
{{- poc.printOdxCategorySubtags(comparam_subset)|indent(3) }}
|
38
20
|
{%- if comparam_subset.comparams %}
|
39
21
|
<COMPARAMS>
|
40
22
|
{%- for cp in comparam_subset.comparams %}
|