#!/usr/bin/env python ''' CSV log file reader Copyright Peter Barker 2020 Released under GNU GPL version 3 or later Timestamp must be in first column, microseconds First row must be column headings. e.g. pbarker@bluebottle:~/rc/pymavlink(pr/csvreader)$ head -2 $LOG WEEK_NB;TOW;ACCL_X;ACCL_Y;ACCL_Z;GYRO_X;GYRO_Y;GYRO_Z;TEMP;STATUS;COUNTER;VALID_CHCKSM 2100.0;371149.08302956074;-0.25742456316947937;-0.017161637544631958;9.684066772460938;0.003054326167330146;-0.00578140327706933;0.0006544984644278884;34.599998474121094;0.0;54637.0;0.0 pbarker@bluebottle:~/rc/pymavlink(pr/csvreader)$ MAVExplorer.py "csv:$LOG:separator=;:timestamp_expression=gps_time_to_epoch(CSV.WEEK_NB,CSV.TOW*1000.0)" MAV> graph CSV.GYRO_X in this case the GPS time was in seconds-since-week-start, so a conversion to ms is required ''' from __future__ import print_function from builtins import range from builtins import object import csv import struct import os from . import mavutil from . import mavextra from . import mavexpression class CSVMessage(object): def __init__(self, message_type, fmt, line): self.fmt = fmt self.message_type = message_type self.line = [] for entry in line: try: self.line.append(float(entry)) except ValueError: self.line.append(entry) def get_type(self): return self.message_type def __str__(self): ret = "%s {" % self.message_type for (c, val) in zip(self.fmt.headings, self.line): ret += "%s : %s, " % (c, str(val)) return ret + '}' # methods for MAVExplorer: def get_fieldnames(self): return self.fmt.headings # end methods for MAVExplorer def __getattr__(self, field): '''override field getter''' if field == '_timestamp': if self.fmt.timestamp_expression is not None: return mavexpression.evaluate_expression(self.fmt.timestamp_expression, self.fmt.messages) return int(self.line[0]) return self.line[self.fmt.field_offset[field]] class CSVFormat(object): def __init__(self, headings, messages, timestamp_expression=None): self.headings = headings self.messages = messages self.timestamp_expression = timestamp_expression # map from a field name to an offset in the line: self.field_offset = {} count = 0 for heading in self.headings: self.field_offset[heading] = count count += 1 class CSVReader(object): '''parse a CSV file''' def __init__(self, filename, zero_time_base=False, progress_callback=None, separator=';', message_type='CSV', timestamp_expression=None, ): separator = os.environ.get("CSV_SEPARATOR", separator) self.messages = { 'MAV' : self } self.filename = filename self.separator = separator self.message_type = message_type self.progress_callback = progress_callback self.timestamp_expression = timestamp_expression self.timestamp = 0 self.verbose = False self.f = None self.linecount = None self.params = {} self._rewind() # opens files etc etc self.recv_msg() # populate self.messages self._rewind() # opens files etc etc # start attributes for MAVExplorer self._flightmodes = [] # end attributes for MAVExplorer # start methods for MAVExplorer: def flightmode_list(self): return self._flightmodes @property def _count(self): # implicitly rewinds if self.linecount is None: self.linecount = self.count_lines() return self.linecount def count_lines(self): self._rewind() count = 0 while True: try: if count == 0: if self._parse_next() == None: raise StopIteration else: next(self.reader) count += 1 except StopIteration: self._rewind() return count def rewind(self): self._rewind() def name_to_id(self, name): return 1 # FIXME # end methods for MAVExplorer def _rewind(self): '''reset state on rewind''' self.percent = 0 if self.f is not None: self.f.close() self.f = open(self.filename, mode='r') self.reader = csv.reader(self.f, delimiter=self.separator) self.fmt = CSVFormat(next(self.reader), self.messages, timestamp_expression=self.timestamp_expression) def recv_msg(self): return self._parse_next() def recv_match(self, condition=None, type=None, blocking=False): # print("recv_match called (condition=%s type=%s blocking=%s" % (str(condition), str(type), str(blocking))) '''recv the next message that matches the given condition type can be a string or a list of strings''' if type is not None and not isinstance(type, list) and not isinstance(type, set): type = [type] while True: m = self.recv_msg() if m is None: return None if type is not None and not m.get_type() in type: continue if not mavutil.evaluate_condition(condition, self.messages): continue return m def check_condition(self, condition): '''check if a condition is true''' return mavutil.evaluate_condition(condition, self.messages) def _parse_next(self): '''read one message, returning it as an object''' try: line = next(self.reader) except StopIteration: return None m = CSVMessage(self.message_type, self.fmt, line) self._add_msg(m) return m def _add_msg(self, m): '''add a new message''' self.messages[self.message_type] = m if __name__ == "__main__": print("FIXME")