8000 IO module enhancements by felixdivo · Pull Request #348 · hardbyte/python-can · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
8b18407
added a generic file handler
felixdivo Jul 3, 2018
deee1b6
better docs in listener.py and made the Listener class abstract
felixdivo Jul 3, 2018
3cf6368
make CSV reader & writer is the new BaseIOHandler
felixdivo Jul 3, 2018
ebb36fb
make Canutils reader & writer is the new BaseIOHandler
felixdivo Jul 3, 2018
8798460
docs & fixes
felixdivo Jul 3, 2018
10ec6bf
make ASC ahnders use BaseIOHandler
felixdivo Jul 3, 2018
a601391
fix universal nelines support
felixdivo Jul 3, 2018
c40ab53
fix sphinx error
felixdivo Jul 3, 2018
190653b
make stdout use BaseIOHandler
felixdivo Jul 3, 2018
3368c24
docs
felixdivo Jul 3, 2018
4d97194
make SQlite reader/writer use the new BaseIOHandler
felixdivo Jul 3, 2018
906cfb1
added BaseIOHandler in BLF reder/Writer
felixdivo Jul 3, 2018
d68c430
sqlite fix
felixdivo Jul 3, 2018
b10080e
fix file opening in BLF handlers
felixdivo Jul 3, 2018
0fcbf07
rome "text mode" parameter form file open calls
felixdivo Jul 3, 2018
6d97d20
fix bad test cases
felixdivo Jul 3, 2018
9e88d10
only writer header if not appending
felixdivo Jul 3, 2018
a9a961c
address code review by @christiansandberg
felixdivo Jul 3, 2018
def5a04
add docs
felixdivo Jul 3, 2018
0490712
allow BaseIOHandler to use path-like objectas and file-like objects
felixdivo Jul 4, 2018
e2cf45a
add tests for context manger in readers & writers
felixdivo Jul 4, 2018
fd624bf
fix error in test case
felixdivo Jul 4, 2018
aefefcd
fix alignment of message timestamp printing
felixdivo Jul 4, 2018
6274245
add __ne__ method for message object
felixdivo Jul 4, 2018
31aab59
add docs and privatize attrs in Sqlite handlers
felixdivo Jul 5, 2018
7234a76
add missing handlers to docs
felixdivo Jul 5, 2018
9ac38fe
change param from filename ot file and comments
felixdivo Jul 5, 2018
86244c4
rename loger.py to printer.py
felixdivo Jul 5, 2018
d857366
fix import
felixdivo Jul 5, 2018
36b6eda
update docstrings for file parameter
felixdivo Jul 5, 2018
d1ffbaf
better docs
felixdivo Jul 5, 2018
86c1070
fix imports
felixdivo Jul 5, 2018
65ffce9
fix imports
felixdivo Jul 5, 2018
5abea26
add possibility for testing appending of writers
felixdivo Jul 6, 2018
32f01c8
enable append testing
felixdivo Jul 6, 2018
c9ea0b3
fix append mode test for Sqlite
felixdivo Jul 6, 2018
b3ff334
add test for can.Printer
felixdivo Jul 6, 2018
eb501be
slightly simpler test case
felixdivo Jul 6, 2018
e5df130
fix sqlite parser
felixdivo Jul 6, 2018
b769137
add sleepingto append mode testing
felixdivo Jul 6, 2018
b51b719
fix test method call
felixdivo Jul 6, 2018
54aa145
enuse data is written to disk
felixdivo Jul 6, 2018
732fbb6
restructured logformats_test
felixdivo Jul 6, 2018
3b34dcd
exclude base class from tests
felixdivo Jul 6, 2018
aa59dc0
try to fix ReaderWriterTest
felixdivo Jul 6, 2018
3096feb
pass along params to superclass in test case
felixdivo Jul 6, 2018
65b2725
fix wrong param in init call
felixdivo Jul 6, 2018
994497b
attempt to fix test case invocation
8000 felixdivo Jul 6, 2018
7337055
attempt different approach at ReaderWriterTest
felixdivo Jul 6, 2018
b937ab4
add magic "__test__ " attribute
felixdivo Jul 6, 2018
e91b0b3
change constructor for ReaderWriterTest
felixdivo Jul 18, 2018
35c4676
fix Sqlite test
felixdivo Jul 18, 2018
f2c3ee2
Sqlite test fixes
felixdivo Jul 18, 2018
02f86e4
fix skipping of tests
felixdivo Jul 18, 2018
baf2416
remove redundant Sqlite test
felixdivo Jul 19, 2018
9c843d3
Merge branch 'develop' into fix-file-io
felixdivo Jul 19, 2018
1bc7484
Merge branch 'develop' into fix-file-io
felixdivo Jul 20, 2018
a1ed6bb
improved logformats_test and added test_file_like_explicit_stop
felixdivo Jul 20, 2018
4afbd0c
add test_file_like_context_manager
felixdivo Jul 20, 2018
9c19bcd
added support for testing binary readers/writers + added docs on file…
felixdivo Jul 20, 2018
4c835cd
added *args and **kwargs forwaring to LogReader and Logger
felixdivo Jul 20, 2018
c4fde3b
add some documentation to Sqlite
felixdivo Jul 20, 2018
ff9d821
fix bug in test call
felixdivo Jul 20, 2018
75ff215
add more xtensive logging in test (in assertMessagesEqual)
felixdivo Jul 20, 2018
0a7ab37
less verbose logging
felixdivo Jul 20, 2018
f2fff00
added simple _ensure_fsync method to logformats_test
felixdivo Jul 20, 2018
c86fed3
removed unused imports
felixdivo Jul 20, 2018
796fb8d
use more lightwight SimpleQueue in BufferedReader
felixdivo Jul 20, 2018
1975d52
add a stop() method to BufferedReader
felixdivo Jul 20, 2018
bd06089
various SqliteWriter improvements
felixdivo Jul 20, 2018
ea73445
corrected import for queues in listener.py
felixdivo Jul 20, 2018
9e38a95
bugfix in BufferedReader
felixdivo Jul 20, 2018
a3001dc
added cleanups for listener tests
felixdivo Jul 20, 2018
f11e165
added superclasses for Logger and LogReader
felixdivo Jul 20, 2018
3c87b3e
doc improvements
felixdivo Jul 20, 2018
f53aa0a
updated history
felixdivo Jul 20, 2018
a62c917
fix for listener test
felixdivo Jul 20, 2018
118a455
add tests for SqliteReader.read_all()
felixdivo Jul 20, 2018
fe638e5
fix SqliteReader.read_all() and fix usage of memoryview
felixdivo Jul 20, 2018
5a75ef7
fix test_read_all
felixdivo Jul 20, 2018
87ddb61
logging adjustments
felixdivo Jul 20, 2018
0866657
fix typo
felixdivo Jul 28, 2018
74b59cf
fix logger_test.py
felixdivo Jul 29, 2018
31734d0
use RuntimeError on read attempt on a stopped BufferedReader
felixdivo Jul 30, 2018
338449f
Merge branch 'develop' into fix-file-io
felixdivo Jul 30, 2018
bf91364
add writable tempdir to appveyor tests
felixdivo Jul 30, 2018
5449ac4
use memoryview = buffer in Sqlite in Python 2
felixdivo Jul 30, 2018
517ca2a
Merge branch 'develop' into fix-file-io
felixdivo Jul 30, 2018
3beb5b5
fix Message's __eq__() and __ne__()
felixdivo Jul 31, 2018
201bf22
fix a bug in Python 3.7
felixdivo Jul 31, 2018
39eaa66
new attempt at making the TEMPDIR writable on AppVeyor
felixdivo Jul 31, 2018
53c7ec9
create test folder in AppVeyor
felixdivo Jul 31, 2018
3bf5a6a
also give permissions to future temp files & clean up afterwards
felixdivo Jul 31, 2018
767838b
fix #379
felixdivo Jul 31, 2018
2c81ec6
Merge branch 'develop' into fix-file-io
felixdivo Jul 31, 2018
34a640b
fix wrong timestamps problem in tests
felixdivo Aug 1, 2018
fa771a8
Merge branch 'fix-file-io' of github.com:hardbyte/python-can into fix…
felixdivo Aug 1, 2018
d1b0503
fix apparent permissions problem on windows
felixdivo Aug 1, 2018
7be909d
add removal of temporyry file in listener_test.py
felixdivo Aug 1, 2018
23a9d9a
removed debugging code from .appveyor.yml
felixdivo Aug 1, 2018
a839a0a
only delete some temp files
felixdivo Aug 1, 2018
a161684
Merge branch 'develop' into fix-file-io
felixdivo Aug 6, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# https://github.com/github/gitignore/blob/da00310ccba9de9a988cc973ef5238ad2c1460e9/Python.gitignore
test/__tempdir__/
.pytest_cache/

# -------------------------
# below: https://github.com/github/gitignore/blob/da00310ccba9de9a988cc973ef5238ad2c1460e9/Python.gitignore

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
2 changes: 1 addition & 1 deletion can/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@
from .canutils import CanutilsLogReader, CanutilsLogWriter
from .csv import CSVWriter, CSVReader
from .sqlite import SqliteReader, SqliteWriter
from .stdout import Printer
from .printer import Printer
94 changes: 52 additions & 42 deletions can/io/asc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"""
Contains handling of ASC logging files.

Example .asc file: https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default
Example .asc files:
- https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default
- under `test/data/logfile.asc`
"""

from __future__ import absolute_import
Expand All @@ -16,22 +18,28 @@
from ..message import Message
from ..listener import Listener
from ..util import channel2int
from .generic import BaseIOHandler

CAN_MSG_EXT = 0x80000000
CAN_ID_MASK = 0x1FFFFFFF

logger = logging.getLogger('can.io.asc')


class ASCReader(object):
class ASCReader(BaseIOHandler):
"""
Iterator of CAN messages from a ASC logging file.

TODO: turn relative timestamps back to absolute form
"""

def __init__(self, filename):
self.file = open(filename, 'r')
def __init__(self, file):
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in text
read mode, not binary read mode.
"""
super(ASCReader, self).__init__(file, mode='r')

@staticmethod
def _extract_can_id(str_can_id):
Expand All @@ -41,19 +49,19 @@ def _extract_can_id(str_can_id):
else:
is_extended = False
can_id = int(str_can_id, 16)
logging.debug('ASCReader: _extract_can_id("%s") -> %x, %r', str_can_id, can_id, is_extended)
return (can_id, is_extended)
#logging.debug('ASCReader: _extract_can_id("%s") -> %x, %r', str_can_id, can_id, is_extended)
return can_id, is_extended

def __iter__(self):
for line in self.file:
logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0])
#logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0])

temp = line.strip()
if not temp or not temp[0].isdigit():
continue

try:
(timestamp, channel, dummy) = temp.split(None, 2) # , frameType, dlc, frameData
timestamp, channel, dummy = temp.split(None, 2) # , frameType, dlc, frameData
except ValueError:
# we parsed an empty comment
continue
Expand All @@ -74,8 +82,8 @@ def __iter__(self):
pass

elif dummy[-1:].lower() == 'r':
(can_id_str, _) = dummy.split(None, 1)
(can_id_num, is_extended_id) = self._extract_can_id(can_id_str)
can_id_str, _ = dummy.split(None, 1)
can_id_num, is_extended_id = self._extract_can_id(can_id_str)
msg = Message(timestamp=timestamp,
arbitration_id=can_id_num & CAN_ID_MASK,
extended_id=is_extended_id,
Expand All @@ -86,10 +94,10 @@ def __iter__(self):
else:
try:
# this only works if dlc > 0 and thus data is availabe
(can_id_str, _, _, dlc, data) = dummy.split(None, 4)
can_id_str, _, _, dlc, data = dummy.split(None, 4)
except ValueError:
# but if not, we only want to get the stuff up to the dlc
(can_id_str, _, _, dlc ) = dummy.split(None, 3)
can_id_str, _, _, dlc = dummy.split(None, 3)
# and we set data to an empty sequence manually
data = ''

Expand All @@ -99,24 +107,26 @@ def __iter__(self):
for byte in data[0:dlc]:
frame.append(int(byte, 16))

(can_id_num, is_extended_id) = self._extract_can_id(can_id_str)
can_id_num, is_extended_id = self._extract_can_id(can_id_str)

msg = Message(
timestamp=timestamp,
arbitration_id=can_id_num & CAN_ID_MASK,
extended_id=is_extended_id,
is_remote_frame=False,
dlc=dlc,
data=frame,
channel=channel)
yield msg
yield Message(
timestamp=timestamp,
arbitration_id=can_id_num & CAN_ID_MASK,
extended_id=is_extended_id,
is_remote_frame=False,
dlc=dlc,
data=frame,
channel=channel
)

self.stop()


class ASCWriter(Listener):
class ASCWriter(BaseIOHandler, Listener):
"""Logs CAN data to an ASCII log file (.asc).

The measurement starts with the timestamp of the first registered message.
If a message has a timestamp smaller than the previous one (or 0 or None),
If a message has a timestamp smaller than the previous one or None,
it gets assigned the timestamp that was written for the last message.
It the first message does not have a timestamp, it is set to zero.
"""
Expand All @@ -125,27 +135,32 @@ class ASCWriter(Listener):
FORMAT_DATE = "%a %b %m %I:%M:%S %p %Y"
FORMAT_EVENT = "{timestamp: 9.4f} {message}\n"

def __init__(self, filename, channel=1):
# setup
def __init__(self, file, channel=1):
"""
:param file: a path-like object or as file-like object to write to
If this is a file-like object, is has to opened in text
write mode, not binary write mode.
:param channel: a default channel to use when the message does not
have a channel set
"""
super(ASCWriter, self).__init__(file, mode='w')
self.channel = channel
self.log_file = open(filename, 'w')

# write start of file header
now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y")
self.log_file.write("date %s\n" % now)
self.log_file.write("base hex timestamps absolute\n")
self.log_file.write("internal events logged\n")
self.file.write("date %s\n" % now)
self.file.write("base hex timestamps absolute\n")
self.file.write("internal events logged\n")

# the last part is written with the timestamp of the first message
self.header_written = False
self.last_timestamp = None
self.started = None

def stop(self):
"""Stops logging and closes the file."""
if not self.log_file.closed:
self.log_file.write("End TriggerBlock\n")
self.log_file.close()
if not self.file.closed:
self.file.write("End TriggerBlock\n")
super(ASCWriter, self).stop()

def log_event(self, message, timestamp=None):
"""Add a message to the log file.
Expand All @@ -163,10 +178,9 @@ def log_event(self, message, timestamp=None):
self.last_timestamp = (timestamp or 0.0)
self.started = self.last_timestamp
formatted_date = time.strftime(self.FORMAT_DATE, time.localtime(self.last_timestamp))
self.log_file.write("base hex timestamps absolute\n")
self.log_file.write("Begin Triggerblock %s\n" % formatted_date)
self.file.write("Begin Triggerblock %s\n" % formatted_date)
self.header_written = True
self.log_event("Start of measurement") # recursive call
self.log_event("Start of measurement") # caution: this is a recursive call!

# figure out the correct timestamp
if timestamp is None or timestamp < self.last_timestamp:
Expand All @@ -177,11 +191,7 @@ def log_event(self, message, timestamp=None):
timestamp -= self.started

line = self.FORMAT_EVENT.format(timestamp=timestamp, message=message)

if self.log_file.closed:
logger.warn("ASCWriter: ignoring write call to closed file")
else:
self.log_file.write(line)
self.file.write(line)

def on_message_received(self, msg):

Expand Down
61 changes: 35 additions & 26 deletions can/io/blf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
objects types.
"""

from __future__ import absolute_import

import struct
import zlib
import datetime
Expand All @@ -24,6 +26,7 @@
from can.message import Message
from can.listener import Listener
from can.util import len2dlc, dlc2len, channel2int
from .generic import BaseIOHandler


class BLFParseError(Exception):
Expand Down Expand Up @@ -112,19 +115,23 @@ def systemtime_to_timestamp(systemtime):
return 0


class BLFReader(object):
class BLFReader(BaseIOHandler):
"""
Iterator of CAN messages from a Binary Logging File.

Only CAN messages and error frames are supported. Other object types are
silently ignored.
"""

def __init__(self, filename):
self.fp = open(filename, "rb")
data = self.fp.read(FILE_HEADER_STRUCT.size)
def __init__(self, file):
"""
:param file: a path-like object or as file-like object to read from
If this is a file-like object, is has to opened in binary
read mode, not text read mode.
"""
super(BLFReader, self).__init__(file, mode='rb')
data = self.file.read(FILE_HEADER_STRUCT.size)
header = FILE_HEADER_STRUCT.unpack(data)
#print(header)
if header[0] != b"LOGG":
raise BLFParseError("Unexpected file format")
self.file_size = header[10]
Expand All @@ -133,25 +140,24 @@ def __init__(self, filename):
self.start_timestamp = systemtime_to_timestamp(header[14:22])
self.stop_timestamp = systemtime_to_timestamp(header[22:30])
# Read rest of header
self.fp.read(header[1] - FILE_HEADER_STRUCT.size)
self.file.read(header[1] - FILE_HEADER_STRUCT.size)

def __iter__(self):
tail = b""
while True:
data = self.fp.read(OBJ_HEADER_BASE_STRUCT.size)
data = self.file.read(OBJ_HEADER_BASE_STRUCT.size)
if not data:
# EOF
break

header = OBJ_HEADER_BASE_STRUCT.unpack(data)
#print(header)
if header[0] != b"LOBJ":
raise BLFParseError()
obj_type = header[4]
obj_data_size = header[3] - OBJ_HEADER_BASE_STRUCT.size
obj_data = self.fp.read(obj_data_size)
obj_data = self.file.read(obj_data_size)
# Read padding bytes
self.fp.read(obj_data_size % 4)
self.file.read(obj_data_size % 4)

if obj_type == LOG_CONTAINER:
method, uncompressed_size = LOG_CONTAINER_STRUCT.unpack_from(
Expand Down Expand Up @@ -245,13 +251,13 @@ def __iter__(self):

pos = next_pos

# Save remaing data that could not be processed
# save the remaining data that could not be processed
tail = data[pos:]

self.fp.close()
self.stop()


class BLFWriter(Listener):
class BLFWriter(BaseIOHandler, Listener):
"""
Logs CAN data to a Binary Logging File compatible with Vector's tools.
"""
Expand All @@ -262,11 +268,16 @@ class BLFWriter(Listener):
#: ZLIB compression level
COMPRESSION_LEVEL = 9

def __init__(self, filename, channel=1):
self.fp = open(filename, "wb")
def __init__(self, file, channel=1):
"""
:param file: a path-like object or as file-like object to write to
If this is a file-like object, is has to opened in binary
write mode, not text write mode.
"""
super(BLFWriter, self).__init__(file, mode='wb')
self.channel = channel
# Header will be written after log is done
self.fp.write(b"\x00" * FILE_HEADER_SIZE)
self.file.write(b"\x00" * FILE_HEADER_SIZE)
self.cache = []
self.cache_size = 0
self.count_of_objects = 0
Expand Down Expand Up @@ -360,7 +371,7 @@ def _add_object(self, obj_type, data, timestamp=None):

def _flush(self):
"""Compresses and writes data in the cache to file."""
if self.fp.closed:
if self.file.closed:
return
cache = b"".join(self.cache)
if not cache:
Expand All @@ -379,21 +390,19 @@ def _flush(self):
b"LOBJ", OBJ_HEADER_BASE_STRUCT.size, 1, obj_size, LOG_CONTAINER)
container_header = LOG_CONTAINER_STRUCT.pack(
ZLIB_DEFLATE, len(uncompressed_data))
self.fp.write(base_header)
self.fp.write(container_header)
self.fp.write(compressed_data)
self.file.write(base_header)
self.file.write(container_header)
self.file.write(compressed_data)
# Write padding bytes
self.fp.write(b"\x00" * (obj_size % 4))
self.file.write(b"\x00" * (obj_size % 4))
self.uncompressed_size += OBJ_HEADER_V1_STRUCT.size + LOG_CONTAINER_STRUCT.size
self.uncompressed_size += len(uncompressed_data)

def stop(self):
"""Stops logging and closes the file."""
if self.fp.closed:
return
self._flush()
filesize = self.fp.tell()
self.fp.close()
filesize = self.file.tell()
super(BLFWriter, self).stop()

# Write header in the beginning of the file
header = [b"LOGG", FILE_HEADER_SIZE,
Expand All @@ -403,5 +412,5 @@ def stop(self):
self.count_of_objects, 0])
header.extend(timestamp_to_systemtime(self.start_timestamp))
header.extend(timestamp_to_systemtime(self.stop_timestamp))
with open(self.fp.name, "r+b") as f:
with open(self.file.name, "r+b") as f:
f.write(FILE_HEADER_STRUCT.pack(*header))
Loading
0