17
17
import os
18
18
import mmap
19
19
import platform
20
+ import time
20
21
21
22
import struct
22
23
import sys
50
51
"Q" : ("Q" , None , long ), # Backward compat
51
52
}
52
53
54
+ MULT_TO_PREFIX = {
55
+ 0 : "" ,
56
+ 1 : "" ,
57
+ 1.0e-1 : "d" , # deci
58
+ 1.0e-2 : "c" , # centi
59
+ 1.0e-3 : "m" , # milli
60
+ 1.0e-6 : "µ" , # micro
61
+ 1.0e-9 : "n" # nano
62
+ }
63
+
53
64
def u_ord (c ):
54
65
return ord (c ) if sys .version_info .major < 3 else c
55
66
67
+ def is_quiet_nan (val ):
68
+ '''determine if the argument is a quiet nan'''
69
+ # Is this a float, and some sort of nan?
70
+ if isinstance (val , float ) and math .isnan (val ):
71
+ # quiet nans have more non-zero values:
72
+ if sys .version_info .major >= 3 :
73
+ noisy_nan = bytearray ([0x7f , 0xf8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ])
74
+ else :
75
+ noisy_nan = "\x7f \xf8 \x00 \x00 \x00 \x00 \x00 \x00 "
76
+ return struct .pack (">d" , val ) != noisy_nan
77
+ else :
78
+ return False
79
+
56
80
class DFFormat (object ):
57
81
def __init__ (self , type , name , flen , format , columns , oldfmt = None ):
58
82
self .type = type
@@ -61,8 +85,7 @@ def __init__(self, type, name, flen, format, columns, oldfmt=None):
61
85
self .format = format
62
86
self .columns = columns .split (',' )
63
87
self .instance_field = None
64
- self .unit_ids = None
65
- self .mult_ids = None
88
+ self .units = None
66
89
67
90
if self .columns == ['' ]:
68
91
self .columns = []
@@ -101,32 +124,64 @@ def __init__(self, type, name, flen, format, columns, oldfmt=None):
101
124
if self .msg_fmts [i ] == 'a' :
102
125
self .a_indexes .append (i )
103
126
127
+ # If this format was alrady defined, copy over units and instance info
104
128
if oldfmt is not None :
105
- self .set_unit_ids (oldfmt .unit_ids )
106
- self .set_mult_ids (oldfmt .mult_ids )
107
-
108
- def set_unit_ids (self , unit_ids ):
129
+ self .units = oldfmt .units
130
+ if oldfmt .instance_field is not None :
131
+ self .set_instance_field (self .colhash [oldfmt .instance_field ])
132
+
133
+ def set_instance_field (self , instance_idx ):
134
+ '''set up the instance field for this format'''
135
+ self .instance_field = self .columns [instance_idx ]
136
+ # work out offset and length of instance field in message
137
+ pre_fmt = self .format [:instance_idx ]
138
+ pre_sfmt = ""
139
+ for c in pre_fmt :
140
+ (s , mul , type ) = FORMAT_TO_STRUCT [c ]
141
+ pre_sfmt += s
142
+ self .instance_ofs = struct .calcsize (pre_sfmt )
143
+ (ifmt ,) = self .format [instance_idx ]
144
+ self .instance_len = struct .calcsize (ifmt )
145
+
146
+ def set_unit_ids (self , unit_ids , unit_lookup ):
109
147
'''set unit IDs string from FMTU'''
110
148
if unit_ids is None :
111
149
return
112
- self . unit_ids = unit_ids
150
+ # Does this unit string define an instance field?
113
151
instance_idx = unit_ids .find ('#' )
114
152
if instance_idx != - 1 :
115
- self .instance_field = self .columns [instance_idx ]
116
- # work out offset and length of instance field in message
117
- pre_fmt = self .format [:instance_idx ]
118
- pre_sfmt = ""
119
- for c in pre_fmt :
120
- (s , mul , type ) = FORMAT_TO_STRUCT [c ]
121
- pre_sfmt += s
122
- self .instance_ofs = struct .calcsize (pre_sfmt )
123
- (ifmt ,) = self .format [instance_idx ]
124
- self .instance_len = struct .calcsize (ifmt )
125
-
153
+ self .set_instance_field (instance_idx )
154
+ # Build the units array from the IDs
155
+ self .units = ["" ]* len (self .columns )
156
+ for i in range (len (self .columns )):
157
+ if i < len (unit_ids ):
158
+ if unit_ids [i ] in unit_lookup :
159
+ self .units [i ] = unit_lookup [unit_ids [i ]]
126
160
127
- def set_mult_ids (self , mult_ids ):
161
+ def set_mult_ids (self , mult_ids , mult_lookup ):
128
162
'''set mult IDs string from FMTU'''
129
- self .mult_ids = mult_ids
163
+ # Update the units based on the multiplier
164
+ for i in range (len (self .units )):
165
+ # If the format has its own multiplier, do not adjust the unit,
166
+ # and if no unit is specified there is nothing to adjust
167
+ if self .msg_mults [i ] is not None or self .units [i ] == "" :
168
+ continue
169
+ # Get the unit multiplier from the lookup table
170
+ if mult_ids [i ] in mult_lookup :
171
+ unitmult = mult_lookup [mult_ids [i ]]
172
+ # Combine the multipler and unit to derive the real unit
173
+ if unitmult in MULT_TO_PREFIX :
174
+ self .units [i ] = MULT_TO_PREFIX [unitmult ]+ self .units [i ]
175
+ else :
176
+ self .units [i ] = "%.4g %s" % (unitmult , self .units [i ])
177
+
178
+ def get_unit (self , col ):
179
+ '''Return the unit for the specified field'''
180
+ if self .units is None :
181
+ return ""
182
+ else :
183
+ idx = self .colhash [col ]
184
+ return self .units [idx ]
130
185
131
186
def __str__ (self ):
132
187
return ("DFFormat(%s,%s,%s,%s)" %
@@ -192,7 +247,14 @@ def __getattr__(self, field):
192
247
if self .fmt .msg_types [i ] == str :
193
248
v = null_term (v )
194
249
if self .fmt .msg_mults [i ] is not None and self ._apply_multiplier :
195
- v *= self .fmt .msg_mults [i ]
250
+ # For reasons relating to floating point accuracy, you get a more
251
+ # accurate result by dividing by 1e2 or 1e7 than multiplying by
252
+ # 1e-2 or 1e-7
253
+ if self .fmt .msg_mults [i ] > 0.0 and self .fmt .msg_mults [i ] < 1.0 :
254
+ divisor = 1 / self .fmt .msg_mults [i ]
255
+ v /= divisor
256
+ else :
257
+ v *= self .fmt .msg_mults [i ]
196
258
return v
197
259
198
260
def __setattr__ (self , field , value ):
@@ -214,15 +276,9 @@ def __str__(self):
214
276
col_count = 0
215
277
for c in self .fmt .columns :
216
278
val = self .__getattr__ (c )
217
- if isinstance (val , float ) and math .isnan (val ):
218
- # quiet nans have more non-zero values:
219
- if is_py3 :
220
- noisy_nan = bytearray ([0x7f , 0xf8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 ])
221
- else :
222
- noisy_nan = "\x7f \xf8 \x00 \x00 \x00 \x00 \x00 \x00 "
223
- if struct .pack (">d" , val ) != noisy_nan :
224
- val = "qnan"
225
-
279
+ if is_quiet_nan (val ):
280
+ val = "qnan"
281
+ # Add the value to the return string
226
282
if is_py3 :
227
283
ret += "%s : %s, " % (c , val )
228
284
else :
@@ -235,6 +291,38 @@ def __str__(self):
235
291
ret = ret [:- 2 ]
236
292
return ret + '}'
237
293
294
+ def dump_verbose (self , f ):
295
+ is_py3 = sys .version_info >= (3 ,0 )
296
+ timestamp = "%s.%03u" % (
297
+ time .strftime ("%Y-%m-%d %H:%M:%S" , time .localtime (self ._timestamp )),
298
+ int (self ._timestamp * 1000.0 )% 1000 )
299
+ f .write ("%s: %s\n " % (timestamp , self .fmt .name ))
300
+ for c in self .fmt .columns :
301
+ # Get the value
302
+ val = self .__getattr__ (c )
303
+ # Handle quiet nan
304
+ if is_quiet_nan (val ):
305
+ val = "qnan"
306
+ # Output the field label and value
307
+ if is_py3 :
308
+ f .write (" %s: %s" % (c , val ))
309
+ else :
310
+ try :
311
+ f .write (" %s: %s" % (c , val ))
312
+ except UnicodeDecodeError :
313
+ f .write (" %s: %s" % (c , to_string (val )))
314
+ # Append the unit to the output
315
+ unit = self .fmt .get_unit (c )
316
+ if unit == "" :
317
+ # No unit specified - just output the newline
318
+ f .write ("\n " )
319
+ elif unit .startswith ("rad" ):
320
+ # For rad or rad/s, add the degrees conversion too
321
+ f .write (" %s (%s %s)\n " % (unit , math .degrees (val ), unit .replace ("rad" ,"deg" )))
322
+ else :
323
+ # Append the unit
324
+ f .write (" %s\n " % (unit ))
325
+
238
326
def get_msgbuf (self ):
239
327
'''create a binary message buffer for a message'''
240
328
values = []
@@ -482,6 +570,8 @@ def __init__(self):
482
570
'__MAV__' : self , # avoids conflicts with messages actually called "MAV"
483
571
}
484
572
self .percent = 0
573
+ self .unit_lookup = {} # lookup table of units defined by UNIT messages
574
+ self .mult_lookup = {} # lookup table of multipliers defined by MULT messages
485
575
486
576
def _rewind (self ):
487
577
'''reset state on rewind'''
@@ -798,6 +888,8 @@ def init_arrays(self, progress_callback=None):
798
888
self .counts .append (0 )
799
889
fmt_type = 0x80
800
890
fmtu_type = None
891
+ unit_type = None
892
+ mult_type = None
801
893
ofs = 0
802
894
pct = 0
803
895
HEAD1 = self .HEAD1
@@ -859,7 +951,13 @@ def init_arrays(self, progress_callback=None):
859
951
self .id_to_name [mfmt .type ] = mfmt .name
860
952
if mfmt .name == 'FMTU' :
861
953
fmtu_type = mfmt .type
954
+ if mfmt .name == 'UNIT' :
955
+ unit_type = mfmt .type
956
+ if mfmt .name == 'MULT' :
957
+ mult_type = mfmt .type
862
958
959
+ # Handle FMTU messages by updating the DFFormat class with the
960
+ # unit/multiplier information
863
961
if fmtu_type is not None and mtype == fmtu_type :
864
962
fmt = self .formats [mtype ]
865
963
body = self .data_map [ofs + 3 :ofs + mlen ]
@@ -870,9 +968,33 @@ def init_arrays(self, progress_callback=None):
870
968
if ftype in self .formats :
871
969
fmt2 = self .formats [ftype ]
872
970
if 'UnitIds' in fmt .colhash :
873
- fmt2 .set_unit_ids (null_term (elements [fmt .colhash ['UnitIds' ]]))
971
+ fmt2 .set_unit_ids (null_term (elements [fmt .colhash ['UnitIds' ]]), self . unit_lookup )
874
972
if 'MultIds' in fmt .colhash :
875
- fmt2 .set_mult_ids (null_term (elements [fmt .colhash ['MultIds' ]]))
973
+ fmt2 .set_mult_ids (null_term (elements [fmt .colhash ['MultIds' ]]), self .mult_lookup )
974
+
975
+ # Handle UNIT messages by updating the unit_lookup dictionary
976
+ if unit_type is not None and mtype == unit_type :
977
+ fmt = self .formats [mtype ]
978
+ body = self .data_map [ofs + 3 :ofs + mlen ]
979
+ if len (body )+ 3 < mlen :
980
+ break
981
+ elements = list (struct .unpack (fmt .msg_struct , body ))
982
+ self .unit_lookup [chr (elements [1 ])] = null_term (elements [2 ])
983
+
984
+ # Handle MULT messages by updating the mult_lookup dictionary
985
+ if mult_type is not None and mtype == mult_type :
986
+ fmt = self .formats [mtype ]
987
+ body = self .data_map [ofs + 3 :ofs + mlen ]
988
+ if len (body )+ 3 < mlen :
989
+ break
990
+ elements = list (struct .unpack (fmt .msg_struct , body ))
991
+ # Even though the multiplier value is logged as a double, the
992
+ # values in log files look to be single-precision values that have
993
+ # been cast to a double.
994
+ # To ensure that the values saved here can be used to index the
995
+ # MULT_TO_PREFIX table, we round them to 7 significant decimal digits
996
+ mult = float ("%.7g" % (elements [2 ]))
997
+ self .mult_lookup [chr (elements [1 ])] = mult
876
998
877
999
ofs += mlen
878
1000
if progress_callback is not None :
@@ -1038,8 +1160,8 @@ def _parse_next(self):
1038
1160
MultIds = elements [2 ]
1039
1161
if FmtType in self .formats :
1040
1162
fmt = self .formats [FmtType ]
1041
- fmt .set_unit_ids (UnitIds )
1042
- fmt .set_mult_ids (MultIds )
1163
+ fmt .set_unit_ids (UnitIds , self . unit_lookup )
1164
+ fmt .set_mult_ids (MultIds , self . mult_lookup )
1043
1165
1044
1166
try :
1045
1167
self ._add_msg (m )
@@ -1279,8 +1401,24 @@ def _parse_next(self):
1279
1401
fmtid = getattr (m , 'FmtType' , None )
1280
1402
if fmtid is not None and fmtid in self .id_to_name :
1281
1403
fmtu = self .formats [self .id_to_name [fmtid ]]
1282
- fmtu .set_unit_ids (getattr (m , 'UnitIds' , None ))
1283
- fmtu .set_mult_ids (getattr (m , 'MultIds' , None ))
1404
+ fmtu .set_unit_ids (getattr (m , 'UnitIds' , None ), self .unit_lookup )
1405
+ fmtu .set_mult_ids (getattr (m , 'MultIds' , None ), self .mult_lookup )
1406
+
1407
+ if m .get_type () == 'UNIT' :
1408
+ unitid = getattr (m , 'Id' , None )
1409
+ label = getattr (m , 'Label' , None )
1410
+ self .unit_lookup [chr (unitid )] = null_term (label )
1411
+
1412
+ if m .get_type () == 'MULT' :
1413
+ multid = getattr (m , 'Id' , None )
1414
+ mult = getattr (m , 'Mult' , None )
1415
+ # Even though the multiplier value is logged as a double, the
1416
+ # values in log files look to be single-precision values that have
1417
+ # been cast to a double.
1418
+ # To ensure that the values saved here can be used to index the
1419
+ # MULT_TO_PREFIX table, we round them to 7 significant decimal digits
1420
+ mult = float ("%.7g" % (mult ))
1421
+ self .mult_lookup [chr (multid )] = mult
1284
1422
1285
1423
self ._add_msg (m )
1286
1424
0 commit comments