66 - under `test/data/logfile.asc`
77"""
88
9- from typing import cast , Any , Generator , IO , List , Optional , Tuple , Union
9+ from typing import cast , Any , Generator , IO , List , Optional , Union , Dict
1010from can import typechecking
1111
1212from datetime import datetime
@@ -53,122 +53,165 @@ def __init__(
5353 if not self .file :
5454 raise ValueError ("The given file cannot be None" )
5555 self .base = base
56+ self ._converted_base = self ._check_base (base )
57+ self .date = None
58+ self .timestamps_format = None
59+ self .internal_events_logged = None
5660
57- @staticmethod
58- def _extract_can_id (str_can_id : str , base : int ) -> Tuple [int , bool ]:
61+ def _extract_header (self ):
62+ for line in self .file :
63+ line = line .strip ()
64+ lower_case = line .lower ()
65+ if lower_case .startswith ("date" ):
66+ self .date = line [5 :]
67+ elif lower_case .startswith ("base" ):
68+ try :
69+ _ , base , _ , timestamp_format = line .split ()
70+ except ValueError :
71+ raise Exception ("Unsupported header string format: {}" .format (line ))
72+ self .base = base
73+ self ._converted_base = self ._check_base (self .base )
74+ self .timestamps_format = timestamp_format
75+ elif lower_case .endswith ("internal events logged" ):
76+ self .internal_events_logged = not lower_case .startswith ("no" )
77+ # Currently the last line in the header which is parsed
78+ break
79+ else :
80+ break
81+
82+ def _extract_can_id (self , str_can_id : str , msg_kwargs : Dict [str , Any ]) -> None :
5983 if str_can_id [- 1 :].lower () == "x" :
60- is_extended = True
61- can_id = int (str_can_id [0 :- 1 ], base )
84+ msg_kwargs [ "is_extended_id" ] = True
85+ can_id = int (str_can_id [0 :- 1 ], self . _converted_base )
6286 else :
63- is_extended = False
64- can_id = int (str_can_id , base )
65- return can_id , is_extended
87+ msg_kwargs [ "is_extended_id" ] = False
88+ can_id = int (str_can_id , self . _converted_base )
89+ msg_kwargs [ "arbitration_id" ] = can_id
6690
6791 @staticmethod
6892 def _check_base (base : str ) -> int :
6993 if base not in ["hex" , "dec" ]:
7094 raise ValueError ('base should be either "hex" or "dec"' )
7195 return BASE_DEC if base == "dec" else BASE_HEX
7296
97+ def _process_data_string (
98+ self , data_str : str , data_length : int , msg_kwargs : Dict [str , Any ]
99+ ) -> None :
100+ frame = bytearray ()
101+ data = data_str .split ()
102+ for byte in data [:data_length ]:
103+ frame .append (int (byte , self ._converted_base ))
104+ msg_kwargs ["data" ] = frame
105+
106+ def _process_classic_can_frame (
107+ self , line : str , msg_kwargs : Dict [str , Any ]
108+ ) -> Message :
109+
110+ # CAN error frame
111+ if line .strip ()[0 :10 ].lower () == "errorframe" :
112+ # Error Frame
113+ msg_kwargs ["is_error_frame" ] = True
114+ else :
115+ abr_id_str , dir , rest_of_message = line .split (None , 2 )
116+ msg_kwargs ["is_rx" ] = dir == "Rx"
117+ self ._extract_can_id (abr_id_str , msg_kwargs )
118+
119+ if rest_of_message [0 ].lower () == "r" :
120+ # CAN Remote Frame
121+ msg_kwargs ["is_remote_frame" ] = True
122+ remote_data = rest_of_message .split ()
123+ if len (remote_data ) > 1 :
124+ dlc_str = remote_data [1 ]
125+ if dlc_str .isdigit ():
126+ msg_kwargs ["dlc" ] = int (dlc_str , self ._converted_base )
127+ else :
128+ # Classic CAN Message
129+ try :
130+ # There is data after DLC
131+ _ , dlc_str , data = rest_of_message .split (None , 2 )
132+ except ValueError :
133+ # No data after DLC
134+ _ , dlc_str = rest_of_message .split (None , 1 )
135+ data = ""
136+
137+ dlc = int (dlc_str , self ._converted_base )
138+ msg_kwargs ["dlc" ] = dlc
139+ self ._process_data_string (data , dlc , msg_kwargs )
140+
141+ return Message (** msg_kwargs )
142+
143+ def _process_fd_can_frame (self , line : str , msg_kwargs : Dict [str , Any ]) -> Message :
144+ channel , dir , rest_of_message = line .split (None , 2 )
145+ # See ASCWriter
146+ msg_kwargs ["channel" ] = int (channel ) - 1
147+ msg_kwargs ["is_rx" ] = dir == "Rx"
148+
149+ # CAN FD error frame
150+ if rest_of_message .strip ()[:10 ].lower () == "errorframe" :
151+ # Error Frame
152+ # TODO: maybe use regex to parse BRS, ESI, etc?
153+ msg_kwargs ["is_error_frame" ] = True
154+ else :
155+ can_id_str , frame_name_or_brs , rest_of_message = rest_of_message .split (
156+ None , 2
157+ )
158+
159+ if frame_name_or_brs .isdigit ():
160+ brs = frame_name_or_brs
161+ esi , dlc_str , data_length_str , data = rest_of_message .split (None , 3 )
162+ else :
163+ brs , esi , dlc_str , data_length_str , data = rest_of_message .split (
164+ None , 4
165+ )
166+
167+ self ._extract_can_id (can_id_str , msg_kwargs )
168+ msg_kwargs ["bitrate_switch" ] = brs == "1"
169+ msg_kwargs ["error_state_indicator" ] = esi == "1"
170+ dlc = int (dlc_str , self ._converted_base )
171+ msg_kwargs ["dlc" ] = dlc
172+ data_length = int (data_length_str )
173+
174+ # CAN remote Frame
175+ msg_kwargs ["is_remote_frame" ] = data_length == 0
176+
177+ self ._process_data_string (data , data_length , msg_kwargs )
178+
179+ return Message (** msg_kwargs )
180+
73181 def __iter__ (self ) -> Generator [Message , None , None ]:
74- base = self ._check_base (self .base )
75182 # This is guaranteed to not be None since we raise ValueError in __init__
76183 self .file = cast (IO [Any ], self .file )
77- for line in self .file :
78- # logger.debug("ASCReader: parsing line: '%s'", line.splitlines()[0])
79- if line .split (" " )[0 ] == "base" :
80- base = self ._check_base (line .split (" " )[1 ])
184+ self ._extract_header ()
81185
186+ for line in self .file :
82187 temp = line .strip ()
83188 if not temp or not temp [0 ].isdigit ():
189+ # Could be a comment
84190 continue
85- is_fd = False
86- is_rx = True
191+ msg_kwargs = {}
87192 try :
88- timestamp , channel , dummy = temp .split (
89- None , 2
90- ) # , frameType, dlc, frameData
193+ timestamp , channel , rest_of_message = temp .split (None , 2 )
194+ timestamp = float ( timestamp )
195+ msg_kwargs [ "timestamp" ] = timestamp
91196 if channel == "CANFD" :
92- timestamp , _ , channel , direction , dummy = temp .split (None , 4 )
93- is_fd = True
94- is_rx = direction == "Rx"
197+ msg_kwargs ["is_fd" ] = True
198+ elif channel .isdigit ():
199+ # See ASCWriter
200+ msg_kwargs ["channel" ] = int (channel ) - 1
201+ else :
202+ # Not a CAN message. Possible values include "statistic", J1939TP
203+ continue
95204 except ValueError :
96- # we parsed an empty comment
205+ # Some other unprocessed or unknown format
97206 continue
98- timestamp = float (timestamp )
99- try :
100- # See ASCWriter
101- channel = int (channel ) - 1
102- except ValueError :
103- pass
104- if dummy .strip ()[0 :10 ].lower () == "errorframe" :
105- msg = Message (timestamp = timestamp , is_error_frame = True , channel = channel )
106- yield msg
107- elif (
108- not isinstance (channel , int )
109- or dummy .strip ()[0 :10 ].lower () == "statistic:"
110- or dummy .split (None , 1 )[0 ] == "J1939TP"
111- ):
112- pass
113- elif dummy [- 1 :].lower () == "r" :
114- can_id_str , direction , _ = dummy .split (None , 2 )
115- can_id_num , is_extended_id = self ._extract_can_id (can_id_str , base )
116- msg = Message (
117- timestamp = timestamp ,
118- arbitration_id = can_id_num & CAN_ID_MASK ,
119- is_extended_id = is_extended_id ,
120- is_remote_frame = True ,
121- is_rx = direction == "Rx" ,
122- channel = channel ,
123- )
124- yield msg
207+
208+ if "is_fd" not in msg_kwargs :
209+ msg = self ._process_classic_can_frame (rest_of_message , msg_kwargs )
125210 else :
126- brs = None
127- esi = None
128- data_length = 0
129- try :
130- # this only works if dlc > 0 and thus data is available
131- if not is_fd :
132- can_id_str , direction , _ , dlc , data = dummy .split (None , 4 )
133- is_rx = direction == "Rx"
134- else :
135- can_id_str , frame_name , brs , esi , dlc , data_length , data = dummy .split (
136- None , 6
137- )
138- if frame_name .isdigit ():
139- # Empty frame_name
140- can_id_str , brs , esi , dlc , data_length , data = dummy .split (
141- None , 5
142- )
143- except ValueError :
144- # but if not, we only want to get the stuff up to the dlc
145- can_id_str , _ , _ , dlc = dummy .split (None , 3 )
146- # and we set data to an empty sequence manually
147- data = ""
148- dlc = int (dlc , base )
149- if is_fd :
150- # For fd frames, dlc and data length might not be equal and
151- # data_length is the actual size of the data
152- dlc = int (data_length )
153- frame = bytearray ()
154- data = data .split ()
155- for byte in data [0 :dlc ]:
156- frame .append (int (byte , base ))
157- can_id_num , is_extended_id = self ._extract_can_id (can_id_str , base )
158-
159- yield Message (
160- timestamp = timestamp ,
161- arbitration_id = can_id_num & CAN_ID_MASK ,
162- is_extended_id = is_extended_id ,
163- is_remote_frame = False ,
164- dlc = dlc ,
165- data = frame ,
166- is_fd = is_fd ,
167- is_rx = is_rx ,
168- channel = channel ,
169- bitrate_switch = is_fd and brs == "1" ,
170- error_state_indicator = is_fd and esi == "1" ,
171- )
211+ msg = self ._process_fd_can_frame (rest_of_message , msg_kwargs )
212+ if msg is not None :
213+ yield msg
214+
172215 self .stop ()
173216
174217
@@ -190,7 +233,7 @@ class ASCWriter(BaseIOHandler, Listener):
190233 "{id:>8} {symbolic_name:>32}" ,
191234 "{brs}" ,
192235 "{esi}" ,
193- "{dlc}" ,
236+ "{dlc:x }" ,
194237 "{data_length:>2}" ,
195238 "{data}" ,
196239 "{message_duration:>8}" ,
@@ -281,10 +324,10 @@ def on_message_received(self, msg: Message) -> None:
281324 self .log_event ("{} ErrorFrame" .format (self .channel ), msg .timestamp )
282325 return
283326 if msg .is_remote_frame :
284- dtype = "r"
327+ dtype = "r {:x}" . format ( msg . dlc ) # New after v8.5
285328 data : List [str ] = []
286329 else :
287- dtype = "d {}" .format (msg .dlc )
330+ dtype = "d {:x }" .format (msg .dlc )
288331 data = ["{:02X}" .format (byte ) for byte in msg .data ]
289332 arb_id = "{:X}" .format (msg .arbitration_id )
290333 if msg .is_extended_id :
0 commit comments