delete sdlog2

This commit is contained in:
Daniel Agar
2018-07-13 09:02:59 +02:00
committed by Lorenz Meier
parent 83fd5a5fd1
commit 8537863848
41 changed files with 20 additions and 5042 deletions
@@ -16,7 +16,6 @@ exec find src platforms \
-path src/lib/matrix -prune -o \
-path src/modules/commander -prune -o \
-path src/modules/micrortps_bridge/micro-CDR -prune -o \
-path src/modules/sdlog2 -prune -o \
-path src/modules/systemlib/uthash -prune -o \
-path src/modules/uavcan/libuavcan -prune -o \
-type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" \) | grep $PATTERN
-29
View File
@@ -1,29 +0,0 @@
====== PX4 LOG CONVERSION ======
On each log session (commonly started and stopped by arming and disarming the vehicle) a new file logxxx.bin is created. In many cases there will be only one logfile named log001.bin (only one flight).
There are two conversion scripts in this ZIP file:
logconv.m: This is a MATLAB script which will automatically convert and display the flight data with a GUI. If running this script, the second script can be ignored.
sdlog2_dump.py: This is a Python script (compatible with v2 and v3) which converts the self-describing binary log format to a CSV file. To export a CSV file from within a shell (Windows CMD or BASH on Linux / Mac OS), run:
```
python sdlog2_dump.py log001.bin -f "export.csv" -t "TIME" -d "," -n ""
```
geo_tag_images.py: Use this script to geotag a set of images. It uses GPS time and file creation date to synchronize the images, so it needs that the images have a valid creation date. Can generate a KML file to view where the photos were taken in Google Earth (including height).
```sh
python geo_tag_images.py --logfile=mylog.bin --input=images/ --output=tagged/ --kml -v
python geo_tag_images.py -l=mylog.bin -i=images/ -o=tagged/ --kml -v
```
geotagging.py: Use this script to geotag a set of images. It uses the CAM trigger data from the log file for image association.
```sh
python geotagging.py --logfile=mylog.bin --input=images/ --output=tagged/
```
Python can be downloaded from http://python.org, but is available as default on Mac OS and Linux.
-434
View File
@@ -1,434 +0,0 @@
#!/usr/bin/env python
#
# Tag the images recorded during a flight with geo location extracted from
# a PX4 binary log file.
#
# This file accepts *.jpg format images and reads position information
# from a *.px4log file
#
# Example Syntax:
# python geo_tag_images.py --logfile=log001.px4log --input=images/ --output=imagesWithTag/ --offset=-0.4 -v
#
# Optional: Correct image times first
# jhead -exonly -ft -n%Y-%m-%d\ %H.%M.%S -ta+HH:MM:SS *.JPG
#
# Author: Hector Azpurua hector@azpurua.com
# Based on the script of Andreas Bircher
import os
import re
import sys
import bisect
import pyexiv2
import argparse
from lxml import etree
import datetime
import calendar
from shutil import copyfile
from subprocess import check_output
from pykml.factory import KML_ElementMaker as KML
from pykml.factory import GX_ElementMaker as GX
class GpsPosition(object):
def __init__(self, timestamp, lat, lon, alt):
self.timestamp = timestamp
self.lat = float(lat)
self.lon = float(lon)
self.alt = float(alt)
class Main:
def __init__(self):
"""
:param logfile:
:param input:
:param output:
:param offset:
:param verbose:
:return:
"""
args = self.get_arg()
self.logfile = args['logfile']
self.input = args['input']
self.output = args['output']
self.kml = args['kml']
self.verbose = args['verbose']
self.offset = args['offset']
self.time_thresh = args['threshold']
self.tdiff_list = []
self.non_processed_files = []
self.tagged_gps = []
print '[INFO] Loading logs and images locations...'
self.gps_list = self.load_gps_from_log(self.logfile, self.offset)
self.img_list = self.load_image_list(self.input)
if len(self.img_list) <= 0:
print '[ERROR] Cannot load JPG images from input folder, please check filename extensions.'
sys.exit(1)
if not os.path.exists(self.output):
os.makedirs(self.output)
if not self.output.endswith(os.path.sep):
self.output += os.path.sep
self.tag_images()
if self.kml and len(self.tdiff_list) > 0:
self.gen_kml()
if len(self.non_processed_files) > 0:
print '[WARNING] Some images werent processed (', len(self.non_processed_files), 'of', len(self.img_list), '):'
for elem in self.non_processed_files:
print '\t', elem
@staticmethod
def to_degree(value, loc):
"""
Convert a lat or lon value to degrees/minutes/seconds
:param value: the latitude or longitude value
:param loc: could be ["S", "N"] or ["W", "E"]
:return:
"""
if value < 0:
loc_value = loc[0]
elif value > 0:
loc_value = loc[1]
else:
loc_value = ""
absolute_value = abs(value)
deg = int(absolute_value)
t1 = (absolute_value - deg) * 60
minute = int(t1)
sec = round((t1 - minute) * 60, 5)
return deg, minute, sec, loc_value
@staticmethod
def gps_week_seconds_to_datetime(gpsweek, gpsmillis, leapmillis=0):
"""
Convert GPS week and seconds to datetime object, using leap milliseconds if necessary
:param gpsweek:
:param gpsmillis:
:param leapmillis:
:return:
"""
datetimeformat = "%Y-%m-%d %H:%M:%S.%f"
epoch = datetime.datetime.strptime(
"1980-01-06 00:00:00.000", datetimeformat)
elapsed = datetime.timedelta(
days=(gpsweek * 7), milliseconds=(gpsmillis + leapmillis))
return Main.utc_to_local(epoch + elapsed)
@staticmethod
def unix_microseconds_to_datetime(unix_us, offset=0):
"""
Convert unix microseconds to datetime object, using offset milliseconds if necessary
:param unix_us:
:param offset:
:return:
"""
# time in seconds
time_s = int(unix_us) / 1000000 + (offset / 1000)
datetime_from_unix = datetime.datetime.fromtimestamp(time_s)
return datetime_from_unix
@staticmethod
def utc_to_local(utc_dt):
"""
Convert UTC time in local time
:param utc_dt:
:return:
"""
# use integer timestamp to avoid precision loss
timestamp = calendar.timegm(utc_dt.timetuple())
local_dt = datetime.datetime.fromtimestamp(timestamp)
assert utc_dt.resolution >= datetime.timedelta(microseconds=1)
return local_dt.replace(microsecond=utc_dt.microsecond)
def gen_kml(self):
"""
Generate a KML file with keypoints on the locations of the pictures, including height
:return:
"""
style_dot = "sn_shaded_dot"
style_path = "red_path"
doc = KML.kml(
KML.Document(
KML.Name("GPS of the images"),
KML.Style(
KML.IconStyle(
KML.scale(0.4),
KML.Icon(
KML.href(
"http://maps.google.com/mapfiles/kml/shapes/shaded_dot.png")
),
),
id=style_dot,
),
KML.Style(
KML.LineStyle(
KML.color('7f0000ff'),
KML.width(6),
GX.labelVisibility('1'),
),
id=style_path
)
)
)
# create points
for i, gps in enumerate(self.tagged_gps):
ii = i + 1
doc.Document.append(
KML.Placemark(
KML.styleUrl('#{0}'.format(style_dot)),
KML.Point(
KML.extrude(True),
KML.altitudeMode('absolute'),
KML.coordinates(
"{},{},{}".format(gps.lon, gps.lat, gps.alt))
),
KML.name(
str(ii)) if ii % 5 == 0 or ii == 1 else KML.name()
)
)
# create the path
doc.Document.append(
KML.Placemark(
KML.styleUrl('#{0}'.format(style_path)),
KML.LineString(
KML.altitudeMode('absolute'),
KML.coordinates(
' '.join(["{},{},{}".format(gps.lon, gps.lat, gps.alt)
for gps in self.tagged_gps])
)
)
)
)
s = etree.tostring(doc)
file_path = self.output + 'GoogleEarth_points.kml'
f = open(file_path, 'w')
f.write(s)
f.close()
print '[INFO] KML file generated on:', file_path
def get_closest_datetime_index(self, datetime_list, elem):
"""
Get the closest element between a list of datetime objects and a date
:param datetime_list:
:param elem:
:return:
"""
dlist_len = len(datetime_list)
i = bisect.bisect_left(datetime_list, elem)
# Cleanup of the indices
if i < 0:
i = 0
elif i >= dlist_len:
i = dlist_len - 1
date = datetime_list[i]
diff = abs((date - elem).total_seconds())
if diff > self.time_thresh:
return -1, diff
return i, diff
def set_gps_location(self, file_name, lat, lng, alt):
"""
Add the GPS tag and altitude to a image file
:param file_name:
:param lat:
:param lng:
:param alt:
:return:
"""
lat_deg = self.to_degree(lat, ["S", "N"])
lng_deg = self.to_degree(lng, ["W", "E"])
exiv_lat = (pyexiv2.Rational(lat_deg[0] * 60 + lat_deg[1], 60),
pyexiv2.Rational(lat_deg[2] * 100, 6000), pyexiv2.Rational(0, 1))
exiv_lng = (pyexiv2.Rational(lng_deg[0] * 60 + lng_deg[1], 60),
pyexiv2.Rational(lng_deg[2] * 100, 6000), pyexiv2.Rational(0, 1))
try:
exiv_image = pyexiv2.ImageMetadata(file_name)
exiv_image.read()
exiv_image["Exif.GPSInfo.GPSLatitude"] = exiv_lat
exiv_image["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3]
exiv_image["Exif.GPSInfo.GPSLongitude"] = exiv_lng
exiv_image["Exif.GPSInfo.GPSLongitudeRef"] = lng_deg[3]
exiv_image["Exif.GPSInfo.GPSAltitude"] = pyexiv2.Rational(alt, 1)
exiv_image["Exif.GPSInfo.GPSAltitudeRef"] = '0'
exiv_image["Exif.Image.GPSTag"] = 654
exiv_image["Exif.GPSInfo.GPSMapDatum"] = "WGS-84"
exiv_image["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0'
exiv_image.write(True)
except Exception as e:
print '[ERROR]', e
def load_gps_from_log(self, log_file, offset):
"""
Load gps list from PX4 binary log
:param log_file:
:param offset:
:return:
"""
gps_list = []
out = check_output(
["python", "sdlog2_dump.py", log_file, "-m GPS", "-v"])
for line in out.splitlines():
if not line.startswith("MSG GPS:"):
continue
vdict = {}
pairs = re.split(r'[;,:]\s*', line)
for pair in pairs:
e = pair.split('=')
if len(e) == 2:
vdict[e[0]] = float(e[1])
# PX4 GPS.GPSTime is unix time in microseconds
gps_time = vdict['GPSTime']
gps_lat = vdict['Lat']
gps_lon = vdict['Lon']
gps_alt = vdict['Alt']
date = self.unix_microseconds_to_datetime(gps_time, offset)
gps_list.append(GpsPosition(date, gps_lat, gps_lon, gps_alt))
return gps_list
def get_image_creation_date(self, filename):
exiv_image = pyexiv2.ImageMetadata(filename)
exiv_image.read()
# Prefer DateTime/Original over the other values
if 'Exif.Photo.DateTimeOriginal' in exiv_image:
cdate = exiv_image['Exif.Photo.DateTimeOriginal'].value
return cdate
elif 'Exif.Image.DateTime' in exiv_image:
cdate = exiv_image['Exif.Image.DateTime'].value
return cdate
else:
epoch = os.path.getmtime(filename)
return datetime.datetime.fromtimestamp(epoch)
def load_image_list(self, input_folder, file_type='jpg'):
"""
Load image list from a folder given a file type
:param input_folder:
:param file_type:
:return:
"""
self.img_list = [input_folder + filename for filename in os.listdir(input_folder)
if re.search(r'\.' + file_type + '$', filename, re.IGNORECASE)]
self.img_list = sorted(self.img_list)
return self.img_list
def tag_images(self):
"""
Tag the image list using the GPS loaded from the LOG file
:return:
"""
tagged_gps = []
img_size = len(self.img_list)
print '[INFO] Number of images:', img_size
print '[INFO] Number of gps logs:', len(self.gps_list)
dt_list = [x.timestamp for x in self.gps_list]
img_seq = 1
for i in xrange(img_size):
cdate = self.get_image_creation_date(self.img_list[i])
gps_i, img_tdiff = self.get_closest_datetime_index(dt_list, cdate)
base_path, filename = os.path.split(self.img_list[i])
if gps_i == -1:
self.non_processed_files.append(filename)
continue
closest_gps = self.gps_list[gps_i]
self.tdiff_list.append(img_tdiff)
if self.verbose:
msg = "[DEBUG] %s/%s) %s\n\timg %s -> gps %s (%ss)\n\tlat:%s, lon:%s, alt:%s".ljust(60) %\
(i + 1, img_size, filename, cdate, closest_gps.timestamp,
img_tdiff, closest_gps.lat, closest_gps.lon, closest_gps.alt)
print msg
output_filename = self.output + str(img_seq) + '_' + filename
copyfile(self.img_list[i], output_filename)
self.set_gps_location(
output_filename, closest_gps.lat, closest_gps.lon, closest_gps.alt)
self.tagged_gps.append(closest_gps)
img_seq += 1
if len(self.tdiff_list) > 0:
print '[INFO] Mean diff in seconds:', sum(self.tdiff_list) / float(len(self.tdiff_list))
@staticmethod
def get_arg():
parser = argparse.ArgumentParser(
description='Geotag script to add GPS info to pictures from PX4 binary log files.'
'It uses synchronized time to allocate GPS positions.'
)
parser.add_argument(
'-l', '--logfile', help='PX4 log file containing recorded positions.', required=True
)
parser.add_argument(
'-i', '--input', help='Input folder containing untagged images.', required=True
)
parser.add_argument(
'-o', '--output', help='Output folder to contain tagged images.', required=True
)
parser.add_argument(
'-t', '--threshold', help='Time threshold between the GPS time and the local image time.',
default=1, required=False, type=float
)
parser.add_argument(
'-of', '--offset', help='Time offset in MILLISECONDS between the GPS time and the local time.',
default=0, required=False, type=float
)
parser.add_argument(
'-kml', '--kml', help='Save the in KML format the information of all tagged images.',
required=False, action='store_true'
)
parser.add_argument(
'-v', '--verbose', help='Prints lots of information.',
required=False, action='store_true'
)
args = vars(parser.parse_args())
return args
if __name__ == "__main__":
m = Main()
-237
View File
@@ -1,237 +0,0 @@
#!/usr/bin/env python
#
# __geotagging__
# Tag the images recorded during a flight with geo location extracted from
# a PX4 log file.
#
# This file accepts *.jpg format images and reads position information
# from a *.px4log file
#
# Example Syntax:
# python geotagging.py --logfile=log001.px4log --input=images/
# --output=imagesWithTag/
#
# Author: Andreas Bircher, Wingtra, http://wingtra.com, in 2016
#
import glob
import os
import pyexiv2
from shutil import copyfile
from optparse import OptionParser
import csv
from datetime import datetime, timedelta
class TriggerList(object):
def __init__(self):
self.CAMT_seq = []
self.CAMT_timestamp = []
self.GPOS_Lat = []
self.GPOS_Lon = []
self.GPOS_Alt = []
self.GPS_GPSTime = []
class ImageList(object):
def __init__(self):
self.jpg = []
self.raw = []
def to_degree(value, loc):
if value < 0:
loc_value = loc[0]
elif value > 0:
loc_value = loc[1]
else:
loc_value = ""
absolute_value = abs(value)
deg = int(absolute_value)
t1 = (absolute_value - deg) * 60
min = int(t1)
sec = round((t1 - min) * 60, 5)
return (deg, min, sec, loc_value)
def SetGpsLocation(file_name, gps_datetime, lat, lng, alt):
"""
Adding GPS tag
"""
lat_deg = to_degree(lat, ["S", "N"])
lng_deg = to_degree(lng, ["W", "E"])
exiv_lat = (pyexiv2.Rational(lat_deg[0] * 60 + lat_deg[1], 60), pyexiv2.Rational(
lat_deg[2] * 100, 6000), pyexiv2.Rational(0, 1))
exiv_lng = (pyexiv2.Rational(lng_deg[0] * 60 + lng_deg[1], 60), pyexiv2.Rational(
lng_deg[2] * 100, 6000), pyexiv2.Rational(0, 1))
exiv_image = pyexiv2.ImageMetadata(file_name)
exiv_image.read()
date_tag = exiv_image['Exif.Image.DateTime']
date_max = max(date_tag.value, gps_datetime)
date_min = min(date_tag.value, gps_datetime)
time_diff = date_max - date_min
if (time_diff > timedelta(seconds=5)):
print(
"WARNING, camera trigger and photo time different by {}".format(time_diff))
print(" Photo tag time: {}".format(date_tag.value))
print(" Camera trigger time: {}".format(gps_datetime))
exiv_image["Exif.GPSInfo.GPSLatitude"] = exiv_lat
exiv_image["Exif.GPSInfo.GPSLatitudeRef"] = lat_deg[3]
exiv_image["Exif.GPSInfo.GPSLongitude"] = exiv_lng
exiv_image["Exif.GPSInfo.GPSLongitudeRef"] = lng_deg[3]
exiv_image["Exif.GPSInfo.GPSAltitude"] = pyexiv2.Rational(alt, 1)
exiv_image["Exif.GPSInfo.GPSAltitudeRef"] = '0'
exiv_image["Exif.Image.GPSTag"] = 654
exiv_image["Exif.GPSInfo.GPSMapDatum"] = "WGS-84"
exiv_image["Exif.GPSInfo.GPSVersionID"] = '2 0 0 0'
exiv_image.write(True)
def LoadPX4log(px4_log_file):
"""
load px4 log file and extract trigger locations
"""
os.system('python sdlog2_dump.py ' + px4_log_file +
' -t time -m TIME -m CAMT -m GPOS -m GPS -f log.csv')
f = open('log.csv', 'rb')
reader = csv.reader(f)
headers = reader.next()
line = {}
for h in headers:
line[h] = []
for row in reader:
for h, v in zip(headers, row):
line[h].append(v)
trigger_list = TriggerList()
for seq in range(0, len(line['CAMT_seq']) - 1):
if line['CAMT_seq'][seq] != line['CAMT_seq'][seq + 1]:
trigger_list.CAMT_seq.append(line['CAMT_seq'][seq + 1])
trigger_list.CAMT_timestamp.append(line['CAMT_timestamp'][seq + 1])
trigger_list.GPOS_Lat.append(line['GPOS_Lat'][seq + 1])
trigger_list.GPOS_Lon.append(line['GPOS_Lon'][seq + 1])
trigger_list.GPOS_Alt.append(line['GPOS_Alt'][seq + 1])
trigger_list.GPS_GPSTime.append(line['GPS_GPSTime'][seq + 1])
return trigger_list
def LoadImageList(input_folder):
"""
load the image list
"""
image_list = ImageList()
for jpg_image in glob.glob(input_folder + "/*.jpg"):
image_list.jpg.append(jpg_image)
for jpg_image in glob.glob(input_folder + "/*.JPG"):
image_list.jpg.append(jpg_image)
for raw_image in glob.glob(input_folder + "/*.RC"):
image_list.raw.append(raw_image)
if len(image_list.jpg) != len(image_list.raw) and len(image_list.jpg) * len(image_list.raw) != 0:
print("Unequal number of jpg and raw images")
if len(image_list.jpg) == 0 and len(image_list.raw) == 0:
print("No images found")
image_list.jpg = sorted(image_list.jpg)
image_list.raw = sorted(image_list.raw)
return image_list
def FilterTrigger(trigger_list, image_list):
"""
filter triggers to allow exact matching with recorded images
"""
# filter trigger list to match the number of pics
if len(image_list.jpg) != len(trigger_list.CAMT_seq):
print('WARNING! differ number of jpg images ({}) and camera triggers ({})'.format(
len(image_list.jpg), len(trigger_list.CAMT_seq)))
n_overlap = min(len(image_list.jpg), len(trigger_list.CAMT_seq))
del image_list.jpg[n_overlap:]
if len(image_list.raw) != len(trigger_list.CAMT_seq):
print('WARNING! differ number of raw images ({}) and camera triggers ({})'.format(
len(image_list.raw), len(trigger_list.CAMT_seq)))
n_overlap = min(len(image_list.raw), len(trigger_list.CAMT_seq))
del image_list.raw[n_overlap:]
return trigger_list
def TagImages(trigger_list, image_list, output_folder):
"""
load px4 log file and extract trigger locations
"""
for image in range(len(image_list.jpg)):
print("############################################################")
print('Photo {}: {}'.format(image, image_list.jpg[image]))
cam_time = int(trigger_list.GPS_GPSTime[image]) / 1000000
gps_datetime = datetime.fromtimestamp(cam_time)
base_path, filename = os.path.split(image_list.jpg[image])
copyfile(image_list.jpg[image], output_folder + "/" + filename)
SetGpsLocation(output_folder + "/" + filename, gps_datetime, float(
trigger_list.GPOS_Lat[image]), float(trigger_list.GPOS_Lon[image]), float(trigger_list.GPOS_Alt[image]))
for image in range(len(image_list.raw)):
print("############################################################")
print('Photo {}: {}'.format(image, image_list.raw[image]))
cam_time = int(trigger_list.GPS_GPSTime[image]) / 1000000
gps_datetime = datetime.fromtimestamp(cam_time)
base_path, filename = os.path.split(image_list.raw[image])
copyfile(image_list.raw[image], output_folder + "/" + filename)
SetGpsLocation(output_folder + "/" + filename, gps_datetime, float(
trigger_list.GPOS_Lat[image]), float(trigger_list.GPOS_Lon[image]), float(trigger_list.GPOS_Alt[image]))
def main():
"""
Main method
"""
parser = OptionParser()
parser.add_option("-l", "--logfile", dest="LogFile",
help="PX4 log file containing recorded positions",
metavar="string")
parser.add_option("-i", "--input", dest="InputFolder",
help="Input folder containing untagged images in alphabetical order",
type="string")
parser.add_option("-o", "--output", dest="OutputFolder",
help="Output folder to contain tagged images",
type="string")
(options, args) = parser.parse_args()
if not options.LogFile:
print "please type python " \
"geotagging.py --logfile=[filename] --intput=[folder] [--output=[folder]]"
elif not options.InputFolder:
print "please type python " \
"geotagging.py --logfile=[filename] --intput=[folder] [--output=[folder]]s"
else:
trigger_list = LoadPX4log(options.LogFile)
image_list = LoadImageList(options.InputFolder)
if not options.OutputFolder:
options.OutputFolder = "imagesWithTag"
if not os.path.exists(options.OutputFolder):
os.makedirs(options.OutputFolder)
trigger_list = FilterTrigger(trigger_list, image_list)
TagImages(trigger_list, image_list, options.OutputFolder)
if __name__ == "__main__":
main()
-535
View File
@@ -1,535 +0,0 @@
% This Matlab Script can be used to import the binary logged values of the
% PX4FMU into data that can be plotted and analyzed.
%% ************************************************************************
% PX4LOG_PLOTSCRIPT: Main function
% ************************************************************************
function PX4Log_Plotscript
% Clear everything
clc
clear all
close all
% ************************************************************************
% SETTINGS
% ************************************************************************
% Set the path to your sysvector.bin file here
filePath = 'log001.bin';
% Set the minimum and maximum times to plot here [in seconds]
mintime=0; %The minimum time/timestamp to display, as set by the user [0 for first element / start]
maxtime=0; %The maximum time/timestamp to display, as set by the user [0 for last element / end]
%Determine which data to plot. Not completely implemented yet.
bDisplayGPS=true;
%conversion factors
fconv_gpsalt=1; %[mm] to [m]
fconv_gpslatlong=1; %[gps_raw_position_unit] to [deg]
fconv_timestamp=1E-6; % [microseconds] to [seconds]
% ************************************************************************
% Import the PX4 logs
% ************************************************************************
ImportPX4LogData();
%Translate min and max plot times to indices
time=double(sysvector.TIME_StartTime) .*fconv_timestamp;
mintime_log=time(1); %The minimum time/timestamp found in the log
maxtime_log=time(end); %The maximum time/timestamp found in the log
CurTime=mintime_log; %The current time at which to draw the aircraft position
[imintime,imaxtime]=FindMinMaxTimeIndices();
% ************************************************************************
% PLOT & GUI SETUP
% ************************************************************************
NrFigures=5;
NrAxes=10;
h.figures(1:NrFigures)=0.0; % Temporary initialization of figure handle array - these are numbered consecutively
h.axes(1:NrAxes)=0.0; % Temporary initialization of axes handle array - these are numbered consecutively
h.pathpoints=[]; % Temporary initiliazation of path points
% Setup the GUI to control the plots
InitControlGUI();
% Setup the plotting-GUI (figures, axes) itself.
InitPlotGUI();
% ************************************************************************
% DRAW EVERYTHING
% ************************************************************************
DrawRawData();
DrawCurrentAircraftState();
%% ************************************************************************
% *** END OF MAIN SCRIPT ***
% NESTED FUNCTION DEFINTIONS FROM HERE ON
% ************************************************************************
%% ************************************************************************
% IMPORTPX4LOGDATA (nested function)
% ************************************************************************
% Attention: This is the import routine for firmware from ca. 03/2013.
% Other firmware versions might require different import
% routines.
%% ************************************************************************
% IMPORTPX4LOGDATA (nested function)
% ************************************************************************
% Attention: This is the import routine for firmware from ca. 03/2013.
% Other firmware versions might require different import
% routines.
function ImportPX4LogData()
% ************************************************************************
% RETRIEVE SYSTEM VECTOR
% *************************************************************************
% //All measurements in NED frame
% Convert to CSV
%arg1 = 'log-fx61-20130721-2.bin';
arg1 = filePath;
delim = ',';
time_field = 'TIME';
data_file = 'data.csv';
csv_null = '';
if not(exist(data_file, 'file'))
s = system( sprintf('python sdlog2_dump.py "%s" -f "%s" -t"%s" -d"%s" -n"%s"', arg1, data_file, time_field, delim, csv_null) );
end
if exist(data_file, 'file')
%data = csvread(data_file);
sysvector = tdfread(data_file, ',');
% shot the flight time
time_us = sysvector.TIME_StartTime(end) - sysvector.TIME_StartTime(1);
time_s = uint64(time_us*1e-6);
time_m = uint64(time_s/60);
time_s = time_s - time_m * 60;
disp([sprintf('Flight log duration: %d:%d (minutes:seconds)', time_m, time_s) char(10)]);
disp(['logfile conversion finished.' char(10)]);
else
disp(['file: ' data_file ' does not exist' char(10)]);
end
end
%% ************************************************************************
% INITCONTROLGUI (nested function)
% ************************************************************************
%Setup central control GUI components to control current time where data is shown
function InitControlGUI()
%**********************************************************************
% GUI size definitions
%**********************************************************************
dxy=5; %margins
%Panel: Plotctrl
dlabels=120;
dsliders=200;
dedits=80;
hslider=20;
hpanel1=40; %panel1
hpanel2=220;%panel2
hpanel3=3*hslider+4*dxy+3*dxy;%panel3.
width=dlabels+dsliders+dedits+4*dxy+2*dxy; %figure width
height=hpanel1+hpanel2+hpanel3+4*dxy; %figure height
%**********************************************************************
% Create GUI
%**********************************************************************
h.figures(1)=figure('Units','pixels','position',[200 200 width height],'Name','Control GUI');
h.guistatepanel=uipanel('Title','Current GUI state','Units','pixels','Position',[dxy dxy width-2*dxy hpanel1],'parent',h.figures(1));
h.aircraftstatepanel=uipanel('Title','Current aircraft state','Units','pixels','Position',[dxy hpanel1+2*dxy width-2*dxy hpanel2],'parent',h.figures(1));
h.plotctrlpanel=uipanel('Title','Plot Control','Units','pixels','Position',[dxy hpanel1+hpanel2+3*dxy width-2*dxy hpanel3],'parent',h.figures(1));
%%Control GUI-elements
%Slider: Current time
h.labels.CurTime=uicontrol(gcf,'style','text','Position',[dxy dxy dlabels hslider],'String','Current time t[s]:','parent',h.plotctrlpanel,'HorizontalAlignment','left');
h.sliders.CurTime=uicontrol(gcf,'style','slider','units','pix','position',[2*dxy+dlabels dxy dsliders hslider],...
'min',mintime,'max',maxtime,'value',mintime,'callback',@curtime_callback,'parent',h.plotctrlpanel);
temp=get(h.sliders.CurTime,'Max')-get(h.sliders.CurTime,'Min');
set(h.sliders.CurTime,'SliderStep',[1.0/temp 5.0/temp]);
h.edits.CurTime=uicontrol(gcf,'style','edit','position',[3*dxy+dlabels+dsliders dxy dedits hslider],'String',get(h.sliders.CurTime,'value'),...
'BackgroundColor','white','callback',@curtime_callback,'parent',h.plotctrlpanel);
%Slider: MaxTime
h.labels.MaxTime=uicontrol(gcf,'style','text','position',[dxy 2*dxy+hslider dlabels hslider],'String','Max. time t[s] to display:','parent',h.plotctrlpanel,'HorizontalAlignment','left');
h.sliders.MaxTime=uicontrol(gcf,'style','slider','units','pix','position',[2*dxy+dlabels 2*dxy+hslider dsliders hslider],...
'min',mintime_log,'max',maxtime_log,'value',maxtime,'callback',@minmaxtime_callback,'parent',h.plotctrlpanel);
h.edits.MaxTime=uicontrol(gcf,'style','edit','position',[3*dxy+dlabels+dsliders 2*dxy+hslider dedits hslider],'String',get(h.sliders.MaxTime,'value'),...
'BackgroundColor','white','callback',@minmaxtime_callback,'parent',h.plotctrlpanel);
%Slider: MinTime
h.labels.MinTime=uicontrol(gcf,'style','text','position',[dxy 3*dxy+2*hslider dlabels hslider],'String','Min. time t[s] to dispay :','parent',h.plotctrlpanel,'HorizontalAlignment','left');
h.sliders.MinTime=uicontrol(gcf,'style','slider','units','pix','position',[2*dxy+dlabels 3*dxy+2*hslider dsliders hslider],...
'min',mintime_log,'max',maxtime_log,'value',mintime,'callback',@minmaxtime_callback,'parent',h.plotctrlpanel);
h.edits.MinTime=uicontrol(gcf,'style','edit','position',[3*dxy+dlabels+dsliders 3*dxy+2*hslider dedits hslider],'String',get(h.sliders.MinTime,'value'),...
'BackgroundColor','white','callback',@minmaxtime_callback,'parent',h.plotctrlpanel);
%%Current data/state GUI-elements (Multiline-edit-box)
h.edits.AircraftState=uicontrol(gcf,'style','edit','Units','normalized','position',[.02 .02 0.96 0.96],'Min',1,'Max',10,'String','This shows the current aircraft state',...
'HorizontalAlignment','left','parent',h.aircraftstatepanel);
h.labels.GUIState=uicontrol(gcf,'style','text','Units','pixels','position',[dxy dxy width-4*dxy hslider],'String','Current state of this GUI',...
'HorizontalAlignment','left','parent',h.guistatepanel);
end
%% ************************************************************************
% INITPLOTGUI (nested function)
% ************************************************************************
function InitPlotGUI()
% Setup handles to lines and text
h.markertext=[];
templinehandle=0.0;%line([0 1],[0 5]); % Just a temporary handle to init array
h.markerline(1:NrAxes)=templinehandle; % the actual handle-array to the lines - these are numbered consecutively
h.markerline(1:NrAxes)=0.0;
% Setup all other figures and axes for plotting
% PLOT WINDOW 1: GPS POSITION
h.figures(2)=figure('units','normalized','Toolbar','figure', 'Name', 'GPS Position');
h.axes(1)=axes();
set(h.axes(1),'Parent',h.figures(2));
% PLOT WINDOW 2: IMU, baro altitude
h.figures(3)=figure('Name', 'IMU / Baro Altitude');
h.axes(2)=subplot(4,1,1);
h.axes(3)=subplot(4,1,2);
h.axes(4)=subplot(4,1,3);
h.axes(5)=subplot(4,1,4);
set(h.axes(2:5),'Parent',h.figures(3));
% PLOT WINDOW 3: ATTITUDE ESTIMATE, ACTUATORS/CONTROLS, AIRSPEEDS,...
h.figures(4)=figure('Name', 'Attitude Estimate / Actuators / Airspeeds');
h.axes(6)=subplot(4,1,1);
h.axes(7)=subplot(4,1,2);
h.axes(8)=subplot(4,1,3);
h.axes(9)=subplot(4,1,4);
set(h.axes(6:9),'Parent',h.figures(4));
% PLOT WINDOW 4: LOG STATS
h.figures(5) = figure('Name', 'Log Statistics');
h.axes(10)=subplot(1,1,1);
set(h.axes(10:10),'Parent',h.figures(5));
end
%% ************************************************************************
% DRAWRAWDATA (nested function)
% ************************************************************************
%Draws the raw data from the sysvector, but does not add any
%marker-lines or so
function DrawRawData()
% ************************************************************************
% PLOT WINDOW 1: GPS POSITION & GUI
% ************************************************************************
figure(h.figures(2));
% Only plot GPS data if available
if (sum(double(sysvector.GPS_Lat(imintime:imaxtime)))>0) && (bDisplayGPS)
%Draw data
plot3(h.axes(1),double(sysvector.GPS_Lat(imintime:imaxtime))*fconv_gpslatlong, ...
double(sysvector.GPS_Lon(imintime:imaxtime))*fconv_gpslatlong, ...
double(sysvector.GPS_Alt(imintime:imaxtime))*fconv_gpsalt,'r.');
title(h.axes(1),'GPS Position Data(if available)');
xlabel(h.axes(1),'Latitude [deg]');
ylabel(h.axes(1),'Longitude [deg]');
zlabel(h.axes(1),'Altitude above MSL [m]');
grid on
%Reset path
h.pathpoints=0;
end
% ************************************************************************
% PLOT WINDOW 2: IMU, baro altitude
% ************************************************************************
figure(h.figures(3));
plot(h.axes(2),time(imintime:imaxtime),[sysvector.IMU_MagX(imintime:imaxtime), sysvector.IMU_MagY(imintime:imaxtime), sysvector.IMU_MagZ(imintime:imaxtime)]);
title(h.axes(2),'Magnetometers [Gauss]');
legend(h.axes(2),'x','y','z');
plot(h.axes(3),time(imintime:imaxtime),[sysvector.IMU_AccX(imintime:imaxtime), sysvector.IMU_AccY(imintime:imaxtime), sysvector.IMU_AccZ(imintime:imaxtime)]);
title(h.axes(3),'Accelerometers [m/s²]');
legend(h.axes(3),'x','y','z');
plot(h.axes(4),time(imintime:imaxtime),[sysvector.IMU_GyroX(imintime:imaxtime), sysvector.IMU_GyroY(imintime:imaxtime), sysvector.IMU_GyroZ(imintime:imaxtime)]);
title(h.axes(4),'Gyroscopes [rad/s]');
legend(h.axes(4),'x','y','z');
plot(h.axes(5),time(imintime:imaxtime),sysvector.SENS_BaroAlt(imintime:imaxtime),'color','blue');
if(bDisplayGPS)
hold on;
plot(h.axes(5),time(imintime:imaxtime),double(sysvector.GPS_Alt(imintime:imaxtime)).*fconv_gpsalt,'color','red');
hold off
legend('Barometric Altitude [m]','GPS Altitude [m]');
else
legend('Barometric Altitude [m]');
end
title(h.axes(5),'Altitude above MSL [m]');
% ************************************************************************
% PLOT WINDOW 3: ATTITUDE ESTIMATE, ACTUATORS/CONTROLS, AIRSPEEDS,...
% ************************************************************************
figure(h.figures(4));
%Attitude Estimate
plot(h.axes(6),time(imintime:imaxtime), [sysvector.ATT_Roll(imintime:imaxtime), sysvector.ATT_Pitch(imintime:imaxtime), sysvector.ATT_Yaw(imintime:imaxtime)] .*180./3.14159);
title(h.axes(6),'Estimated attitude [deg]');
legend(h.axes(6),'roll','pitch','yaw');
%Actuator Controls
plot(h.axes(7),time(imintime:imaxtime), [sysvector.ATTC_Roll(imintime:imaxtime), sysvector.ATTC_Pitch(imintime:imaxtime), sysvector.ATTC_Yaw(imintime:imaxtime), sysvector.ATTC_Thrust(imintime:imaxtime)]);
title(h.axes(7),'Actuator control [-]');
legend(h.axes(7),'ATT CTRL Roll [-1..+1]','ATT CTRL Pitch [-1..+1]','ATT CTRL Yaw [-1..+1]','ATT CTRL Thrust [0..+1]');
%Actuator Controls
plot(h.axes(8),time(imintime:imaxtime), [sysvector.OUT0_Out0(imintime:imaxtime), sysvector.OUT0_Out1(imintime:imaxtime), sysvector.OUT0_Out2(imintime:imaxtime), sysvector.OUT0_Out3(imintime:imaxtime), sysvector.OUT0_Out4(imintime:imaxtime), sysvector.OUT0_Out5(imintime:imaxtime), sysvector.OUT0_Out6(imintime:imaxtime), sysvector.OUT0_Out7(imintime:imaxtime)]);
title(h.axes(8),'Actuator PWM (raw-)outputs [µs]');
legend(h.axes(8),'CH1','CH2','CH3','CH4','CH5','CH6','CH7','CH8');
set(h.axes(8), 'YLim',[800 2200]);
%Airspeeds
plot(h.axes(9),time(imintime:imaxtime), sysvector.AIRS_IndSpeed(imintime:imaxtime));
hold on
plot(h.axes(9),time(imintime:imaxtime), sysvector.AIRS_TrueSpeed(imintime:imaxtime));
hold off
%add GPS total airspeed here
title(h.axes(9),'Airspeed [m/s]');
legend(h.axes(9),'Indicated Airspeed (IAS)','True Airspeed (TAS)','GPS Airspeed');
%calculate time differences and plot them
intervals = zeros(0,imaxtime - imintime);
for k = imintime+1:imaxtime
intervals(k) = time(k) - time(k-1);
end
plot(h.axes(10), time(imintime:imaxtime), intervals);
%Set same timescale for all plots
for i=2:NrAxes
set(h.axes(i),'XLim',[mintime maxtime]);
end
set(h.labels.GUIState,'String','OK','BackgroundColor',[240/255 240/255 240/255]);
end
%% ************************************************************************
% DRAWCURRENTAIRCRAFTSTATE(nested function)
% ************************************************************************
function DrawCurrentAircraftState()
%find current data index
i=find(time>=CurTime,1,'first');
%**********************************************************************
% Current aircraft state label update
%**********************************************************************
acstate{1,:}=[sprintf('%s \t\t','GPS Pos:'),'[lat=',num2str(double(sysvector.GPS_Lat(i))*fconv_gpslatlong),'°, ',...
'lon=',num2str(double(sysvector.GPS_Lon(i))*fconv_gpslatlong),'°, ',...
'alt=',num2str(double(sysvector.GPS_Alt(i))*fconv_gpsalt),'m]'];
acstate{2,:}=[sprintf('%s \t\t','Mags[gauss]'),'[x=',num2str(sysvector.IMU_MagX(i)),...
', y=',num2str(sysvector.IMU_MagY(i)),...
', z=',num2str(sysvector.IMU_MagZ(i)),']'];
acstate{3,:}=[sprintf('%s \t\t','Accels[m/s²]'),'[x=',num2str(sysvector.IMU_AccX(i)),...
', y=',num2str(sysvector.IMU_AccY(i)),...
', z=',num2str(sysvector.IMU_AccZ(i)),']'];
acstate{4,:}=[sprintf('%s \t\t','Gyros[rad/s]'),'[x=',num2str(sysvector.IMU_GyroX(i)),...
', y=',num2str(sysvector.IMU_GyroY(i)),...
', z=',num2str(sysvector.IMU_GyroZ(i)),']'];
acstate{5,:}=[sprintf('%s \t\t','Altitude[m]'),'[Barometric: ',num2str(sysvector.SENS_BaroAlt(i)),'m, GPS: ',num2str(double(sysvector.GPS_Alt(i))*fconv_gpsalt),'m]'];
acstate{6,:}=[sprintf('%s \t','Est. attitude[deg]:'),'[Roll=',num2str(sysvector.ATT_Roll(i).*180./3.14159),...
', Pitch=',num2str(sysvector.ATT_Pitch(i).*180./3.14159),...
', Yaw=',num2str(sysvector.ATT_Yaw(i).*180./3.14159),']'];
acstate{7,:}=sprintf('%s \t[','Actuator Ctrls [-]:');
%for j=1:4
acstate{7,:}=[acstate{7,:},num2str(sysvector.ATTC_Roll(i)),','];
acstate{7,:}=[acstate{7,:},num2str(sysvector.ATTC_Pitch(i)),','];
acstate{7,:}=[acstate{7,:},num2str(sysvector.ATTC_Yaw(i)),','];
acstate{7,:}=[acstate{7,:},num2str(sysvector.ATTC_Thrust(i)),','];
%end
acstate{7,:}=[acstate{7,:},']'];
acstate{8,:}=sprintf('%s \t[','Actuator Outputs [PWM/µs]:');
%for j=1:8
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out0(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out1(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out2(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out3(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out4(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out5(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out6(i)),','];
acstate{8,:}=[acstate{8,:},num2str(sysvector.OUT0_Out7(i)),','];
%end
acstate{8,:}=[acstate{8,:},']'];
acstate{9,:}=[sprintf('%s \t','Airspeed[m/s]:'),'[IAS: ',num2str(sysvector.AIRS_IndSpeed(i)),', TAS: ',num2str(sysvector.AIRS_TrueSpeed(i)),']'];
set(h.edits.AircraftState,'String',acstate);
%**********************************************************************
% GPS Plot Update
%**********************************************************************
%Plot traveled path, and and time.
figure(h.figures(2));
hold on;
if(CurTime>mintime+1) %the +1 is only a small bugfix
h.pathline=plot3(h.axes(1),double(sysvector.GPS_Lat(imintime:i))*fconv_gpslatlong, ...
double(sysvector.GPS_Lon(imintime:i))*fconv_gpslatlong, ...
double(sysvector.GPS_Alt(imintime:i))*fconv_gpsalt,'b','LineWidth',2);
end;
hold off
%Plot current position
newpoint=[double(sysvector.GPS_Lat(i))*fconv_gpslatlong double(sysvector.GPS_Lat(i))*fconv_gpslatlong double(sysvector.GPS_Alt(i))*fconv_gpsalt];
if(numel(h.pathpoints)<=3) %empty path
h.pathpoints(1,1:3)=newpoint;
else %Not empty, append new point
h.pathpoints(size(h.pathpoints,1)+1,:)=newpoint;
end
axes(h.axes(1));
line(h.pathpoints(:,1),h.pathpoints(:,2),h.pathpoints(:,3),'LineStyle','none','Marker','.','MarkerEdge','black','MarkerSize',20);
% Plot current time (small label next to current gps position)
textdesc=strcat(' t=',num2str(time(i)),'s');
if(isvalidhandle(h.markertext))
delete(h.markertext); %delete old text
end
h.markertext=text(double(sysvector.GPS_Lat(i))*fconv_gpslatlong,double(sysvector.GPS_Lon(i))*fconv_gpslatlong,...
double(sysvector.GPS_Alt(i))*fconv_gpsalt,textdesc);
set(h.edits.CurTime,'String',CurTime);
%**********************************************************************
% Plot the lines showing the current time in the 2-d plots
%**********************************************************************
for i=2:NrAxes
if(isvalidhandle(h.markerline(i))) delete(h.markerline(i)); end
ylims=get(h.axes(i),'YLim');
h.markerline(i)=line([CurTime CurTime] ,get(h.axes(i),'YLim'),'Color','black');
set(h.markerline(i),'parent',h.axes(i));
end
set(h.labels.GUIState,'String','OK','BackgroundColor',[240/255 240/255 240/255]);
end
%% ************************************************************************
% MINMAXTIME CALLBACK (nested function)
% ************************************************************************
function minmaxtime_callback(hObj,event) %#ok<INUSL>
new_mintime=get(h.sliders.MinTime,'Value');
new_maxtime=get(h.sliders.MaxTime,'Value');
%Safety checks:
bErr=false;
%1: mintime must be < maxtime
if((new_mintime>maxtime) || (new_maxtime<mintime))
set(h.labels.GUIState,'String','Error: Mintime cannot be bigger than maxtime! Values were not changed.','BackgroundColor','red');
bErr=true;
else
%2: MinTime must be <=CurTime
if(new_mintime>CurTime)
set(h.labels.GUIState,'String','Error: Mintime cannot be bigger than CurTime! CurTime set to new mintime.','BackgroundColor','red');
mintime=new_mintime;
CurTime=mintime;
bErr=true;
end
%3: MaxTime must be >CurTime
if(new_maxtime<CurTime)
set(h.labels.GUIState,'String','Error: Maxtime cannot be smaller than CurTime! CurTime set to new maxtime.','BackgroundColor','red');
maxtime=new_maxtime;
CurTime=maxtime;
bErr=true;
end
end
if(bErr==false)
maxtime=new_maxtime;
mintime=new_mintime;
end
%Needs to be done in case values were reset above
set(h.sliders.MinTime,'Value',mintime);
set(h.sliders.MaxTime,'Value',maxtime);
%Update curtime-slider
set(h.sliders.CurTime,'Value',CurTime);
set(h.sliders.CurTime,'Max',maxtime);
set(h.sliders.CurTime,'Min',mintime);
temp=get(h.sliders.CurTime,'Max')-get(h.sliders.CurTime,'Min');
set(h.sliders.CurTime,'SliderStep',[1.0/temp 5.0/temp]); %Set Stepsize to constant [in seconds]
%update edit fields
set(h.edits.CurTime,'String',get(h.sliders.CurTime,'Value'));
set(h.edits.MinTime,'String',get(h.sliders.MinTime,'Value'));
set(h.edits.MaxTime,'String',get(h.sliders.MaxTime,'Value'));
%Finally, we have to redraw. Update time indices first.
[imintime,imaxtime]=FindMinMaxTimeIndices();
DrawRawData(); %Rawdata only
DrawCurrentAircraftState(); %path info & markers
end
%% ************************************************************************
% CURTIME CALLBACK (nested function)
% ************************************************************************
function curtime_callback(hObj,event) %#ok<INUSL>
%find current time
if(hObj==h.sliders.CurTime)
CurTime=get(h.sliders.CurTime,'Value');
elseif (hObj==h.edits.CurTime)
temp=str2num(get(h.edits.CurTime,'String'));
if(temp<maxtime && temp>mintime)
CurTime=temp;
else
%Error
set(h.labels.GUIState,'String','Error: You tried to set an invalid current time! Previous value restored.','BackgroundColor','red');
end
else
%Error
set(h.labels.GUIState,'String','Error: curtime_callback','BackgroundColor','red');
end
set(h.sliders.CurTime,'Value',CurTime);
set(h.edits.CurTime,'String',num2str(CurTime));
%Redraw time markers, but don't have to redraw the whole raw data
DrawCurrentAircraftState();
end
%% ************************************************************************
% FINDMINMAXINDICES (nested function)
% ************************************************************************
function [idxmin,idxmax] = FindMinMaxTimeIndices()
for i=1:size(sysvector.TIME_StartTime,1)
if time(i)>=mintime; idxmin=i; break; end
end
for i=1:size(sysvector.TIME_StartTime,1)
if maxtime==0; idxmax=size(sysvector.TIME_StartTime,1); break; end
if time(i)>=maxtime; idxmax=i; break; end
end
mintime=time(idxmin);
maxtime=time(idxmax);
end
%% ************************************************************************
% ISVALIDHANDLE (nested function)
% ************************************************************************
function isvalid = isvalidhandle(handle)
if(exist(varname(handle))>0 && length(ishandle(handle))>0)
if(ishandle(handle)>0)
if(handle>0.0)
isvalid=true;
return;
end
end
end
isvalid=false;
end
%% ************************************************************************
% JUST SOME SMALL HELPER FUNCTIONS (nested function)
% ************************************************************************
function out = varname(var)
out = inputname(1);
end
%This is the end of the matlab file / the main function
end
-343
View File
@@ -1,343 +0,0 @@
#!/usr/bin/env python
from __future__ import print_function
"""Dump binary log generated by PX4's sdlog2 or APM as CSV
Usage: python sdlog2_dump.py <log.bin> [-v] [-e] [-d delimiter] [-n null] [-m MSG[_field1,field2,...]]
Example Usage: python sdlog2_dump.py log.bin -m GPS_Lat,Lng -m AHR2_Roll,Pitch,Yaw
-v Use plain debug output instead of CSV.
-e Recover from errors.
-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.
"""
__author__ = "Anton Babushkin"
__version__ = "1.2"
import struct, sys
if sys.hexversion >= 0x030000F0:
runningPython3 = True
def _parseCString(cstr):
return str(cstr, 'ascii').split('\0')[0]
else:
runningPython3 = False
def _parseCString(cstr):
return str(cstr).split('\0')[0]
class SDLog2Parser:
BLOCK_SIZE = 8192
MSG_HEADER_LEN = 3
MSG_HEAD1 = 0xA3
MSG_HEAD2 = 0x95
MSG_FORMAT_PACKET_LEN = 89
MSG_FORMAT_STRUCT = "BB4s16s64s"
MSG_TYPE_FORMAT = 0x80
FORMAT_TO_STRUCT = {
"b": ("b", None),
"B": ("B", None),
"h": ("h", None),
"H": ("H", None),
"i": ("i", None),
"I": ("I", None),
"f": ("f", None),
"d": ("d", 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),
}
__csv_delim = ","
__csv_null = ""
__msg_filter = []
__time_msg = None
__debug_out = False
__correct_errors = False
__file_name = None
__file = None
def __init__(self):
return
def reset(self):
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
self.__buffer = bytearray() # buffer for input binary data
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
self.__csv_updated = False
self.__msg_filter_map = {} # filter in form of map, with '*" expanded to full list of fields
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
def setTimeMsg(self, time_msg):
self.__time_msg = time_msg
def setDebugOut(self, debug_out):
self.__debug_out = debug_out
def setCorrectErrors(self, correct_errors):
self.__correct_errors = correct_errors
def setFileName(self, file_name):
self.__file_name = file_name
if file_name != None:
self.__file = open(file_name, 'w+')
else:
self.__file = None
def process(self, fn):
self.reset()
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
first_data_msg = True
f = open(fn, "rb")
bytes_read = 0
while True:
chunk = f.read(self.BLOCK_SIZE)
if len(chunk) == 0:
break
self.__buffer = self.__buffer[self.__ptr:] + chunk
self.__ptr = 0
while self.__bytesLeft() >= self.MSG_HEADER_LEN:
head1 = self.__buffer[self.__ptr]
head2 = self.__buffer[self.__ptr+1]
if (head1 != self.MSG_HEAD1 or head2 != self.MSG_HEAD2):
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))
msg_type = self.__buffer[self.__ptr+2]
if msg_type == self.MSG_TYPE_FORMAT:
# parse FORMAT message
if self.__bytesLeft() < self.MSG_FORMAT_PACKET_LEN:
break
self.__parseMsgDescr()
else:
# parse data message
msg_descr = self.__msg_descrs.get(msg_type, None)
if msg_descr == None:
if self.__correct_errors:
self.__ptr += 1
continue
else:
raise Exception("Unknown msg type: %i" % msg_type)
msg_length = msg_descr[0]
if self.__bytesLeft() < msg_length:
break
if first_data_msg:
# build CSV columns and init data map
if not self.__debug_out:
self.__initCSV()
first_data_msg = False
self.__parseMsg(msg_descr)
bytes_read += self.__ptr
if not self.__debug_out and self.__time_msg != None and self.__csv_updated:
self.__printCSVRow()
f.close()
def __bytesLeft(self):
return len(self.__buffer) - self.__ptr
def __filterMsg(self, msg_name):
show_fields = "*"
if len(self.__msg_filter_map) > 0:
show_fields = self.__msg_filter_map.get(msg_name)
return show_fields
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:
full_label = msg_name + "_" + field
self.__csv_columns.append(full_label)
self.__csv_data[full_label] = None
if self.__file != None:
print(self.__csv_delim.join(self.__csv_columns), file=self.__file)
else:
print(self.__csv_delim.join(self.__csv_columns))
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)
if self.__file != None:
print(self.__csv_delim.join(s), file=self.__file)
else:
print(self.__csv_delim.join(s))
def __parseMsgDescr(self):
if runningPython3:
data = struct.unpack(self.MSG_FORMAT_STRUCT, self.__buffer[self.__ptr + 3 : self.__ptr + self.MSG_FORMAT_PACKET_LEN])
else:
data = struct.unpack(self.MSG_FORMAT_STRUCT, str(self.__buffer[self.__ptr + 3 : self.__ptr + self.MSG_FORMAT_PACKET_LEN]))
msg_type = data[0]
if msg_type != self.MSG_TYPE_FORMAT:
msg_length = data[1]
msg_name = _parseCString(data[2])
msg_format = _parseCString(data[3])
msg_labels = _parseCString(data[4]).split(",")
# 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)
if self.__debug_out:
if self.__filterMsg(msg_name) != None:
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))
self.__ptr += self.MSG_FORMAT_PACKET_LEN
def __parseMsg(self, msg_descr):
msg_length, msg_name, msg_format, msg_labels, msg_struct, msg_mults = msg_descr
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
show_fields = self.__filterMsg(msg_name)
if (show_fields != None):
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])))
for i in range(len(data)):
if type(data[i]) is str:
data[i] = _parseCString(data[i])
m = msg_mults[i]
if m != None:
data[i] = data[i] * m
if self.__debug_out:
s = []
for i in range(len(data)):
label = msg_labels[i]
if show_fields == "*" or label in show_fields:
s.append(label + "=" + str(data[i]))
print("MSG %s: %s" % (msg_name, ", ".join(s)))
else:
# update CSV data buffer
for i in range(len(data)):
label = msg_labels[i]
if label in show_fields:
self.__csv_data[msg_name + "_" + label] = data[i]
if self.__time_msg != None and msg_name != self.__time_msg:
self.__csv_updated = True
if self.__time_msg == None:
self.__printCSVRow()
self.__ptr += msg_length
def _main():
if len(sys.argv) < 2:
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\tUse plain debug output instead of CSV.\n")
print("\t-e\tRecover from errors.\n")
print("\t-d\tUse \"delimiter\" in CSV. Default is \",\".\n")
print("\t-n\tUse \"null\" as placeholder for empty values in CSV. Default is empty.\n")
print("\t-m MSG[_field1,field2,...]\n\t\tDump only messages of specified type, and only specified fields.\n\t\tMultiple -m options allowed.")
print("\t-t\tSpecify TIME message name to group data messages by time and significantly reduce duplicate output.\n")
print("\t-fPrint to file instead of stdout")
return
fn = sys.argv[1]
debug_out = False
correct_errors = False
msg_filter = []
csv_null = ""
csv_delim = ","
time_msg = "TIME"
file_name = None
opt = None
for arg in sys.argv[2:]:
if opt != None:
if opt == "d":
csv_delim = arg
elif opt == "n":
csv_null = arg
elif opt == "t":
time_msg = arg
elif opt == "f":
file_name = arg
elif opt == "m":
show_fields = "*"
a = arg.split("_")
if len(a) > 1:
show_fields = a[1].split(",")
msg_filter.append((a[0], show_fields))
opt = None
else:
if arg == "-v":
debug_out = True
elif arg == "-e":
correct_errors = True
elif arg == "-d":
opt = "d"
elif arg == "-n":
opt = "n"
elif arg == "-m":
opt = "m"
elif arg == "-t":
opt = "t"
elif arg == "-f":
opt = "f"
if csv_delim == "\\t":
csv_delim = "\t"
parser = SDLog2Parser()
parser.setCSVDelimiter(csv_delim)
parser.setCSVNull(csv_null)
parser.setMsgFilter(msg_filter)
parser.setTimeMsg(time_msg)
parser.setFileName(file_name)
parser.setDebugOut(debug_out)
parser.setCorrectErrors(correct_errors)
parser.process(fn)
if __name__ == "__main__":
_main()
-1
View File
@@ -222,7 +222,6 @@ class Graph:
special_cases_sub = [
('sensors', r'voted_sensors_update\.cpp$', r'\binit_sensor_class\b\(([^,)]+)', r'^meta$'),
('mavlink', r'.*', r'\badd_orb_subscription\b\(([^,)]+)', r'^_topic$'),
('sdlog2', r'.*', None, r'^topic$'),
('logger', r'.*', None, r'^(topic|sub\.metadata|_polling_topic_meta)$'),
('uavcan', r'uavcan_main\.cpp$', r'\b_control_topics\[[0-9]\]=([^,)]+)', r'^_control_topics\[i\]$'),