Move generic packages from frc971/analysis to aos
To eliminate a dependency of aos on frc971.
Signed-off-by: Stephan Pleines <pleines.stephan@gmail.com>
Change-Id: Ic4a8f75da29f8e8c6675d96f02824cd08a9b99be
diff --git a/frc971/analysis/BUILD b/frc971/analysis/BUILD
index 93f50f4..b179ad9 100644
--- a/frc971/analysis/BUILD
+++ b/frc971/analysis/BUILD
@@ -1,37 +1,7 @@
-load("//aos/flatbuffers:generate.bzl", "static_flatbuffer")
load("//tools/build_rules:js.bzl", "rollup_bundle", "ts_project")
-load("@com_github_google_flatbuffers//:typescript.bzl", "flatbuffer_ts_library")
-load("//aos:config.bzl", "aos_config")
package(default_visibility = ["//visibility:public"])
-cc_binary(
- name = "py_log_reader.so",
- srcs = ["py_log_reader.cc"],
- linkshared = True,
- target_compatible_with = ["@platforms//os:linux"],
- deps = [
- "//aos:configuration",
- "//aos:json_to_flatbuffer",
- "//aos/events:shm_event_loop",
- "//aos/events:simulated_event_loop",
- "//aos/events/logging:log_reader",
- "//third_party/python",
- "@com_github_google_glog//:glog",
- ],
-)
-
-py_test(
- name = "log_reader_test",
- srcs = ["log_reader_test.py"],
- data = [
- ":py_log_reader.so",
- "@sample_logfile//file",
- ],
- target_compatible_with = ["@platforms//os:linux"],
- deps = ["//aos:configuration_fbs_python"],
-)
-
ts_project(
name = "plot_index",
srcs = ["plot_index.ts"],
@@ -73,15 +43,6 @@
],
)
-genrule(
- name = "copy_css",
- srcs = [
- "//aos/network/www:styles.css",
- ],
- outs = ["styles.css"],
- cmd = "cp $< $@",
-)
-
filegroup(
name = "plotter_files",
srcs = [
@@ -112,67 +73,13 @@
target_compatible_with = ["@platforms//os:linux"],
)
-static_flatbuffer(
- name = "plot_data_fbs",
+genrule(
+ name = "copy_css",
srcs = [
- "plot_data.fbs",
+ "//aos/network/www:styles.css",
],
- target_compatible_with = ["@platforms//os:linux"],
-)
-
-flatbuffer_ts_library(
- name = "plot_data_ts_fbs",
- srcs = [
- "plot_data.fbs",
- ],
- target_compatible_with = ["@platforms//os:linux"],
-)
-
-ts_project(
- name = "plot_data_utils",
- srcs = ["plot_data_utils.ts"],
- visibility = ["//visibility:public"],
- deps = [
- ":plot_data_ts_fbs",
- "//aos:configuration_ts_fbs",
- "//aos/network/www:aos_plotter",
- "//aos/network/www:plotter",
- "//aos/network/www:proxy",
- "@com_github_google_flatbuffers//reflection:reflection_ts_fbs",
- "@com_github_google_flatbuffers//ts:flatbuffers_ts",
- ],
-)
-
-aos_config(
- name = "plotter",
- src = "plotter_config.json",
- flatbuffers = [":plot_data_fbs"],
- target_compatible_with = ["@platforms//os:linux"],
- deps = ["//aos/events:aos_config"],
-)
-
-cc_library(
- name = "in_process_plotter",
- srcs = ["in_process_plotter.cc"],
- hdrs = ["in_process_plotter.h"],
- data = [
- ":plotter",
- "//frc971/analysis/cpp_plot:cpp_plot_files",
- ],
- deps = [
- ":plot_data_fbs",
- "//aos/events:simulated_event_loop",
- "//aos/network:web_proxy",
- ],
-)
-
-cc_binary(
- name = "in_process_plotter_demo",
- srcs = ["in_process_plotter_demo.cc"],
- deps = [
- ":in_process_plotter",
- "//aos:init",
- ],
+ outs = ["styles.css"],
+ cmd = "cp $< $@",
)
cc_binary(
@@ -204,18 +111,6 @@
],
)
-cc_binary(
- name = "local_foxglove",
- srcs = ["local_foxglove.cc"],
- data = ["@foxglove_studio"],
- deps = [
- "//aos:init",
- "//aos/network:gen_embedded",
- "//aos/seasocks:seasocks_logger",
- "//third_party/seasocks",
- ],
-)
-
py_binary(
name = "trim_and_plot_foxglove",
srcs = ["trim_and_plot_foxglove.py"],
diff --git a/frc971/analysis/cpp_plot/BUILD b/frc971/analysis/cpp_plot/BUILD
deleted file mode 100644
index 7f34afe..0000000
--- a/frc971/analysis/cpp_plot/BUILD
+++ /dev/null
@@ -1,42 +0,0 @@
-load("//tools/build_rules:js.bzl", "rollup_bundle", "ts_project")
-
-package(default_visibility = ["//visibility:public"])
-
-ts_project(
- name = "cpp_plot",
- srcs = ["cpp_plot.ts"],
- target_compatible_with = ["@platforms//os:linux"],
- deps = [
- "//aos:configuration_ts_fbs",
- "//aos/network/www:proxy",
- "//frc971/analysis:plot_data_utils",
- ],
-)
-
-rollup_bundle(
- name = "cpp_plot_bundle",
- entry_point = "cpp_plot.ts",
- target_compatible_with = ["@platforms//os:linux"],
- deps = [
- ":cpp_plot",
- ],
-)
-
-genrule(
- name = "copy_css",
- srcs = [
- "//aos/network/www:styles.css",
- ],
- outs = ["styles.css"],
- cmd = "cp $< $@",
-)
-
-filegroup(
- name = "cpp_plot_files",
- srcs = [
- "cpp_plot_bundle.js",
- "cpp_plot_bundle.min.js",
- "index.html",
- "styles.css",
- ],
-)
diff --git a/frc971/analysis/cpp_plot/cpp_plot.ts b/frc971/analysis/cpp_plot/cpp_plot.ts
deleted file mode 100644
index 08e2b1c..0000000
--- a/frc971/analysis/cpp_plot/cpp_plot.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-// Plotter for the C++ in-process plotter.
-import {Configuration} from '../../../aos/configuration_generated';
-import {Connection} from '../../../aos/network/www/proxy';
-import {plotData} from '../plot_data_utils';
-
-const rootDiv = document.createElement('div');
-rootDiv.classList.add('aos_cpp_plot');
-document.body.appendChild(rootDiv);
-
-const conn = new Connection();
-conn.connect();
-
-conn.addConfigHandler((config: Configuration) => {
- plotData(conn, rootDiv);
-});
diff --git a/frc971/analysis/cpp_plot/index.html b/frc971/analysis/cpp_plot/index.html
deleted file mode 100644
index fbb5199..0000000
--- a/frc971/analysis/cpp_plot/index.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<html>
- <head>
- <script src="cpp_plot_bundle.min.js" defer></script>
- <link rel="stylesheet" href="styles.css">
- </head>
- <body>
- </body>
-</html>
-
diff --git a/frc971/analysis/in_process_plotter.cc b/frc971/analysis/in_process_plotter.cc
deleted file mode 100644
index b537aa4..0000000
--- a/frc971/analysis/in_process_plotter.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-#include "frc971/analysis/in_process_plotter.h"
-
-#include "aos/configuration.h"
-
-namespace frc971::analysis {
-
-namespace {
-const char *kDataPath = "frc971/analysis/cpp_plot";
-const char *kConfigPath = "frc971/analysis/plotter.json";
-} // namespace
-
-Plotter::Plotter()
- : config_(aos::configuration::ReadConfig(kConfigPath)),
- event_loop_factory_(&config_.message()),
- event_loop_(event_loop_factory_.MakeEventLoop("plotter")),
- plot_sender_(event_loop_->MakeSender<Plot>("/analysis")),
- web_proxy_(event_loop_.get(), event_loop_factory_.scheduler_epoll(),
- aos::web_proxy::StoreHistory::kYes, -1),
- builder_(plot_sender_.MakeBuilder()) {
- web_proxy_.SetDataPath(kDataPath);
- event_loop_->SkipTimingReport();
-
- color_wheel_.emplace_back(ColorWheelColor{.name = "red", .color = {1, 0, 0}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "green", .color = {0, 1, 0}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "purple", .color = {0.54, 0.3, 0.75}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "blue", .color = {0, 0, 1}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "yellow", .color = {1, 1, 0}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "teal", .color = {0, 1, 1}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "pink", .color = {1, 0, 1}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "orange", .color = {1, 0.6, 0}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "brown", .color = {0.6, 0.3, 0}});
- color_wheel_.emplace_back(
- ColorWheelColor{.name = "white", .color = {1, 1, 1}});
-}
-
-void Plotter::Spin() {
- // Set non-infinite replay rate to avoid pegging a full CPU.
- event_loop_factory_.SetRealtimeReplayRate(1.0);
- event_loop_factory_.Run();
-}
-
-void Plotter::Title(std::string_view title) {
- title_ = builder_.fbb()->CreateString(title);
-}
-
-void Plotter::AddFigure(std::string_view title, double width, double height) {
- MaybeFinishFigure();
-
- if (!title.empty()) {
- figure_title_ = builder_.fbb()->CreateString(title);
- }
-
- // For positioning, just stack figures vertically.
- auto position_builder = builder_.MakeBuilder<Position>();
- position_builder.add_top(next_top_);
- position_builder.add_left(0);
- position_builder.add_width(width);
- position_builder.add_height(height);
- position_ = position_builder.Finish();
-
- next_top_ += height;
-}
-
-void Plotter::XLabel(std::string_view label) {
- xlabel_ = builder_.fbb()->CreateString(label);
-}
-
-void Plotter::YLabel(std::string_view label) {
- ylabel_ = builder_.fbb()->CreateString(label);
-}
-
-void Plotter::AddLine(const std::vector<double> &x,
- const std::vector<double> &y, LineOptions options) {
- CHECK_EQ(x.size(), y.size()) << ": " << options.label;
- CHECK(!position_.IsNull())
- << "You must call AddFigure() before calling AddLine().";
-
- flatbuffers::Offset<flatbuffers::String> label_offset;
- if (!options.label.empty()) {
- label_offset = builder_.fbb()->CreateString(options.label);
- }
-
- std::vector<Point> points;
- for (size_t ii = 0; ii < x.size(); ++ii) {
- points.emplace_back(x[ii], y[ii]);
- }
- const flatbuffers::Offset<flatbuffers::Vector<const Point *>> points_offset =
- builder_.fbb()->CreateVectorOfStructs(points);
-
- const Color *color;
- if (options.color.empty()) {
- color = &color_wheel_.at(color_wheel_position_).color;
- color_wheel_position_ = (color_wheel_position_ + 1) % color_wheel_.size();
- } else {
- auto it = std::find_if(
- color_wheel_.begin(), color_wheel_.end(),
- [options_color = options.color](const ColorWheelColor &color) {
- return color.name == options_color;
- });
- CHECK(it != color_wheel_.end()) << ": Failed to find " << options.color;
- color = &(it->color);
- }
-
- LineStyle::Builder style_builder = builder_.MakeBuilder<LineStyle>();
- if (options.line_style.find('*') != options.line_style.npos) {
- style_builder.add_point_size(options.point_size);
- } else {
- style_builder.add_point_size(0.0);
- }
- style_builder.add_draw_line(options.line_style.find('-') !=
- options.line_style.npos);
- const flatbuffers::Offset<LineStyle> style_offset = style_builder.Finish();
-
- auto line_builder = builder_.MakeBuilder<Line>();
- line_builder.add_label(label_offset);
- line_builder.add_points(points_offset);
- line_builder.add_color(color);
- line_builder.add_style(style_offset);
- lines_.push_back(line_builder.Finish());
-}
-
-void Plotter::MaybeFinishFigure() {
- if (!lines_.empty()) {
- const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Line>>>
- lines_offset = builder_.fbb()->CreateVector(lines_);
- auto figure_builder = builder_.MakeBuilder<Figure>();
- figure_builder.add_title(figure_title_);
- figure_builder.add_position(position_);
- figure_builder.add_lines(lines_offset);
- figure_builder.add_xlabel(xlabel_);
- figure_builder.add_share_x_axis(share_x_axis_);
- figure_builder.add_ylabel(ylabel_);
- figures_.push_back(figure_builder.Finish());
- }
- lines_.clear();
- figure_title_.o = 0;
- xlabel_.o = 0;
- ylabel_.o = 0;
- position_.o = 0;
- share_x_axis_ = false;
- color_wheel_position_ = 0;
-}
-
-void Plotter::Publish() {
- MaybeFinishFigure();
- const flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Figure>>>
- figures_offset = builder_.fbb()->CreateVector(figures_);
-
- auto plot_builder = builder_.MakeBuilder<Plot>();
- plot_builder.add_title(title_);
- plot_builder.add_figures(figures_offset);
-
- CHECK_EQ(builder_.Send(plot_builder.Finish()), aos::RawSender::Error::kOk);
-
- builder_ = plot_sender_.MakeBuilder();
-
- title_.o = 0;
- figures_.clear();
- next_top_ = 0;
-}
-
-} // namespace frc971::analysis
diff --git a/frc971/analysis/in_process_plotter.h b/frc971/analysis/in_process_plotter.h
deleted file mode 100644
index df81041..0000000
--- a/frc971/analysis/in_process_plotter.h
+++ /dev/null
@@ -1,99 +0,0 @@
-#ifndef FRC971_ANALYSIS_IN_PROCESS_PLOTTER_H_
-#define FRC971_ANALYSIS_IN_PROCESS_PLOTTER_H_
-
-#include <vector>
-
-#include "aos/events/simulated_event_loop.h"
-#include "aos/network/web_proxy.h"
-#include "frc971/analysis/plot_data_generated.h"
-
-namespace frc971::analysis {
-
-// This class wraps the WebProxy class to provide a convenient C++ interface to
-// dynamically generate plots.
-// Currently, the main useful interface that this provides is a matplotlib-like
-// interface--see in_process_plotter_demo.cc for sample usage. It doesn't
-// precisely follow matplotlib's conventions, but the basic style does mimic
-// matplotlib. Future iterations may ditch this in favor of a more modern
-// interface where we actually return handles for plots and lines and the such.
-//
-// Note that currently the port for the seb server is hard-coded to 8080, so
-// only one instance of the Plotter can be present at once.
-//
-// You must call Spin() for the web server to actually do anything helpful.
-class Plotter {
- public:
- Plotter();
-
- // matplotlib-like interface
- // The basic pattern is:
- // 1) Call Figure()
- // 2) Set up the lines, labels, etc. for the figure.
- // 3) Repeat 1-2 however many times.
- // 4) Call Publish().
- // 5) Repeat 1-5 however many times.
- //
- // Publish() actually pushes the figures that you setup to the web-page,
- // either with an autogenerated title or the title set by Title(). All state
- // is cleared (or should be cleared) by the call to Publish().
-
- // Sets the title for the current set of plots; if you
- void Title(std::string_view title);
- void AddFigure(std::string_view title = "", double width = 0,
- double height = 0);
- struct LineOptions {
- std::string_view label = "";
- std::string_view line_style = "*-";
- std::string_view color = "";
- double point_size = 3.0;
- };
-
- void AddLine(const std::vector<double> &x, const std::vector<double> &y,
- std::string_view label) {
- AddLine(x, y, LineOptions{.label = label});
- }
- void AddLine(const std::vector<double> &x, const std::vector<double> &y,
- std::string_view label, std::string_view line_style) {
- AddLine(x, y, LineOptions{.label = label, .line_style = line_style});
- }
- void AddLine(const std::vector<double> &x, const std::vector<double> &y,
- LineOptions options);
-
- void ShareXAxis(bool share) { share_x_axis_ = share; }
- void XLabel(std::string_view label);
- void YLabel(std::string_view label);
- void Publish();
-
- void Spin();
-
- private:
- void MaybeFinishFigure();
-
- aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
- aos::SimulatedEventLoopFactory event_loop_factory_;
- std::unique_ptr<aos::EventLoop> event_loop_;
- aos::Sender<Plot> plot_sender_;
- aos::web_proxy::WebProxy web_proxy_;
-
- aos::Sender<Plot>::Builder builder_;
- flatbuffers::Offset<flatbuffers::String> title_;
- flatbuffers::Offset<flatbuffers::String> figure_title_;
- flatbuffers::Offset<flatbuffers::String> xlabel_;
- flatbuffers::Offset<flatbuffers::String> ylabel_;
- bool share_x_axis_ = false;
- float next_top_ = 0;
- flatbuffers::Offset<Position> position_;
- std::vector<flatbuffers::Offset<Figure>> figures_;
- std::vector<flatbuffers::Offset<Line>> lines_;
-
- struct ColorWheelColor {
- std::string name;
- Color color;
- };
-
- size_t color_wheel_position_ = 0;
- std::vector<ColorWheelColor> color_wheel_;
-};
-
-} // namespace frc971::analysis
-#endif // FRC971_ANALYSIS_IN_PROCESS_PLOTTER_H_
diff --git a/frc971/analysis/in_process_plotter_demo.cc b/frc971/analysis/in_process_plotter_demo.cc
deleted file mode 100644
index 99e0c1d..0000000
--- a/frc971/analysis/in_process_plotter_demo.cc
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "aos/init.h"
-#include "frc971/analysis/in_process_plotter.h"
-
-// To run this example, do:
-// bazel run -c opt //frc971/analysis:in_process_plotter_demo
-// And then open localhost:8080, select "C++ Plotter" from the drop-down, and
-// then select "TITLE!" or "Trig Functions" from the second drop-down to see
-// each plot.
-int main(int argc, char *argv[]) {
- aos::InitGoogle(&argc, &argv);
- frc971::analysis::Plotter plotter;
- plotter.Title("TITLE!");
- plotter.AddFigure("Fig Foo");
- plotter.ShareXAxis(true);
- plotter.AddLine({1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, "y = x");
- plotter.AddLine({5, 4, 3, 2, 1}, {1, 2, 3, 4, 5}, "y = -x");
- plotter.YLabel("Y Axis");
- plotter.AddFigure("Fig Bar");
- plotter.ShareXAxis(true);
- plotter.AddLine({1, 2, 3}, {3, 4, 5}, "y = x + 2");
- plotter.XLabel("X Axis (Linked to both above plots)");
- plotter.Publish();
-
- plotter.Title("Trig Functions");
-
- plotter.AddFigure("Sin & Cos");
- std::vector<double> x;
- std::vector<double> sinx;
- std::vector<double> cosx;
- constexpr int kNumPoints = 100000;
- for (int ii = 0; ii < kNumPoints; ++ii) {
- x.push_back(ii * 2 * M_PI / kNumPoints);
- sinx.push_back(std::sin(x.back()));
- cosx.push_back(std::cos(x.back()));
- }
- plotter.AddLine(x, sinx, "sin(x)");
- plotter.AddLine(x, cosx, "cos(x)");
- plotter.Publish();
-
- plotter.Spin();
-}
diff --git a/frc971/analysis/local_foxglove.cc b/frc971/analysis/local_foxglove.cc
deleted file mode 100644
index ade56a5..0000000
--- a/frc971/analysis/local_foxglove.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-#include "glog/logging.h"
-
-#include "aos/init.h"
-#include "aos/seasocks/seasocks_logger.h"
-#include "internal/Embedded.h"
-#include "seasocks/Server.h"
-
-DEFINE_string(data_path, "external/foxglove_studio",
- "Path to foxglove studio files to serve.");
-DEFINE_uint32(port, 8000, "Port to serve files at.");
-
-int main(int argc, char *argv[]) {
- aos::InitGoogle(&argc, &argv);
- // Magic for seasocks.
- findEmbeddedContent("");
- ::seasocks::Server server(std::make_shared<aos::seasocks::SeasocksLogger>(
- ::seasocks::Logger::Level::Info));
- server.serve(FLAGS_data_path.c_str(), FLAGS_port);
-}
diff --git a/frc971/analysis/log_reader_test.py b/frc971/analysis/log_reader_test.py
deleted file mode 100644
index 7af08e6..0000000
--- a/frc971/analysis/log_reader_test.py
+++ /dev/null
@@ -1,115 +0,0 @@
-#!/usr/bin/python3
-import json
-import unittest
-
-from frc971.analysis.py_log_reader import LogReader
-
-
-class LogReaderTest(unittest.TestCase):
-
- def setUp(self):
- self.reader = LogReader("external/sample_logfile/file/log.fbs")
- # A list of all the channels in the logfile--this is used to confirm that
- # we did indeed read the config correctly.
- self.all_channels = [
- ("/aos", "aos.JoystickState"), ("/aos", "aos.RobotState"),
- ("/aos", "aos.timing.Report"), ("/aos", "frc971.PDPValues"),
- ("/aos", "frc971.wpilib.PneumaticsToLog"),
- ("/autonomous", "aos.common.actions.Status"),
- ("/autonomous", "frc971.autonomous.AutonomousMode"),
- ("/autonomous", "frc971.autonomous.Goal"),
- ("/camera", "y2019.CameraLog"),
- ("/camera", "y2019.control_loops.drivetrain.CameraFrame"),
- ("/drivetrain", "frc971.IMUValues"),
- ("/drivetrain", "frc971.control_loops.drivetrain.Goal"),
- ("/drivetrain",
- "frc971.control_loops.drivetrain.LocalizerControl"),
- ("/drivetrain", "frc971.control_loops.drivetrain.Output"),
- ("/drivetrain", "frc971.control_loops.drivetrain.Position"),
- ("/drivetrain", "frc971.control_loops.drivetrain.Status"),
- ("/drivetrain", "frc971.sensors.GyroReading"),
- ("/drivetrain",
- "y2019.control_loops.drivetrain.TargetSelectorHint"),
- ("/superstructure", "y2019.StatusLight"),
- ("/superstructure", "y2019.control_loops.superstructure.Goal"),
- ("/superstructure", "y2019.control_loops.superstructure.Output"),
- ("/superstructure", "y2019.control_loops.superstructure.Position"),
- ("/superstructure", "y2019.control_loops.superstructure.Status")
- ]
- # A channel that is known to have data on it which we will use for testing.
- self.test_channel = ("/aos", "aos.timing.Report")
- # A non-existent channel
- self.bad_channel = ("/aos", "aos.timing.FooBar")
-
- def test_do_nothing(self):
- """Tests that we sanely handle doing nothing.
-
- A previous iteration of the log reader seg faulted when doing this."""
- pass
-
- @unittest.skip("broken by flatbuffer upgrade")
- def test_read_config(self):
- """Tests that we can read the configuration from the logfile."""
- config_bytes = self.reader.configuration()
- config = Configuration.GetRootAsConfiguration(config_bytes, 0)
-
- channel_set = set(self.all_channels)
- for ii in range(config.ChannelsLength()):
- channel = config.Channels(ii)
- # Will raise KeyError if the channel does not exist
- channel_set.remove((channel.Name().decode("utf-8"),
- channel.Type().decode("utf-8")))
-
- self.assertEqual(0, len(channel_set))
-
- def test_empty_process(self):
- """Tests running process() without subscribing to anything succeeds."""
- self.reader.process()
- for channel in self.all_channels:
- with self.assertRaises(ValueError) as context:
- self.reader.get_data_for_channel(channel[0], channel[1])
-
- def test_subscribe(self):
- """Tests that we can subscribe to a channel and get data out."""
- name = self.test_channel[0]
- message_type = self.test_channel[1]
- self.assertTrue(self.reader.subscribe(name, message_type))
- self.reader.process()
- data = self.reader.get_data_for_channel(name, message_type)
- self.assertLess(100, len(data))
- last_monotonic_time = 0
- for entry in data:
- monotonic_time = entry[0]
- realtime_time = entry[1]
- json_data = entry[2].replace('nan', '\"nan\"')
- self.assertLess(last_monotonic_time, monotonic_time)
- # Sanity check that the realtime times are in the correct range.
- self.assertLess(1500000000e9, realtime_time)
- self.assertGreater(2000000000e9, realtime_time)
- parsed_json = json.loads(json_data)
- self.assertIn("name", parsed_json)
-
- last_monotonic_time = monotonic_time
-
- def test_bad_subscribe(self):
- """Tests that we return false when subscribing to a non-existent channel."""
- self.assertFalse(
- self.reader.subscribe(self.bad_channel[0], self.bad_channel[1]),
- self.bad_channel)
-
- def test_subscribe_after_process(self):
- """Tests that an exception is thrown if we subscribe after calling process()."""
- self.reader.process()
- for channel in self.all_channels:
- with self.assertRaises(RuntimeError) as context:
- self.reader.subscribe(channel[0], channel[1])
-
- def test_get_data_before_processj(self):
- """Tests that an exception is thrown if we retrieve data before calling process()."""
- for channel in self.all_channels:
- with self.assertRaises(RuntimeError) as context:
- self.reader.get_data_for_channel(channel[0], channel[1])
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/frc971/analysis/plot_data.fbs b/frc971/analysis/plot_data.fbs
deleted file mode 100644
index 641cd6e..0000000
--- a/frc971/analysis/plot_data.fbs
+++ /dev/null
@@ -1,59 +0,0 @@
-// This flatbuffer defines the interface that is used by the in-process
-// web plotter to plot data dynamically. Both the structure of the plot and
-// the data to plot is all packaged within a single Plot message. Each Plot
-// message will correspond to a single view/tab on the web-page, and can have
-// multiple figures, each of which can have multiple lines.
-namespace frc971.analysis;
-
-// Position within the web-page to plot a figure at. [0, 0] will be the upper
-// left corner of the allowable places where plots can be put, and should
-// generally be the default location. All values in pixels.
-table Position {
- top:float (id: 0);
- left:float (id: 1);
- width:float (id: 2);
- height:float (id: 3);
-}
-
-struct Point {
- x:double (id: 0);
- y:double (id: 1);
-}
-
-// RGB values are in the range [0, 1].
-struct Color {
- r:float (id: 0);
- g:float (id: 1);
- b:float (id: 2);
-}
-
-table LineStyle {
- point_size:float (id: 0);
- draw_line:bool (id: 1);
-}
-
-table Line {
- label:string (id: 0);
- points:[Point] (id: 1);
- color:Color (id: 2);
- style:LineStyle (id: 3);
-}
-
-table Figure {
- position:Position (id: 0);
- lines:[Line] (id: 1);
- // Specifies whether to link the x-axis of this Figure with that of other
- // figures in this Plot. Only the axes of Figure's with this flag set will
- // be linked.
- share_x_axis:bool (id: 2);
- title:string (id: 3);
- xlabel:string (id: 4);
- ylabel:string (id: 5);
-}
-
-table Plot {
- figures:[Figure] (id: 0);
- title:string (id: 1);
-}
-
-root_type Plot;
diff --git a/frc971/analysis/plot_data_utils.ts b/frc971/analysis/plot_data_utils.ts
deleted file mode 100644
index 25131fb..0000000
--- a/frc971/analysis/plot_data_utils.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-// Provides a plot which handles plotting the plot defined by a
-// frc971.analysis.Plot message.
-import {Plot as PlotFb} from './plot_data_generated';
-import {MessageHandler, TimestampedMessage} from '../../aos/network/www/aos_plotter';
-import {ByteBuffer} from 'flatbuffers';
-import {Plot, Point} from '../../aos/network/www/plotter';
-import {Connection} from '../../aos/network/www/proxy';
-import {Schema} from 'flatbuffers_reflection/reflection_generated';
-
-export function plotData(conn: Connection, parentDiv: Element) {
- // Set up a selection box to allow the user to choose between plots to show.
- const plotSelect = document.createElement('select');
- parentDiv.appendChild(plotSelect);
- const plots = new Map<string, HTMLElement>();
- const invalidSelectValue = 'null';
- plotSelect.addEventListener('input', () => {
- for (const plot of plots.values()) {
- plot.style.display = 'none';
- }
- if (plotSelect.value == invalidSelectValue) {
- return;
- }
- plots.get(plotSelect.value).style.display = 'block';
- });
- plotSelect.add(new Option('Select Plot', invalidSelectValue));
-
- const plotDiv = document.createElement('div');
- parentDiv.appendChild(plotDiv);
-
- conn.addReliableHandler(
- '/analysis', 'frc971.analysis.Plot', (data: Uint8Array, time: number) => {
- const plotFb = PlotFb.getRootAsPlot(new ByteBuffer(data));
- const name = (!plotFb.title()) ? 'Plot ' + plots.size : plotFb.title();
- const div = document.createElement('div');
- div.style.display = 'none';
- plots.set(name, div);
- plotDiv.appendChild(div);
- plotSelect.add(new Option(name, name));
-
- const linkedXAxes: Plot[] = [];
-
- for (let ii = 0; ii < plotFb.figuresLength(); ++ii) {
- const figure = plotFb.figures(ii);
- const figureDiv = document.createElement('div');
- if (figure.position().width() == 0) {
- figureDiv.style.width = '100%';
- } else {
- figureDiv.style.width = figure.position().width().toString() + 'px';
- }
- if (figure.position().height() == 0) {
- figureDiv.style.height = '100%';
- } else {
- figureDiv.style.height =
- figure.position().height().toString() + 'px';
- }
- figureDiv.style.position = 'relative';
- div.appendChild(figureDiv);
- const plot = new Plot(figureDiv);
-
- if (figure.title()) {
- plot.getAxisLabels().setTitle(figure.title());
- }
- if (figure.xlabel()) {
- plot.getAxisLabels().setXLabel(figure.xlabel());
- }
- if (figure.ylabel()) {
- plot.getAxisLabels().setYLabel(figure.ylabel());
- }
- if (figure.shareXAxis()) {
- for (const other of linkedXAxes) {
- plot.linkXAxis(other);
- }
- linkedXAxes.push(plot);
- }
-
- for (let jj = 0; jj < figure.linesLength(); ++jj) {
- const lineFb = figure.lines(jj);
- const line = plot.getDrawer().addLine();
- if (lineFb.label()) {
- line.setLabel(lineFb.label());
- }
- const points = [];
- for (let kk = 0; kk < lineFb.pointsLength(); ++kk) {
- const point = lineFb.points(kk);
- points.push(new Point(point.x(), point.y()));
- }
- if (lineFb.color()) {
- line.setColor(
- [lineFb.color().r(), lineFb.color().g(), lineFb.color().b()]);
- }
- if (lineFb.style()) {
- if (lineFb.style().pointSize() !== null) {
- line.setPointSize(lineFb.style().pointSize());
- }
- if (lineFb.style().drawLine() !== null) {
- line.setDrawLine(lineFb.style().drawLine());
- }
- }
- line.setPoints(points);
- }
- }
-
- // If this is the first new element (ignoring the placeholder up top),
- // select it by default.
- if (plotSelect.length == 2) {
- plotSelect.value = name;
- plotSelect.dispatchEvent(new Event('input'));
- }
- });
-}
diff --git a/frc971/analysis/plotter_config.json b/frc971/analysis/plotter_config.json
deleted file mode 100644
index fef4a50..0000000
--- a/frc971/analysis/plotter_config.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "channels": [
- {
- "name": "/analysis",
- "type": "frc971.analysis.Plot",
- "max_size": 1000000000
- }
- ],
- "imports": [
- "../../aos/events/aos.json"
- ]
-}
diff --git a/frc971/analysis/py_log_reader.cc b/frc971/analysis/py_log_reader.cc
deleted file mode 100644
index 57d57d0..0000000
--- a/frc971/analysis/py_log_reader.cc
+++ /dev/null
@@ -1,306 +0,0 @@
-// This file provides a Python module for reading logfiles. See
-// log_reader_test.py for usage.
-//
-// NOTE: This code has not been maintained recently, and so is missing key
-// features to support reading multi-node logfiles (namely, it assumes the the
-// logfile is just a single file). Updating this code should not be difficult,
-// but hasn't been needed thus far.
-//
-// This reader works by having the user specify exactly what channels they want
-// data for. We then process the logfile and store all the data on that channel
-// into a list of timestamps + JSON message data. The user can then use an
-// accessor method (get_data_for_channel) to retrieve the cached data.
-
-// Defining PY_SSIZE_T_CLEAN seems to be suggested by most of the Python
-// documentation.
-#define PY_SSIZE_T_CLEAN
-// Note that Python.h needs to be included before anything else.
-#include <Python.h>
-
-#include <cerrno>
-#include <memory>
-
-#include "aos/configuration.h"
-#include "aos/events/logging/log_reader.h"
-#include "aos/events/simulated_event_loop.h"
-#include "aos/flatbuffer_merge.h"
-#include "aos/init.h"
-#include "aos/json_to_flatbuffer.h"
-
-namespace frc971::analysis {
-namespace {
-
-// All the data corresponding to a single message.
-struct MessageData {
- aos::monotonic_clock::time_point monotonic_sent_time;
- aos::realtime_clock::time_point realtime_sent_time;
- // JSON representation of the message.
- std::string json_data;
-};
-
-// Data corresponding to an entire channel.
-struct ChannelData {
- std::string name;
- std::string type;
- // Each message published on the channel, in order by monotonic time.
- std::vector<MessageData> messages;
-};
-
-// All the objects that we need for managing reading a logfile.
-struct LogReaderTools {
- std::unique_ptr<aos::logger::LogReader> reader;
- // Event loop to use for subscribing to buses.
- std::unique_ptr<aos::EventLoop> event_loop;
- std::vector<ChannelData> channel_data;
- // Whether we have called process() on the reader yet.
- bool processed = false;
-};
-
-struct LogReaderType {
- PyObject_HEAD;
- LogReaderTools *tools = nullptr;
-};
-
-void LogReader_dealloc(LogReaderType *self) {
- LogReaderTools *tools = self->tools;
- delete tools;
- Py_TYPE(self)->tp_free((PyObject *)self);
-}
-
-PyObject *LogReader_new(PyTypeObject *type, PyObject * /*args*/,
- PyObject * /*kwds*/) {
- LogReaderType *self;
- self = (LogReaderType *)type->tp_alloc(type, 0);
- if (self != nullptr) {
- self->tools = new LogReaderTools();
- if (self->tools == nullptr) {
- return nullptr;
- }
- }
- return (PyObject *)self;
-}
-
-int LogReader_init(LogReaderType *self, PyObject *args, PyObject *kwds) {
- int count = 1;
- if (!aos::IsInitialized()) {
- // Fake out argc and argv to let InitGoogle run properly to instrument
- // malloc, setup glog, and such.
- char *name = program_invocation_name;
- char **argv = &name;
- aos::InitGoogle(&count, &argv);
- }
-
- const char *kwlist[] = {"log_file_name", nullptr};
-
- const char *log_file_name;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", const_cast<char **>(kwlist),
- &log_file_name)) {
- return -1;
- }
-
- LogReaderTools *tools = CHECK_NOTNULL(self->tools);
- tools->reader = std::make_unique<aos::logger::LogReader>(log_file_name);
- tools->reader->Register();
-
- if (aos::configuration::MultiNode(tools->reader->configuration())) {
- tools->event_loop = tools->reader->event_loop_factory()->MakeEventLoop(
- "data_fetcher",
- aos::configuration::GetNode(tools->reader->configuration(), "roborio"));
- } else {
- tools->event_loop =
- tools->reader->event_loop_factory()->MakeEventLoop("data_fetcher");
- }
- tools->event_loop->SkipTimingReport();
- tools->event_loop->SkipAosLog();
-
- return 0;
-}
-
-PyObject *LogReader_get_data_for_channel(LogReaderType *self, PyObject *args,
- PyObject *kwds) {
- const char *kwlist[] = {"name", "type", nullptr};
-
- const char *name;
- const char *type;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss",
- const_cast<char **>(kwlist), &name, &type)) {
- return nullptr;
- }
-
- LogReaderTools *tools = CHECK_NOTNULL(self->tools);
-
- if (!tools->processed) {
- PyErr_SetString(PyExc_RuntimeError,
- "Called get_data_for_bus before calling process().");
- return nullptr;
- }
-
- for (const auto &channel : tools->channel_data) {
- if (channel.name == name && channel.type == type) {
- PyObject *list = PyList_New(channel.messages.size());
- for (size_t ii = 0; ii < channel.messages.size(); ++ii) {
- const auto &message = channel.messages[ii];
- PyObject *monotonic_time = PyLong_FromLongLong(
- std::chrono::duration_cast<std::chrono::nanoseconds>(
- message.monotonic_sent_time.time_since_epoch())
- .count());
- PyObject *realtime_time = PyLong_FromLongLong(
- std::chrono::duration_cast<std::chrono::nanoseconds>(
- message.realtime_sent_time.time_since_epoch())
- .count());
- PyObject *json_data = PyUnicode_FromStringAndSize(
- message.json_data.data(), message.json_data.size());
- PyObject *entry =
- PyTuple_Pack(3, monotonic_time, realtime_time, json_data);
- if (PyList_SetItem(list, ii, entry) != 0) {
- return nullptr;
- }
- }
- return list;
- }
- }
- PyErr_SetString(PyExc_ValueError,
- "The provided channel was never subscribed to.");
- return nullptr;
-}
-
-PyObject *LogReader_subscribe(LogReaderType *self, PyObject *args,
- PyObject *kwds) {
- const char *kwlist[] = {"name", "type", nullptr};
-
- const char *name;
- const char *type;
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "ss",
- const_cast<char **>(kwlist), &name, &type)) {
- return nullptr;
- }
-
- LogReaderTools *tools = CHECK_NOTNULL(self->tools);
-
- if (tools->processed) {
- PyErr_SetString(PyExc_RuntimeError,
- "Called subscribe after calling process().");
- return nullptr;
- }
-
- const aos::Channel *const channel = aos::configuration::GetChannel(
- tools->reader->configuration(), name, type, "", nullptr);
- if (channel == nullptr) {
- return Py_False;
- }
- const int index = tools->channel_data.size();
- tools->channel_data.push_back({.name = name, .type = type, .messages = {}});
- tools->event_loop->MakeRawWatcher(
- channel, [channel, index, tools](const aos::Context &context,
- const void *message) {
- tools->channel_data[index].messages.push_back(
- {.monotonic_sent_time = context.monotonic_event_time,
- .realtime_sent_time = context.realtime_event_time,
- .json_data = aos::FlatbufferToJson(
- channel->schema(), static_cast<const uint8_t *>(message))});
- });
- return Py_True;
-}
-
-static PyObject *LogReader_process(LogReaderType *self,
- PyObject *Py_UNUSED(ignored)) {
- LogReaderTools *tools = CHECK_NOTNULL(self->tools);
-
- if (tools->processed) {
- PyErr_SetString(PyExc_RuntimeError, "process() may only be called once.");
- return nullptr;
- }
-
- tools->processed = true;
-
- tools->reader->event_loop_factory()->Run();
-
- Py_RETURN_NONE;
-}
-
-static PyObject *LogReader_configuration(LogReaderType *self,
- PyObject *Py_UNUSED(ignored)) {
- LogReaderTools *tools = CHECK_NOTNULL(self->tools);
-
- // I have no clue if the Configuration that we get from the log reader is in a
- // contiguous chunk of memory, and I'm too lazy to either figure it out or
- // figure out how to extract the actual data buffer + offset.
- // Instead, copy the flatbuffer and return a copy of the new buffer.
- aos::FlatbufferDetachedBuffer<aos::Configuration> buffer =
- aos::CopyFlatBuffer(tools->reader->configuration());
-
- return PyBytes_FromStringAndSize(
- reinterpret_cast<const char *>(buffer.span().data()),
- buffer.span().size());
-}
-
-static PyMethodDef LogReader_methods[] = {
- {"configuration", (PyCFunction)LogReader_configuration, METH_NOARGS,
- "Return a bytes buffer for the Configuration of the logfile."},
- {"process", (PyCFunction)LogReader_process, METH_NOARGS,
- "Processes the logfile and all the subscribed to channels."},
- {"subscribe", (PyCFunction)LogReader_subscribe,
- METH_VARARGS | METH_KEYWORDS,
- "Attempts to subscribe to the provided channel name + type. Returns True "
- "if successful."},
- {"get_data_for_channel", (PyCFunction)LogReader_get_data_for_channel,
- METH_VARARGS | METH_KEYWORDS,
- "Returns the logged data for a given channel. Raises an exception if you "
- "did not subscribe to the provided channel. Returned data is a list of "
- "tuples where each tuple is of the form (monotonic_nsec, realtime_nsec, "
- "json_message_data)."},
- {nullptr, 0, 0, nullptr} /* Sentinel */
-};
-
-#ifdef __clang__
-// These extensions to C++ syntax do surprising things in C++, but for these
-// uses none of them really matter I think, and the alternatives are really
-// annoying.
-#pragma clang diagnostic ignored "-Wc99-designator"
-#endif
-
-static PyTypeObject LogReaderType = {
- PyVarObject_HEAD_INIT(NULL, 0)
- // The previous macro initializes some fields, leave a comment to help
- // clang-format not make this uglier.
- .tp_name = "py_log_reader.LogReader",
- .tp_basicsize = sizeof(LogReaderType),
- .tp_itemsize = 0,
- .tp_dealloc = (destructor)LogReader_dealloc,
- .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
- .tp_doc = "LogReader objects",
- .tp_methods = LogReader_methods,
- .tp_init = (initproc)LogReader_init,
- .tp_new = LogReader_new,
-};
-
-static PyModuleDef log_reader_module = {
- PyModuleDef_HEAD_INIT,
- .m_name = "py_log_reader",
- .m_doc = "Example module that creates an extension type.",
- .m_size = -1,
-};
-
-PyObject *InitModule() {
- PyObject *m;
- if (PyType_Ready(&LogReaderType) < 0) return nullptr;
-
- m = PyModule_Create(&log_reader_module);
- if (m == nullptr) return nullptr;
-
- Py_INCREF(&LogReaderType);
- if (PyModule_AddObject(m, "LogReader", (PyObject *)&LogReaderType) < 0) {
- Py_DECREF(&LogReaderType);
- Py_DECREF(m);
- return nullptr;
- }
-
- return m;
-}
-
-} // namespace
-} // namespace frc971::analysis
-
-PyMODINIT_FUNC PyInit_py_log_reader(void) {
- return frc971::analysis::InitModule();
-}
diff --git a/frc971/control_loops/drivetrain/BUILD b/frc971/control_loops/drivetrain/BUILD
index 6f98839..afbce9b 100644
--- a/frc971/control_loops/drivetrain/BUILD
+++ b/frc971/control_loops/drivetrain/BUILD
@@ -606,8 +606,8 @@
target_compatible_with = ["@platforms//os:linux"],
deps = [
":spline",
+ "//aos/analysis:in_process_plotter",
"//aos/testing:googletest",
- "//frc971/analysis:in_process_plotter",
"@com_github_gflags_gflags//:gflags",
],
)
diff --git a/frc971/control_loops/drivetrain/spline_test.cc b/frc971/control_loops/drivetrain/spline_test.cc
index 6d84d53..fcd0030 100644
--- a/frc971/control_loops/drivetrain/spline_test.cc
+++ b/frc971/control_loops/drivetrain/spline_test.cc
@@ -5,7 +5,7 @@
#include "gflags/gflags.h"
#include "gtest/gtest.h"
-#include "frc971/analysis/in_process_plotter.h"
+#include "aos/analysis/in_process_plotter.h"
DEFINE_bool(plot, false, "If true, plot");
diff --git a/frc971/vision/BUILD b/frc971/vision/BUILD
index 5d8bf7c..6cee780 100644
--- a/frc971/vision/BUILD
+++ b/frc971/vision/BUILD
@@ -131,7 +131,6 @@
":foxglove_image_converter_lib",
"//aos:init",
"//aos/events/logging:log_reader",
- "//frc971/analysis:in_process_plotter",
"//frc971/control_loops/drivetrain:improved_down_estimator",
"//frc971/vision:visualize_robot",
"//frc971/wpilib:imu_batch_fbs",
@@ -142,6 +141,12 @@
"@com_google_absl//absl/strings:str_format",
"@com_google_ceres_solver//:ceres",
"@org_tuxfamily_eigen//:eigen",
+ ] + [
+ # TODO(Stephan): This is a whacky hack. If we include this
+ # in the proper spot above (alphabetically), then we get a
+ # linker error: duplicate symbol: crc32.
+ # It's part of both @zlib and @com_github_rawrtc_re.
+ "//aos/analysis:in_process_plotter",
],
)
diff --git a/frc971/vision/extrinsics_calibration.cc b/frc971/vision/extrinsics_calibration.cc
index 6017258..5c4ae31 100644
--- a/frc971/vision/extrinsics_calibration.cc
+++ b/frc971/vision/extrinsics_calibration.cc
@@ -7,8 +7,8 @@
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
+#include "aos/analysis/in_process_plotter.h"
#include "aos/time/time.h"
-#include "frc971/analysis/in_process_plotter.h"
#include "frc971/control_loops/runge_kutta.h"
#include "frc971/vision/calibration_accumulator.h"
#include "frc971/vision/charuco_lib.h"