odxtools 5.2.6__py3-none-any.whl → 5.3.1__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.
Files changed (73) hide show
  1. odxtools/basicstructure.py +2 -2
  2. odxtools/cli/browse.py +18 -20
  3. odxtools/cli/dummy_sub_parser.py +3 -3
  4. odxtools/cli/main.py +1 -1
  5. odxtools/compumethods/compuscale.py +1 -2
  6. odxtools/compumethods/createanycompumethod.py +0 -1
  7. odxtools/compumethods/identicalcompumethod.py +0 -2
  8. odxtools/compumethods/limit.py +1 -1
  9. odxtools/compumethods/linearcompumethod.py +1 -1
  10. odxtools/compumethods/scalelinearcompumethod.py +0 -1
  11. odxtools/compumethods/tabintpcompumethod.py +0 -1
  12. odxtools/determinenumberofitems.py +18 -0
  13. odxtools/diagdatadictionaryspec.py +14 -4
  14. odxtools/diaglayer.py +54 -59
  15. odxtools/dynamiclengthfield.py +59 -0
  16. odxtools/element.py +2 -4
  17. odxtools/endofpdufield.py +6 -77
  18. odxtools/field.py +94 -0
  19. odxtools/isotp_state_machine.py +20 -24
  20. odxtools/matchingparameter.py +1 -1
  21. odxtools/multiplexer.py +6 -18
  22. odxtools/multiplexercase.py +22 -6
  23. odxtools/multiplexerdefaultcase.py +10 -2
  24. odxtools/multiplexerswitchkey.py +8 -43
  25. odxtools/odxlink.py +8 -4
  26. odxtools/odxtypes.py +1 -1
  27. odxtools/parameters/codedconstparameter.py +4 -2
  28. odxtools/parameters/dynamicparameter.py +6 -4
  29. odxtools/parameters/lengthkeyparameter.py +8 -3
  30. odxtools/parameters/matchingrequestparameter.py +5 -3
  31. odxtools/parameters/nrcconstparameter.py +4 -2
  32. odxtools/parameters/parameter.py +21 -6
  33. odxtools/parameters/parameterwithdop.py +6 -1
  34. odxtools/parameters/physicalconstantparameter.py +4 -2
  35. odxtools/parameters/reservedparameter.py +4 -2
  36. odxtools/parameters/systemparameter.py +5 -3
  37. odxtools/parameters/tableentryparameter.py +5 -3
  38. odxtools/parameters/tablekeyparameter.py +8 -4
  39. odxtools/parameters/tablestructparameter.py +4 -2
  40. odxtools/parameters/valueparameter.py +5 -3
  41. odxtools/positioneddataobjectproperty.py +74 -0
  42. odxtools/progcode.py +2 -3
  43. odxtools/tablerow.py +1 -2
  44. odxtools/templates/macros/printAudience.xml.jinja2 +3 -9
  45. odxtools/templates/macros/printCompanyData.xml.jinja2 +4 -27
  46. odxtools/templates/macros/printComparam.xml.jinja2 +4 -18
  47. odxtools/templates/macros/printDOP.xml.jinja2 +3 -9
  48. odxtools/templates/macros/printDynamicLengthField.xml.jinja2 +22 -0
  49. odxtools/templates/macros/printElementID.xml.jinja2 +6 -6
  50. odxtools/templates/macros/printEndOfPdu.xml.jinja2 +3 -2
  51. odxtools/templates/macros/printEnvData.xml.jinja2 +2 -2
  52. odxtools/templates/macros/printEnvDataDesc.xml.jinja2 +3 -2
  53. odxtools/templates/macros/printFunctionalClass.xml.jinja2 +3 -9
  54. odxtools/templates/macros/printMux.xml.jinja2 +13 -6
  55. odxtools/templates/macros/printParam.xml.jinja2 +2 -7
  56. odxtools/templates/macros/printRequest.xml.jinja2 +2 -9
  57. odxtools/templates/macros/printResponse.xml.jinja2 +2 -9
  58. odxtools/templates/macros/printService.xml.jinja2 +2 -9
  59. odxtools/templates/macros/printSpecialData.xml.jinja2 +1 -1
  60. odxtools/templates/macros/printState.xml.jinja2 +3 -9
  61. odxtools/templates/macros/printStateChart.xml.jinja2 +2 -9
  62. odxtools/templates/macros/printStateTransition.xml.jinja2 +3 -9
  63. odxtools/templates/macros/printStructure.xml.jinja2 +2 -4
  64. odxtools/templates/macros/printTable.xml.jinja2 +2 -7
  65. odxtools/templates/macros/printUnitSpec.xml.jinja2 +3 -3
  66. odxtools/templates/macros/printVariant.xml.jinja2 +10 -9
  67. odxtools/version.py +4 -2
  68. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/METADATA +70 -13
  69. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/RECORD +73 -68
  70. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/LICENSE +0 -0
  71. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/WHEEL +0 -0
  72. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/entry_points.txt +0 -0
  73. {odxtools-5.2.6.dist-info → odxtools-5.3.1.dist-info}/top_level.txt +0 -0
odxtools/endofpdufield.py CHANGED
@@ -1,62 +1,28 @@
1
1
  # SPDX-License-Identifier: MIT
2
2
  from copy import copy
3
3
  from dataclasses import dataclass
4
- from typing import TYPE_CHECKING, List, Optional
4
+ from typing import List, Optional
5
5
  from xml.etree import ElementTree
6
6
 
7
- from .basicstructure import BasicStructure
8
- from .createsdgs import create_sdgs_from_et
9
7
  from .decodestate import DecodeState
10
- from .dopbase import DopBase
11
- from .element import IdentifiableElement
12
8
  from .encodestate import EncodeState
13
9
  from .exceptions import odxassert
14
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
15
- from .odxtypes import ParameterValueDict, odxstr_to_bool
10
+ from .field import Field
11
+ from .odxlink import OdxDocFragment
12
+ from .odxtypes import ParameterValueDict
16
13
  from .utils import dataclass_fields_asdict
17
14
 
18
- if TYPE_CHECKING:
19
- from .diaglayer import DiagLayer
20
-
21
15
 
22
16
  @dataclass
23
- class EndOfPduField(DopBase):
17
+ class EndOfPduField(Field):
24
18
  """End of PDU fields are structures that are repeated until the end of the PDU"""
25
-
26
- structure_ref: Optional[OdxLinkRef]
27
- structure_snref: Optional[str]
28
- env_data_desc_ref: Optional[OdxLinkRef]
29
- env_data_desc_snref: Optional[str]
30
19
  min_number_of_items: Optional[int]
31
20
  max_number_of_items: Optional[int]
32
21
 
33
- def __post_init__(self) -> None:
34
- num_struct_refs = 0 if self.structure_ref is None else 1
35
- num_struct_refs += 0 if self.structure_snref is None else 1
36
-
37
- num_edd_refs = 0 if self.env_data_desc_ref is None else 1
38
- num_edd_refs += 0 if self.env_data_desc_snref is None else 1
39
-
40
- odxassert(
41
- num_struct_refs + num_edd_refs == 1,
42
- "END-OF-PDU-FIELDs need to specify exactly one reference to a "
43
- "structure or an environment data description")
44
-
45
22
  @staticmethod
46
23
  def from_et(et_element: ElementTree.Element,
47
24
  doc_frags: List[OdxDocFragment]) -> "EndOfPduField":
48
- kwargs = dataclass_fields_asdict(IdentifiableElement.from_et(et_element, doc_frags))
49
- sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
50
-
51
- structure_ref = OdxLinkRef.from_et(et_element.find("BASIC-STRUCTURE-REF"), doc_frags)
52
- structure_snref = None
53
- if (edsnr_elem := et_element.find("BASIC-STRUCTURE-SNREF")) is not None:
54
- structure_snref = edsnr_elem.get("SHORT-NAME")
55
-
56
- env_data_desc_ref = OdxLinkRef.from_et(et_element.find("ENV-DATA-DESC-REF"), doc_frags)
57
- env_data_desc_snref = None
58
- if (edsnr_elem := et_element.find("ENV-DATA-DESC-SNREF")) is not None:
59
- env_data_desc_snref = edsnr_elem.get("SHORT-NAME")
25
+ kwargs = dataclass_fields_asdict(Field.from_et(et_element, doc_frags))
60
26
 
61
27
  if (min_n_str := et_element.findtext("MIN-NUMBER-OF-ITEMS")) is not None:
62
28
  min_number_of_items = int(min_n_str)
@@ -67,32 +33,13 @@ class EndOfPduField(DopBase):
67
33
  else:
68
34
  max_number_of_items = None
69
35
 
70
- is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
71
36
  eopf = EndOfPduField(
72
- sdgs=sdgs,
73
- structure_ref=structure_ref,
74
- structure_snref=structure_snref,
75
- env_data_desc_ref=env_data_desc_ref,
76
- env_data_desc_snref=env_data_desc_snref,
77
37
  min_number_of_items=min_number_of_items,
78
38
  max_number_of_items=max_number_of_items,
79
- is_visible_raw=is_visible_raw,
80
39
  **kwargs)
81
40
 
82
41
  return eopf
83
42
 
84
- @property
85
- def structure(self) -> "BasicStructure":
86
- """may be a Structure or a env-data-desc"""
87
- return self._structure
88
-
89
- @property
90
- def bit_length(self):
91
- return self.structure.bit_length
92
-
93
- def convert_physical_to_internal(self, physical_value):
94
- return self.structure.convert_physical_to_internal(physical_value)
95
-
96
43
  def convert_physical_to_bytes(
97
44
  self,
98
45
  physical_value: ParameterValueDict,
@@ -133,21 +80,3 @@ class EndOfPduField(DopBase):
133
80
  value.append(new_value)
134
81
 
135
82
  return value, next_byte_position
136
-
137
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
138
- """Recursively resolve any odxlinks references"""
139
- if self.structure_ref is not None:
140
- self._structure = odxlinks.resolve(self.structure_ref)
141
-
142
- if self.env_data_desc_ref is not None:
143
- self._env_data_desc = odxlinks.resolve(self.env_data_desc_ref)
144
-
145
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
146
- """Recursively resolve any short-name references"""
147
- if self.structure_snref is not None:
148
- structures = diag_layer.diag_data_dictionary_spec.structures
149
- self._structure = structures[self.structure_snref]
150
-
151
- if self.env_data_desc_snref is not None:
152
- env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
153
- self._env_data_desc = env_data_descs[self.env_data_desc_snref]
odxtools/field.py ADDED
@@ -0,0 +1,94 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING, List, Optional
3
+ from xml.etree import ElementTree
4
+
5
+ from .environmentdatadescription import EnvironmentDataDescription
6
+ from .basicstructure import BasicStructure
7
+ from .createsdgs import create_sdgs_from_et
8
+ from .dopbase import DopBase
9
+ from .element import IdentifiableElement
10
+ from .exceptions import odxassert, odxrequire
11
+ from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkRef
12
+ from .odxtypes import odxstr_to_bool
13
+ from .utils import dataclass_fields_asdict
14
+
15
+ if TYPE_CHECKING:
16
+ from .diaglayer import DiagLayer
17
+
18
+
19
+ @dataclass
20
+ class Field(DopBase):
21
+ structure_ref: Optional[OdxLinkRef]
22
+ structure_snref: Optional[str]
23
+ env_data_desc_ref: Optional[OdxLinkRef]
24
+ env_data_desc_snref: Optional[str]
25
+
26
+ def __post_init__(self) -> None:
27
+ self._structure: Optional[BasicStructure] = None
28
+ self._env_data_desc: Optional[EnvironmentDataDescription] = None
29
+ num_struct_refs = 0 if self.structure_ref is None else 1
30
+ num_struct_refs += 0 if self.structure_snref is None else 1
31
+
32
+ num_edd_refs = 0 if self.env_data_desc_ref is None else 1
33
+ num_edd_refs += 0 if self.env_data_desc_snref is None else 1
34
+
35
+ odxassert(
36
+ num_struct_refs + num_edd_refs == 1,
37
+ "FIELDs need to specify exactly one reference to a "
38
+ "structure or an environment data description")
39
+
40
+ @staticmethod
41
+ def from_et(et_element: ElementTree.Element, doc_frags: List[OdxDocFragment]) -> "Field":
42
+ kwargs = dataclass_fields_asdict(DopBase.from_et(et_element, doc_frags))
43
+ sdgs = create_sdgs_from_et(et_element.find("SDGS"), doc_frags)
44
+
45
+ structure_ref = OdxLinkRef.from_et(et_element.find("BASIC-STRUCTURE-REF"), doc_frags)
46
+ structure_snref = None
47
+ if (edsnr_elem := et_element.find("BASIC-STRUCTURE-SNREF")) is not None:
48
+ structure_snref = edsnr_elem.get("SHORT-NAME")
49
+
50
+ env_data_desc_ref = OdxLinkRef.from_et(et_element.find("ENV-DATA-DESC-REF"), doc_frags)
51
+ env_data_desc_snref = None
52
+ if (edsnr_elem := et_element.find("ENV-DATA-DESC-SNREF")) is not None:
53
+ env_data_desc_snref = edsnr_elem.get("SHORT-NAME")
54
+
55
+ is_visible_raw = odxstr_to_bool(et_element.get("IS-VISIBLE"))
56
+ return Field(
57
+ sdgs=sdgs,
58
+ structure_ref=structure_ref,
59
+ structure_snref=structure_snref,
60
+ env_data_desc_ref=env_data_desc_ref,
61
+ env_data_desc_snref=env_data_desc_snref,
62
+ is_visible_raw=is_visible_raw,
63
+ **kwargs)
64
+
65
+ @property
66
+ def structure(self) -> BasicStructure:
67
+ """may be a Structure or a env-data-desc"""
68
+ return odxrequire(self._structure, "EnvironmentDataDescription is not supported")
69
+
70
+ @property
71
+ def bit_length(self):
72
+ return self.structure.bit_length
73
+
74
+ def convert_physical_to_internal(self, physical_value):
75
+ return self.structure.convert_physical_to_internal(physical_value)
76
+
77
+ def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
78
+ """Recursively resolve any odxlinks references"""
79
+ if self.structure_ref is not None:
80
+ self._structure = odxlinks.resolve(self.structure_ref, BasicStructure)
81
+
82
+ if self.env_data_desc_ref is not None:
83
+ self._env_data_desc = odxlinks.resolve(self.env_data_desc_ref,
84
+ EnvironmentDataDescription)
85
+
86
+ def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
87
+ """Recursively resolve any short-name references"""
88
+ if self.structure_snref is not None:
89
+ structures = diag_layer.diag_data_dictionary_spec.structures
90
+ self._structure = odxrequire(structures.get(self.structure_snref))
91
+
92
+ if self.env_data_desc_snref is not None:
93
+ env_data_descs = diag_layer.diag_data_dictionary_spec.env_data_descs
94
+ self._env_data_desc = odxrequire(env_data_descs.get(self.env_data_desc_snref))
@@ -5,6 +5,7 @@ import asyncio
5
5
  import re
6
6
  import sys
7
7
  from enum import IntEnum
8
+ from typing import Iterable, List, Optional, Tuple, Union
8
9
 
9
10
  import bitstruct
10
11
  import can
@@ -28,7 +29,7 @@ class IsoTpStateMachine:
28
29
  "([a-zA-Z0-9_-]*) *([0-9A-Fa-f ]*) *\[[0-9]*\] *([ 0-9A-Fa-f]*)")
29
30
  can_log_frame_re = re.compile("\([0-9.]*\) *([a-zA-Z0-9_-]*) ([0-9A-Fa-f]*)#([0-9A-Fa-f]*)")
30
31
 
31
- def __init__(self, can_rx_ids):
32
+ def __init__(self, can_rx_ids: Union[int, List[int]]):
32
33
  if isinstance(can_rx_ids, int):
33
34
  can_rx_ids = [can_rx_ids]
34
35
 
@@ -36,21 +37,21 @@ class IsoTpStateMachine:
36
37
  assert isinstance(self._can_rx_ids, list)
37
38
 
38
39
  self._telegram_specified_len = [0] * len(can_rx_ids)
39
- self._telegram_data = [None] * len(can_rx_ids)
40
+ self._telegram_data: List[Optional[bytearray]] = [None] * len(can_rx_ids)
40
41
  self._telegram_last_rx_fragment_idx = [0] * len(can_rx_ids)
41
42
 
42
- def decode_rx_frame(self, rx_id, data, do_yield=False):
43
+ def decode_rx_frame(self, rx_id: int, data: bytes) -> Iterable[Tuple[int, bytes]]:
43
44
  """Handle the ISO-TP state transitions caused by a CAN frame.
44
45
 
45
- E.g., add some data to a telegram, etc.
46
+ E.g., add some data to a telegram, etc. Returns a generator of
47
+ (receive_id, payload_data) tuples.
46
48
  """
47
49
  try:
48
50
  telegram_idx = self._can_rx_ids.index(rx_id)
49
51
  except ValueError:
50
- return False # unknown CAN ID
52
+ return # unknown CAN ID
51
53
 
52
54
  # decode the isotp segment
53
- result = True
54
55
  (frame_type,) = bitstruct.unpack("u4", data)
55
56
  telegram_len = None
56
57
  if frame_type == IsoTp.FRAME_TYPE_SINGLE:
@@ -59,14 +60,13 @@ class IsoTpStateMachine:
59
60
  self.on_single_frame(telegram_idx, data[1:1 + telegram_len])
60
61
  self.on_telegram_complete(telegram_idx, data[1:1 + telegram_len])
61
62
 
62
- if do_yield:
63
- yield (rx_id, data[1:1 + telegram_len])
63
+ yield (rx_id, data[1:1 + telegram_len])
64
64
 
65
65
  elif frame_type == IsoTp.FRAME_TYPE_FIRST:
66
66
  frame_type, telegram_len = bitstruct.unpack("u4u12", data)
67
67
 
68
68
  self._telegram_specified_len[telegram_idx] = telegram_len
69
- self._telegram_data[telegram_idx] = data[2:]
69
+ self._telegram_data[telegram_idx] = bytearray(data[2:])
70
70
  self._telegram_last_rx_fragment_idx[telegram_idx] = 0
71
71
 
72
72
  self.on_first_frame(telegram_idx, data)
@@ -75,38 +75,34 @@ class IsoTpStateMachine:
75
75
  frame_type, rx_segment_idx = bitstruct.unpack("u4u4", data)
76
76
 
77
77
  expected_segment_idx = (self._telegram_last_rx_fragment_idx[telegram_idx] + 1) % 16
78
+ telegram_data = self._telegram_data[telegram_idx]
79
+ assert isinstance(telegram_data, bytearray)
78
80
 
79
81
  if expected_segment_idx == rx_segment_idx:
80
82
  self._telegram_last_rx_fragment_idx[telegram_idx] = rx_segment_idx
81
- self._telegram_data[telegram_idx] += data[1:]
83
+ telegram_data += data[1:]
82
84
 
83
85
  n = self._telegram_specified_len[telegram_idx]
84
- if len(self._telegram_data[telegram_idx]) > n:
86
+ if len(telegram_data) > n:
85
87
  # can frames can include padding, i.e. the length
86
88
  # of the telegram payload is not necessarily a
87
89
  # multiple of the segment payloads
88
- self._telegram_data[telegram_idx] = self._telegram_data[telegram_idx][:n]
89
- else:
90
- result = False
90
+ telegram_data = telegram_data[:n]
91
91
 
92
92
  self.on_consecutive_frame(telegram_idx, rx_segment_idx, data[1:])
93
93
 
94
94
  if expected_segment_idx != rx_segment_idx:
95
95
  self.on_sequence_error(telegram_idx, expected_segment_idx, rx_segment_idx)
96
- elif len(self._telegram_data[telegram_idx]) == n:
97
- self.on_telegram_complete(telegram_idx, self._telegram_data[telegram_idx])
98
- if do_yield:
99
- yield (rx_id, self._telegram_data[telegram_idx])
96
+ elif len(telegram_data) == n:
97
+ self.on_telegram_complete(telegram_idx, telegram_data)
98
+ yield (rx_id, telegram_data)
100
99
 
101
100
  elif frame_type == IsoTp.FRAME_TYPE_FLOW_CONTROL:
102
101
  frame_type, flow_control_flag = bitstruct.unpack("u4u4", data)
103
102
  self.on_flow_control_frame(telegram_idx, flow_control_flag)
104
103
  else:
105
- result = False
106
104
  self.on_frame_type_error(telegram_idx, frame_type)
107
105
 
108
- return result
109
-
110
106
  async def read_telegrams(self, bus):
111
107
  """This is equivalent to the :py:meth:`file.readlines()` method, but
112
108
  it yields ISO-TP telegrams instead of lines.
@@ -130,7 +126,7 @@ class IsoTpStateMachine:
130
126
  await rx_event.wait()
131
127
 
132
128
  msg = bus.recv()
133
- for tmp in self.decode_rx_frame(msg.arbitration_id, msg.data, do_yield=True):
129
+ for tmp in self.decode_rx_frame(msg.arbitration_id, msg.data):
134
130
  yield tmp
135
131
  else:
136
132
  # input is a file
@@ -148,7 +144,7 @@ class IsoTpStateMachine:
148
144
  frame_data = bytearray(
149
145
  map(lambda x: int(x, 16), frame_data_formatted.split(" ")))
150
146
 
151
- for tmp in self.decode_rx_frame(frame_id, frame_data, do_yield=True):
147
+ for tmp in self.decode_rx_frame(frame_id, frame_data):
152
148
  yield tmp
153
149
 
154
150
  elif m := self.can_log_frame_re.match(cur_line.strip()):
@@ -162,7 +158,7 @@ class IsoTpStateMachine:
162
158
  ]
163
159
  frame_data = bytearray(map(lambda x: int(x, 16), frame_data_list))
164
160
 
165
- for tmp in self.decode_rx_frame(frame_id, frame_data, do_yield=True):
161
+ for tmp in self.decode_rx_frame(frame_id, frame_data):
166
162
  yield tmp
167
163
 
168
164
  else:
@@ -23,7 +23,7 @@ class MatchingParameter:
23
23
  diag_comm_snref: str
24
24
  out_param_if: str
25
25
 
26
- def __post_init__(self):
26
+ def __post_init__(self) -> None:
27
27
  odxassert(is_short_name(self.diag_comm_snref))
28
28
  odxassert(is_short_name_path(self.out_param_if))
29
29
 
odxtools/multiplexer.py CHANGED
@@ -63,7 +63,7 @@ class Multiplexer(DopBase):
63
63
  return None
64
64
 
65
65
  def _get_case_limits(self, case: MultiplexerCase):
66
- key_type = self.switch_key._dop.physical_type.base_data_type
66
+ key_type = self.switch_key.dop.physical_type.base_data_type
67
67
  lower_limit = key_type.make_from(case.lower_limit)
68
68
  upper_limit = key_type.make_from(case.upper_limit)
69
69
  return lower_limit, upper_limit
@@ -80,7 +80,6 @@ class Multiplexer(DopBase):
80
80
  with only one key equal to the desired case""")
81
81
 
82
82
  case_name, case_value = next(iter(physical_value.items()))
83
- key_pos = self.switch_key.byte_position
84
83
  case_pos = self.byte_position
85
84
 
86
85
  for case in self.cases or []:
@@ -92,14 +91,11 @@ class Multiplexer(DopBase):
92
91
  case_bytes = bytes()
93
92
 
94
93
  key_value, _ = self._get_case_limits(case)
95
- sk_bit_position = self.switch_key.bit_position
96
- sk_bit_position = sk_bit_position if sk_bit_position is not None else 0
97
- key_bytes = self.switch_key._dop.convert_physical_to_bytes(
98
- key_value, encode_state, sk_bit_position)
94
+ key_bytes = self.switch_key.convert_physical_to_bytes(key_value, encode_state)
99
95
 
100
- mux_len = max(len(key_bytes) + key_pos, len(case_bytes) + case_pos)
96
+ mux_len = max(len(key_bytes), len(case_bytes) + case_pos)
101
97
  mux_bytes = bytearray(mux_len)
102
- mux_bytes[key_pos:key_pos + len(key_bytes)] = key_bytes
98
+ mux_bytes[:len(key_bytes)] = key_bytes
103
99
  mux_bytes[case_pos:case_pos + len(case_bytes)] = case_bytes
104
100
 
105
101
  return bytes(mux_bytes)
@@ -111,17 +107,9 @@ class Multiplexer(DopBase):
111
107
  if bit_position != 0:
112
108
  raise DecodeError("Multiplexer must be aligned, i.e. bit_position=0, but "
113
109
  f"{self.short_name} was passed the bit position {bit_position}")
114
- byte_code = decode_state.coded_message[decode_state.next_byte_position:]
115
- key_decode_state = DecodeState(
116
- coded_message=byte_code[self.switch_key.byte_position:],
117
- parameter_values=dict(),
118
- next_byte_position=0,
119
- )
120
- bit_position_int = (
121
- self.switch_key.bit_position if self.switch_key.bit_position is not None else 0)
122
- key_value, key_next_byte = self.switch_key._dop.convert_bytes_to_physical(
123
- key_decode_state, bit_position=bit_position_int)
110
+ key_value, key_next_byte = self.switch_key.convert_bytes_to_physical(decode_state)
124
111
 
112
+ byte_code = decode_state.coded_message[decode_state.next_byte_position:]
125
113
  case_decode_state = DecodeState(
126
114
  coded_message=byte_code[self.byte_position:],
127
115
  parameter_values=dict(),
@@ -17,30 +17,46 @@ if TYPE_CHECKING:
17
17
  class MultiplexerCase(NamedElement):
18
18
  """This class represents a Case which represents multiple options in a Multiplexer."""
19
19
 
20
- structure_ref: OdxLinkRef
20
+ structure_ref: Optional[OdxLinkRef]
21
+ structure_snref: Optional[str]
21
22
  lower_limit: str
22
23
  upper_limit: str
23
24
 
24
25
  def __post_init__(self) -> None:
25
- self._structure: Optional[BasicStructure] = None
26
+ self._structure: BasicStructure
26
27
 
27
28
  @staticmethod
28
29
  def from_et(et_element: ElementTree.Element,
29
30
  doc_frags: List[OdxDocFragment]) -> "MultiplexerCase":
30
31
  """Reads a Case for a Multiplexer."""
31
32
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
32
- structure_ref = odxrequire(OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags))
33
+ structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
34
+ structure_snref = None
35
+ if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
36
+ structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
37
+
33
38
  lower_limit = odxrequire(et_element.findtext("LOWER-LIMIT"))
34
39
  upper_limit = odxrequire(et_element.findtext("UPPER-LIMIT"))
35
40
 
36
41
  return MultiplexerCase(
37
- structure_ref=structure_ref, lower_limit=lower_limit, upper_limit=upper_limit, **kwargs)
42
+ structure_ref=structure_ref,
43
+ structure_snref=structure_snref,
44
+ lower_limit=lower_limit,
45
+ upper_limit=upper_limit,
46
+ **kwargs)
38
47
 
39
48
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
40
49
  return {}
41
50
 
42
51
  def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
43
- self._structure = odxlinks.resolve(self.structure_ref)
52
+ if self.structure_ref:
53
+ self._structure = odxlinks.resolve(self.structure_ref)
44
54
 
45
55
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
46
- pass
56
+ if self.structure_snref:
57
+ ddds = diag_layer.diag_data_dictionary_spec
58
+ self._structure = odxrequire(ddds.structures.get(self.structure_snref))
59
+
60
+ @property
61
+ def structure(self) -> BasicStructure:
62
+ return self._structure
@@ -5,6 +5,7 @@ from xml.etree import ElementTree
5
5
 
6
6
  from .basicstructure import BasicStructure
7
7
  from .element import NamedElement
8
+ from .exceptions import odxrequire
8
9
  from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
9
10
  from .utils import dataclass_fields_asdict
10
11
 
@@ -16,6 +17,7 @@ if TYPE_CHECKING:
16
17
  class MultiplexerDefaultCase(NamedElement):
17
18
  """This class represents a Default Case, which is selected when there are no cases defined in the Multiplexer."""
18
19
  structure_ref: Optional[OdxLinkRef]
20
+ structure_snref: Optional[str]
19
21
 
20
22
  def __post_init__(self) -> None:
21
23
  self._structure: Optional[BasicStructure] = None
@@ -27,8 +29,12 @@ class MultiplexerDefaultCase(NamedElement):
27
29
  kwargs = dataclass_fields_asdict(NamedElement.from_et(et_element, doc_frags))
28
30
 
29
31
  structure_ref = OdxLinkRef.from_et(et_element.find("STRUCTURE-REF"), doc_frags)
32
+ structure_snref = None
33
+ if (structure_snref_elem := et_element.find("STRUCTURE-SNREF")) is not None:
34
+ structure_snref = odxrequire(structure_snref_elem.get("SHORT-NAME"))
30
35
 
31
- return MultiplexerDefaultCase(structure_ref=structure_ref, **kwargs)
36
+ return MultiplexerDefaultCase(
37
+ structure_ref=structure_ref, structure_snref=structure_snref, **kwargs)
32
38
 
33
39
  def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
34
40
  return {}
@@ -38,4 +44,6 @@ class MultiplexerDefaultCase(NamedElement):
38
44
  self._structure = odxlinks.resolve(self.structure_ref)
39
45
 
40
46
  def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
41
- pass
47
+ if self.structure_snref:
48
+ ddds = diag_layer.diag_data_dictionary_spec
49
+ self._structure = odxrequire(ddds.structures.get(self.structure_snref))
@@ -1,53 +1,18 @@
1
- # SPDX-License-Identifier: MIT
2
1
  from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Dict, List, Optional
2
+ from typing import List
4
3
  from xml.etree import ElementTree
5
4
 
6
- from .dataobjectproperty import DataObjectProperty
7
- from .exceptions import odxrequire
8
- from .globals import logger
9
- from .odxlink import OdxDocFragment, OdxLinkDatabase, OdxLinkId, OdxLinkRef
10
-
11
- if TYPE_CHECKING:
12
- from .diaglayer import DiagLayer
5
+ from .odxlink import OdxDocFragment
6
+ from .positioneddataobjectproperty import PositionedDataObjectProperty
7
+ from .utils import dataclass_fields_asdict
13
8
 
14
9
 
15
10
  @dataclass
16
- class MultiplexerSwitchKey:
17
- """This class represents a Switch Key, which is used to select one of the cases defined in the Multiplexer."""
18
-
19
- byte_position: int
20
- bit_position: Optional[int]
21
- dop_ref: OdxLinkRef
22
-
23
- def __post_init__(self):
24
- self._dop: DataObjectProperty = None # type: ignore
11
+ class MultiplexerSwitchKey(PositionedDataObjectProperty):
25
12
 
26
13
  @staticmethod
27
14
  def from_et(et_element: ElementTree.Element,
28
15
  doc_frags: List[OdxDocFragment]) -> "MultiplexerSwitchKey":
29
- """Reads a Switch Key for a Multiplexer."""
30
- byte_position = int(odxrequire(et_element.findtext("BYTE-POSITION", "0")))
31
- bit_position_str = et_element.findtext("BIT-POSITION")
32
- bit_position = int(bit_position_str) if bit_position_str is not None else None
33
- dop_ref = odxrequire(OdxLinkRef.from_et(et_element.find("DATA-OBJECT-PROP-REF"), doc_frags))
34
-
35
- return MultiplexerSwitchKey(
36
- byte_position=byte_position,
37
- bit_position=bit_position,
38
- dop_ref=dop_ref,
39
- )
40
-
41
- def _build_odxlinks(self) -> Dict[OdxLinkId, Any]:
42
- return {}
43
-
44
- def _resolve_odxlinks(self, odxlinks: OdxLinkDatabase) -> None:
45
- dop = odxlinks.resolve(self.dop_ref)
46
- if isinstance(dop, DataObjectProperty):
47
- self._dop = dop
48
- else:
49
- logger.warning(
50
- f"DATA-OBJECT-PROP-REF '{self.dop_ref}' could not be resolved in SWITCH-KEY.")
51
-
52
- def _resolve_snrefs(self, diag_layer: "DiagLayer") -> None:
53
- pass
16
+ kwargs = dataclass_fields_asdict(
17
+ PositionedDataObjectProperty.from_et(et_element, doc_frags))
18
+ return MultiplexerSwitchKey(**kwargs)
odxtools/odxlink.py CHANGED
@@ -12,11 +12,15 @@ class OdxDocFragment:
12
12
  doc_name: str
13
13
  doc_type: Optional[str]
14
14
 
15
- def __eq__(self, other) -> bool:
15
+ def __eq__(self, other: Any) -> bool:
16
16
  if other is None:
17
- # if the other document fragment is not specified, we treat it as a wildcard...
17
+ # if the other document fragment is not specified, we
18
+ # treat it as a wildcard...
18
19
  return True
19
20
 
21
+ if not isinstance(other, OdxDocFragment):
22
+ return False
23
+
20
24
  # the ODX spec says that the doctype can be ignored...
21
25
  return self.doc_name == other.doc_name
22
26
 
@@ -59,7 +63,7 @@ class OdxLinkId:
59
63
  # i.e. the same OdxId object can be put into all of them.
60
64
  return self.local_id == other.local_id
61
65
 
62
- def __str__(self):
66
+ def __str__(self) -> str:
63
67
  return f"OdxLinkId('{self.local_id}')"
64
68
 
65
69
  @staticmethod
@@ -139,7 +143,7 @@ class OdxLinkRef:
139
143
  """Construct an OdxLinkRef for a given OdxLinkId."""
140
144
  return OdxLinkRef(odxid.local_id, odxid.doc_fragments)
141
145
 
142
- def __str__(self):
146
+ def __str__(self) -> str:
143
147
  return f"OdxLinkRef('{self.ref_id}')"
144
148
 
145
149
  def __contains__(self, odx_id: OdxLinkId) -> bool:
odxtools/odxtypes.py CHANGED
@@ -6,7 +6,7 @@ from xml.etree import ElementTree
6
6
  from .exceptions import odxassert, odxraise, odxrequire
7
7
 
8
8
  if TYPE_CHECKING:
9
- from odxtools.parameters.parameter import Parameter
9
+ from .parameters.parameter import Parameter
10
10
 
11
11
 
12
12
  def bytefield_to_bytearray(bytefield: str) -> bytearray:
@@ -47,10 +47,12 @@ class CodedConstParameter(Parameter):
47
47
  def internal_data_type(self) -> DataType:
48
48
  return self.diag_coded_type.base_data_type
49
49
 
50
- def is_required(self):
50
+ @property
51
+ def is_required(self) -> bool:
51
52
  return False
52
53
 
53
- def is_optional(self):
54
+ @property
55
+ def is_settable(self) -> bool:
54
56
  return False
55
57
 
56
58
  def get_coded_value(self):
@@ -11,11 +11,13 @@ class DynamicParameter(Parameter):
11
11
  def parameter_type(self) -> ParameterType:
12
12
  return "DYNAMIC"
13
13
 
14
- def is_required(self):
15
- raise NotImplementedError("DynamicParameter.is_required is not implemented yet.")
14
+ @property
15
+ def is_required(self) -> bool:
16
+ raise NotImplementedError(".is_required for a DynamicParameter")
16
17
 
17
- def is_optional(self):
18
- raise NotImplementedError("DynamicParameter.is_optional is not implemented yet.")
18
+ @property
19
+ def is_settable(self) -> bool:
20
+ raise NotImplementedError(".is_settable for a DynamicParameter")
19
21
 
20
22
  def get_coded_value(self):
21
23
  raise NotImplementedError("Encoding a DynamicParameter is not implemented yet.")