Skip to content

Commit

Permalink
Add support for protocol version J2602_1_1.0 (#116)
Browse files Browse the repository at this point in the history
* Add support to handle J2602_*_1 version LDF files
* Add W504 to flake8 ignore list, as W504 and W503 are conflicting rules.
---------
Co-authored-by: Yan Du <[email protected]>
Co-authored-by: Balázs Eszes <[email protected]>
  • Loading branch information
nuts4coffee authored Jun 12, 2023
1 parent f611870 commit dac9b42
Show file tree
Hide file tree
Showing 15 changed files with 413 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
ignore = W191,E501,E302,E305
ignore = W504,W191,E501,E302,E305
exclude =
.git
__pycache__
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.20.0] - 2023-06-12

### Added

- Support for SAE J2602 legacy version
- Grammar support for the following J2602 fields:
- `max_header_length`, `response_tolerance` for the master node
- `response_tolerance` for slave node attributes

## [0.19.0] - 2023-03-12

### Added
Expand Down
5 changes: 4 additions & 1 deletion ldfparser/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def nodes(self, tree):
return ("nodes", {'master': tree[0], 'slaves': tree[1]})

def nodes_master(self, tree):
return {"name": tree[0], "timebase": tree[1] * 0.001, "jitter": tree[2] * 0.001}
return {"name": tree[0], "timebase": tree[1] * 0.001, "jitter": tree[2] * 0.001, "max_header_length": tree[3] if len(tree) > 3 else None, "response_tolerance": tree[4] * 0.01 if len(tree) > 4 else None}

def nodes_slaves(self, tree):
return tree
Expand Down Expand Up @@ -201,6 +201,9 @@ def node_definition_configurable_frames_20(self, tree):
def node_definition_configurable_frames_21(self, tree):
return ("configurable_frames", tree[0:])

def node_definition_response_tolerance(self, tree):
return ("response_tolerance", tree[0] * 0.01)

def schedule_tables(self, tree):
return ("schedule_tables", tree)

Expand Down
11 changes: 8 additions & 3 deletions ldfparser/grammars/ldf.lark
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ start: ldf
ldf: (header_lin_description_file | header_protocol_version | header_language_version | header_speed | header_channel | header_file_revision | header_sig_byte_order_big_endian | header_sig_byte_order_little_endian | nodes | node_compositions | signals | diagnostic_signals | diagnostic_addresses | frames | sporadic_frames | event_triggered_frames | diagnostic_frames | node_attributes | schedule_tables | signal_groups | signal_encoding_types | signal_representations)*

ldf_identifier: CNAME
ldf_version: LIN_VERSION | ISO_VERSION
ldf_version: LIN_VERSION | ISO_VERSION | J2602_VERSION
ldf_integer: C_INT
ldf_float: C_FLOAT
ldf_channel_name: ESCAPED_STRING
Expand All @@ -21,8 +21,9 @@ header_sig_byte_order_big_endian: "LIN_sig_byte_order_big_endian" ";"
header_sig_byte_order_little_endian: "LIN_sig_byte_order_little_endian" ";"

// LIN 2.1 Specification, section 9.2.2
// SAE J2602-3_201001, SECTION 7.2
nodes: "Nodes" "{" nodes_master? nodes_slaves? "}"
nodes_master: "Master" ":" ldf_identifier "," ldf_float "ms" "," ldf_float "ms" ";"
nodes_master: "Master" ":" ldf_identifier "," ldf_float "ms" "," ldf_float "ms" ("," ldf_integer "bits" "," ldf_float "%")? ";"
nodes_slaves: "Slaves" ":" ldf_identifier ("," ldf_identifier)* ";"

// LIN 2.1 Specification, section 9.2.2.3
Expand Down Expand Up @@ -66,8 +67,9 @@ diagnostic_addresses: "Diagnostic_addresses" "{" (diagnostic_address)* "}"
diagnostic_address: ldf_identifier ":" ldf_integer ";"

// LIN 2.1 Specification, section 9.2.2.2
// response_tolerance: SAE J2602_1_201001, section 7.2.1
node_attributes: "Node_attributes" "{" (node_definition*) "}"
node_definition: ldf_identifier "{" (node_definition_protocol | node_definition_configured_nad | node_definition_initial_nad | node_definition_product_id | node_definition_response_error | node_definition_fault_state_signals | node_definition_p2_min | node_definition_st_min | node_definition_n_as_timeout | node_definition_n_cr_timeout | node_definition_configurable_frames)* "}"
node_definition: ldf_identifier "{" (node_definition_protocol | node_definition_configured_nad | node_definition_initial_nad | node_definition_product_id | node_definition_response_error | node_definition_fault_state_signals | node_definition_p2_min | node_definition_st_min | node_definition_n_as_timeout | node_definition_n_cr_timeout | node_definition_configurable_frames | node_definition_response_tolerance)* "}"
node_definition_protocol: "LIN_protocol" "=" (["\"" ldf_version "\""] | ldf_version) ";"
node_definition_configured_nad: "configured_NAD" "=" ldf_integer ";"
node_definition_initial_nad: "initial_NAD" "=" ldf_integer ";"
Expand All @@ -81,6 +83,8 @@ node_definition_n_cr_timeout: "N_Cr_timeout" "=" ldf_float "ms" ";"
node_definition_configurable_frames: node_definition_configurable_frames_20 | node_definition_configurable_frames_21
node_definition_configurable_frames_20: "configurable_frames" "{" (ldf_identifier "=" ldf_integer ";")+ "}"
node_definition_configurable_frames_21: "configurable_frames" "{" (ldf_identifier ";")+ "}"
node_definition_response_tolerance: "response_tolerance" "=" ldf_float "%" ";"


// LIN 2.1 Specification, section 9.2.5
schedule_tables: "Schedule_tables" "{" (schedule_table_definition+) "}"
Expand Down Expand Up @@ -120,6 +124,7 @@ C_INT: ("0x"HEXDIGIT+) | ("-"? INT)
C_FLOAT: ("-"? INT ("." INT)?) ("e" ("+" | "-")? INT)?
LIN_VERSION: INT "." INT
ISO_VERSION: "ISO17987" ":" INT
J2602_VERSION: "J2602" "_" INT "_" INT "." INT

ANY_SEMICOLON_TERMINATED_LINE: /.*;/

Expand Down
10 changes: 5 additions & 5 deletions ldfparser/ldf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
from typing import Union, Dict, List

from .lin import LinVersion, Iso17987Version
from .lin import LinVersion, Iso17987Version, J2602Version
from .frame import LinFrame, LinSporadicFrame, LinUnconditionalFrame, LinEventTriggeredFrame
from .diagnostics import LinDiagnosticFrame, LinDiagnosticRequest, LinDiagnosticResponse
from .signal import LinSignal
Expand All @@ -19,8 +19,8 @@ class LDF():

def __init__(self):
self._source: Dict = None
self._protocol_version: Union[LinVersion, Iso17987Version] = None
self._language_version: Union[LinVersion, Iso17987Version] = None
self._protocol_version: Union[LinVersion, Iso17987Version, J2602Version] = None
self._language_version: Union[LinVersion, Iso17987Version, J2602Version] = None
self._baudrate: int = None
self._channel: str = None
self._master: LinMaster = None
Expand All @@ -38,11 +38,11 @@ def __init__(self):
self._schedule_tables: Dict[str, ScheduleTable] = {}
self._comments: List[str] = []

def get_protocol_version(self) -> Union[LinVersion, Iso17987Version]:
def get_protocol_version(self) -> Union[LinVersion, Iso17987Version, J2602Version]:
"""Returns the protocol version of the LIN network"""
return self._protocol_version

def get_language_version(self) -> Union[LinVersion, Iso17987Version]:
def get_language_version(self) -> Union[LinVersion, Iso17987Version, J2602Version]:
"""Returns the LDF language version"""
return self._language_version

Expand Down
113 changes: 106 additions & 7 deletions ldfparser/lin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from typing import Union


class LinVersion:
"""
LinVersion represents the LIN protocol and LDF language versions
Expand Down Expand Up @@ -40,6 +41,8 @@ def __float__(self) -> float:
def __eq__(self, o: object) -> bool:
if isinstance(o, LinVersion):
return self.major == o.major and self.minor == o.minor
elif isinstance(o, J2602Version):
return self.major == 2 and self.minor == 0
return False

def __gt__(self, o) -> bool:
Expand All @@ -49,6 +52,8 @@ def __gt__(self, o) -> bool:
if self.major == o.major and self.minor > o.minor:
return True
return False
elif isinstance(o, J2602Version):
return (self.major == 2 and self.minor > 0) or self.major > 2
raise TypeError()

def __lt__(self, o) -> bool:
Expand All @@ -58,6 +63,8 @@ def __lt__(self, o) -> bool:
if self.major == o.major and self.minor < o.minor:
return True
return False
elif isinstance(o, J2602Version):
return self.major < 2
raise TypeError()

def __ge__(self, o) -> bool:
Expand Down Expand Up @@ -118,11 +125,103 @@ def __ne__(self, o: object) -> bool:

ISO17987_2015 = Iso17987Version(2015)

def parse_lin_version(version: str) -> Union[LinVersion, Iso17987Version]:
try:
return LinVersion.from_string(version)
except ValueError:
class J2602Version:
def __init__(self, major, minor, part):
"""
Abstract the J2602 version.
"""
self.major = major
self.minor = minor
self.part = part

@staticmethod
def from_string(version: str) -> 'J2602Version':
"""
Create an instance from the version string.
The version string J2602_1_2.0 will render:
major=2, minor=0, part=1
Support for J2602_1_2.0 is not implemented at this time.
"""
if "J2602" not in version:
raise ValueError(f'{version} is not an SAE J2602 version.')

version = version.replace("J2602_", '')
(part, versions) = version.split('_')
major, minor = [int(value) for value in versions.split('.')]
if major == 1:
return J2602Version(major=major, minor=minor, part=int(part))

raise ValueError(f'{version} is not supported yet.')

def __str__(self) -> str:
return f"J2602_{self.part}_{self.major}.{self.minor}"

def __eq__(self, o: object) -> bool:
"""
According to J2602-3_202110, section 7.1.3:
“J2602_1_1.0” -> J2602:2012 and earlier -> based on LIN 2.0
“J2602_1_2.0” -> J2602:2021 -> based on ISO 17987:2016
Therefore,
“J2602_1_1.0” is considered equal to LinVersion(2, 0)
"""
if isinstance(o, J2602Version):
return (
self.major == o.major and
self.minor == o.minor and
self.part == o.part
)
elif isinstance(o, Iso17987Version):
return False
elif isinstance(o, LinVersion):
return o == LIN_VERSION_2_0
return False

def __gt__(self, o) -> bool:
if isinstance(o, J2602Version):
return (
self.major > o.major or
(
self.major == o.major and self.minor > o.minor
)
)
if isinstance(o, Iso17987Version):
return False
if isinstance(o, LinVersion):
return o < LIN_VERSION_2_0
raise TypeError()

def __lt__(self, o) -> bool:
if isinstance(o, J2602Version):
return (
self.major < o.major or
(
self.major == o.major and self.minor < o.minor
)
)
if isinstance(o, Iso17987Version):
return True
if isinstance(o, LinVersion):
return o > LIN_VERSION_2_0
raise TypeError()

def __ge__(self, o) -> bool:
return not self.__lt__(o)

def __le__(self, o) -> bool:
return not self.__gt__(o)

def __ne__(self, o: object) -> bool:
return not self.__eq__(o)

def parse_lin_version(version: str) -> Union[LinVersion, Iso17987Version, J2602Version]:
for version_class in [LinVersion, Iso17987Version, J2602Version]:
try:
return Iso17987Version.from_string(version)
except ValueError:
raise ValueError(f'{version} is not a valid LIN version.')
return version_class.from_string(version)
except (ValueError, IndexError):
pass

raise ValueError(f'{version} is not a valid LIN version.')
25 changes: 22 additions & 3 deletions ldfparser/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
if TYPE_CHECKING:
from .frame import LinFrame
from .signal import LinSignal
from .lin import LinVersion, Iso17987Version
from .lin import LinVersion, Iso17987Version, J2602Version

LIN_SUPPLIER_ID_WILDCARD = 0x7FFF
LIN_FUNCTION_ID_WILDCARD = 0xFFFF
Expand Down Expand Up @@ -76,12 +76,27 @@ class LinMaster(LinNode):
:type timebase: float
:param jitter: LIN network jitter in seconds
:type jitter: float
:param max_header_length: The maximum number of bits of the header length
:type max_header_length: int
:param response_tolerance: The value between 0.0 - 1.0 that represents the
percentage of the frame response tolerance.
:type response_tolerance: float
"""

def __init__(self, name: str, timebase: float, jitter: float):
def __init__(
self,
name: str,
timebase: float,
jitter: float,
max_header_length: int,
response_tolerance: float,
):
super().__init__(name)
self.timebase: float = timebase
self.jitter: float = jitter
self.max_header_length: int = max_header_length
self.response_tolerance: float = response_tolerance


class LinSlave(LinNode):
"""
Expand Down Expand Up @@ -109,11 +124,14 @@ class LinSlave(LinNode):
:type n_cr_timeout:
:param configurable_frames:
:type configurable_frames:
:param response_tolerance: The value between 0.0 - 1.0 that represents the
percentage of the frame response tolerance. For example, 0.4 for 40%.
:type response_tolerance: float
"""

def __init__(self, name: str) -> None:
super().__init__(name)
self.lin_protocol: Union[LinVersion, Iso17987Version] = None
self.lin_protocol: Union[LinVersion, Iso17987Version, J2602Version] = None
self.configured_nad: int = None
self.initial_nad: int = None
self.product_id: LinProductId = None
Expand All @@ -124,6 +142,7 @@ def __init__(self, name: str) -> None:
self.n_as_timeout: float = 1
self.n_cr_timeout: float = 1
self.configurable_frames = {}
self.response_tolerance: float = None

class LinNodeCompositionConfiguration:

Expand Down
15 changes: 13 additions & 2 deletions ldfparser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .frame import LinEventTriggeredFrame, LinSporadicFrame, LinUnconditionalFrame
from .signal import LinSignal
from .encoding import ASCIIValue, BCDValue, LinSignalEncodingType, LogicalValue, PhysicalValue, ValueConverter
from .lin import LIN_VERSION_2_0, LIN_VERSION_2_1, parse_lin_version
from .lin import LIN_VERSION_2_0, LIN_VERSION_2_1, J2602Version, parse_lin_version
from .node import LinMaster, LinProductId, LinSlave
from .ldf import LDF
from .grammar import LdfTransformer
Expand Down Expand Up @@ -148,9 +148,17 @@ def _populate_ldf_sporadic_frames(json: dict, ldf: LDF):

def _populate_ldf_nodes(json: dict, ldf: LDF):
nodes = _require_key(json, 'nodes', 'Missing Nodes section.')

if nodes.get('master'):
master_node = nodes['master']
ldf._master = LinMaster(master_node['name'], master_node['timebase'], master_node['jitter'])
is_j2602_protocol = isinstance(ldf.get_protocol_version(), J2602Version)
default_max_header_len = 48 if is_j2602_protocol else None
default_resp_tolerance = 0.4 if is_j2602_protocol else None
if master_node['max_header_length'] is None:
master_node['max_header_length'] = default_max_header_len
if master_node['response_tolerance'] is None:
master_node['response_tolerance'] = default_resp_tolerance
ldf._master = LinMaster(**master_node)

if nodes.get('slaves'):
if ldf.get_language_version() >= LIN_VERSION_2_0:
Expand Down Expand Up @@ -188,6 +196,9 @@ def _create_ldf2x_node(node: dict, language_version: float):
slave.st_min = node.get('ST_min', 0)
slave.n_as_timeout = node.get('N_As_timeout', 1)
slave.n_cr_timeout = node.get('N_Cr_timeout', 1)
is_j2602_protocol = isinstance(slave.lin_protocol, J2602Version)
default_resp_tolerance = 0.4 if is_j2602_protocol else None
slave.response_tolerance = node.get('response_tolerance', default_resp_tolerance)

return slave

Expand Down
7 changes: 5 additions & 2 deletions ldfparser/templates/ldf.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ Channel_name = "{{ ldf.get_channel() }}";

Nodes {
{%- if ldf.get_master() %}
Master: {{ ldf.get_master().name }}, {{ ldf.get_master().timebase * 1000 | float}} ms, {{ ldf.get_master().jitter * 1000 }} ms;
Master: {{ ldf.get_master().name }}, {{ ldf.get_master().timebase * 1000 | float}} ms, {{ ldf.get_master().jitter * 1000 }} ms{%- if ldf.get_master().max_header_length is not none %}, {{ldf.get_master().max_header_length}} bits{%- endif %}{%- if ldf.get_master().response_tolerance is not none %}, {{ldf.get_master().response_tolerance * 100}} %{%- endif %};
{%- endif %}
{%- if ldf.get_slaves() %}
Slaves: {%- for slave in ldf.get_slaves() %} {{ slave.name }}{%- if not loop.last %},{% endif -%}{%- endfor -%};
{%- endif %}
}

{%- if ldf.get_language_version().major == 2 or "Iso17987Version" in ldf.get_language_version().__class__.__name__ %}
{%- if ldf.get_language_version().major == 2 or "Iso17987Version" in ldf.get_language_version().__class__.__name__ or "J2602" in ldf.get_language_version().__class__.__name__ %}
Node_attributes {
{%- for slave in ldf.get_slaves() %}
{{slave.name}} {
Expand All @@ -45,6 +45,9 @@ Node_attributes {
{%- endfor %}
}
{%- endif %}
{%- if slave.response_tolerance %}
response_tolerance = {{slave.response_tolerance * 100}} %;
{%- endif %}
}
{%- endfor %}
}
Expand Down
Loading

0 comments on commit dac9b42

Please sign in to comment.