88from __future__ import print_function
99
1010import logging
11+ from datetime import datetime
12+ import time
13+ import base64
14+ import sqlite3
1115
1216try :
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
4655class 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+
72109class 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
123164class 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 )
0 commit comments