Added debug plotting of the fridge moving.
Change-Id: I879fa5b63e831680683e84884e36fb57132d014b
diff --git a/frc971/analysis/analysis.py b/frc971/analysis/analysis.py
index 046c789..66c5056 100755
--- a/frc971/analysis/analysis.py
+++ b/frc971/analysis/analysis.py
@@ -1,4 +1,106 @@
#!/usr/bin/python3
+import matplotlib
+from matplotlib import pylab
+
+class Dataset(object):
+ def __init__(self):
+ self.time = []
+ self.data = []
+
+ def Add(self, time, data):
+ self.time.append(time)
+ self.data.append(data)
+
+
+class Plotter(object):
+ def __init__(self):
+ self.signal = dict()
+
+ def Add(self, binary, struct_instance_name, *data_search_path):
+ """
+ Specifies a specific piece of data to plot
+
+ Args:
+ binary: str, The name of the executable that generated the log.
+ struct_instance_name: str, The name of the struct instance whose data
+ contents should be plotted.
+ data_search_path: [str], The path into the struct of the exact piece of
+ data to plot.
+
+ Returns:
+ None
+ """
+ self.signal[(binary, struct_instance_name, data_search_path)] = Dataset()
+
+ def HandleLine(self, line):
+ """
+ Parses a line from a log file and adds the data to the plot data.
+
+ Args:
+ line: str, The line from the log file to parse
+
+ Returns:
+ None
+ """
+ pline = ParseLine(line)
+ for key in self.signal:
+ value = self.signal[key]
+ binary = key[0]
+ struct_instance_name = key[1]
+ data_search_path = key[2]
+
+ # Make sure that we're looking at the right binary structure instance.
+ if binary == pline.name:
+ if pline.msg.startswith(struct_instance_name + ': '):
+ # Parse the structure and traverse it as specified in
+ # `data_search_path`. This lets the user access very deeply nested
+ # structures.
+ _, _, data = pline.ParseStruct()
+ for path in data_search_path:
+ data = data[path]
+
+ value.Add(pline.time, data)
+
+ def Plot(self):
+ """
+ Plots all the data after it's parsed.
+
+ This should only be called after `HandleFile` has been called so that there
+ is actual data to plot.
+ """
+ for key in self.signal:
+ value = self.signal[key]
+ pylab.plot(value.time, value.data, label=key[0] + ' ' + '.'.join(key[2]))
+ pylab.legend()
+ pylab.show()
+
+ def PlotFile(self, f):
+ """
+ Parses and plots all the data.
+
+ Args:
+ f: str, The filename of the log whose data to parse and plot.
+
+ Returns:
+ None
+ """
+ self.HandleFile(f)
+ self.Plot()
+
+ def HandleFile(self, f):
+ """
+ Parses the specified log file.
+
+ Args:
+ f: str, The filename of the log whose data to parse.
+
+ Returns:
+ None
+ """
+ with open(f, 'r') as fd:
+ for line in fd:
+ self.HandleLine(line)
+
class LogEntry:
"""This class provides a way to parse log entries."""
diff --git a/frc971/analysis/plot_action.py b/frc971/analysis/plot_action.py
new file mode 100755
index 0000000..1adb4db
--- /dev/null
+++ b/frc971/analysis/plot_action.py
@@ -0,0 +1,78 @@
+#!/usr/bin/python3
+
+import sys
+import numpy
+import analysis
+import argparse
+
+def ReadPlotDefinitions(filename):
+ """
+ Read a file with plotting definitions.
+
+ A plotting definition is a single line that defines what data to search for
+ in order to plot it. The following in a file would duplicate the default
+ behaviour:
+
+ fridge goal height
+ fridge goal angle
+ fridge goal velocity
+ fridge goal angular_velocity
+ fridge output left_arm
+ fridge output right_arm
+ fridge output left_elevator
+ fridge output right_elevator
+
+ Lines are ignored if they start with a hash mark (i.e. '#').
+
+ Args:
+ filename: The name of the file to read the definitions from.
+
+ Returns:
+ [[str]]: The definitions in the specified file.
+ """
+ defs = []
+ with open(filename) as fd:
+ for line in fd:
+ raw_defs = line.split()
+
+ # Only add to the list of definitions if the line's not empty and it
+ # doesn't start with a hash.
+ if raw_defs and not raw_defs[0].startswith('#'):
+ defs.append(raw_defs)
+
+ return defs
+
+def main():
+ # Parse all command line arguments.
+ arg_parser = argparse.ArgumentParser(description='Log Plotter')
+ arg_parser.add_argument('log_file', metavar='LOG_FILE', type=str, \
+ help='The file from which to read logs and plot.')
+ arg_parser.add_argument('--plot-defs', action='store', type=str, \
+ help='Read the items to plot from this file.')
+
+ args = arg_parser.parse_args(sys.argv[1:])
+
+ p = analysis.Plotter()
+
+ # If the user defines the list of data to plot in a file, read it from there.
+ if args.plot_defs:
+ defs = ReadPlotDefinitions(args.plot_defs)
+ for definition in defs:
+ p.Add(definition[0], definition[1], *definition[2:])
+
+ # Otherwise use a pre-defined set of data to plot.
+ else:
+ p.Add('fridge', 'goal', 'height')
+ p.Add('fridge', 'goal', 'angle')
+ p.Add('fridge', 'goal', 'velocity')
+ p.Add('fridge', 'goal', 'angular_velocity')
+
+ p.Add('fridge', 'output', 'left_arm')
+ p.Add('fridge', 'output', 'right_arm')
+ p.Add('fridge', 'output', 'left_elevator')
+ p.Add('fridge', 'output', 'right_elevator')
+
+ p.PlotFile(args.log_file)
+
+if __name__ == '__main__':
+ main()