From 046e6fb89d4ba40740810ad28f04457e1c86e4c5 Mon Sep 17 00:00:00 2001 From: Tian-Jionglu <68932343+Tian-Jionglu@users.noreply.github.com> Date: Thu, 3 Aug 2023 18:38:27 +0800 Subject: [PATCH 1/5] apply asc format since format v8.1 --- can/io/asc.py | 106 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 88 insertions(+), 18 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 3114acfbe..dd33e38a5 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -56,31 +56,52 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) + self.version = '0.0.0' + # TODO: what is relative timestamp? Seems it should be timestamps_format self.relative_timestamp = relative_timestamp self.date: Optional[str] = None self.start_time = 0.0 # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format: Optional[str] = None self.internal_events_logged = False + self._extract_header() def _extract_header(self) -> None: for line in self.file: line = line.strip() + # parse date datetime_match = re.match( r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE ) + + # parse base base_match = re.match( r"base\s+(?Phex|dec)(?:\s+timestamps\s+" r"(?Pabsolute|relative))?", line, re.IGNORECASE, ) + + # parse version + version_match = re.match( + r"// version (?P.+)", + line, + re.IGNORECASE + ) + comment_match = re.match(r"//.*", line) events_match = re.match( r"(?Pno)?\s*internal\s+events\s+logged", line, re.IGNORECASE ) + # parse start time + trigger_match = re.match( + r"begin\s+triggerblock\s+\w+\s+(?P.+)", + line, + re.IGNORECASE, + ) + if datetime_match: self.date = datetime_match.group("datetime_string") self.start_time = ( @@ -98,14 +119,26 @@ def _extract_header(self) -> None: self.timestamps_format = timestamp_format or "absolute" continue + if version_match: + version = version_match.group("version") + self.version = version + continue + if comment_match: continue if events_match: self.internal_events_logged = events_match.group("no_events") is None - break + continue - break + if trigger_match: + datetime_str = trigger_match.group("datetime_string") + self.start_time = ( + 0.0 + if self.timestamps_format == 'relative' + else self._datetime_to_timestamp(datetime_str) + ) + break @staticmethod def _datetime_to_timestamp(datetime_string: str) -> float: @@ -252,26 +285,61 @@ def _process_fd_can_frame(self, line: str, msg_kwargs: Dict[str, Any]) -> Messag return Message(**msg_kwargs) + def _process_fd_can_frame_2(self, line: str, msg_kwargs: Dict[str, Any]) -> Message: + channel, direction, rest_of_message = line.split(None, 2) + # See ASCWriter + msg_kwargs["channel"] = int(channel) - 1 + msg_kwargs["is_rx"] = direction == "Rx" + + # CAN FD error frame + if rest_of_message.strip()[:10].lower() == "errorframe": + # Error Frame + # TODO: maybe use regex to parse BRS, ESI, etc? + msg_kwargs["is_error_frame"] = True + else: + can_id_str, symbolic_name, frame_name_or_brs, rest_of_message = rest_of_message.split( + None, 3 + ) + + if frame_name_or_brs.isdigit(): + brs = frame_name_or_brs + esi, dlc_str, data_length_str, data = rest_of_message.split(None, 3) + else: + brs, esi, dlc_str, data_length_str, data = rest_of_message.split( + None, 4 + ) + + self._extract_can_id(can_id_str, msg_kwargs) + msg_kwargs["bitrate_switch"] = brs == "1" + msg_kwargs["error_state_indicator"] = esi == "1" + dlc = int(dlc_str, self._converted_base) + data_length = int(data_length_str) + if data_length == 0: + # CAN remote Frame + msg_kwargs["is_remote_frame"] = True + msg_kwargs["dlc"] = dlc + else: + if dlc2len(dlc) != data_length: + logger.warning( + "DLC vs Data Length mismatch %d[%d] != %d", + dlc, + dlc2len(dlc), + data_length, + ) + msg_kwargs["dlc"] = data_length + + self._process_data_string(data, data_length, msg_kwargs) + + return Message(**msg_kwargs) + + def __iter__(self) -> Generator[Message, None, None]: - self._extract_header() + # extract head in initial + # self._extract_header() for line in self.file: line = line.strip() - trigger_match = re.match( - r"begin\s+triggerblock\s+\w+\s+(?P.+)", - line, - re.IGNORECASE, - ) - if trigger_match: - datetime_str = trigger_match.group("datetime_string") - self.start_time = ( - 0.0 - if self.relative_timestamp - else self._datetime_to_timestamp(datetime_str) - ) - continue - if not re.match( r"\d+\.\d+\s+(\d+\s+(\w+\s+(Tx|Rx)|ErrorFrame)|CANFD)", line, @@ -300,8 +368,10 @@ def __iter__(self) -> Generator[Message, None, None]: if "is_fd" not in msg_kwargs: msg = self._process_classic_can_frame(rest_of_message, msg_kwargs) - else: + elif self.version < '8.1': msg = self._process_fd_can_frame(rest_of_message, msg_kwargs) + else: + msg = self._process_fd_can_frame_2(rest_of_message, msg_kwargs) if msg is not None: yield msg From a57190c0e066e785acbf67789db34487bc8b98ee Mon Sep 17 00:00:00 2001 From: Tian-Jionglu Date: Thu, 3 Aug 2023 10:40:03 +0000 Subject: [PATCH 2/5] Format code with black --- can/io/asc.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index dd33e38a5..34a0f06c7 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -56,7 +56,7 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) - self.version = '0.0.0' + self.version = "0.0.0" # TODO: what is relative timestamp? Seems it should be timestamps_format self.relative_timestamp = relative_timestamp self.date: Optional[str] = None @@ -74,7 +74,7 @@ def _extract_header(self) -> None: datetime_match = re.match( r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE ) - + # parse base base_match = re.match( r"base\s+(?Phex|dec)(?:\s+timestamps\s+" @@ -84,11 +84,7 @@ def _extract_header(self) -> None: ) # parse version - version_match = re.match( - r"// version (?P.+)", - line, - re.IGNORECASE - ) + version_match = re.match(r"// version (?P.+)", line, re.IGNORECASE) comment_match = re.match(r"//.*", line) events_match = re.match( @@ -135,7 +131,7 @@ def _extract_header(self) -> None: datetime_str = trigger_match.group("datetime_string") self.start_time = ( 0.0 - if self.timestamps_format == 'relative' + if self.timestamps_format == "relative" else self._datetime_to_timestamp(datetime_str) ) break @@ -297,9 +293,12 @@ def _process_fd_can_frame_2(self, line: str, msg_kwargs: Dict[str, Any]) -> Mess # TODO: maybe use regex to parse BRS, ESI, etc? msg_kwargs["is_error_frame"] = True else: - can_id_str, symbolic_name, frame_name_or_brs, rest_of_message = rest_of_message.split( - None, 3 - ) + ( + can_id_str, + symbolic_name, + frame_name_or_brs, + rest_of_message, + ) = rest_of_message.split(None, 3) if frame_name_or_brs.isdigit(): brs = frame_name_or_brs @@ -332,7 +331,6 @@ def _process_fd_can_frame_2(self, line: str, msg_kwargs: Dict[str, Any]) -> Mess return Message(**msg_kwargs) - def __iter__(self) -> Generator[Message, None, None]: # extract head in initial # self._extract_header() @@ -368,7 +366,7 @@ def __iter__(self) -> Generator[Message, None, None]: if "is_fd" not in msg_kwargs: msg = self._process_classic_can_frame(rest_of_message, msg_kwargs) - elif self.version < '8.1': + elif self.version < "8.1": msg = self._process_fd_can_frame(rest_of_message, msg_kwargs) else: msg = self._process_fd_can_frame_2(rest_of_message, msg_kwargs) From 932843c9db535520e39e5542cbf19bd4c22f3166 Mon Sep 17 00:00:00 2001 From: Tian-Jionglu <68932343+Tian-Jionglu@users.noreply.github.com> Date: Wed, 1 Nov 2023 14:56:35 +0800 Subject: [PATCH 3/5] parse version using packaging --- can/io/asc.py | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 0c5900dd1..3334ef353 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -10,6 +10,7 @@ import time from datetime import datetime from typing import Any, Dict, Generator, List, Optional, TextIO, Union +from packaging.version import Version from ..message import Message from ..typechecking import StringPathLike @@ -56,7 +57,8 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) - self.version = "0.0.0" + self.asc_version = Version("0.0.0") # init asc format version + self._new_asc_version = Version("8.1.0") # TODO: what is relative timestamp? Seems it should be timestamps_format self.relative_timestamp = relative_timestamp self.date: Optional[str] = None @@ -72,7 +74,9 @@ def _extract_header(self) -> None: # parse date datetime_match = re.match( - r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE + r"date\s+\w+\s+(?P.+)", + line, + re.IGNORECASE ) # parse base @@ -83,12 +87,29 @@ def _extract_header(self) -> None: re.IGNORECASE, ) - # parse version - version_match = re.match(r"// version (?P.+)", line, re.IGNORECASE) + # parse asc format version + asc_version_match = re.match( + r"// version (?P.+)", + line, + re.IGNORECASE + ) + + comment_match = re.match( + r"//.*", + line + ) - comment_match = re.match(r"//.*", line) events_match = re.match( - r"(?Pno)?\s*internal\s+events\s+logged", line, re.IGNORECASE + r"(?Pno)?\s*internal\s+events\s+logged", + line, + re.IGNORECASE + ) + + # parse start time + trigger_match = re.match( + r"begin\s+triggerblock\s+\w+\s+(?P.+)", + line, + re.IGNORECASE, ) # parse start time @@ -115,9 +136,9 @@ def _extract_header(self) -> None: self.timestamps_format = timestamp_format or "absolute" continue - if version_match: - version = version_match.group("version") - self.version = version + if asc_version_match: + asc_version = asc_version_match.group("version") + self.asc_version = Version(asc_version) continue if comment_match: @@ -366,7 +387,7 @@ def __iter__(self) -> Generator[Message, None, None]: if "is_fd" not in msg_kwargs: msg = self._process_classic_can_frame(rest_of_message, msg_kwargs) - elif self.version < "8.1": + elif self.asc_version < self._new_asc_version: msg = self._process_fd_can_frame(rest_of_message, msg_kwargs) else: msg = self._process_fd_can_frame_2(rest_of_message, msg_kwargs) From 2c28f7ebd59f5511b4e237e2b0614af5d99db9c1 Mon Sep 17 00:00:00 2001 From: Tian-Jionglu Date: Wed, 1 Nov 2023 07:01:29 +0000 Subject: [PATCH 4/5] Format code with black --- can/io/asc.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index 3334ef353..f68a4885d 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -57,7 +57,7 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) - self.asc_version = Version("0.0.0") # init asc format version + self.asc_version = Version("0.0.0") # init asc format version self._new_asc_version = Version("8.1.0") # TODO: what is relative timestamp? Seems it should be timestamps_format self.relative_timestamp = relative_timestamp @@ -74,9 +74,7 @@ def _extract_header(self) -> None: # parse date datetime_match = re.match( - r"date\s+\w+\s+(?P.+)", - line, - re.IGNORECASE + r"date\s+\w+\s+(?P.+)", line, re.IGNORECASE ) # parse base @@ -89,20 +87,13 @@ def _extract_header(self) -> None: # parse asc format version asc_version_match = re.match( - r"// version (?P.+)", - line, - re.IGNORECASE + r"// version (?P.+)", line, re.IGNORECASE ) - comment_match = re.match( - r"//.*", - line - ) + comment_match = re.match(r"//.*", line) events_match = re.match( - r"(?Pno)?\s*internal\s+events\s+logged", - line, - re.IGNORECASE + r"(?Pno)?\s*internal\s+events\s+logged", line, re.IGNORECASE ) # parse start time From 1f7102a6f93835b81acf4e9a208332cd13491179 Mon Sep 17 00:00:00 2001 From: Tian-Jionglu Date: Fri, 7 Jun 2024 11:12:18 +0800 Subject: [PATCH 5/5] add version warning --- can/io/asc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index f68a4885d..db596ca25 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -381,7 +381,12 @@ def __iter__(self) -> Generator[Message, None, None]: elif self.asc_version < self._new_asc_version: msg = self._process_fd_can_frame(rest_of_message, msg_kwargs) else: - msg = self._process_fd_can_frame_2(rest_of_message, msg_kwargs) + if self.asc_version < self._new_asc_version: + logger.warning( + "ASC format is under 8.1 or unknown, process may not safe." + ) + else: + msg = self._process_fd_can_frame(rest_of_message, msg_kwargs) if msg is not None: yield msg