7
7
Version 1.1 will be implemented as it is most commonly used
8
8
""" # noqa
9
9
10
- from datetime import datetime , timedelta
10
+ from datetime import datetime , timedelta , timezone
11
11
from enum import Enum
12
12
import io
13
13
import os
14
14
import logging
15
- from typing import Generator , Optional , Union , TextIO , Callable , List
15
+ from typing import Generator , Optional , Union , TextIO , Callable , List , Dict
16
16
17
17
from ..message import Message
18
- from ..util import channel2int
18
+ from ..util import channel2int , len2dlc , dlc2len
19
19
from .generic import FileIOMessageWriter , MessageReader
20
20
from ..typechecking import StringPathLike
21
21
@@ -32,6 +32,11 @@ class TRCFileVersion(Enum):
32
32
V2_0 = 200
33
33
V2_1 = 201
34
34
35
+ def __ge__ (self , other ):
36
+ if self .__class__ is other .__class__ :
37
+ return self .value >= other .value
38
+ return NotImplemented
39
+
35
40
36
41
class TRCReader (MessageReader ):
37
42
"""
@@ -51,6 +56,8 @@ def __init__(
51
56
"""
52
57
super ().__init__ (file , mode = "r" )
53
58
self .file_version = TRCFileVersion .UNKNOWN
59
+ self .start_time : Optional [datetime ] = None
60
+ self .columns : Dict [str , int ] = {}
54
61
55
62
if not self .file :
56
63
raise ValueError ("The given file cannot be None" )
@@ -67,17 +74,42 @@ def _extract_header(self):
67
74
file_version = line .split ("=" )[1 ]
68
75
if file_version == "1.1" :
69
76
self .file_version = TRCFileVersion .V1_1
77
+ elif file_version == "2.0" :
78
+ self .file_version = TRCFileVersion .V2_0
70
79
elif file_version == "2.1" :
71
80
self .file_version = TRCFileVersion .V2_1
72
81
else :
73
82
self .file_version = TRCFileVersion .UNKNOWN
74
83
except IndexError :
75
84
logger .debug ("TRCReader: Failed to parse version" )
85
+ elif line .startswith (";$STARTTIME" ):
86
+ logger .debug ("TRCReader: Found start time '%s'" , line )
87
+ try :
88
+ self .start_time = datetime (
89
+ 1899 , 12 , 30 , tzinfo = timezone .utc
90
+ ) + timedelta (days = float (line .split ("=" )[1 ]))
91
+ except IndexError :
92
+ logger .debug ("TRCReader: Failed to parse start time" )
93
+ elif line .startswith (";$COLUMNS" ):
94
+ logger .debug ("TRCReader: Found columns '%s'" , line )
95
+ try :
96
+ columns = line .split ("=" )[1 ].split ("," )
97
+ self .columns = {column : columns .index (column ) for column in columns }
98
+ except IndexError :
99
+ logger .debug ("TRCReader: Failed to parse columns" )
76
100
elif line .startswith (";" ):
77
101
continue
78
102
else :
79
103
break
80
104
105
+ if self .file_version >= TRCFileVersion .V1_1 :
106
+ if self .start_time is None :
107
+ raise ValueError ("File has no start time information" )
108
+
109
+ if self .file_version >= TRCFileVersion .V2_0 :
110
+ if not self .columns :
111
+ raise ValueError ("File has no column information" )
112
+
81
113
if self .file_version == TRCFileVersion .UNKNOWN :
82
114
logger .info (
83
115
"TRCReader: No file version was found, so version 1.0 is assumed"
@@ -87,8 +119,8 @@ def _extract_header(self):
87
119
self ._parse_cols = self ._parse_msg_V1_0
88
120
elif self .file_version == TRCFileVersion .V1_1 :
89
121
self ._parse_cols = self ._parse_cols_V1_1
90
- elif self .file_version == TRCFileVersion .V2_1 :
91
- self ._parse_cols = self ._parse_cols_V2_1
122
+ elif self .file_version in [ TRCFileVersion .V2_0 , TRCFileVersion . V2_1 ] :
123
+ self ._parse_cols = self ._parse_cols_V2_x
92
124
else :
93
125
raise NotImplementedError ("File version not fully implemented for reading" )
94
126
@@ -113,7 +145,12 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]:
113
145
arbit_id = cols [3 ]
114
146
115
147
msg = Message ()
116
- msg .timestamp = float (cols [1 ]) / 1000
148
+ if isinstance (self .start_time , datetime ):
149
+ msg .timestamp = (
150
+ self .start_time + timedelta (milliseconds = float (cols [1 ]))
151
+ ).timestamp ()
152
+ else :
153
+ msg .timestamp = float (cols [1 ]) / 1000
117
154
msg .arbitration_id = int (arbit_id , 16 )
118
155
msg .is_extended_id = len (arbit_id ) > 4
119
156
msg .channel = 1
@@ -122,15 +159,38 @@ def _parse_msg_V1_1(self, cols: List[str]) -> Optional[Message]:
122
159
msg .is_rx = cols [2 ] == "Rx"
123
160
return msg
124
161
125
- def _parse_msg_V2_1 (self , cols : List [str ]) -> Optional [Message ]:
162
+ def _parse_msg_V2_x (self , cols : List [str ]) -> Optional [Message ]:
163
+ type_ = cols [self .columns ["T" ]]
164
+ bus = self .columns .get ("B" , None )
165
+
166
+ if "l" in self .columns :
167
+ length = int (cols [self .columns ["l" ]])
168
+ dlc = len2dlc (length )
169
+ elif "L" in self .columns :
170
+ dlc = int (cols [self .columns ["L" ]])
171
+ length = dlc2len (dlc )
172
+ else :
173
+ raise ValueError ("No length/dlc columns present." )
174
+
126
175
msg = Message ()
127
- msg .timestamp = float (cols [1 ]) / 1000
128
- msg .arbitration_id = int (cols [4 ], 16 )
129
- msg .is_extended_id = len (cols [4 ]) > 4
130
- msg .channel = int (cols [3 ])
131
- msg .dlc = int (cols [7 ])
132
- msg .data = bytearray ([int (cols [i + 8 ], 16 ) for i in range (msg .dlc )])
133
- msg .is_rx = cols [5 ] == "Rx"
176
+ if isinstance (self .start_time , datetime ):
177
+ msg .timestamp = (
178
+ self .start_time + timedelta (milliseconds = float (cols [self .columns ["O" ]]))
179
+ ).timestamp ()
180
+ else :
181
+ msg .timestamp = float (cols [1 ]) / 1000
182
+ msg .arbitration_id = int (cols [self .columns ["I" ]], 16 )
183
+ msg .is_extended_id = len (cols [self .columns ["I" ]]) > 4
184
+ msg .channel = int (cols [bus ]) if bus is not None else 1
185
+ msg .dlc = dlc
186
+ msg .data = bytearray (
187
+ [int (cols [i + self .columns ["D" ]], 16 ) for i in range (length )]
188
+ )
189
+ msg .is_rx = cols [self .columns ["d" ]] == "Rx"
190
+ msg .is_fd = type_ in ["FD" , "FB" , "FE" , "BI" ]
191
+ msg .bitrate_switch = type_ in ["FB" , " FE" ]
192
+ msg .error_state_indicator = type_ in ["FE" , "BI" ]
193
+
134
194
return msg
135
195
136
196
def _parse_cols_V1_1 (self , cols : List [str ]) -> Optional [Message ]:
@@ -141,10 +201,10 @@ def _parse_cols_V1_1(self, cols: List[str]) -> Optional[Message]:
141
201
logger .info ("TRCReader: Unsupported type '%s'" , dtype )
142
202
return None
143
203
144
- def _parse_cols_V2_1 (self , cols : List [str ]) -> Optional [Message ]:
145
- dtype = cols [2 ]
146
- if dtype == "DT" :
147
- return self ._parse_msg_V2_1 (cols )
204
+ def _parse_cols_V2_x (self , cols : List [str ]) -> Optional [Message ]:
205
+ dtype = cols [self . columns [ "T" ] ]
206
+ if dtype in [ "DT" , "FD" , "FB" ] :
207
+ return self ._parse_msg_V2_x (cols )
148
208
else :
149
209
logger .info ("TRCReader: Unsupported type '%s'" , dtype )
150
210
return None
@@ -228,7 +288,7 @@ def __init__(
228
288
self ._msg_fmt_string = self .FORMAT_MESSAGE_V1_0
229
289
self ._format_message = self ._format_message_init
230
290
231
- def _write_header_V1_0 (self , start_time : timedelta ) -> None :
291
+ def _write_header_V1_0 (self , start_time : datetime ) -> None :
232
292
lines = [
233
293
";##########################################################################" ,
234
294
f"; { self .filepath } " ,
@@ -249,13 +309,11 @@ def _write_header_V1_0(self, start_time: timedelta) -> None:
249
309
]
250
310
self .file .writelines (line + "\n " for line in lines )
251
311
252
- def _write_header_V2_1 (self , header_time : timedelta , start_time : datetime ) -> None :
253
- milliseconds = int (
254
- (header_time .seconds * 1000 ) + (header_time .microseconds / 1000 )
255
- )
312
+ def _write_header_V2_1 (self , start_time : datetime ) -> None :
313
+ header_time = start_time - datetime (year = 1899 , month = 12 , day = 30 )
256
314
lines = [
257
315
";$FILEVERSION=2.1" ,
258
- f";$STARTTIME={ header_time . days } . { milliseconds } " ,
316
+ f";$STARTTIME={ header_time / timedelta ( days = 1 ) } " ,
259
317
";$COLUMNS=N,O,T,B,I,d,R,L,D" ,
260
318
";" ,
261
319
f"; { self .filepath } " ,
@@ -308,14 +366,12 @@ def _format_message_init(self, msg, channel):
308
366
309
367
def write_header (self , timestamp : float ) -> None :
310
368
# write start of file header
311
- ref_time = datetime (year = 1899 , month = 12 , day = 30 )
312
- start_time = datetime .now () + timedelta (seconds = timestamp )
313
- header_time = start_time - ref_time
369
+ start_time = datetime .utcfromtimestamp (timestamp )
314
370
315
371
if self .file_version == TRCFileVersion .V1_0 :
316
- self ._write_header_V1_0 (header_time )
372
+ self ._write_header_V1_0 (start_time )
317
373
elif self .file_version == TRCFileVersion .V2_1 :
318
- self ._write_header_V2_1 (header_time , start_time )
374
+ self ._write_header_V2_1 (start_time )
319
375
else :
320
376
raise NotImplementedError ("File format is not supported" )
321
377
self .header_written = True
0 commit comments