2013-05-25 17:14:10 -03:00
#!/usr/bin/env python
2013-08-12 18:50:29 -03:00
from __future__ import print_function
""" Dump binary log generated by PX4 ' s sdlog2 or APM as CSV
2013-05-25 17:14:10 -03:00
2013-06-02 05:22:43 -03:00
Usage : python sdlog2_dump . py < log . bin > [ - v ] [ - e ] [ - d delimiter ] [ - n null ] [ - m MSG [ . field1 , field2 , . . . ] ]
2013-05-26 13:15:46 -03:00
- v Use plain debug output instead of CSV .
2013-06-02 05:22:43 -03:00
- e Recover from errors .
2013-05-26 13:15:46 -03:00
- d Use " delimiter " in CSV . Default is " , " .
- n Use " null " as placeholder for empty values in CSV . Default is empty .
- m MSG [ . field1 , field2 , . . . ]
Dump only messages of specified type , and only specified fields .
Multiple - m options allowed . """
2013-05-25 17:14:10 -03:00
__author__ = " Anton Babushkin "
2013-06-02 05:22:43 -03:00
__version__ = " 1.2 "
2013-05-25 17:14:10 -03:00
import struct , sys
2013-08-13 02:26:05 -03:00
if sys . hexversion > = 0x030000F0 :
runningPython3 = True
2013-10-23 13:57:44 -03:00
def _parseCString ( cstr ) :
return str ( cstr , ' ascii ' ) . split ( ' \0 ' ) [ 0 ]
2013-08-13 02:26:05 -03:00
else :
runningPython3 = False
2013-10-23 13:57:44 -03:00
def _parseCString ( cstr ) :
return str ( cstr ) . split ( ' \0 ' ) [ 0 ]
2013-08-13 02:26:05 -03:00
2013-05-25 17:14:10 -03:00
class SDLog2Parser :
BLOCK_SIZE = 8192
MSG_HEADER_LEN = 3
MSG_HEAD1 = 0xA3
MSG_HEAD2 = 0x95
MSG_FORMAT_PACKET_LEN = 89
2013-05-26 08:35:43 -03:00
MSG_FORMAT_STRUCT = " BB4s16s64s "
2013-05-25 17:14:10 -03:00
MSG_TYPE_FORMAT = 0x80
2013-05-26 08:35:43 -03:00
FORMAT_TO_STRUCT = {
" b " : ( " b " , None ) ,
" B " : ( " B " , None ) ,
" h " : ( " h " , None ) ,
" H " : ( " H " , None ) ,
" i " : ( " i " , None ) ,
" I " : ( " I " , None ) ,
" f " : ( " f " , None ) ,
" n " : ( " 4s " , None ) ,
" N " : ( " 16s " , None ) ,
" Z " : ( " 64s " , None ) ,
" c " : ( " h " , 0.01 ) ,
" C " : ( " H " , 0.01 ) ,
" e " : ( " i " , 0.01 ) ,
" E " : ( " I " , 0.01 ) ,
" L " : ( " i " , 0.0000001 ) ,
" M " : ( " b " , None ) ,
" q " : ( " q " , None ) ,
" Q " : ( " Q " , None ) ,
}
2013-05-26 13:15:46 -03:00
__csv_delim = " , "
__csv_null = " "
2013-05-27 11:01:03 -03:00
__msg_filter = [ ]
2013-05-30 14:27:55 -03:00
__time_msg = None
2013-05-26 13:15:46 -03:00
__debug_out = False
2013-06-02 03:43:39 -03:00
__correct_errors = False
2013-07-28 19:30:37 -03:00
__file_name = None
__file = None
2013-05-25 17:14:10 -03:00
def __init__ ( self ) :
return
2013-05-27 11:01:03 -03:00
2013-05-25 17:14:10 -03:00
def reset ( self ) :
2013-05-27 11:01:03 -03:00
self . __msg_descrs = { } # message descriptions by message type map
self . __msg_labels = { } # message labels by message name map
self . __msg_names = [ ] # message names in the same order as FORMAT messages
2013-08-13 02:26:05 -03:00
self . __buffer = bytearray ( ) # buffer for input binary data
2013-05-27 11:01:03 -03:00
self . __ptr = 0 # read pointer in buffer
self . __csv_columns = [ ] # CSV file columns in correct order in format "MSG.label"
self . __csv_data = { } # current values for all columns
2013-05-30 14:27:55 -03:00
self . __csv_updated = False
2013-05-27 11:01:03 -03:00
self . __msg_filter_map = { } # filter in form of map, with '*" expanded to full list of fields
2013-05-26 13:15:46 -03:00
def setCSVDelimiter ( self , csv_delim ) :
self . __csv_delim = csv_delim
def setCSVNull ( self , csv_null ) :
self . __csv_null = csv_null
def setMsgFilter ( self , msg_filter ) :
self . __msg_filter = msg_filter
2013-05-30 14:27:55 -03:00
def setTimeMsg ( self , time_msg ) :
self . __time_msg = time_msg
2013-05-26 13:15:46 -03:00
def setDebugOut ( self , debug_out ) :
self . __debug_out = debug_out
2013-06-02 03:43:39 -03:00
def setCorrectErrors ( self , correct_errors ) :
self . __correct_errors = correct_errors
2013-07-28 19:30:37 -03:00
def setFileName ( self , file_name ) :
self . __file_name = file_name
if file_name != None :
self . __file = open ( file_name , ' w+ ' )
else :
self . __file = None
2013-05-25 17:14:10 -03:00
def process ( self , fn ) :
self . reset ( )
2013-05-27 11:01:03 -03:00
if self . __debug_out :
# init __msg_filter_map
for msg_name , show_fields in self . __msg_filter :
self . __msg_filter_map [ msg_name ] = show_fields
2013-05-26 13:15:46 -03:00
first_data_msg = True
2013-08-12 16:49:49 -03:00
f = open ( fn , " rb " )
2013-06-02 03:43:39 -03:00
bytes_read = 0
2013-05-25 17:14:10 -03:00
while True :
chunk = f . read ( self . BLOCK_SIZE )
if len ( chunk ) == 0 :
break
2013-05-26 13:15:46 -03:00
self . __buffer = self . __buffer [ self . __ptr : ] + chunk
self . __ptr = 0
while self . __bytesLeft ( ) > = self . MSG_HEADER_LEN :
2013-08-13 02:26:05 -03:00
head1 = self . __buffer [ self . __ptr ]
head2 = self . __buffer [ self . __ptr + 1 ]
2013-05-25 17:14:10 -03:00
if ( head1 != self . MSG_HEAD1 or head2 != self . MSG_HEAD2 ) :
2013-06-02 03:43:39 -03:00
if self . __correct_errors :
self . __ptr + = 1
continue
else :
raise Exception ( " Invalid header at %i (0x %X ): %02X %02X , must be %02X %02X " % ( bytes_read + self . __ptr , bytes_read + self . __ptr , head1 , head2 , self . MSG_HEAD1 , self . MSG_HEAD2 ) )
2013-08-13 02:26:05 -03:00
msg_type = self . __buffer [ self . __ptr + 2 ]
2013-05-25 17:14:10 -03:00
if msg_type == self . MSG_TYPE_FORMAT :
2013-05-27 11:01:03 -03:00
# parse FORMAT message
2013-05-27 11:04:19 -03:00
if self . __bytesLeft ( ) < self . MSG_FORMAT_PACKET_LEN :
break
2013-05-26 13:15:46 -03:00
self . __parseMsgDescr ( )
2013-05-25 17:14:10 -03:00
else :
2013-05-27 11:01:03 -03:00
# parse data message
2013-05-26 13:15:46 -03:00
msg_descr = self . __msg_descrs [ msg_type ]
2013-05-26 08:35:43 -03:00
if msg_descr == None :
2013-05-25 17:14:10 -03:00
raise Exception ( " Unknown msg type: %i " % msg_type )
2013-05-26 08:35:43 -03:00
msg_length = msg_descr [ 0 ]
2013-05-26 13:15:46 -03:00
if self . __bytesLeft ( ) < msg_length :
2013-05-25 17:14:10 -03:00
break
2013-05-26 13:15:46 -03:00
if first_data_msg :
2013-05-27 11:01:03 -03:00
# build CSV columns and init data map
self . __initCSV ( )
2013-05-26 13:15:46 -03:00
first_data_msg = False
self . __parseMsg ( msg_descr )
2013-06-02 03:43:39 -03:00
bytes_read + = self . __ptr
2013-05-30 14:27:55 -03:00
if not self . __debug_out and self . __time_msg != None and self . __csv_updated :
self . __printCSVRow ( )
2013-05-25 17:14:10 -03:00
f . close ( )
2013-05-27 11:01:03 -03:00
2013-05-26 13:15:46 -03:00
def __bytesLeft ( self ) :
return len ( self . __buffer ) - self . __ptr
2013-05-27 11:01:03 -03:00
2013-05-26 13:15:46 -03:00
def __filterMsg ( self , msg_name ) :
show_fields = " * "
2013-05-27 11:01:03 -03:00
if len ( self . __msg_filter_map ) > 0 :
show_fields = self . __msg_filter_map . get ( msg_name )
2013-05-26 13:15:46 -03:00
return show_fields
2013-05-27 11:01:03 -03:00
def __initCSV ( self ) :
if len ( self . __msg_filter ) == 0 :
for msg_name in self . __msg_names :
self . __msg_filter . append ( ( msg_name , " * " ) )
for msg_name , show_fields in self . __msg_filter :
if show_fields == " * " :
show_fields = self . __msg_labels . get ( msg_name , [ ] )
self . __msg_filter_map [ msg_name ] = show_fields
for field in show_fields :
2013-07-28 19:30:37 -03:00
full_label = msg_name + " _ " + field
2013-05-27 11:01:03 -03:00
self . __csv_columns . append ( full_label )
self . __csv_data [ full_label ] = None
2013-07-28 19:30:37 -03:00
if self . __file != None :
2013-10-23 13:57:44 -03:00
print ( self . __csv_delim . join ( self . __csv_columns ) , file = self . __file )
2013-07-28 19:30:37 -03:00
else :
2013-10-23 13:57:44 -03:00
print ( self . __csv_delim . join ( self . __csv_columns ) )
2013-05-30 14:27:55 -03:00
def __printCSVRow ( self ) :
s = [ ]
for full_label in self . __csv_columns :
v = self . __csv_data [ full_label ]
if v == None :
v = self . __csv_null
else :
v = str ( v )
s . append ( v )
2013-07-28 19:30:37 -03:00
if self . __file != None :
2013-10-23 13:57:44 -03:00
print ( self . __csv_delim . join ( s ) , file = self . __file )
2013-07-28 19:30:37 -03:00
else :
2013-10-23 13:57:44 -03:00
print ( self . __csv_delim . join ( s ) )
2013-05-30 14:27:55 -03:00
2013-05-26 13:15:46 -03:00
def __parseMsgDescr ( self ) :
2013-08-13 02:26:05 -03:00
if runningPython3 :
data = struct . unpack ( self . MSG_FORMAT_STRUCT , self . __buffer [ self . __ptr + 3 : self . __ptr + self . MSG_FORMAT_PACKET_LEN ] )
else :
2013-08-13 02:33:32 -03:00
data = struct . unpack ( self . MSG_FORMAT_STRUCT , str ( self . __buffer [ self . __ptr + 3 : self . __ptr + self . MSG_FORMAT_PACKET_LEN ] ) )
2013-05-26 08:35:43 -03:00
msg_type = data [ 0 ]
2013-05-27 11:01:03 -03:00
if msg_type != self . MSG_TYPE_FORMAT :
msg_length = data [ 1 ]
2013-10-23 13:57:44 -03:00
msg_name = _parseCString ( data [ 2 ] )
msg_format = _parseCString ( data [ 3 ] )
msg_labels = _parseCString ( data [ 4 ] ) . split ( " , " )
2013-05-27 11:01:03 -03:00
# Convert msg_format to struct.unpack format string
msg_struct = " "
msg_mults = [ ]
for c in msg_format :
try :
f = self . FORMAT_TO_STRUCT [ c ]
msg_struct + = f [ 0 ]
msg_mults . append ( f [ 1 ] )
except KeyError as e :
raise Exception ( " Unsupported format char: %s in message %s ( %i ) " % ( c , msg_name , msg_type ) )
msg_struct = " < " + msg_struct # force little-endian
self . __msg_descrs [ msg_type ] = ( msg_length , msg_name , msg_format , msg_labels , msg_struct , msg_mults )
self . __msg_labels [ msg_name ] = msg_labels
self . __msg_names . append ( msg_name )
2013-05-26 13:15:46 -03:00
if self . __debug_out :
2013-05-27 11:01:03 -03:00
if self . __filterMsg ( msg_name ) != None :
2013-08-12 17:03:57 -03:00
print ( " MSG FORMAT: type = %i , length = %i , name = %s , format = %s , labels = %s , struct = %s , mults = %s " % (
msg_type , msg_length , msg_name , msg_format , str ( msg_labels ) , msg_struct , msg_mults ) )
2013-05-26 13:15:46 -03:00
self . __ptr + = self . MSG_FORMAT_PACKET_LEN
2013-05-27 11:01:03 -03:00
2013-05-26 13:15:46 -03:00
def __parseMsg ( self , msg_descr ) :
2013-05-26 08:35:43 -03:00
msg_length , msg_name , msg_format , msg_labels , msg_struct , msg_mults = msg_descr
2013-05-30 14:27:55 -03:00
if not self . __debug_out and self . __time_msg != None and msg_name == self . __time_msg and self . __csv_updated :
self . __printCSVRow ( )
self . __csv_updated = False
2013-05-26 13:15:46 -03:00
show_fields = self . __filterMsg ( msg_name )
if ( show_fields != None ) :
2013-08-13 02:33:32 -03:00
if runningPython3 :
data = list ( struct . unpack ( msg_struct , self . __buffer [ self . __ptr + self . MSG_HEADER_LEN : self . __ptr + msg_length ] ) )
else :
data = list ( struct . unpack ( msg_struct , str ( self . __buffer [ self . __ptr + self . MSG_HEADER_LEN : self . __ptr + msg_length ] ) ) )
2013-08-12 17:03:57 -03:00
for i in range ( len ( data ) ) :
2013-05-26 13:15:46 -03:00
if type ( data [ i ] ) is str :
2013-10-23 13:57:44 -03:00
data [ i ] = _parseCString ( data [ i ] )
2013-05-26 13:15:46 -03:00
m = msg_mults [ i ]
if m != None :
data [ i ] = data [ i ] * m
if self . __debug_out :
s = [ ]
2013-08-12 17:03:57 -03:00
for i in range ( len ( data ) ) :
2013-05-26 13:15:46 -03:00
label = msg_labels [ i ]
if show_fields == " * " or label in show_fields :
s . append ( label + " = " + str ( data [ i ] ) )
2013-08-12 17:03:57 -03:00
print ( " MSG %s : %s " % ( msg_name , " , " . join ( s ) ) )
2013-05-26 13:15:46 -03:00
else :
# update CSV data buffer
2013-08-12 17:03:57 -03:00
for i in range ( len ( data ) ) :
2013-05-26 13:15:46 -03:00
label = msg_labels [ i ]
2013-05-27 11:01:03 -03:00
if label in show_fields :
2013-07-28 19:30:37 -03:00
self . __csv_data [ msg_name + " _ " + label ] = data [ i ]
2013-05-30 14:27:55 -03:00
if self . __time_msg != None and msg_name != self . __time_msg :
self . __csv_updated = True
if self . __time_msg == None :
self . __printCSVRow ( )
2013-05-26 13:15:46 -03:00
self . __ptr + = msg_length
2013-05-25 17:14:10 -03:00
def _main ( ) :
if len ( sys . argv ) < 2 :
2013-08-12 17:03:57 -03:00
print ( " Usage: python sdlog2_dump.py <log.bin> [-v] [-e] [-d delimiter] [-n null] [-m MSG[.field1,field2,...]] [-t TIME_MSG_NAME] \n " )
print ( " \t -v \t Use plain debug output instead of CSV. \n " )
print ( " \t -e \t Recover from errors. \n " )
print ( " \t -d \t Use \" delimiter \" in CSV. Default is \" , \" . \n " )
print ( " \t -n \t Use \" null \" as placeholder for empty values in CSV. Default is empty. \n " )
print ( " \t -m MSG[.field1,field2,...] \n \t \t Dump only messages of specified type, and only specified fields. \n \t \t Multiple -m options allowed. " )
print ( " \t -t \t Specify TIME message name to group data messages by time and significantly reduce duplicate output. \n " )
print ( " \t -fPrint to file instead of stdout " )
2013-05-25 17:14:10 -03:00
return
fn = sys . argv [ 1 ]
2013-05-26 13:15:46 -03:00
debug_out = False
2013-06-02 03:43:39 -03:00
correct_errors = False
2013-05-27 11:01:03 -03:00
msg_filter = [ ]
2013-05-26 13:15:46 -03:00
csv_null = " "
csv_delim = " , "
2013-07-28 19:30:37 -03:00
time_msg = " TIME "
file_name = None
2013-05-26 13:15:46 -03:00
opt = None
for arg in sys . argv [ 2 : ] :
if opt != None :
if opt == " d " :
csv_delim = arg
elif opt == " n " :
csv_null = arg
2013-05-30 14:27:55 -03:00
elif opt == " t " :
time_msg = arg
2013-07-28 19:30:37 -03:00
elif opt == " f " :
file_name = arg
2013-05-30 14:27:55 -03:00
elif opt == " m " :
2013-05-26 13:15:46 -03:00
show_fields = " * "
2013-07-28 19:30:37 -03:00
a = arg . split ( " _ " )
2013-05-26 13:15:46 -03:00
if len ( a ) > 1 :
2013-05-27 11:01:03 -03:00
show_fields = a [ 1 ] . split ( " , " )
msg_filter . append ( ( a [ 0 ] , show_fields ) )
2013-05-26 13:15:46 -03:00
opt = None
else :
if arg == " -v " :
debug_out = True
2013-06-02 03:43:39 -03:00
elif arg == " -e " :
correct_errors = True
2013-05-26 13:15:46 -03:00
elif arg == " -d " :
opt = " d "
elif arg == " -n " :
opt = " n "
elif arg == " -m " :
opt = " m "
2013-05-30 14:27:55 -03:00
elif arg == " -t " :
opt = " t "
2013-07-28 19:30:37 -03:00
elif arg == " -f " :
opt = " f "
2013-05-30 14:27:55 -03:00
2013-05-26 13:15:46 -03:00
if csv_delim == " \\ t " :
csv_delim = " \t "
2013-05-25 17:14:10 -03:00
parser = SDLog2Parser ( )
2013-05-26 13:15:46 -03:00
parser . setCSVDelimiter ( csv_delim )
parser . setCSVNull ( csv_null )
parser . setMsgFilter ( msg_filter )
2013-05-30 14:27:55 -03:00
parser . setTimeMsg ( time_msg )
2013-07-28 19:30:37 -03:00
parser . setFileName ( file_name )
2013-05-26 13:15:46 -03:00
parser . setDebugOut ( debug_out )
2013-06-02 03:43:39 -03:00
parser . setCorrectErrors ( correct_errors )
2013-05-25 17:14:10 -03:00
parser . process ( fn )
if __name__ == " __main__ " :
_main ( )