Skip to content

Commit 4699845

Browse files
committed
Merge virtual-interface into default.
Updated tests so that by default use virtual channel for network_test
2 parents f0b1ac4 + a23c32c commit 4699845

30 files changed

+454
-212
lines changed

bin/can_logger.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
parser = argparse.ArgumentParser(description="Log CAN traffic, printing messages to stdout or to a given file")
2828

2929
parser.add_argument("-f", "--file_name", dest="log_file",
30-
help="""Path and base log filename, extension can be .txt, .csv, .db, .npz""",
30+
help="""Path and base log filename, extension can be .txt, .asc, .csv, .db, .npz""",
3131
default=None)
3232

3333
parser.add_argument("-v", action="count", dest="verbosity",
@@ -70,10 +70,12 @@
7070

7171
bus = can.interface.Bus(results.channel, bustype=results.interface, can_filters=can_filters)
7272
print('Can Logger (Started on {})\n'.format(datetime.datetime.now()))
73-
notifier = can.Notifier(bus, [can.Printer(results.log_file)], timeout=0.1)
73+
logger = can.Logger(results.log_file)
74+
notifier = can.Notifier(bus, [logger], timeout=0.1)
7475

7576
try:
7677
while True:
7778
time.sleep(1)
7879
except KeyboardInterrupt:
7980
bus.shutdown()
81+
notifier.stop()

can/CAN.py

Lines changed: 149 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
from __future__ import print_function
99

1010
import logging
11+
from datetime import datetime
12+
import time
13+
import base64
14+
import sqlite3
1115

1216
try:
1317
import queue
@@ -42,6 +46,11 @@ def on_message_received(self, msg):
4246
def __call__(self, msg):
4347
return self.on_message_received(msg)
4448

49+
def stop(self):
50+
"""
51+
Override to cleanup any open resources.
52+
"""
53+
4554

4655
class BufferedReader(Listener):
4756
"""
@@ -60,15 +69,43 @@ def on_message_received(self, msg):
6069
def get_message(self, timeout=0.5):
6170
"""
6271
Attempts to retrieve the latest message received by the instance. If no message is
63-
available it blocks for 0.5 seconds or until a message is received (whichever
64-
is shorter), and returns the message if there is one, or None if there is not.
72+
available it blocks for given timeout or until a message is received (whichever
73+
is shorter),
74+
75+
:param float timeout: The number of seconds to wait for a new message.
76+
:return: the :class:`~can.Message` if there is one, or None if there is not.
6577
"""
6678
try:
6779
return self.buffer.get(block=True, timeout=timeout)
6880
except queue.Empty:
6981
return None
7082

7183

84+
class Logger(object):
85+
"""
86+
Logs CAN messages to a file.
87+
88+
The format is determined from the file format which can be one of:
89+
* .asc: :class:`can.ASCWriter`
90+
* .csv: :class:`can.CSVWriter`
91+
* .db: :class:`can.SqliteWriter`
92+
* other: :class:`can.Printer`
93+
"""
94+
95+
@classmethod
96+
def __new__(cls, other, filename):
97+
if not filename:
98+
return Printer()
99+
elif filename.endswith(".asc"):
100+
return ASCWriter(filename)
101+
elif filename.endswith(".csv"):
102+
return CSVWriter(filename)
103+
elif filename.endswith(".db"):
104+
return SqliteWriter(filename)
105+
else:
106+
return Printer(filename)
107+
108+
72109
class Printer(Listener):
73110
"""
74111
The Printer class is a subclass of :class:`~can.Listener` which simply prints
@@ -89,9 +126,9 @@ def on_message_received(self, msg):
89126
else:
90127
print(msg)
91128

92-
def __del__(self):
93-
self.output_file.write("\n")
129+
def stop(self):
94130
if self.output_file:
131+
self.output_file.write("\n")
95132
self.output_file.close()
96133

97134

@@ -105,30 +142,125 @@ def __init__(self, filename):
105142
self.csv_file = open(filename, 'wt')
106143

107144
# Write a header row
108-
self.csv_file.write("timestamp, arbitrationid, flags, dlc, data")
145+
self.csv_file.write("timestamp, arbitration id, extended, remote, error, dlc, data\n")
109146

110147
def on_message_received(self, msg):
111-
row = ','.join([msg.timestamp,
112-
msg.arbitration_id,
113-
msg.flags,
114-
msg.dlc,
115-
msg.data])
148+
row = ','.join([
149+
str(msg.timestamp),
150+
hex(msg.arbitration_id),
151+
'1' if msg.id_type else '0',
152+
'1' if msg.is_remote_frame else '0',
153+
'1' if msg.is_error_frame else '0',
154+
str(msg.dlc),
155+
base64.b64encode(msg.data).decode('utf8')
156+
])
116157
self.csv_file.write(row + '\n')
117158

118-
def __del__(self):
159+
def stop(self):
160+
self.csv_file.flush()
119161
self.csv_file.close()
120-
super(CSVWriter, self).__del__()
121162

122163

123164
class SqliteWriter(Listener):
124-
"""TODO"""
165+
"""Logs received CAN data to a simple SQL database.
166+
167+
The sqlite database may already exist, otherwise it will
168+
be created when the first message arrives.
169+
"""
170+
171+
insert_msg_template = '''
172+
INSERT INTO messages VALUES
173+
(?, ?, ?, ?, ?, ?, ?)
174+
'''
125175

126176
def __init__(self, filename):
127-
self.db_file = open(filename, 'wt')
177+
self.db_fn = filename
178+
self.db_setup = False
179+
180+
def _create_db(self):
181+
# Note you can't share sqlite3 connections between threads
182+
# hence we setup the db here.
183+
log.info("Creating sqlite db")
184+
self.conn = sqlite3.connect(self.db_fn)
185+
c = self.conn.cursor()
128186

129187
# create table structure
130-
raise NotImplementedError("TODO")
188+
c.execute('''
189+
CREATE TABLE IF NOT EXISTS messages
190+
(
191+
ts REAL,
192+
arbitration_id INTEGER,
193+
extended INTEGER,
194+
remote INTEGER,
195+
error INTEGER,
196+
dlc INTEGER,
197+
data BLOB
198+
)
199+
''')
200+
self.conn.commit()
201+
202+
self.db_setup = True
203+
204+
def on_message_received(self, msg):
205+
if not self.db_setup:
206+
self._create_db()
207+
208+
# add row to db
209+
row_data = (
210+
msg.timestamp,
211+
msg.arbitration_id,
212+
msg.id_type,
213+
msg.is_remote_frame,
214+
msg.is_error_frame,
215+
msg.dlc,
216+
msg.data
217+
)
218+
c = self.conn.cursor()
219+
c.execute(SqliteWriter.insert_msg_template, row_data)
220+
221+
self.conn.commit()
222+
223+
def stop(self):
224+
if self.db_setup:
225+
self.conn.commit()
226+
self.conn.close()
227+
228+
229+
class ASCWriter(Listener):
230+
"""Logs CAN data to an ASCII log file (.asc)"""
231+
232+
LOG_STRING = "{time: 9.4f} {channel} {id:<15} Rx d {dlc} {data}\n"
233+
234+
def __init__(self, filename):
235+
now = datetime.now().strftime("%a %b %m %I:%M:%S %p %Y")
236+
self.start = time.time()
237+
self.log_file = open(filename, "w")
238+
self.log_file.write("date %s\n" % now)
239+
self.log_file.write("base hex timestamps absolute\n")
240+
self.log_file.write("internal events logged\n")
241+
self.log_file.write("Begin Triggerblock %s\n" % now)
242+
self.log_file.write(" 0.0000 Start of measurement\n")
243+
244+
def stop(self):
245+
"""Stops logging and closes the file."""
246+
if self.log_file:
247+
self.log_file.write("End TriggerBlock\n")
248+
self.log_file.close()
249+
self.log_file = None
131250

132251
def on_message_received(self, msg):
133-
# add row
134-
raise NotImplementedError("TODO")
252+
data = ["{:02X}".format(byte) for byte in msg.data]
253+
arb_id = "{:X}".format(msg.arbitration_id)
254+
if msg.id_type:
255+
arb_id = arb_id + "x"
256+
timestamp = msg.timestamp
257+
if timestamp >= self.start:
258+
timestamp -= self.start
259+
260+
line = self.LOG_STRING.format(time=timestamp,
261+
channel=1,
262+
id=arb_id,
263+
dlc=msg.dlc,
264+
data=" ".join(data))
265+
if self.log_file:
266+
self.log_file.write(line)

can/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
class CanError(IOError):
1717
pass
1818

19-
from can.CAN import BufferedReader, Listener, Printer, CSVWriter, SqliteWriter, set_logging_level
19+
from can.CAN import BufferedReader, Listener, Logger, Printer, CSVWriter, SqliteWriter, ASCWriter, set_logging_level
2020
from can.message import Message
2121
from can.bus import BusABC
2222
from can.notifier import Notifier

can/bus.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ def __init__(self, channel=None, can_filters=None, **config):
3333
3434
>>> [{"can_id": 0x11, "can_mask": 0x21}]
3535
36+
A filter matches, when ``<received_can_id> & can_mask == can_id & can_mask``
37+
3638
:param dict config:
3739
Any backend dependent configurations are passed in this dictionary
3840
"""
@@ -80,6 +82,10 @@ def flush_tx_buffer(self):
8082
pass
8183

8284
def shutdown(self):
85+
"""
86+
Called to carry out any interface specific cleanup required
87+
in shutting down a bus.
88+
"""
8389
self.flush_tx_buffer()
8490

8591
__metaclass__ = abc.ABCMeta

can/ctypesutil.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ def map_symbol(self, func_name, restype=None, argtypes=(), errcheck=None):
4646
return symbol
4747

4848

49-
class CLibrary_Win32(ctypes.WinDLL, LibraryMixin):
49+
try:
50+
_LibBase = ctypes.WinDLL
51+
except AttributeError:
52+
_LibBase = ctypes.CDLL
53+
54+
class CLibrary_Win32(_LibBase, LibraryMixin):
5055
" Basic ctypes.WinDLL derived class + LibraryMixin "
5156

5257
def __init__(self, library_or_path):

can/interfaces/interface.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import can
2-
from can.util import load_config, choose_socketcan_implementation
32
from can.broadcastmanager import CyclicSendTaskABC, MultiRateCyclicSendTaskABC
3+
from can.util import load_config, choose_socketcan_implementation
44

55
VALID_INTERFACES = set(['kvaser', 'serial', 'pcan', 'socketcan_native',
66
'socketcan_ctypes', 'socketcan', 'usb2can', 'ixxat',
@@ -11,6 +11,9 @@ class Bus(object):
1111
"""
1212
Instantiates a CAN Bus of the given `bustype`, falls back to reading a
1313
configuration file from default locations.
14+
15+
:raises: NotImplementedError if the bustype isn't recognized
16+
1417
"""
1518

1619
@classmethod
@@ -43,7 +46,7 @@ def __new__(cls, other, channel=None, *args, **kwargs):
4346
from can.interfaces.socketcan_native import SocketscanNative_Bus
4447
cls = SocketscanNative_Bus
4548
elif interface == 'serial':
46-
from can.interfaces.serial_can import SerialBus
49+
from can.interfaces.serial.serial_can import SerialBus
4750
cls = SerialBus
4851
elif interface == 'pcan':
4952
from can.interfaces.pcan import PcanBus

can/interfaces/ixxat/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"""
33
Ctypes wrapper module for IXXAT Virtual CAN Interface V3 on win32 systems
44
Copyright (C) 2016 Giuseppe Corbelli <[email protected]>
5-
"""
6-
7-
from can.interfaces.ixxat.canlib import *
5+
"""
6+
7+
from can.interfaces.ixxat.canlib import IXXATBus as Bus

can/interfaces/ixxat/canlib.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ def __check_status(result, function, arguments):
141141

142142

143143
class IXXATBus(BusABC):
144-
" The CAN Bus implemented for the IXXAT interface. "
144+
"""The CAN Bus implemented for the IXXAT interface.
145+
"""
145146

146147
CHANNEL_BITRATES = {
147148
0: {
@@ -178,12 +179,10 @@ def __init__(self, channel, can_filters=None, **config):
178179
179180
>>> [{"can_id": 0x11, "can_mask": 0x21}]
180181
181-
Backend Configuration
182-
---------------------
183-
184182
:param int UniqueHardwareId:
185183
UniqueHardwareId to connect (optional, will use the first found if not supplied)
186-
:param int bitrate
184+
185+
:param int bitrate:
187186
Channel bitrate in bit/s
188187
"""
189188
if (_canlib is None):
@@ -241,13 +240,13 @@ def __init__(self, channel, can_filters=None, **config):
241240
self.CHANNEL_BITRATES[1][bitrate]
242241
)
243242
_canlib.canControlGetCaps(self._control_handle, ctypes.byref(self._channel_capabilities))
243+
244244
# With receive messages, this field contains the relative reception time of
245245
# the message in ticks. The resolution of a tick can be calculated from the fields
246-
# dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accor-
247-
# dance with the following formula:
248-
# Resolution [s] = dwTscDivisor / dwClockFreq
249-
# Reversed the terms, so that we have the number of ticks in a second
250-
self._tick_resolution = self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor
246+
# dwClockFreq and dwTscDivisor of the structure CANCAPABILITIES in accordance with the following formula:
247+
# frequency [1/s] = dwClockFreq / dwTscDivisor
248+
# We explicitly cast to float for Python 2.x users
249+
self._tick_resolution = float(self._channel_capabilities.dwClockFreq / self._channel_capabilities.dwTscDivisor)
251250

252251
# Setup filters before starting the channel
253252
if can_filters is not None and len(can_filters):

0 commit comments

Comments
 (0)