From 853fbeb42c312fe34f24c854dfe0165c80334d2a Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Wed, 29 Jan 2020 14:21:26 -0600 Subject: [PATCH 1/8] adding absolute timestamp to ASC reader --- can/io/asc.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d646a1924..b5a46bb77 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -31,8 +31,6 @@ class ASCReader(BaseIOHandler): """ Iterator of CAN messages from a ASC logging file. Meta data (comments, bus statistics, J1939 Transport Protocol messages) is ignored. - - TODO: turn relative timestamps back to absolute form """ def __init__( @@ -185,6 +183,13 @@ def __iter__(self) -> Generator[Message, None, None]: for line in self.file: temp = line.strip() + + #check for timestamp + if "begin triggerblock" in temp.lower(): + _, _, start_time = temp.split(None, 2) + start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + continue + if not temp or not temp[0].isdigit(): # Could be a comment continue From 14c7a689a347024a03bf176f679a64c05053e48a Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 18 Jun 2020 23:10:54 +1200 Subject: [PATCH 2/8] Error handling in ASC time parsing Co-authored-by: Alexander Bessman --- can/io/asc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index b5a46bb77..657a0ce7c 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -186,7 +186,11 @@ def __iter__(self) -> Generator[Message, None, None]: #check for timestamp if "begin triggerblock" in temp.lower(): - _, _, start_time = temp.split(None, 2) + try: + _, _, start_time = temp.split(None, 2) + start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + except ValueError: + start_time = 0.0 start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() continue From d86020ee319ad166a9d7a2b7139dda405767cec8 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Thu, 18 Jun 2020 23:11:54 +1200 Subject: [PATCH 3/8] Update can/io/asc.py Co-authored-by: Alexander Bessman --- can/io/asc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index 657a0ce7c..ea1f7417e 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -191,7 +191,6 @@ def __iter__(self) -> Generator[Message, None, None]: start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() except ValueError: start_time = 0.0 - start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() continue if not temp or not temp[0].isdigit(): From 39541b1a48b27febf1f7ceb19a30d497fc0e38f2 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 12:40:55 -0500 Subject: [PATCH 4/8] rebase changes still need to update docs, create test, and add option flag --- can/io/asc.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index ea1f7417e..be773f86f 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -53,6 +53,7 @@ def __init__( self.base = base self._converted_base = self._check_base(base) self.date = None + # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format = None self.internal_events_logged = None @@ -72,6 +73,14 @@ def _extract_header(self): self.timestamps_format = timestamp_format elif lower_case.endswith("internal events logged"): self.internal_events_logged = not lower_case.startswith("no") + # grab absolute timestamp + elif lower_case.startswith("begin triggerblock"): + try: + _, _, start_time = lower_case.split(None, 2) + start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + except ValueError: + start_time = 0.0 + self.start_time = start_time # Currently the last line in the header which is parsed break else: @@ -183,23 +192,13 @@ def __iter__(self) -> Generator[Message, None, None]: for line in self.file: temp = line.strip() - - #check for timestamp - if "begin triggerblock" in temp.lower(): - try: - _, _, start_time = temp.split(None, 2) - start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() - except ValueError: - start_time = 0.0 - continue - if not temp or not temp[0].isdigit(): # Could be a comment continue msg_kwargs = {} try: timestamp, channel, rest_of_message = temp.split(None, 2) - timestamp = float(timestamp) + timestamp = float(timestamp) + self.start_time msg_kwargs["timestamp"] = timestamp if channel == "CANFD": msg_kwargs["is_fd"] = True From 3b5f0a776d328e49995c392e7cad90d0f72f8e11 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 17:45:44 +0000 Subject: [PATCH 5/8] Format code with black --- can/io/asc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index be773f86f..411daddfc 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -77,7 +77,9 @@ def _extract_header(self): elif lower_case.startswith("begin triggerblock"): try: _, _, start_time = lower_case.split(None, 2) - start_time = datetime.strptime(start_time, "%a %b %m %I:%M:%S.%f %p %Y").timestamp() + start_time = datetime.strptime( + start_time, "%a %b %m %I:%M:%S.%f %p %Y" + ).timestamp() except ValueError: start_time = 0.0 self.start_time = start_time From f941f2fb7a509dc2ca5c1c524874ba34b30fa682 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 12:59:51 -0500 Subject: [PATCH 6/8] adding option for relative or absolute --- can/io/asc.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/can/io/asc.py b/can/io/asc.py index 411daddfc..d6d705ca2 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -37,6 +37,7 @@ def __init__( self, file: Union[typechecking.FileLike, typechecking.StringPathLike], base: str = "hex", + relative: bool = True, ) -> None: """ :param file: a path-like object or as file-like object to read from @@ -45,6 +46,9 @@ def __init__( :param base: Select the base(hex or dec) of id and data. If the header of the asc file contains base information, this value will be overwritten. Default "hex". + :param relative: Select whether the timestamps are `relative` (starting + at 0.0) of `absolute` (starting at the system time). + Default `True = relative`. """ super().__init__(file, mode="r") @@ -52,6 +56,7 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) + self.relative_timestamp = relative self.date = None # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format = None @@ -82,7 +87,10 @@ def _extract_header(self): ).timestamp() except ValueError: start_time = 0.0 - self.start_time = start_time + if self.relative_timestamp: + self.start_time = 0.0 + else: + self.start_time = start_time # Currently the last line in the header which is parsed break else: From 2111d7ce11f8f41fd5cc93915a7f0b375b431e83 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 14:45:14 -0500 Subject: [PATCH 7/8] simple refactor and add test --- can/io/asc.py | 17 +++++++++++------ test/data/test_CanMessage.asc | 2 +- test/logformats_test.py | 35 +++++++++++++++++++++++++++++++++-- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index d6d705ca2..b4e83df7d 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -33,11 +33,13 @@ class ASCReader(BaseIOHandler): bus statistics, J1939 Transport Protocol messages) is ignored. """ + FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" + def __init__( self, file: Union[typechecking.FileLike, typechecking.StringPathLike], base: str = "hex", - relative: bool = True, + relative_timestamp: bool = True, ) -> None: """ :param file: a path-like object or as file-like object to read from @@ -46,9 +48,9 @@ def __init__( :param base: Select the base(hex or dec) of id and data. If the header of the asc file contains base information, this value will be overwritten. Default "hex". - :param relative: Select whether the timestamps are `relative` (starting - at 0.0) of `absolute` (starting at the system time). - Default `True = relative`. + :param relative_timestamp: Select whether the timestamps are + `relative` (starting at 0.0) or `absolute` (starting at + the system time). Default `True = relative`. """ super().__init__(file, mode="r") @@ -56,7 +58,7 @@ def __init__( raise ValueError("The given file cannot be None") self.base = base self._converted_base = self._check_base(base) - self.relative_timestamp = relative + self.relative_timestamp = relative_timestamp self.date = None # TODO - what is this used for? The ASC Writer only prints `absolute` self.timestamps_format = None @@ -78,12 +80,15 @@ def _extract_header(self): self.timestamps_format = timestamp_format elif lower_case.endswith("internal events logged"): self.internal_events_logged = not lower_case.startswith("no") + elif lower_case.startswith("// version"): + # the test files include `// version 9.0.0` - not sure what this is + continue # grab absolute timestamp elif lower_case.startswith("begin triggerblock"): try: _, _, start_time = lower_case.split(None, 2) start_time = datetime.strptime( - start_time, "%a %b %m %I:%M:%S.%f %p %Y" + start_time, self.FORMAT_START_OF_FILE_DATE ).timestamp() except ValueError: start_time = 0.0 diff --git a/test/data/test_CanMessage.asc b/test/data/test_CanMessage.asc index 52dda34d9..881f16132 100644 --- a/test/data/test_CanMessage.asc +++ b/test/data/test_CanMessage.asc @@ -2,7 +2,7 @@ date Sam Sep 30 15:06:13.191 2017 base hex timestamps absolute internal events logged // version 9.0.0 -Begin Triggerblock Sam Sep 30 15:06:13.191 2017 +Begin Triggerblock Sat Sep 30 10:06:13.191 PM 2017 0.000000 Start of measurement 2.5010 2 C8 Tx d 8 09 08 07 06 05 04 03 02 17.876708 1 6F9 Rx d 8 05 0C 00 00 00 00 00 00 Length = 240015 BitCount = 124 ID = 1785 diff --git a/test/logformats_test.py b/test/logformats_test.py index dfd6fa860..65a20097d 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -20,6 +20,7 @@ import os from abc import abstractmethod, ABCMeta from itertools import zip_longest +from datetime import datetime import can @@ -364,6 +365,8 @@ def assertIncludesComments(self, filename): class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" + FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" + def _setup_instance(self): super()._setup_instance_helper( can.ASCWriter, @@ -374,11 +377,39 @@ def _setup_instance(self): adds_default_channel=0, ) - def _read_log_file(self, filename): + def _read_log_file(self, filename, **kwargs): logfile = os.path.join(os.path.dirname(__file__), "data", filename) - with can.ASCReader(logfile) as reader: + with can.ASCReader(logfile, **kwargs) as reader: return list(reader) + def test_absolute_time(self): + time_from_file = "Sat Sep 30 10:06:13.191 PM 2017" + start_time = datetime.strptime( + time_from_file, self.FORMAT_START_OF_FILE_DATE + ).timestamp() + + expected_messages = [ + can.Message( + timestamp=2.5010 + start_time, + arbitration_id=0xC8, + is_extended_id=False, + is_rx=False, + channel=1, + dlc=8, + data=[9, 8, 7, 6, 5, 4, 3, 2], + ), + can.Message( + timestamp=17.876708 + start_time, + arbitration_id=0x6F9, + is_extended_id=False, + channel=0, + dlc=0x8, + data=[5, 0xC, 0, 0, 0, 0, 0, 0], + ), + ] + actual = self._read_log_file("test_CanMessage.asc", relative_timestamp=False) + self.assertMessagesEqual(actual, expected_messages) + def test_can_message(self): expected_messages = [ can.Message( From 7389b23f8dc2a53adba5d58568333d5e350fad42 Mon Sep 17 00:00:00 2001 From: Caleb Perkinson Date: Fri, 23 Apr 2021 19:47:15 +0000 Subject: [PATCH 8/8] Format code with black --- can/io/asc.py | 2 +- test/logformats_test.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/can/io/asc.py b/can/io/asc.py index b4e83df7d..01d91d695 100644 --- a/can/io/asc.py +++ b/can/io/asc.py @@ -48,7 +48,7 @@ def __init__( :param base: Select the base(hex or dec) of id and data. If the header of the asc file contains base information, this value will be overwritten. Default "hex". - :param relative_timestamp: Select whether the timestamps are + :param relative_timestamp: Select whether the timestamps are `relative` (starting at 0.0) or `absolute` (starting at the system time). Default `True = relative`. """ diff --git a/test/logformats_test.py b/test/logformats_test.py index 65a20097d..5dfa48e5c 100644 --- a/test/logformats_test.py +++ b/test/logformats_test.py @@ -366,7 +366,7 @@ class TestAscFileFormat(ReaderWriterTest): """Tests can.ASCWriter and can.ASCReader""" FORMAT_START_OF_FILE_DATE = "%a %b %d %I:%M:%S.%f %p %Y" - + def _setup_instance(self): super()._setup_instance_helper( can.ASCWriter, @@ -385,8 +385,8 @@ def _read_log_file(self, filename, **kwargs): def test_absolute_time(self): time_from_file = "Sat Sep 30 10:06:13.191 PM 2017" start_time = datetime.strptime( - time_from_file, self.FORMAT_START_OF_FILE_DATE - ).timestamp() + time_from_file, self.FORMAT_START_OF_FILE_DATE + ).timestamp() expected_messages = [ can.Message(