Merge "Add clear button"
diff --git a/aos/events/logging/log_namer.cc b/aos/events/logging/log_namer.cc
index 1383aed..9a5d038 100644
--- a/aos/events/logging/log_namer.cc
+++ b/aos/events/logging/log_namer.cc
@@ -201,7 +201,7 @@
const UUID &source_node_boot_uuid = state[node_index].boot_uuid;
const Node *const source_node =
configuration::GetNode(configuration_, node_index);
- CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 24u);
+ CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 28u);
flatbuffers::FlatBufferBuilder fbb;
fbb.ForceDefaults(true);
@@ -258,41 +258,32 @@
std::vector<flatbuffers::Offset<flatbuffers::String>> boot_uuid_offsets;
boot_uuid_offsets.reserve(state.size());
- for (const NewDataWriter::State &state : state) {
- if (state.boot_uuid != UUID::Zero()) {
- boot_uuid_offsets.emplace_back(state.boot_uuid.PackString(&fbb));
- } else {
- boot_uuid_offsets.emplace_back(fbb.CreateString(""));
- }
- }
- flatbuffers::Offset<
- flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
- boot_uuids_offset = fbb.CreateVector(boot_uuid_offsets);
-
- int64_t *oldest_remote_monotonic_timestamps;
+ int64_t *unused;
flatbuffers::Offset<flatbuffers::Vector<int64_t>>
oldest_remote_monotonic_timestamps_offset = fbb.CreateUninitializedVector(
- state.size(), &oldest_remote_monotonic_timestamps);
+ state.size(), &unused);
- int64_t *oldest_local_monotonic_timestamps;
flatbuffers::Offset<flatbuffers::Vector<int64_t>>
oldest_local_monotonic_timestamps_offset = fbb.CreateUninitializedVector(
- state.size(), &oldest_local_monotonic_timestamps);
+ state.size(), &unused);
- int64_t *oldest_remote_unreliable_monotonic_timestamps;
flatbuffers::Offset<flatbuffers::Vector<int64_t>>
oldest_remote_unreliable_monotonic_timestamps_offset =
fbb.CreateUninitializedVector(
- state.size(), &oldest_remote_unreliable_monotonic_timestamps);
+ state.size(), &unused);
- int64_t *oldest_local_unreliable_monotonic_timestamps;
flatbuffers::Offset<flatbuffers::Vector<int64_t>>
oldest_local_unreliable_monotonic_timestamps_offset =
fbb.CreateUninitializedVector(
- state.size(), &oldest_local_unreliable_monotonic_timestamps);
+ state.size(), &unused);
for (size_t i = 0; i < state.size(); ++i) {
+ if (state[i].boot_uuid != UUID::Zero()) {
+ boot_uuid_offsets.emplace_back(state[i].boot_uuid.PackString(&fbb));
+ } else {
+ boot_uuid_offsets.emplace_back(fbb.CreateString(""));
+ }
if (state[i].boot_uuid == UUID::Zero()) {
CHECK_EQ(state[i].oldest_remote_monotonic_timestamp,
monotonic_clock::max_time);
@@ -304,20 +295,32 @@
monotonic_clock::max_time);
}
- oldest_remote_monotonic_timestamps[i] =
- state[i].oldest_remote_monotonic_timestamp.time_since_epoch().count();
- oldest_local_monotonic_timestamps[i] =
- state[i].oldest_local_monotonic_timestamp.time_since_epoch().count();
- oldest_remote_unreliable_monotonic_timestamps[i] =
- state[i]
- .oldest_remote_unreliable_monotonic_timestamp.time_since_epoch()
- .count();
- oldest_local_unreliable_monotonic_timestamps[i] =
- state[i]
- .oldest_local_unreliable_monotonic_timestamp.time_since_epoch()
- .count();
+ flatbuffers::GetMutableTemporaryPointer(
+ fbb, oldest_remote_monotonic_timestamps_offset)
+ ->Mutate(i, state[i]
+ .oldest_remote_monotonic_timestamp.time_since_epoch()
+ .count());
+ flatbuffers::GetMutableTemporaryPointer(
+ fbb, oldest_local_monotonic_timestamps_offset)
+ ->Mutate(i, state[i]
+ .oldest_local_monotonic_timestamp.time_since_epoch()
+ .count());
+ flatbuffers::GetMutableTemporaryPointer(
+ fbb, oldest_remote_unreliable_monotonic_timestamps_offset)
+ ->Mutate(i, state[i]
+ .oldest_remote_unreliable_monotonic_timestamp.time_since_epoch()
+ .count());
+ flatbuffers::GetMutableTemporaryPointer(
+ fbb, oldest_local_unreliable_monotonic_timestamps_offset)
+ ->Mutate(i, state[i]
+ .oldest_local_unreliable_monotonic_timestamp.time_since_epoch()
+ .count());
}
+ flatbuffers::Offset<
+ flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>>
+ boot_uuids_offset = fbb.CreateVector(boot_uuid_offsets);
+
aos::logger::LogFileHeader::Builder log_file_header_builder(fbb);
log_file_header_builder.add_name(name_offset);
diff --git a/aos/events/logging/logfile_sorting.cc b/aos/events/logging/logfile_sorting.cc
index 3f637f8..aeced9f 100644
--- a/aos/events/logging/logfile_sorting.cc
+++ b/aos/events/logging/logfile_sorting.cc
@@ -89,7 +89,7 @@
}
bool ConfigOnly(const LogFileHeader *header) {
- CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 24u);
+ CHECK_EQ(LogFileHeader::MiniReflectTypeTable()->num_elems, 28u);
if (header->has_monotonic_start_time()) return false;
if (header->has_realtime_start_time()) return false;
if (header->has_max_out_of_order_duration()) return false;
@@ -1238,8 +1238,15 @@
return a.second < b.second;
});
new_parts.parts.reserve(parts.second.parts.size());
- for (std::pair<std::string, int> &p : parts.second.parts) {
- new_parts.parts.emplace_back(std::move(p.first));
+ {
+ int last_parts_index = -1;
+ for (std::pair<std::string, int> &p : parts.second.parts) {
+ CHECK_LT(last_parts_index, p.second)
+ << ": Found duplicate parts in '" << new_parts.parts.back()
+ << "' and '" << p.first << "'";
+ last_parts_index = p.second;
+ new_parts.parts.emplace_back(std::move(p.first));
+ }
}
CHECK(!parts.second.config_sha256.empty());
diff --git a/aos/events/logging/logger.fbs b/aos/events/logging/logger.fbs
index c71baad..6d9f7cb 100644
--- a/aos/events/logging/logger.fbs
+++ b/aos/events/logging/logger.fbs
@@ -136,13 +136,17 @@
// For all channels sent from a specific node, these vectors hold the
// timestamp of the oldest known message from that node, and the
// corresponding monotonic timestamp for when that was received on our node.
- oldest_remote_monotonic_timestamps:[int64] (id: 20);
- oldest_local_monotonic_timestamps:[int64] (id: 21);
+ corrupted_oldest_remote_monotonic_timestamps:[int64] (id: 20, deprecated);
+ corrupted_oldest_local_monotonic_timestamps:[int64] (id: 21, deprecated);
+ oldest_remote_monotonic_timestamps:[int64] (id: 24);
+ oldest_local_monotonic_timestamps:[int64] (id: 25);
// For all channels *excluding* the reliable channels (ttl == 0), record the
// same quantity.
- oldest_remote_unreliable_monotonic_timestamps:[int64] (id: 22);
- oldest_local_unreliable_monotonic_timestamps:[int64] (id: 23);
+ corrupted_oldest_remote_unreliable_monotonic_timestamps:[int64] (id: 22, deprecated);
+ corrupted_oldest_local_unreliable_monotonic_timestamps:[int64] (id: 23, deprecated);
+ oldest_remote_unreliable_monotonic_timestamps:[int64] (id: 26);
+ oldest_local_unreliable_monotonic_timestamps:[int64] (id: 27);
}
// Table holding a message.
diff --git a/aos/events/logging/logger_test.cc b/aos/events/logging/logger_test.cc
index 226c084..8d8a2a6 100644
--- a/aos/events/logging/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -2501,6 +2501,30 @@
ConfirmReadable(pi1_single_direction_logfiles_);
}
+// Tests that we explode if someone passes in a part file twice with a better
+// error than an out of order error.
+TEST_P(MultinodeLoggerTest, DuplicateLogFiles) {
+ time_converter_.AddMonotonic(
+ {BootTimestamp::epoch(),
+ BootTimestamp::epoch() + chrono::seconds(1000)});
+ {
+ LoggerState pi1_logger = MakeLogger(pi1_);
+
+ event_loop_factory_.RunFor(chrono::milliseconds(95));
+
+ StartLogger(&pi1_logger);
+
+ event_loop_factory_.RunFor(chrono::milliseconds(10000));
+ }
+
+ std::vector<std::string> duplicates;
+ for (const std::string &f : pi1_single_direction_logfiles_) {
+ duplicates.emplace_back(f);
+ duplicates.emplace_back(f);
+ }
+ EXPECT_DEATH({ SortParts(duplicates); }, "Found duplicate parts in");
+}
+
// Tests that we properly handle a dead node. Do this by just disconnecting it
// and only using one nodes of logs.
TEST_P(MultinodeLoggerTest, DeadNode) {
diff --git a/aos/network/www/colors.ts b/aos/network/www/colors.ts
index 1a1c0b3..b173e13 100644
--- a/aos/network/www/colors.ts
+++ b/aos/network/www/colors.ts
@@ -5,3 +5,4 @@
export const PINK: number[] = [1, 0.3, 0.6];
export const CYAN: number[] = [0.3, 1, 1];
export const WHITE: number[] = [1, 1, 1];
+export const BLACK: number[] = [0, 0, 0];
diff --git a/debian/matplotlib.BUILD b/debian/matplotlib.BUILD
index 881521c..ad44398 100644
--- a/debian/matplotlib.BUILD
+++ b/debian/matplotlib.BUILD
@@ -2,10 +2,5 @@
build_matplotlib(
"3",
- tkinter_py_version = "3.5",
-)
-
-build_matplotlib(
- "2.7",
- copy_shared_files = False,
+ tkinter_py_version = "3.7",
)
diff --git a/debian/matplotlib.bzl b/debian/matplotlib.bzl
index d94efd9..3ae0786 100644
--- a/debian/matplotlib.bzl
+++ b/debian/matplotlib.bzl
@@ -185,11 +185,19 @@
name = "matplotlib" + version,
srcs = _src_copied + [
version + "/matplotlib/__init__.py",
- ],
+ ] + native.glob(
+ include = ["usr/lib/python" + tkinter_py_version + "/**/*.py"],
+ ),
data = _data_files + _builtin_so_copied + _system_so_copied + [
":usr/share/matplotlib/mpl-data/matplotlibrc",
- ] + native.glob(["etc/**"]),
- imports = ["usr/lib/python" + version + "/dist-packages", version, "."],
+ ] + native.glob(["etc/**", "usr/share/fonts/**"]),
+ imports = [
+ "rpathed3/usr/lib/python" + version + "/dist-packages",
+ "rpathed3/usr/lib/python" + version + ".7/lib-dynload",
+ version,
+ ".",
+ "usr/lib/python" + tkinter_py_version,
+ ],
target_compatible_with = ["@platforms//cpu:x86_64"],
visibility = ["//visibility:public"],
)
diff --git a/debian/matplotlib_init.patch b/debian/matplotlib_init.patch
index 4cc155a..2818cd6 100644
--- a/debian/matplotlib_init.patch
+++ b/debian/matplotlib_init.patch
@@ -26,7 +26,7 @@
+
+# Tell fontconfig where to find matplotlib's sandboxed font files.
+os.environ["FONTCONFIG_PATH"] = os.path.join(_matplotlib_base, "etc/fonts")
-+os.environ["FONTCONFIG_FILE"] = os.path.join(_matplotlib_base, "etc/fonts/fonts.conf")
++os.environ["FONTCONFIG_FILE"] = "fonts.conf"
+os.environ["FONTCONFIG_SYSROOT"] = _matplotlib_base
+
try:
diff --git a/frc971/analysis/BUILD b/frc971/analysis/BUILD
index 2a945bf..5457405 100644
--- a/frc971/analysis/BUILD
+++ b/frc971/analysis/BUILD
@@ -47,6 +47,7 @@
"//frc971/control_loops/drivetrain:robot_state_plotter",
"//frc971/control_loops/drivetrain:spline_plotter",
"//frc971/wpilib:imu_plotter",
+ "//y2020/control_loops/drivetrain:localizer_plotter",
"//y2020/control_loops/superstructure:accelerator_plotter",
"//y2020/control_loops/superstructure:finisher_plotter",
"//y2020/control_loops/superstructure:hood_plotter",
diff --git a/frc971/analysis/plot_index.ts b/frc971/analysis/plot_index.ts
index 8b8c951..7b46a70 100644
--- a/frc971/analysis/plot_index.ts
+++ b/frc971/analysis/plot_index.ts
@@ -32,6 +32,8 @@
'org_frc971/y2020/control_loops/superstructure/finisher_plotter'
import {plotTurret} from
'org_frc971/y2020/control_loops/superstructure/turret_plotter'
+import {plotLocalizer} from
+ 'org_frc971/y2020/control_loops/drivetrain/localizer_plotter'
import {plotAccelerator} from
'org_frc971/y2020/control_loops/superstructure/accelerator_plotter'
import {plotHood} from
@@ -100,6 +102,7 @@
['Accelerator', new PlotState(plotDiv, plotAccelerator)],
['Hood', new PlotState(plotDiv, plotHood)],
['Turret', new PlotState(plotDiv, plotTurret)],
+ ['2020 Localizer', new PlotState(plotDiv, plotLocalizer)],
['C++ Plotter', new PlotState(plotDiv, plotData)],
]);
diff --git a/frc971/control_loops/drivetrain/hybrid_ekf.h b/frc971/control_loops/drivetrain/hybrid_ekf.h
index 04ffa4e..ecdabb8 100644
--- a/frc971/control_loops/drivetrain/hybrid_ekf.h
+++ b/frc971/control_loops/drivetrain/hybrid_ekf.h
@@ -137,7 +137,7 @@
};
static constexpr int kNInputs = 4;
// Number of previous samples to save.
- static constexpr int kSaveSamples = 50;
+ static constexpr int kSaveSamples = 80;
// Whether we should completely rerun the entire stored history of
// kSaveSamples on every correction. Enabling this will increase overall CPU
// usage substantially; however, leaving it disabled makes it so that we are
diff --git a/frc971/control_loops/python/BUILD b/frc971/control_loops/python/BUILD
index b6290d4..a4b8fb8 100644
--- a/frc971/control_loops/python/BUILD
+++ b/frc971/control_loops/python/BUILD
@@ -112,6 +112,7 @@
":drivetrain",
":python_init",
"//external:python-glog",
+ "@matplotlib_repo//:matplotlib3",
],
)
diff --git a/frc971/control_loops/python/control_loop.py b/frc971/control_loops/python/control_loop.py
index cea1fdd..72d3297 100644
--- a/frc971/control_loops/python/control_loop.py
+++ b/frc971/control_loops/python/control_loop.py
@@ -588,6 +588,9 @@
# Torque constant
self.Kt = self.stall_torque / self.stall_current
+ # Motor inertia in kg m^2
+ self.motor_inertia = 0.0001634
+
class NMotor(object):
def __init__(self, motor, n):
diff --git a/frc971/control_loops/python/field_images/2020.png b/frc971/control_loops/python/field_images/2020.png
index 166a1bb..2397fa0 100644
--- a/frc971/control_loops/python/field_images/2020.png
+++ b/frc971/control_loops/python/field_images/2020.png
Binary files differ
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index b7e0246..bf54f60 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -453,6 +453,12 @@
# scale from point in field coordinates
point = self.mousex, self.mousey
+ # This restricts the amount it can be scaled.
+ if self.transform.xx <= 0.4:
+ scale = max(scale, 1)
+ elif self.transform.xx >= 4:
+ scale = min(scale, 1)
+
# move the origin to point
self.transform.translate(point[0], point[1])
diff --git a/frc971/wpilib/dma.cc b/frc971/wpilib/dma.cc
index 432b77b..6cc8bf0 100644
--- a/frc971/wpilib/dma.cc
+++ b/frc971/wpilib/dma.cc
@@ -7,6 +7,7 @@
#include "frc971/wpilib/ahal/AnalogInput.h"
#include "frc971/wpilib/ahal/DigitalSource.h"
#include "frc971/wpilib/ahal/Encoder.h"
+#include "glog/logging.h"
#include "hal/HAL.h"
// Interface to the roboRIO FPGA's DMA features.
@@ -142,6 +143,7 @@
void DMA::SetExternalTrigger(frc::DigitalSource *input, bool rising,
bool falling) {
+ CHECK(input);
tRioStatusCode status = 0;
if (manager_) {
diff --git a/frc971/wpilib/dma_edge_counting.cc b/frc971/wpilib/dma_edge_counting.cc
index 1b7bee9..ca2a10e 100644
--- a/frc971/wpilib/dma_edge_counting.cc
+++ b/frc971/wpilib/dma_edge_counting.cc
@@ -32,6 +32,25 @@
prev_sample_ = sample;
}
+void DMAPulseSeparationReader::UpdateFromSample(const DMASample &sample) {
+ // save the time of the falling edge of the input one
+ if (have_prev_sample_ && !sample.Get(input_one_) &&
+ prev_sample_.Get(input_one_)) {
+ input_one_time_ = sample.GetTimestamp();
+ }
+
+ // take the difference in time between the falling edge of the input one and
+ // the falling edge of the input two
+ if (!sample.Get(input_two_) && input_one_time_.has_value()) {
+ last_width_ = sample.GetTimestamp() - input_one_time_.value();
+ pulses_detected_++;
+ input_one_time_.reset();
+ }
+
+ have_prev_sample_ = true;
+ prev_sample_ = sample;
+}
+
void DMASynchronizer::CheckDMA() {
DMASample current_sample;
diff --git a/frc971/wpilib/dma_edge_counting.h b/frc971/wpilib/dma_edge_counting.h
index a6f2da4..d8fe476 100644
--- a/frc971/wpilib/dma_edge_counting.h
+++ b/frc971/wpilib/dma_edge_counting.h
@@ -2,6 +2,7 @@
#define FRC971_WPILIB_DMA_EDGE_COUNTING_H_
#include <memory>
+#include <optional>
#include <vector>
#include "aos/macros.h"
@@ -69,6 +70,53 @@
DISALLOW_COPY_AND_ASSIGN(DMAPulseWidthReader);
};
+// Takes two digital inputs and times the difference between the first one going
+// low and the second one going low.
+class DMAPulseSeparationReader : public DMASampleHandlerInterface {
+ public:
+ DMAPulseSeparationReader(frc::DigitalInput *input_one,
+ frc::DigitalInput *input_two)
+ : input_one_(input_one), input_two_(input_two) {}
+ DMAPulseSeparationReader() = default;
+
+ void set_input_one(frc::DigitalInput *input) { input_one_ = input; }
+ void set_input_two(frc::DigitalInput *input) { input_two_ = input; }
+
+ double last_width() const { return last_width_; }
+ double pulses_detected() const { return pulses_detected_; }
+
+ private:
+ void UpdateFromSample(const DMASample & /*sample*/) override;
+ void UpdatePolledValue() override {}
+
+ void PollFromSample(const DMASample & /*sample*/) override {}
+ void AddToDMA(DMA *dma) override {
+ dma->Add(input_one_);
+ dma->SetExternalTrigger(input_one_, true, true);
+ dma->Add(input_two_);
+ dma->SetExternalTrigger(input_two_, false, true);
+ }
+
+ static constexpr double kSampleTimeoutSeconds = 0.1;
+
+ frc::DigitalInput *input_one_ = nullptr;
+ frc::DigitalInput *input_two_ = nullptr;
+
+ // The last DMA reading we got.
+ DMASample prev_sample_;
+ // Whether or not we actually have anything in prev_sample_.
+ bool have_prev_sample_ = false;
+
+ // the time when the input one went low.
+ std::optional<double> input_one_time_;
+
+ int pulses_detected_ = 0;
+
+ double last_width_ = ::std::numeric_limits<double>::quiet_NaN();
+
+ DISALLOW_COPY_AND_ASSIGN(DMAPulseSeparationReader);
+};
+
// Counts edges on a sensor using DMA data and latches encoder values
// corresponding to those edges.
class DMAEdgeCounter : public DMASampleHandlerInterface {
diff --git a/y2020/BUILD b/y2020/BUILD
index 45164b4..dfe09f9 100644
--- a/y2020/BUILD
+++ b/y2020/BUILD
@@ -116,6 +116,7 @@
"//frc971/wpilib:wpilib_robot_base",
"//third_party:phoenix",
"//third_party:wpilib",
+ "//y2020/control_loops/superstructure/shooter:shooter_tuning_readings_fbs",
"//y2020/control_loops/superstructure:superstructure_output_fbs",
"//y2020/control_loops/superstructure:superstructure_position_fbs",
],
@@ -230,8 +231,11 @@
"//aos/network:message_bridge_client_fbs",
"//aos/network:message_bridge_server_fbs",
"//aos/network:timestamp_fbs",
+ "//y2020/control_loops/superstructure/shooter:shooter_tuning_params_fbs",
+ "//y2020/control_loops/superstructure/shooter:shooter_tuning_readings_fbs",
"//y2020/control_loops/superstructure:superstructure_goal_fbs",
"//y2019/control_loops/drivetrain:target_selector_fbs",
+ "//y2020/control_loops/drivetrain:localizer_debug_fbs",
"//y2020/control_loops/superstructure:superstructure_output_fbs",
"//y2020/control_loops/superstructure:superstructure_position_fbs",
"//y2020/control_loops/superstructure:superstructure_status_fbs",
diff --git a/y2020/actors/BUILD b/y2020/actors/BUILD
index 4ec7b08..3555531 100644
--- a/y2020/actors/BUILD
+++ b/y2020/actors/BUILD
@@ -77,3 +77,22 @@
"//frc971/autonomous:auto_fbs",
],
)
+
+cc_binary(
+ name = "shooter_tuning_action",
+ srcs = [
+ "shooter_tuning_actor.cc",
+ ],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ "//aos/events:shm_event_loop",
+ "//aos:init",
+ "//frc971/autonomous:base_autonomous_actor",
+ "//frc971/control_loops:control_loops_fbs",
+ "//frc971/control_loops:profiled_subsystem_fbs",
+ "//y2020/control_loops/drivetrain:drivetrain_base",
+ "//y2020/control_loops/superstructure/shooter:shooter_tuning_params_fbs",
+ "//y2020/control_loops/superstructure/shooter:shooter_tuning_readings_fbs",
+ "//y2020/control_loops/superstructure:superstructure_goal_fbs",
+ ],
+)
diff --git a/y2020/actors/shooter_tuning_actor.cc b/y2020/actors/shooter_tuning_actor.cc
new file mode 100644
index 0000000..f9d4123
--- /dev/null
+++ b/y2020/actors/shooter_tuning_actor.cc
@@ -0,0 +1,184 @@
+#include <algorithm>
+#include <cstdlib>
+#include <ctime>
+#include <fstream>
+#include <sstream>
+#include <utility>
+
+#include "aos/init.h"
+#include "frc971/autonomous/base_autonomous_actor.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "glog/logging.h"
+#include "y2020/control_loops/drivetrain/drivetrain_base.h"
+#include "y2020/control_loops/superstructure/shooter/shooter_tuning_params_generated.h"
+#include "y2020/control_loops/superstructure/shooter/shooter_tuning_readings_generated.h"
+#include "y2020/control_loops/superstructure/superstructure_goal_generated.h"
+
+namespace y2020::actors {
+
+namespace superstructure = y2020::control_loops::superstructure;
+
+// Actor for tuning the shooter flywheel velocities.
+// We found that the shooter is most precise when the ball velocity varies the
+// least. To figure out which finisher and accelerator velocities cause the ball
+// velocity to vary the least, this does a sweep of a range of those velocities
+// in randomized order, and shoots balls at each set of velocities. It also
+// fetches the ball velocity at each iteration, which we will use to calculate
+// each iteration's deviation in ball velocity and figure out which flywheel
+// velocities are the best with the csv of velocities that this outputs.
+class ShooterTuningActor : public frc971::autonomous::BaseAutonomousActor {
+ public:
+ explicit ShooterTuningActor(aos::EventLoop *event_loop);
+
+ bool RunAction(
+ const frc971::autonomous::AutonomousActionParams *params) override;
+
+ private:
+ void SendSuperstructureGoal();
+ void WaitAndWriteBallData();
+
+ aos::Sender<superstructure::Goal> superstructure_goal_sender_;
+ aos::Fetcher<superstructure::shooter::TuningReadings>
+ tuning_readings_fetcher_;
+ const superstructure::shooter::TuningParams *tuning_params_;
+
+ // Vector of (velocity_accelerator, velocity finisher) containing all velocity
+ // values in the sweep in randomized order.
+ std::vector<std::pair<double, double>> velocities_;
+
+ bool shooting_ = false;
+ double velocity_finisher_ = 0.0;
+ double velocity_accelerator_ = 0.0;
+
+ std::ofstream fout_;
+};
+
+ShooterTuningActor::ShooterTuningActor(aos::EventLoop *event_loop)
+ : frc971::autonomous::BaseAutonomousActor(
+ event_loop, control_loops::drivetrain::GetDrivetrainConfig()),
+ superstructure_goal_sender_(
+ event_loop->MakeSender<superstructure::Goal>("/superstructure")),
+ tuning_readings_fetcher_(
+ event_loop->MakeFetcher<superstructure::shooter::TuningReadings>(
+ "/superstructure")) {
+ auto tuning_params_fetcher =
+ event_loop->MakeFetcher<superstructure::shooter::TuningParams>(
+ "/superstructure");
+ CHECK(tuning_params_fetcher.Fetch());
+ tuning_params_ = tuning_params_fetcher.get();
+ const auto finisher = tuning_params_->finisher();
+ const auto accelerator = tuning_params_->accelerator();
+
+ LOG(INFO) << "Tuning with fininisher from " << finisher->velocity_initial()
+ << " to " << finisher->velocity_final() << " by "
+ << finisher->velocity_increment();
+ LOG(INFO) << "Tuning with accelerator from "
+ << accelerator->velocity_initial() << " to "
+ << accelerator->velocity_final() << " by "
+ << accelerator->velocity_increment();
+ // Add the velocities for the sweep
+ for (velocity_finisher_ = finisher->velocity_initial();
+ velocity_finisher_ <= finisher->velocity_final();
+ velocity_finisher_ += finisher->velocity_increment()) {
+ for (velocity_accelerator_ = accelerator->velocity_initial();
+ velocity_accelerator_ <= accelerator->velocity_final();
+ velocity_accelerator_ += accelerator->velocity_increment()) {
+ for (int i = 0; i < tuning_params_->balls_per_iteration(); i++) {
+ velocities_.emplace_back(velocity_accelerator_, velocity_finisher_);
+ }
+ }
+ }
+ // Randomize the ordering of the velocities
+ std::srand(std::time(nullptr));
+ std::random_shuffle(velocities_.begin(), velocities_.end());
+}
+
+bool ShooterTuningActor::RunAction(
+ const frc971::autonomous::AutonomousActionParams * /*params*/) {
+ fout_.open("shooter_tuning_data.csv", std::ios_base::app);
+ LOG(INFO) << "velocity_accelerator,velocity_finisher,velocity_ball";
+
+ shooting_ = true;
+ for (size_t i = 0; i < velocities_.size(); i++) {
+ LOG(INFO) << "Shooting ball " << (i + 1) << " out of "
+ << velocities_.size();
+ velocity_accelerator_ = velocities_[i].first;
+ velocity_finisher_ = velocities_[i].second;
+ SendSuperstructureGoal();
+ WaitAndWriteBallData();
+
+ if (i % 100 == 0 && i != 0) {
+ LOG(INFO) << "Pausing to cool for 2 minutes";
+ shooting_ = false;
+ velocity_accelerator_ = 0.0;
+ velocity_finisher_ = 0.0;
+ SendSuperstructureGoal();
+ std::this_thread::sleep_for(std::chrono::seconds(120));
+ shooting_ = true;
+ }
+ }
+
+ shooting_ = false;
+ velocity_finisher_ = 0.0;
+ velocity_accelerator_ = 0.0;
+ SendSuperstructureGoal();
+ fout_.close();
+ LOG(INFO) << "Done!";
+ return true;
+}
+
+void ShooterTuningActor::WaitAndWriteBallData() {
+ aos::time::PhasedLoop phased_loop(frc971::controls::kLoopFrequency,
+ event_loop()->monotonic_now(),
+ frc971::controls::kLoopFrequency / 2);
+ bool ball_detected = false;
+
+ while (!ball_detected) {
+ phased_loop.SleepUntilNext();
+
+ if (tuning_readings_fetcher_.FetchNext()) {
+ ball_detected = true;
+ std::stringstream output;
+ output << velocity_accelerator_ << ',' << velocity_finisher_ << ','
+ << tuning_readings_fetcher_->velocity_ball() << std::endl;
+ const auto output_str = output.str();
+ LOG(INFO) << output_str;
+ fout_ << output_str;
+ }
+ }
+}
+
+void ShooterTuningActor::SendSuperstructureGoal() {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+
+ auto shooter_builder = builder.MakeBuilder<superstructure::ShooterGoal>();
+ shooter_builder.add_velocity_finisher(velocity_finisher_);
+ shooter_builder.add_velocity_accelerator(velocity_accelerator_);
+ auto shooter_offset = shooter_builder.Finish();
+
+ superstructure::Goal::Builder superstructure_builder =
+ builder.MakeBuilder<superstructure::Goal>();
+
+ superstructure_builder.add_shooter(shooter_offset);
+ superstructure_builder.add_shooting(shooting_);
+
+ if (!builder.Send(superstructure_builder.Finish())) {
+ AOS_LOG(ERROR, "Sending superstructure goal failed.\n");
+ }
+}
+
+} // namespace y2020::actors
+
+int main(int argc, char *argv[]) {
+ aos::InitGoogle(&argc, &argv);
+
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig("config.json");
+
+ aos::ShmEventLoop event_loop(&config.message());
+ y2020::actors::ShooterTuningActor actor(&event_loop);
+
+ event_loop.Run();
+
+ return 0;
+}
diff --git a/y2020/actors/splines/README.md b/y2020/actors/splines/README.md
new file mode 100644
index 0000000..058f5bc
--- /dev/null
+++ b/y2020/actors/splines/README.md
@@ -0,0 +1,24 @@
+# Spline Descriptions
+This folder contains reference material for what each spline does
+
+<br>
+
+# Target Aligned - Infinite Recharge
+## After shooting balls initially, collects power cells and returns to shooting position
+
+### [Target Aligned 1](target_aligned_1.json)
+After shooting the pre-loaded balls, this spline directs the robot to the power cells, allowing the robot to collect all three balls available
+
+### [Target Aligned 2](target_aligned_2.json)
+After collecting the 3 balls using the target_aligned_1 spline, this spline directs the robot back towards the shooter tower, setting it up to shoot the balls it collected
+
+<br>
+
+# Target Offset - Infinite Recharge
+## After starting with 3 balls, collects power cells and heads to primary shooting position
+
+### [Target Offset 1](target_offset_1.json)
+When we start offset from the target with the 3 pre-loaded balls in the robot, this spline directs the robot to the additional 2 power cells, allowing the robot to collect both balls available
+
+### [Target Offset 2](target_offset_2.json)
+After collecting the 2 balls using the target_offset_1 spline, this spline directs the robot towards the shooter tower, setting it up to shoot the 5 balls.
\ No newline at end of file
diff --git a/y2020/actors/splines/target_aligned_1.json b/y2020/actors/splines/target_aligned_1.json
new file mode 100644
index 0000000..5a1d082
--- /dev/null
+++ b/y2020/actors/splines/target_aligned_1.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [3.340495531674842, 3.3229239110760016, 4.483964997108113, 6.058132772881773, 7.084092118999326, 8.012654807002901], "spline_y": [5.730681749413974, 6.367337321705237, 7.14109740201818, 7.627912651837696, 7.449284138182865, 7.4135680036085025], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/target_aligned_2.json b/y2020/actors/splines/target_aligned_2.json
new file mode 100644
index 0000000..bca8965
--- /dev/null
+++ b/y2020/actors/splines/target_aligned_2.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [8.02107576923077, 7.157915384615384, 6.042122692307692, 5.010540769230768, 4.2315911538461535, 3.368430769230769], "spline_y": [7.389495, 7.431600384615384, 6.652650769230769, 6.1473861538461545, 5.7684376923076925, 5.7473849999999995], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/target_offset_1.json b/y2020/actors/splines/target_offset_1.json
new file mode 100644
index 0000000..7b56061
--- /dev/null
+++ b/y2020/actors/splines/target_offset_1.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [3.31276940587483, 3.2907022466964535, 4.149941560222061, 3.9732949238578668, 4.6679269035533, 5.751552791878172], "spline_y": [2.721120947225566, 1.4302342949805793, 1.6475505418770833, 1.4726197969543149, 0.8891289340101522, 0.8335583756345177], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
diff --git a/y2020/actors/splines/target_offset_2.json b/y2020/actors/splines/target_offset_2.json
new file mode 100644
index 0000000..6292a7f
--- /dev/null
+++ b/y2020/actors/splines/target_offset_2.json
@@ -0,0 +1 @@
+{"spline_count": 1, "spline_x": [5.709874873096447, 4.5984637055837565, 4.112221319796954, 3.8482611675126903, 3.7232274111675125, 3.3481261421319797], "spline_y": [0.8057730964467005, 0.7779878172588832, 1.4170492385786801, 2.875776395939086, 4.167791878172588, 5.862693908629441], "constraints": [{"constraint_type": "LONGITUDINAL_ACCELERATION", "value": 3.0}, {"constraint_type": "LATERAL_ACCELERATION", "value": 2.0}, {"constraint_type": "VOLTAGE", "value": 10.0}]}
\ No newline at end of file
diff --git a/y2020/constants.cc b/y2020/constants.cc
index 893613a..3f5cd2a 100644
--- a/y2020/constants.cc
+++ b/y2020/constants.cc
@@ -38,14 +38,26 @@
// Rounded up from exact estimate, since I'm not sure if the original estimate
// includes bumpers.
constexpr double kRobotLength = 0.9;
- // { distance_to_target, { hood_angle, accelerator_power, finisher_power }}
- // Current settings based on
- // https://docs.google.com/document/d/1NR9F-ntlSoqZ9LqDzLjn-c14t8ZrppawCCG7wQy47RU/edit
+ // We found that the finisher velocity does not change ball velocity much, so
+ // keep it constant.
+ constexpr double kVelocityFinisher = 350.0;
r->shot_interpolation_table = InterpolationTable<Values::ShotParams>(
- {{7.6 * kFeetToMeters - kRobotLength, {0.115, 197.0, 175.0}},
- {7.6 * kFeetToMeters + kRobotLength, {0.31, 265.0, 235.0}},
- {12.6 * kFeetToMeters + kRobotLength, {0.4, 292.0, 260.0}},
- {17.6 * kFeetToMeters + kRobotLength, {0.52, 365.0, 325.0}}});
+ {{7.6 * kFeetToMeters + kRobotLength, {0.31, 16.9}},
+ {12.6 * kFeetToMeters + kRobotLength, {0.4, 20.4}}});
+
+ r->flywheel_shot_interpolation_table =
+ InterpolationTable<Values::FlywheelShotParams>(
+ {{10.6, {250.0, kVelocityFinisher}},
+ {12.0, {275.0, kVelocityFinisher}},
+ {13.2, {300.0, kVelocityFinisher}},
+ {14.0, {325.0, kVelocityFinisher}},
+ {14.6, {350.0, kVelocityFinisher}},
+ {15.2, {375.0, kVelocityFinisher}},
+ {15.6, {400.0, kVelocityFinisher}},
+ {16.1, {425.0, kVelocityFinisher}},
+ {16.3, {450.0, kVelocityFinisher}},
+ {16.6, {475.0, kVelocityFinisher}},
+ {17.0, {500.0, kVelocityFinisher}}});
// Hood constants.
hood->zeroing_voltage = 2.0;
diff --git a/y2020/constants.h b/y2020/constants.h
index 77f78f4..4483948 100644
--- a/y2020/constants.h
+++ b/y2020/constants.h
@@ -190,24 +190,37 @@
struct ShotParams {
// Measured in radians
double hood_angle;
- // Angular velocity in radians per second of the slowest (lowest) wheel in
- // the kicker. Positive is shooting the ball.
- double accelerator_power;
- // Angular velocity in radians per seconds of the flywheel. Positive is
- // shooting.
- double finisher_power;
+ // Muzzle velocity (m/s) of the ball as it is shot out of the shooter.
+ double velocity_ball;
static ShotParams BlendY(double coefficient, ShotParams a1, ShotParams a2) {
using ::frc971::shooter_interpolation::Blend;
- return ShotParams{
- Blend(coefficient, a1.hood_angle, a2.hood_angle),
- Blend(coefficient, a1.accelerator_power, a2.accelerator_power),
- Blend(coefficient, a1.finisher_power, a2.finisher_power)};
+ return ShotParams{Blend(coefficient, a1.hood_angle, a2.hood_angle),
+ Blend(coefficient, a1.velocity_ball, a2.velocity_ball)};
}
};
- // { distance_to_target, { hood_angle, accelerator_power, finisher_power }}
+ struct FlywheelShotParams {
+ // Angular velocity in radians per second of the slowest (lowest) wheel in
+ // the kicker. Positive is shooting the ball.
+ double velocity_accelerator;
+ // Angular velocity in radians per seconds of the flywheel. Positive is
+ // shooting.
+ double velocity_finisher;
+
+ static FlywheelShotParams BlendY(double coefficient, FlywheelShotParams a1,
+ FlywheelShotParams a2) {
+ using ::frc971::shooter_interpolation::Blend;
+ return FlywheelShotParams{
+ Blend(coefficient, a1.velocity_accelerator, a2.velocity_accelerator),
+ Blend(coefficient, a1.velocity_finisher, a2.velocity_finisher)};
+ }
+ };
+
+ // { distance_to_target, { hood_angle, velocity_ball }}
InterpolationTable<ShotParams> shot_interpolation_table;
+ // { velocity_ball, { velocity_accelerator, velocity_finisher }}
+ InterpolationTable<FlywheelShotParams> flywheel_shot_interpolation_table;
};
// Creates (once) a Values instance for ::aos::network::GetTeamNumber() and
diff --git a/y2020/control_loops/drivetrain/BUILD b/y2020/control_loops/drivetrain/BUILD
index 89d8044..7c4f99f 100644
--- a/y2020/control_loops/drivetrain/BUILD
+++ b/y2020/control_loops/drivetrain/BUILD
@@ -1,6 +1,7 @@
load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
load("//aos:config.bzl", "aos_config")
load("//tools/build_rules:select.bzl", "compiler_select", "cpu_select")
+load("@npm_bazel_typescript//:defs.bzl", "ts_library")
genrule(
name = "genrule_drivetrain",
@@ -56,13 +57,22 @@
],
)
+flatbuffer_cc_library(
+ name = "localizer_debug_fbs",
+ srcs = ["localizer_debug.fbs"],
+ gen_reflections = 1,
+ visibility = ["//visibility:public"],
+)
+
cc_library(
name = "localizer",
srcs = ["localizer.cc"],
hdrs = ["localizer.h"],
target_compatible_with = ["@platforms//os:linux"],
deps = [
+ ":localizer_debug_fbs",
"//aos/containers:ring_buffer",
+ "//aos/containers:sized_array",
"//aos/network:message_bridge_server_fbs",
"//frc971/control_loops:profiled_subsystem_fbs",
"//frc971/control_loops/drivetrain:hybrid_ekf",
@@ -195,3 +205,19 @@
"@com_github_google_glog//:glog",
],
)
+
+ts_library(
+ name = "localizer_plotter",
+ srcs = ["localizer_plotter.ts"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//aos:configuration_ts_fbs",
+ "//aos/network/www:aos_plotter",
+ "//aos/network/www:colors",
+ "//aos/network/www:plotter",
+ "//aos/network/www:proxy",
+ "//aos/network/www:reflection_ts",
+ "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+ ],
+)
diff --git a/y2020/control_loops/drivetrain/drivetrain_replay.cc b/y2020/control_loops/drivetrain/drivetrain_replay.cc
index 65fc0c4..913fbda 100644
--- a/y2020/control_loops/drivetrain/drivetrain_replay.cc
+++ b/y2020/control_loops/drivetrain/drivetrain_replay.cc
@@ -75,6 +75,8 @@
"frc971.control_loops.drivetrain.Status");
reader.RemapLoggedChannel("/drivetrain",
"frc971.control_loops.drivetrain.Output");
+ reader.RemapLoggedChannel("/drivetrain",
+ "y2020.control_loops.drivetrain.LocalizerDebug");
reader.RemapLoggedChannel("/superstructure",
"y2020.control_loops.superstructure.Status");
reader.RemapLoggedChannel("/superstructure",
diff --git a/y2020/control_loops/drivetrain/localizer.cc b/y2020/control_loops/drivetrain/localizer.cc
index 906bb4b..9e0e83f 100644
--- a/y2020/control_loops/drivetrain/localizer.cc
+++ b/y2020/control_loops/drivetrain/localizer.cc
@@ -23,6 +23,10 @@
return result;
}
+// Offset to add to the pi's yaw in its extrinsics, to account for issues in the
+// calibrated extrinsics.
+constexpr double kTurretPiOffset = 0.0;
+
// Indices of the pis to use.
const std::array<std::string, 5> kPisToUse{"pi1", "pi2", "pi3", "pi4", "pi5"};
@@ -72,7 +76,11 @@
ekf_(dt_config),
clock_offset_fetcher_(
event_loop_->MakeFetcher<aos::message_bridge::ServerStatistics>(
- "/aos")) {
+ "/aos")),
+ debug_sender_(
+ event_loop_
+ ->MakeSender<y2020::control_loops::drivetrain::LocalizerDebug>(
+ "/drivetrain")) {
// TODO(james): The down estimator has trouble handling situations where the
// robot is constantly wiggling but not actually moving much, and can cause
// drift when using accelerometer readings.
@@ -145,17 +153,32 @@
double gyro_rate, const Eigen::Vector3d &accel) {
ekf_.UpdateEncodersAndGyro(left_encoder, right_encoder, gyro_rate,
U.cast<float>(), accel.cast<float>(), now);
+ auto builder = debug_sender_.MakeBuilder();
+ aos::SizedArray<flatbuffers::Offset<ImageMatchDebug>, 25> debug_offsets;
for (size_t ii = 0; ii < kPisToUse.size(); ++ii) {
auto &image_fetcher = image_fetchers_[ii];
while (image_fetcher.FetchNext()) {
- HandleImageMatch(kPisToUse[ii], *image_fetcher, now);
+ const auto offsets = HandleImageMatch(ii, kPisToUse[ii], *image_fetcher,
+ now, builder.fbb());
+ for (const auto offset : offsets) {
+ debug_offsets.push_back(offset);
+ }
}
}
+ const auto vector_offset =
+ builder.fbb()->CreateVector(debug_offsets.data(), debug_offsets.size());
+ LocalizerDebug::Builder debug_builder = builder.MakeBuilder<LocalizerDebug>();
+ debug_builder.add_matches(vector_offset);
+ CHECK(builder.Send(debug_builder.Finish()));
}
-void Localizer::HandleImageMatch(
- std::string_view pi, const frc971::vision::sift::ImageMatchResult &result,
- aos::monotonic_clock::time_point now) {
+aos::SizedArray<flatbuffers::Offset<ImageMatchDebug>, 5>
+Localizer::HandleImageMatch(
+ size_t camera_index, std::string_view pi,
+ const frc971::vision::sift::ImageMatchResult &result,
+ aos::monotonic_clock::time_point now, flatbuffers::FlatBufferBuilder *fbb) {
+ aos::SizedArray<flatbuffers::Offset<ImageMatchDebug>, 5> debug_offsets;
+
std::chrono::nanoseconds monotonic_offset(0);
clock_offset_fetcher_.Fetch();
if (clock_offset_fetcher_.get() != nullptr) {
@@ -177,11 +200,21 @@
<< capture_time;
if (capture_time > now) {
LOG(WARNING) << "Got camera frame from the future.";
- return;
+ ImageMatchDebug::Builder builder(*fbb);
+ builder.add_camera(camera_index);
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::IMAGE_FROM_FUTURE);
+ debug_offsets.push_back(builder.Finish());
+ return debug_offsets;
}
if (!result.has_camera_calibration()) {
LOG(WARNING) << "Got camera frame without calibration data.";
- return;
+ ImageMatchDebug::Builder builder(*fbb);
+ builder.add_camera(camera_index);
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::NO_CALIBRATION);
+ debug_offsets.push_back(builder.Finish());
+ return debug_offsets;
}
// Per the ImageMatchResult specification, we can actually determine whether
// the camera is the turret camera just from the presence of the
@@ -194,7 +227,12 @@
// seems reasonable, but may be unnecessarily low or high.
constexpr float kMaxTurretVelocity = 1.0;
if (is_turret && std::abs(turret_data.velocity) > kMaxTurretVelocity) {
- return;
+ ImageMatchDebug::Builder builder(*fbb);
+ builder.add_camera(camera_index);
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::TURRET_TOO_FAST);
+ debug_offsets.push_back(builder.Finish());
+ return debug_offsets;
}
CHECK(result.camera_calibration()->has_fixed_extrinsics());
const Eigen::Matrix<float, 4, 4> fixed_extrinsics =
@@ -206,19 +244,38 @@
if (is_turret) {
H_robot_camera = H_robot_camera *
frc971::control_loops::TransformationMatrixForYaw<float>(
- turret_data.position) *
+ turret_data.position + kTurretPiOffset) *
FlatbufferToTransformationMatrix(
*result.camera_calibration()->turret_extrinsics());
}
if (!result.has_camera_poses()) {
- return;
+ ImageMatchDebug::Builder builder(*fbb);
+ builder.add_camera(camera_index);
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::NO_RESULTS);
+ debug_offsets.push_back(builder.Finish());
+ return debug_offsets;
}
+ int index = -1;
for (const frc971::vision::sift::CameraPose *vision_result :
*result.camera_poses()) {
+ ++index;
+
+ ImageMatchDebug::Builder builder(*fbb);
+ builder.add_camera(camera_index);
+ builder.add_pose_index(index);
+ builder.add_local_image_capture_time_ns(result.image_monotonic_timestamp_ns());
+ builder.add_roborio_image_capture_time_ns(
+ capture_time.time_since_epoch().count());
+ builder.add_image_age_sec(aos::time::DurationInSeconds(now - capture_time));
+
if (!vision_result->has_camera_to_target() ||
!vision_result->has_field_to_target()) {
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::NO_TRANSFORMS);
+ debug_offsets.push_back(builder.Finish());
continue;
}
const Eigen::Matrix<float, 4, 4> H_camera_target =
@@ -257,7 +314,7 @@
// populating some cross-correlation terms.
// Note that these are the noise standard deviations (they are squared below
// to get variances).
- Eigen::Matrix<float, 3, 1> noises(2.0, 2.0, 0.2);
+ Eigen::Matrix<float, 3, 1> noises(2.0, 2.0, 0.5);
// Augment the noise by the approximate rotational speed of the
// camera. This should help account for the fact that, while we are
// spinning, slight timing errors in the camera/turret data will tend to
@@ -271,9 +328,7 @@
H.setZero();
H(0, StateIdx::kX) = 1;
H(1, StateIdx::kY) = 1;
- // This is currently set to zero because we ignore the heading implied by
- // the camera.
- H(2, StateIdx::kTheta) = 0;
+ H(2, StateIdx::kTheta) = 1;
VLOG(1) << "Pose implied by target: " << Z.transpose()
<< " and current pose " << x() << ", " << y() << ", " << theta()
<< " Heading/dist/skew implied by target: "
@@ -282,6 +337,9 @@
// and don't use the correction.
if (std::abs(aos::math::DiffAngle<float>(theta(), Z(2))) > M_PI_2) {
AOS_LOG(WARNING, "Dropped image match due to heading mismatch.\n");
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::HIGH_THETA_DIFFERENCE);
+ debug_offsets.push_back(builder.Finish());
continue;
}
// In order to do the EKF correction, we determine the expected state based
@@ -293,24 +351,42 @@
ekf_.LastStateBeforeTime(capture_time);
if (!state_at_capture.has_value()) {
AOS_LOG(WARNING, "Dropped image match due to age of image.\n");
+ builder.add_accepted(false);
+ builder.add_rejection_reason(RejectionReason::IMAGE_TOO_OLD);
+ debug_offsets.push_back(builder.Finish());
continue;
}
+
+ builder.add_implied_robot_x(Z(0));
+ builder.add_implied_robot_y(Z(1));
+ builder.add_implied_robot_theta(Z(2));
+
+ // Turret is zero when pointed backwards.
+ builder.add_implied_turret_goal(
+ aos::math::NormalizeAngle(M_PI + pose_robot_target.heading()));
+
+ std::optional<RejectionReason> correction_rejection;
const Input U = ekf_.MostRecentInput();
// For the correction step, instead of passing in the measurement directly,
// we pass in (0, 0, 0) as the measurement and then for the expected
// measurement (Zhat) we calculate the error between the implied and actual
// poses. This doesn't affect any of the math, it just makes the code a bit
// more convenient to write given the Correct() interface we already have.
+ // Note: If we start going back to doing back-in-time rewinds, then we can't
+ // get away with passing things by reference.
ekf_.Correct(
Eigen::Vector3f::Zero(), &U, {},
- [H, H_field_target, pose_robot_target, state_at_capture](
+ [H, H_field_target, pose_robot_target, state_at_capture, Z, &correction_rejection](
const State &, const Input &) -> Eigen::Vector3f {
- const Eigen::Vector3f Z =
+ // TODO(james): Figure out how to use the implied pose for...
+ const Eigen::Vector3f Z_implied =
CalculateImpliedPose(H_field_target, pose_robot_target);
+ (void)Z_implied;
// Just in case we ever do encounter any, drop measurements if they
// have non-finite numbers.
if (!Z.allFinite()) {
AOS_LOG(WARNING, "Got measurement with infinites or NaNs.\n");
+ correction_rejection = RejectionReason::NONFINITE_MEASUREMENT;
return Eigen::Vector3f::Zero();
}
Eigen::Vector3f Zhat = H * state_at_capture.value() - Z;
@@ -324,12 +400,21 @@
// because I primarily introduced it to make sure that any grossly
// invalid measurements get thrown out.
if (Zhat.squaredNorm() > std::pow(10.0, 2)) {
+ correction_rejection = RejectionReason::CORRECTION_TOO_LARGE;
return Eigen::Vector3f::Zero();
}
return Zhat;
},
[H](const State &) { return H; }, R, now);
+ if (correction_rejection) {
+ builder.add_accepted(false);
+ builder.add_rejection_reason(*correction_rejection);
+ } else {
+ builder.add_accepted(true);
+ }
+ debug_offsets.push_back(builder.Finish());
}
+ return debug_offsets;
}
} // namespace drivetrain
diff --git a/y2020/control_loops/drivetrain/localizer.h b/y2020/control_loops/drivetrain/localizer.h
index c3b6464..19ee4ad 100644
--- a/y2020/control_loops/drivetrain/localizer.h
+++ b/y2020/control_loops/drivetrain/localizer.h
@@ -4,11 +4,13 @@
#include <string_view>
#include "aos/containers/ring_buffer.h"
+#include "aos/containers/sized_array.h"
#include "aos/events/event_loop.h"
#include "aos/network/message_bridge_server_generated.h"
#include "frc971/control_loops/drivetrain/hybrid_ekf.h"
#include "frc971/control_loops/drivetrain/localizer.h"
#include "y2020/control_loops/superstructure/superstructure_status_generated.h"
+#include "y2020/control_loops/drivetrain/localizer_debug_generated.h"
#include "y2020/vision/sift/sift_generated.h"
namespace y2020 {
@@ -77,9 +79,11 @@
};
// Processes new image data from the given pi and updates the EKF.
- void HandleImageMatch(std::string_view pi,
- const frc971::vision::sift::ImageMatchResult &result,
- aos::monotonic_clock::time_point now);
+ aos::SizedArray<flatbuffers::Offset<ImageMatchDebug>, 5> HandleImageMatch(
+ size_t camera_index, std::string_view pi,
+ const frc971::vision::sift::ImageMatchResult &result,
+ aos::monotonic_clock::time_point now,
+ flatbuffers::FlatBufferBuilder *fbb);
// Processes the most recent turret position and stores it in the turret_data_
// buffer.
@@ -98,6 +102,8 @@
aos::Fetcher<aos::message_bridge::ServerStatistics> clock_offset_fetcher_;
+ aos::Sender<y2020::control_loops::drivetrain::LocalizerDebug> debug_sender_;
+
// Buffer of recent turret data--this is used so that when we receive a camera
// frame from the turret, we can back out what the turret angle was at that
// time.
diff --git a/y2020/control_loops/drivetrain/localizer_debug.fbs b/y2020/control_loops/drivetrain/localizer_debug.fbs
new file mode 100644
index 0000000..89aea68
--- /dev/null
+++ b/y2020/control_loops/drivetrain/localizer_debug.fbs
@@ -0,0 +1,34 @@
+namespace y2020.control_loops.drivetrain;
+
+enum RejectionReason : byte {
+ IMAGE_FROM_FUTURE = 0,
+ NO_CALIBRATION = 1,
+ TURRET_TOO_FAST = 2,
+ NO_RESULTS = 3,
+ NO_TRANSFORMS = 4,
+ HIGH_THETA_DIFFERENCE = 5,
+ IMAGE_TOO_OLD = 6,
+ NONFINITE_MEASUREMENT = 7,
+ CORRECTION_TOO_LARGE = 8,
+}
+
+table ImageMatchDebug {
+ camera:uint8 (id: 0);
+ pose_index: uint8 (id: 1);
+ local_image_capture_time_ns:long (id: 2);
+ roborio_image_capture_time_ns:long (id: 3);
+ implied_robot_x:float (id: 4);
+ implied_robot_y:float (id: 5);
+ implied_robot_theta:float (id: 6);
+ implied_turret_goal:float (id: 7);
+ accepted:bool (id: 8);
+ rejection_reason:RejectionReason (id: 9);
+ // Image age (more human-readable than trying to interpret roborio_image_capture_time_ns).
+ image_age_sec:float (id: 10);
+}
+
+table LocalizerDebug {
+ matches:[ImageMatchDebug] (id: 0);
+}
+
+root_type LocalizerDebug;
diff --git a/y2020/control_loops/drivetrain/localizer_plotter.ts b/y2020/control_loops/drivetrain/localizer_plotter.ts
new file mode 100644
index 0000000..4de2856
--- /dev/null
+++ b/y2020/control_loops/drivetrain/localizer_plotter.ts
@@ -0,0 +1,100 @@
+// Provides a plot for debugging robot state-related issues.
+import {AosPlotter} from 'org_frc971/aos/network/www/aos_plotter';
+import * as proxy from 'org_frc971/aos/network/www/proxy';
+import * as configuration from 'org_frc971/aos/configuration_generated';
+import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE} from 'org_frc971/aos/network/www/colors';
+import {MessageHandler, TimestampedMessage} from 'org_frc971/aos/network/www/aos_plotter';
+import {Point} from 'org_frc971/aos/network/www/plotter';
+import {Table} from 'org_frc971/aos/network/www/reflection';
+import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
+
+import Connection = proxy.Connection;
+import Schema = configuration.reflection.Schema;
+
+const TIME = AosPlotter.TIME;
+const DEFAULT_WIDTH = AosPlotter.DEFAULT_WIDTH;
+const DEFAULT_HEIGHT = AosPlotter.DEFAULT_HEIGHT;
+
+
+export function plotLocalizer(conn: Connection, element: Element) : void {
+ const aosPlotter = new AosPlotter(conn);
+ const localizerDebug =
+ aosPlotter.addMessageSource('/drivetrain', 'y2020.control_loops.drivetrain.LocalizerDebug');
+ const imageMatch =
+ aosPlotter.addMessageSource('/pi1/camera', 'frc971.vision.sift.ImageMatchResult');
+
+ var currentTop = 0;
+
+ const imageAcceptedPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ imageAcceptedPlot.plot.getAxisLabels().setTitle('Image Acceptance');
+ imageAcceptedPlot.plot.getAxisLabels().setXLabel(TIME);
+ imageAcceptedPlot.plot.getAxisLabels().setYLabel('[bool]');
+ imageAcceptedPlot.plot.setDefaultYRange([-0.05, 1.05]);
+
+ imageAcceptedPlot.addMessageLine(localizerDebug, ['matches[]', 'accepted'])
+ .setColor(RED)
+ .setDrawLine(false);
+
+ const impliedXPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ impliedXPlot.plot.getAxisLabels().setTitle('Implied Robot X');
+ impliedXPlot.plot.getAxisLabels().setXLabel(TIME);
+ impliedXPlot.plot.getAxisLabels().setYLabel('[m]');
+
+ impliedXPlot.addMessageLine(localizerDebug, ['matches[]', 'implied_robot_x'])
+ .setColor(RED)
+ .setDrawLine(false);
+ impliedXPlot.addMessageLine(imageMatch, ['camera_poses[]', 'field_to_camera', 'data[3]'])
+ .setColor(BLUE)
+ .setDrawLine(false);
+
+ const impliedYPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ impliedYPlot.plot.getAxisLabels().setTitle('Implied Robot Y');
+ impliedYPlot.plot.getAxisLabels().setXLabel(TIME);
+ impliedYPlot.plot.getAxisLabels().setYLabel('[m]');
+
+ impliedYPlot.addMessageLine(localizerDebug, ['matches[]', 'implied_robot_y'])
+ .setColor(RED)
+ .setDrawLine(false);
+ impliedYPlot.addMessageLine(imageMatch, ['camera_poses[]', 'field_to_camera', 'data[7]'])
+ .setColor(BLUE)
+ .setDrawLine(false);
+
+ const impliedHeadingPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ impliedHeadingPlot.plot.getAxisLabels().setTitle('Implied Robot Theta');
+ impliedHeadingPlot.plot.getAxisLabels().setXLabel(TIME);
+ impliedHeadingPlot.plot.getAxisLabels().setYLabel('[rad]');
+
+ impliedHeadingPlot.addMessageLine(localizerDebug, ['matches[]', 'implied_robot_theta'])
+ .setColor(RED)
+ .setDrawLine(false);
+
+ const impliedTurretGoalPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ impliedTurretGoalPlot.plot.getAxisLabels().setTitle('Implied Turret Goal');
+ impliedTurretGoalPlot.plot.getAxisLabels().setXLabel(TIME);
+ impliedTurretGoalPlot.plot.getAxisLabels().setYLabel('[rad]');
+
+ impliedTurretGoalPlot.addMessageLine(localizerDebug, ['matches[]', 'implied_turret_goal'])
+ .setColor(RED)
+ .setDrawLine(false);
+
+ const imageTimingPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ imageTimingPlot.plot.getAxisLabels().setTitle('Timing Plot');
+ imageTimingPlot.plot.getAxisLabels().setXLabel(TIME);
+ imageTimingPlot.plot.getAxisLabels().setYLabel('[ns]');
+
+ imageTimingPlot.addMessageLine(localizerDebug, ['matches[]', 'image_age_sec'])
+ .setColor(RED)
+ .setDrawLine(false);
+}
diff --git a/y2020/control_loops/python/finisher.py b/y2020/control_loops/python/finisher.py
index 35ff48f..7ccccd6 100644
--- a/y2020/control_loops/python/finisher.py
+++ b/y2020/control_loops/python/finisher.py
@@ -33,7 +33,7 @@
# The position and velocity are measured for the final wheel.
kFinisher = flywheel.FlywheelParams(
name='Finisher',
- motor=AddResistance(control_loop.NMotor(control_loop.Falcon(), 2), 0.01),
+ motor=AddResistance(control_loop.NMotor(control_loop.Falcon(), 2), 0.03),
G=G,
J=J,
q_pos=0.01,
diff --git a/y2020/control_loops/python/turret.py b/y2020/control_loops/python/turret.py
index acddae3..6f15057 100644
--- a/y2020/control_loops/python/turret.py
+++ b/y2020/control_loops/python/turret.py
@@ -20,14 +20,14 @@
kTurret = angular_system.AngularSystemParams(
name='Turret',
- motor=control_loop.Vex775Pro(),
- G=(6.0 / 60.0) * (26.0 / 150.0),
+ motor=control_loop.MiniCIM(),
+ G=(26.0 / 150.0) * (14.0 / 60.0) * (20.0 / 60.0),
J=0.20,
q_pos=0.30,
q_vel=4.5,
kalman_q_pos=0.12,
kalman_q_vel=10.0,
- kalman_q_voltage=12.0,
+ kalman_q_voltage=20.0,
kalman_r_position=0.05)
def main(argv):
diff --git a/y2020/control_loops/superstructure/BUILD b/y2020/control_loops/superstructure/BUILD
index 2d47cf4..db8a060 100644
--- a/y2020/control_loops/superstructure/BUILD
+++ b/y2020/control_loops/superstructure/BUILD
@@ -154,9 +154,13 @@
srcs = ["turret_plotter.ts"],
target_compatible_with = ["@platforms//os:linux"],
deps = [
+ "//aos:configuration_ts_fbs",
"//aos/network/www:aos_plotter",
"//aos/network/www:colors",
+ "//aos/network/www:plotter",
"//aos/network/www:proxy",
+ "//aos/network/www:reflection_ts",
+ "@com_github_google_flatbuffers//ts:flatbuffers_ts",
],
)
diff --git a/y2020/control_loops/superstructure/accelerator_plotter.ts b/y2020/control_loops/superstructure/accelerator_plotter.ts
index eaca1b8..b625a1c 100644
--- a/y2020/control_loops/superstructure/accelerator_plotter.ts
+++ b/y2020/control_loops/superstructure/accelerator_plotter.ts
@@ -1,7 +1,7 @@
// Provides a plot for debugging robot state-related issues.
import {AosPlotter} from 'org_frc971/aos/network/www/aos_plotter';
import * as proxy from 'org_frc971/aos/network/www/proxy';
-import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE} from 'org_frc971/aos/network/www/colors';
+import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE, BLACK} from 'org_frc971/aos/network/www/colors';
import Connection = proxy.Connection;
@@ -26,13 +26,14 @@
velocityPlot.plot.getAxisLabels().setTitle('Velocity');
velocityPlot.plot.getAxisLabels().setXLabel(TIME);
velocityPlot.plot.getAxisLabels().setYLabel('rad/s');
- velocityPlot.plot.setDefaultYRange([0.0, 450.0]);
+ velocityPlot.plot.setDefaultYRange([0.0, 600.0]);
velocityPlot.addMessageLine(goal, ['shooter', 'velocity_accelerator']).setColor(BLUE).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'accelerator_left', 'avg_angular_velocity']).setColor(RED).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'accelerator_right', 'avg_angular_velocity']).setColor(PINK).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'accelerator_left', 'angular_velocity']).setColor(GREEN).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'accelerator_right', 'angular_velocity']).setColor(CYAN).setPointSize(0.0);
+ velocityPlot.addMessageLine(status, ['shooter', 'ready']).setColor(BLACK).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'accelerator_left', 'dt_angular_velocity']).setColor(PINK).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'accelerator_right', 'dt_angular_velocity']).setColor(BLUE).setPointSize(0.0);
diff --git a/y2020/control_loops/superstructure/finisher_plotter.ts b/y2020/control_loops/superstructure/finisher_plotter.ts
index 18052cc..6ec4be4 100644
--- a/y2020/control_loops/superstructure/finisher_plotter.ts
+++ b/y2020/control_loops/superstructure/finisher_plotter.ts
@@ -1,7 +1,7 @@
// Provides a plot for debugging robot state-related issues.
import {AosPlotter} from 'org_frc971/aos/network/www/aos_plotter';
import * as proxy from 'org_frc971/aos/network/www/proxy';
-import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE} from 'org_frc971/aos/network/www/colors';
+import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE, BLACK} from 'org_frc971/aos/network/www/colors';
import Connection = proxy.Connection;
@@ -26,11 +26,12 @@
velocityPlot.plot.getAxisLabels().setTitle('Velocity');
velocityPlot.plot.getAxisLabels().setXLabel(TIME);
velocityPlot.plot.getAxisLabels().setYLabel('rad/s');
- velocityPlot.plot.setDefaultYRange([0.0, 450.0]);
+ velocityPlot.plot.setDefaultYRange([0.0, 600.0]);
velocityPlot.addMessageLine(goal, ['shooter', 'velocity_finisher']).setColor(BLUE).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'finisher', 'avg_angular_velocity']).setColor(RED).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'finisher', 'angular_velocity']).setColor(GREEN).setPointSize(0.0);
+ velocityPlot.addMessageLine(status, ['shooter', 'ready']).setColor(BLACK).setPointSize(0.0);
velocityPlot.addMessageLine(status, ['shooter', 'finisher', 'dt_angular_velocity']).setColor(PINK).setPointSize(0.0);
const voltagePlot =
diff --git a/y2020/control_loops/superstructure/shooter/BUILD b/y2020/control_loops/superstructure/shooter/BUILD
index 0694319..d3b055c 100644
--- a/y2020/control_loops/superstructure/shooter/BUILD
+++ b/y2020/control_loops/superstructure/shooter/BUILD
@@ -1,5 +1,7 @@
package(default_visibility = ["//visibility:public"])
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+
cc_library(
name = "shooter_plants",
target_compatible_with = ["@platforms//os:linux"],
@@ -46,3 +48,35 @@
"//y2020/control_loops/superstructure/finisher:finisher_plants",
],
)
+
+flatbuffer_cc_library(
+ name = "shooter_tuning_readings_fbs",
+ srcs = [
+ "shooter_tuning_readings.fbs",
+ ],
+ gen_reflections = 1,
+ target_compatible_with = ["@platforms//os:linux"],
+)
+
+flatbuffer_cc_library(
+ name = "shooter_tuning_params_fbs",
+ srcs = [
+ "shooter_tuning_params.fbs",
+ ],
+ gen_reflections = 1,
+ target_compatible_with = ["@platforms//os:linux"],
+)
+
+cc_binary(
+ name = "shooter_tuning_params_setter",
+ srcs = ["shooter_tuning_params_setter.cc"],
+ target_compatible_with = ["@platforms//os:linux"],
+ deps = [
+ ":shooter_tuning_params_fbs",
+ ":shooter_tuning_readings_fbs",
+ "//aos:init",
+ "//aos/events:shm_event_loop",
+ "@com_github_gflags_gflags//:gflags",
+ "@com_github_google_glog//:glog",
+ ],
+)
diff --git a/y2020/control_loops/superstructure/shooter/flywheel_controller.cc b/y2020/control_loops/superstructure/shooter/flywheel_controller.cc
index d94573b..36edf30 100644
--- a/y2020/control_loops/superstructure/shooter/flywheel_controller.cc
+++ b/y2020/control_loops/superstructure/shooter/flywheel_controller.cc
@@ -41,7 +41,7 @@
// And we have a quadratic!
const double a = 1;
const double b = -bemf_voltage;
- const double c = -60.0 * 12.0 * resistance_;
+ const double c = -70.0 * 12.0 * resistance_;
// Root is always positive.
const double root = std::sqrt(b * b - 4.0 * a * c);
@@ -50,7 +50,7 @@
// Limit to the battery voltage and the current limit voltage.
mutable_U(0, 0) = std::clamp(U(0, 0), lower_limit, upper_limit);
- mutable_U(0, 0) = std::clamp(U(0, 0), -12.0, 12.0);
+ mutable_U(0, 0) = std::clamp(U(0, 0), 0.0, 12.0);
}
private:
diff --git a/y2020/control_loops/superstructure/shooter/shooter.cc b/y2020/control_loops/superstructure/shooter/shooter.cc
index 9db79b0..900d0f1 100644
--- a/y2020/control_loops/superstructure/shooter/shooter.cc
+++ b/y2020/control_loops/superstructure/shooter/shooter.cc
@@ -12,7 +12,7 @@
namespace shooter {
namespace {
-const double kVelocityTolerance = 20.0;
+const double kVelocityTolerance = 2.0;
} // namespace
Shooter::Shooter()
@@ -86,6 +86,7 @@
status_builder.add_finisher(finisher_status_offset);
status_builder.add_accelerator_left(accelerator_left_status_offset);
status_builder.add_accelerator_right(accelerator_right_status_offset);
+ status_builder.add_ready(ready());
if (output) {
output->finisher_voltage = finisher_.voltage();
diff --git a/y2020/control_loops/superstructure/shooter/shooter_tuning_params.fbs b/y2020/control_loops/superstructure/shooter/shooter_tuning_params.fbs
new file mode 100644
index 0000000..7fe2dc7
--- /dev/null
+++ b/y2020/control_loops/superstructure/shooter/shooter_tuning_params.fbs
@@ -0,0 +1,26 @@
+namespace y2020.control_loops.superstructure.shooter;
+
+// Parameters for tuning a flywheel's velocity.
+// Using this for the parameters of the accelerators and the finisher.
+table FlywheelTuningParams {
+ // Shoot balls at
+ // velocity_initial, velocity_initial + velocity_increment, ...,
+ // up to velocity_final, randomizing the order of these velocities.
+ velocity_initial:double (id: 0);
+ velocity_final:double (id: 1);
+ velocity_increment:double (id: 2);
+}
+
+// Parameters for the auto mode that tunes the shooter
+table TuningParams {
+ // Parameters for the flywheels
+ accelerator:FlywheelTuningParams (id: 0);
+ finisher:FlywheelTuningParams (id: 1);
+
+ // Number of balls to shooter at each iteration in
+ // the sweep of all possible accelerator and finisher
+ // velocities.
+ balls_per_iteration:int (id: 2);
+}
+
+root_type TuningParams;
\ No newline at end of file
diff --git a/y2020/control_loops/superstructure/shooter/shooter_tuning_params_setter.cc b/y2020/control_loops/superstructure/shooter/shooter_tuning_params_setter.cc
new file mode 100644
index 0000000..3c6158e
--- /dev/null
+++ b/y2020/control_loops/superstructure/shooter/shooter_tuning_params_setter.cc
@@ -0,0 +1,58 @@
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+#include "y2020/control_loops/superstructure/shooter/shooter_tuning_params_generated.h"
+
+DEFINE_double(velocity_initial_finisher, 300.0, "Initial finisher velocity");
+DEFINE_double(velocity_final_finisher, 500.0, "Final finisher velocity");
+DEFINE_double(velocity_finisher_increment, 25.0, "Finisher velocity increment");
+
+DEFINE_double(velocity_initial_accelerator, 180.0,
+ "Initial accelerator velocity");
+DEFINE_double(velocity_final_accelerator, 250.0, "Final accelerator velocity");
+DEFINE_double(velocity_accelerator_increment, 20.0,
+ "Accelerator velocity increment");
+
+DEFINE_int32(balls_per_iteration, 10,
+ "Balls to shoot per iteration in the velocity sweep");
+
+namespace shooter = y2020::control_loops::superstructure::shooter;
+
+flatbuffers::Offset<shooter::FlywheelTuningParams> BuildFlywheelTuningParams(
+ aos::Sender<shooter::TuningParams>::Builder &builder,
+ double velocity_initial, double velocity_final, double velocity_increment) {
+ auto flywheel_tuning_params_builder =
+ builder.MakeBuilder<shooter::FlywheelTuningParams>();
+ flywheel_tuning_params_builder.add_velocity_initial(velocity_initial);
+ flywheel_tuning_params_builder.add_velocity_final(velocity_final);
+ flywheel_tuning_params_builder.add_velocity_increment(velocity_increment);
+ return flywheel_tuning_params_builder.Finish();
+}
+
+int main(int argc, char **argv) {
+ aos::InitGoogle(&argc, &argv);
+
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig("config.json");
+
+ aos::ShmEventLoop event_loop(&config.message());
+
+ auto sender = event_loop.MakeSender<shooter::TuningParams>("/superstructure");
+ auto builder = sender.MakeBuilder();
+
+ auto finisher_params = BuildFlywheelTuningParams(
+ builder, FLAGS_velocity_initial_finisher, FLAGS_velocity_final_finisher,
+ FLAGS_velocity_finisher_increment);
+ auto accelerator_params = BuildFlywheelTuningParams(
+ builder, FLAGS_velocity_initial_accelerator,
+ FLAGS_velocity_final_accelerator, FLAGS_velocity_accelerator_increment);
+
+ auto tuning_params_builder = builder.MakeBuilder<shooter::TuningParams>();
+ tuning_params_builder.add_finisher(finisher_params);
+ tuning_params_builder.add_accelerator(accelerator_params);
+ tuning_params_builder.add_balls_per_iteration(FLAGS_balls_per_iteration);
+ CHECK(builder.Send(tuning_params_builder.Finish()));
+
+ return 0;
+}
diff --git a/y2020/control_loops/superstructure/shooter/shooter_tuning_readings.fbs b/y2020/control_loops/superstructure/shooter/shooter_tuning_readings.fbs
new file mode 100644
index 0000000..ae0c379
--- /dev/null
+++ b/y2020/control_loops/superstructure/shooter/shooter_tuning_readings.fbs
@@ -0,0 +1,14 @@
+namespace y2020.control_loops.superstructure.shooter;
+
+// Contains the readings used to tune the accelerator
+// and finisher velocities to minimize variation in ball velocity.
+// We have two sensors, and find the time that the ball takes to
+// pass between the two to find its velocity.
+// This will be sent each time a ball is detected.
+table TuningReadings {
+ // The velocity (m/s) of the last ball passing between the two
+ // sensors
+ velocity_ball:double (id: 0);
+}
+
+root_type TuningReadings;
diff --git a/y2020/control_loops/superstructure/superstructure.cc b/y2020/control_loops/superstructure/superstructure.cc
index ce816a0..70ea319 100644
--- a/y2020/control_loops/superstructure/superstructure.cc
+++ b/y2020/control_loops/superstructure/superstructure.cc
@@ -72,15 +72,18 @@
aos::FlatbufferFixedAllocatorArray<ShooterGoal, 64> shooter_goal;
constants::Values::ShotParams shot_params;
+ constants::Values::FlywheelShotParams flywheel_shot_params;
if (constants::GetValues().shot_interpolation_table.GetInRange(
- distance_to_goal, &shot_params)) {
+ distance_to_goal, &shot_params) &&
+ constants::GetValues().flywheel_shot_interpolation_table.GetInRange(
+ shot_params.velocity_ball, &flywheel_shot_params)) {
hood_goal.Finish(frc971::control_loops::
CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
*hood_goal.fbb(), shot_params.hood_angle));
- shooter_goal.Finish(CreateShooterGoal(*shooter_goal.fbb(),
- shot_params.accelerator_power,
- shot_params.finisher_power));
+ shooter_goal.Finish(CreateShooterGoal(
+ *shooter_goal.fbb(), flywheel_shot_params.velocity_accelerator,
+ flywheel_shot_params.velocity_finisher));
} else {
hood_goal.Finish(
frc971::control_loops::
@@ -164,11 +167,16 @@
if (output != nullptr) {
// Friction is a pain and putting a really high burden on the integrator.
+ // TODO(james): I'm not sure how helpful this gain is.
const double turret_velocity_sign =
turret_status->velocity() * kTurretFrictionGain;
output_struct.turret_voltage +=
std::clamp(turret_velocity_sign, -kTurretFrictionVoltageLimit,
kTurretFrictionVoltageLimit);
+ const double time_sec =
+ aos::time::DurationInSeconds(position_timestamp.time_since_epoch());
+ output_struct.turret_voltage +=
+ kTurretDitherGain * std::sin(2.0 * M_PI * time_sec * 30.0);
output_struct.turret_voltage =
std::clamp(output_struct.turret_voltage, -turret_.operating_voltage(),
turret_.operating_voltage());
diff --git a/y2020/control_loops/superstructure/superstructure.h b/y2020/control_loops/superstructure/superstructure.h
index c03f7a7..59e976e 100644
--- a/y2020/control_loops/superstructure/superstructure.h
+++ b/y2020/control_loops/superstructure/superstructure.h
@@ -26,8 +26,9 @@
// Terms to control the velocity gain for the friction compensation, and the
// voltage cap.
- static constexpr double kTurretFrictionGain = 10.0;
+ static constexpr double kTurretFrictionGain = 0.0;
static constexpr double kTurretFrictionVoltageLimit = 1.5;
+ static constexpr double kTurretDitherGain = 0.0;
using PotAndAbsoluteEncoderSubsystem =
::frc971::control_loops::StaticZeroingSingleDOFProfiledSubsystem<
diff --git a/y2020/control_loops/superstructure/superstructure_lib_test.cc b/y2020/control_loops/superstructure/superstructure_lib_test.cc
index 22dd40e..a54c89c 100644
--- a/y2020/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2020/control_loops/superstructure/superstructure_lib_test.cc
@@ -286,7 +286,7 @@
// Confirm that we aren't drawing too much current.
CHECK_NEAR(accelerator_left_plant_->battery_current(accelerator_left_U),
- 0.0, 70.0);
+ 0.0, 75.0);
::Eigen::Matrix<double, 1, 1> accelerator_right_U;
accelerator_right_U
@@ -295,7 +295,7 @@
// Confirm that we aren't drawing too much current.
CHECK_NEAR(accelerator_right_plant_->battery_current(accelerator_right_U),
- 0.0, 70.0);
+ 0.0, 75.0);
::Eigen::Matrix<double, 1, 1> finisher_U;
finisher_U << superstructure_output_fetcher_->finisher_voltage() +
@@ -742,7 +742,7 @@
superstructure_plant_.set_peak_intake_acceleration(0.2);
superstructure_plant_.set_peak_turret_velocity(23.0);
- superstructure_plant_.set_peak_turret_acceleration(0.2);
+ superstructure_plant_.set_peak_turret_acceleration(6.0);
// Intake needs over 9 seconds to reach the goal
RunFor(chrono::seconds(10));
@@ -819,7 +819,7 @@
}
// Give it a lot of time to get there.
- RunFor(chrono::seconds(15));
+ RunFor(chrono::seconds(25));
VerifyNearGoal();
}
@@ -971,7 +971,7 @@
superstructure_status_fetcher_.Fetch();
EXPECT_NEAR(kShotAngle, superstructure_status_fetcher_->turret()->position(),
- 1e-4);
+ 5e-4);
EXPECT_FLOAT_EQ(kShotAngle,
superstructure_status_fetcher_->aimer()->turret_position());
EXPECT_FLOAT_EQ(0,
@@ -1085,7 +1085,9 @@
constants::Values::kHoodRange().upper, 0.001);
}
-TEST_P(SuperstructureAllianceTest, ShooterInterpolationInRange) {
+// TODO(milind): Add correct values and enable this once interpolation tables
+// have been filled out
+TEST_P(SuperstructureAllianceTest, DISABLED_ShooterInterpolationInRange) {
SetEnabled(true);
const frc971::control_loops::Pose target = turret::OuterPortPose(GetParam());
WaitUntilZeroed();
diff --git a/y2020/control_loops/superstructure/superstructure_status.fbs b/y2020/control_loops/superstructure/superstructure_status.fbs
index adca326..e55e19b 100644
--- a/y2020/control_loops/superstructure/superstructure_status.fbs
+++ b/y2020/control_loops/superstructure/superstructure_status.fbs
@@ -33,6 +33,10 @@
// Velocity is the fastest (top) wheel
accelerator_left:FlywheelControllerStatus (id: 1);
accelerator_right:FlywheelControllerStatus (id: 2);
+
+ // If the shooter is ready, this is true. Note: don't use this for status
+ // checking, only for plotting. Changes in goal take time to impact this.
+ ready:bool (id: 3);
}
table AimerStatus {
diff --git a/y2020/control_loops/superstructure/turret_plotter.ts b/y2020/control_loops/superstructure/turret_plotter.ts
index 3d975d8..948ca97 100644
--- a/y2020/control_loops/superstructure/turret_plotter.ts
+++ b/y2020/control_loops/superstructure/turret_plotter.ts
@@ -1,29 +1,82 @@
// Provides a plot for debugging robot state-related issues.
import {AosPlotter} from 'org_frc971/aos/network/www/aos_plotter';
import * as proxy from 'org_frc971/aos/network/www/proxy';
+import * as configuration from 'org_frc971/aos/configuration_generated';
import {BLUE, BROWN, CYAN, GREEN, PINK, RED, WHITE} from 'org_frc971/aos/network/www/colors';
+import {MessageHandler, TimestampedMessage} from 'org_frc971/aos/network/www/aos_plotter';
+import {Point} from 'org_frc971/aos/network/www/plotter';
+import {Table} from 'org_frc971/aos/network/www/reflection';
+import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
import Connection = proxy.Connection;
+import Schema = configuration.reflection.Schema;
const TIME = AosPlotter.TIME;
const DEFAULT_WIDTH = AosPlotter.DEFAULT_WIDTH;
const DEFAULT_HEIGHT = AosPlotter.DEFAULT_HEIGHT;
+class DerivativeMessageHandler extends MessageHandler {
+ // Calculated magnitude of the measured acceleration from the IMU.
+ private acceleration_magnitudes: Point[] = [];
+ constructor(private readonly schema: Schema) {
+ super(schema);
+ }
+ private readScalar(table: Table, fieldName: string): number {
+ return this.parser.readScalar(table, fieldName);
+ }
+
+ // Computes a numerical derivative for a given input.
+ private derivative(input: Point[]): Point[] {
+ const num_measurements = input.length;
+ const results = [];
+ for (let ii = 0; ii < num_measurements - 1; ++ii) {
+ const x0 = input[ii].x;
+ const x1 = input[ii + 1].x;
+ const y0 = input[ii].y;
+ const y1 = input[ii + 1].y;
+ results.push(new Point((x0 + x1) / 2.0, (y1 - y0) / (x1 - x0)));
+ }
+ return results;
+ }
+
+ getField(field: string[]): Point[] {
+ // Any requested input that ends with "_derivative" will get a derivative
+ // calculated for the provided field.
+ const derivative_suffix = "_derivative";
+ const num_fields = field.length;
+ const end_field = field[num_fields - 1];
+ if (end_field.endsWith(derivative_suffix)) {
+ const field_copy = [];
+ for (let ii = 0; ii < num_fields - 1; ++ii) {
+ field_copy.push(field[ii]);
+ }
+ field_copy.push(end_field.slice(0, end_field.length - derivative_suffix.length));
+ return this.derivative(this.getField(field_copy));
+ } else {
+ return super.getField(field);
+ }
+ }
+}
+
export function plotTurret(conn: Connection, element: Element) : void {
const aosPlotter = new AosPlotter(conn);
const goal = aosPlotter.addMessageSource('/superstructure', 'y2020.control_loops.superstructure.Goal');
const output = aosPlotter.addMessageSource('/superstructure', 'y2020.control_loops.superstructure.Output');
- const status = aosPlotter.addMessageSource(
- '/superstructure', 'y2020.control_loops.superstructure.Status');
+ const status = aosPlotter.addRawMessageSource(
+ '/superstructure', 'y2020.control_loops.superstructure.Status',
+ new DerivativeMessageHandler(conn.getSchema('y2020.control_loops.superstructure.Status'))
+ );
const pdpValues =
aosPlotter.addMessageSource('/roborio/aos', 'frc971.PDPValues');
+ const localizerDebug =
+ aosPlotter.addMessageSource('/drivetrain', 'y2020.control_loops.drivetrain.LocalizerDebug');
var currentTop = 0;
const turretPosPlot = aosPlotter.addPlot(
element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
currentTop += DEFAULT_HEIGHT;
- turretPosPlot.plot.getAxisLabels().setTitle('Turret Goal Position');
+ turretPosPlot.plot.getAxisLabels().setTitle('Turret Position');
turretPosPlot.plot.getAxisLabels().setXLabel(TIME);
turretPosPlot.plot.getAxisLabels().setYLabel('rad');
@@ -33,8 +86,39 @@
turretPosPlot.addMessageLine(status, ['turret', 'position'])
.setColor(GREEN)
.setPointSize(0.0);
+ turretPosPlot.addMessageLine(localizerDebug, ['matches[]', 'implied_turret_goal'])
+ .setColor(GREEN)
+ .setDrawLine(false);
turretPosPlot.addMessageLine(status, ['turret', 'unprofiled_goal_position'])
.setColor(BLUE)
+ .setDrawLine(false);
+
+ const turretVelPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ turretVelPlot.plot.getAxisLabels().setTitle('Turret Velocity');
+ turretVelPlot.plot.getAxisLabels().setXLabel(TIME);
+ turretVelPlot.plot.getAxisLabels().setYLabel('rad / sec');
+
+ turretVelPlot.addMessageLine(status, ['aimer', 'turret_velocity'])
+ .setColor(RED)
+ .setPointSize(0.0);
+ turretVelPlot.addMessageLine(status, ['turret', 'velocity'])
+ .setColor(GREEN)
+ .setPointSize(0.0);
+ turretVelPlot.addMessageLine(status, ['turret', 'unprofiled_goal_velocity'])
+ .setColor(BLUE)
+ .setDrawLine(false);
+
+ const turretAccelPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ turretAccelPlot.plot.getAxisLabels().setTitle('Turret Acceleration');
+ turretAccelPlot.plot.getAxisLabels().setXLabel(TIME);
+ turretAccelPlot.plot.getAxisLabels().setYLabel('rad / sec / sec');
+
+ turretAccelPlot.addMessageLine(status, ['aimer', 'turret_velocity_derivative'])
+ .setColor(RED)
.setPointSize(0.0);
const turretVoltagePlot = aosPlotter.addPlot(
@@ -44,6 +128,15 @@
turretVoltagePlot.plot.getAxisLabels().setXLabel(TIME);
turretVoltagePlot.plot.getAxisLabels().setYLabel('V');
+ turretVoltagePlot.addMessageLine(status, ['turret', 'voltage_error'])
+ .setColor(GREEN)
+ .setPointSize(0.0);
+ turretVoltagePlot.addMessageLine(status, ['turret', 'position_power'])
+ .setColor(BLUE)
+ .setPointSize(0.0);
+ turretVoltagePlot.addMessageLine(status, ['turret', 'velocity_power'])
+ .setColor(CYAN)
+ .setPointSize(0.0);
turretVoltagePlot.addMessageLine(output, ['turret_voltage'])
.setColor(RED)
.setPointSize(0.0);
@@ -54,7 +147,7 @@
currentPlot.plot.getAxisLabels().setTitle('Current');
currentPlot.plot.getAxisLabels().setXLabel(TIME);
currentPlot.plot.getAxisLabels().setYLabel('Amps');
- currentPlot.plot.setDefaultYRange([0.0, 80.0]);
+ currentPlot.plot.setDefaultYRange([0.0, 40.0]);
currentPlot.addMessageLine(pdpValues, ['currents[6]'])
.setColor(GREEN)
@@ -83,4 +176,16 @@
targetChoicePlot.addMessageLine(status, ['aimer', 'aiming_for_inner_port'])
.setColor(RED)
.setPointSize(0.0);
+
+ const imageAcceptedPlot = aosPlotter.addPlot(
+ element, [0, currentTop], [DEFAULT_WIDTH, DEFAULT_HEIGHT]);
+ currentTop += DEFAULT_HEIGHT;
+ imageAcceptedPlot.plot.getAxisLabels().setTitle('Image Acceptance');
+ imageAcceptedPlot.plot.getAxisLabels().setXLabel(TIME);
+ imageAcceptedPlot.plot.getAxisLabels().setYLabel('[bool]');
+ imageAcceptedPlot.plot.setDefaultYRange([-0.05, 1.05]);
+
+ imageAcceptedPlot.addMessageLine(localizerDebug, ['matches[]', 'accepted'])
+ .setColor(RED)
+ .setDrawLine(false);
}
diff --git a/y2020/joystick_reader.cc b/y2020/joystick_reader.cc
index 44d8a42..bb57997 100644
--- a/y2020/joystick_reader.cc
+++ b/y2020/joystick_reader.cc
@@ -101,6 +101,7 @@
double hood_pos = constants::Values::kHoodRange().middle();
double intake_pos = -0.89;
+ double turret_pos = 0.0;
float roller_speed = 0.0f;
float roller_speed_compensation = 0.0f;
double accelerator_speed = 0.0;
@@ -119,6 +120,12 @@
}
}
+ if (setpoint_fetcher_.get()) {
+ turret_pos = setpoint_fetcher_->turret();
+ } else {
+ turret_pos = 0.0;
+ }
+
if (data.IsPressed(kShootFast)) {
if (setpoint_fetcher_.get()) {
accelerator_speed = setpoint_fetcher_->accelerator();
@@ -175,7 +182,7 @@
flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
turret_offset = CreateStaticZeroingSingleDOFProfiledSubsystemGoal(
- *builder.fbb(), 0.0,
+ *builder.fbb(), turret_pos,
CreateProfileParameters(*builder.fbb(), 6.0, 20.0));
flatbuffers::Offset<superstructure::ShooterGoal> shooter_offset =
diff --git a/y2020/setpoint.fbs b/y2020/setpoint.fbs
index 74164e5..9a9a0be 100644
--- a/y2020/setpoint.fbs
+++ b/y2020/setpoint.fbs
@@ -6,6 +6,8 @@
finisher:double (id: 1);
hood:double (id: 2);
+
+ turret:double (id: 3);
}
root_type Setpoint;
diff --git a/y2020/setpoint_setter.cc b/y2020/setpoint_setter.cc
index 3f493c9..1e1a7b8 100644
--- a/y2020/setpoint_setter.cc
+++ b/y2020/setpoint_setter.cc
@@ -6,6 +6,7 @@
DEFINE_double(accelerator, 250.0, "Accelerator speed");
DEFINE_double(finisher, 500.0, "Finsher speed");
DEFINE_double(hood, 0.45, "Hood setpoint");
+DEFINE_double(turret, 0.0, "Turret setpoint");
int main(int argc, char **argv) {
aos::InitGoogle(&argc, &argv);
@@ -27,6 +28,7 @@
setpoint_builder.add_accelerator(FLAGS_accelerator);
setpoint_builder.add_finisher(FLAGS_finisher);
setpoint_builder.add_hood(FLAGS_hood);
+ setpoint_builder.add_turret(FLAGS_turret);
builder.Send(setpoint_builder.Finish());
return 0;
diff --git a/y2020/vision/camera_reader.cc b/y2020/vision/camera_reader.cc
index f5b0612..26f65b7 100644
--- a/y2020/vision/camera_reader.cc
+++ b/y2020/vision/camera_reader.cc
@@ -32,12 +32,12 @@
public:
CameraReader(aos::EventLoop *event_loop,
const sift::TrainingData *training_data, V4L2Reader *reader,
- cv::FlannBasedMatcher *matcher)
+ const cv::Ptr<cv::flann::IndexParams> &index_params,
+ const cv::Ptr<cv::flann::SearchParams> &search_params)
: event_loop_(event_loop),
training_data_(training_data),
camera_calibration_(FindCameraCalibration()),
reader_(reader),
- matcher_(matcher),
image_sender_(event_loop->MakeSender<CameraImage>("/camera")),
result_sender_(
event_loop->MakeSender<sift::ImageMatchResult>("/camera")),
@@ -46,10 +46,16 @@
read_image_timer_(event_loop->AddTimer([this]() { ReadImage(); })),
prev_R_camera_field_vec_(cv::Mat::zeros(3, 1, CV_32F)),
prev_T_camera_field_(cv::Mat::zeros(3, 1, CV_32F)) {
+
+ for (int ii = 0; ii < number_training_images(); ++ii) {
+ matchers_.push_back(cv::FlannBasedMatcher(index_params, search_params));
+ }
+
CopyTrainingFeatures();
- // Technically we don't need to do this, but doing it now avoids the first
- // match attempt being slow.
- matcher_->train();
+
+ for (auto &matcher : matchers_) {
+ matcher.train();
+ }
event_loop->OnRun(
[this]() { read_image_timer_->Setup(event_loop_->monotonic_now()); });
@@ -82,6 +88,7 @@
const std::vector<cv::Mat> &field_camera_list,
const std::vector<cv::Point2f> &target_point_vector,
const std::vector<float> &target_radius_vector,
+ const std::vector<int> &training_image_indices,
aos::Sender<sift::ImageMatchResult> *result_sender,
bool send_details);
@@ -154,7 +161,7 @@
const sift::TrainingData *const training_data_;
const sift::CameraCalibration *const camera_calibration_;
V4L2Reader *const reader_;
- cv::FlannBasedMatcher *const matcher_;
+ std::vector<cv::FlannBasedMatcher> matchers_;
aos::Sender<CameraImage> image_sender_;
aos::Sender<sift::ImageMatchResult> result_sender_;
aos::Sender<sift::ImageMatchResult> detailed_result_sender_;
@@ -188,6 +195,7 @@
}
void CameraReader::CopyTrainingFeatures() {
+ int training_image_index = 0;
for (const sift::TrainingImage *training_image : *training_data_->images()) {
cv::Mat features(training_image->features()->size(), 128, CV_32F);
for (size_t i = 0; i < training_image->features()->size(); ++i) {
@@ -206,7 +214,8 @@
const auto out_mat = features(cv::Range(i, i + 1), cv::Range(0, 128));
in_mat.convertTo(out_mat, CV_32F);
}
- matcher_->add(features);
+ matchers_[training_image_index].add(features);
+ ++training_image_index;
}
}
@@ -218,6 +227,7 @@
const std::vector<cv::Mat> &field_camera_list,
const std::vector<cv::Point2f> &target_point_vector,
const std::vector<float> &target_radius_vector,
+ const std::vector<int> &training_image_indices,
aos::Sender<sift::ImageMatchResult> *result_sender, bool send_details) {
auto builder = result_sender->MakeBuilder();
const auto camera_calibration_offset =
@@ -252,8 +262,8 @@
sift::CreateTransformationMatrix(*builder.fbb(), fc_data_offset);
const flatbuffers::Offset<sift::TransformationMatrix>
- field_to_target_offset =
- aos::RecursiveCopyFlatBuffer(FieldToTarget(i), builder.fbb());
+ field_to_target_offset = aos::RecursiveCopyFlatBuffer(
+ FieldToTarget(training_image_indices[i]), builder.fbb());
sift::CameraPose::Builder pose_builder(*builder.fbb());
pose_builder.add_camera_to_target(transform_offset);
@@ -302,14 +312,8 @@
sift_->detectAndCompute(image_mat, cv::noArray(), keypoints, descriptors);
}
- // Then, match those features against our training data.
- std::vector<std::vector<cv::DMatch>> matches;
- if (!FLAGS_skip_sift) {
- matcher_->knnMatch(/* queryDescriptors */ descriptors, matches, /* k */ 2);
- }
-
struct PerImageMatches {
- std::vector<const std::vector<cv::DMatch> *> matches;
+ std::vector<std::vector<cv::DMatch>> matches;
std::vector<cv::Point3f> training_points_3d;
std::vector<cv::Point2f> query_points;
std::vector<cv::Point2f> training_points;
@@ -317,32 +321,43 @@
};
std::vector<PerImageMatches> per_image_matches(number_training_images());
- // Pull out the good matches which we want for each image.
- // Discard the bad matches per Lowe's ratio test.
- // (Lowe originally proposed 0.7 ratio, but 0.75 was later proposed as a
- // better option. We'll go with the more conservative (fewer, better matches)
- // for now).
- for (const std::vector<cv::DMatch> &match : matches) {
- CHECK_EQ(2u, match.size());
- CHECK_LE(match[0].distance, match[1].distance);
- CHECK_LT(match[0].imgIdx, number_training_images());
- CHECK_LT(match[1].imgIdx, number_training_images());
- CHECK_EQ(match[0].queryIdx, match[1].queryIdx);
- if (!(match[0].distance < 0.7 * match[1].distance)) {
- continue;
+ for (int image_idx = 0; image_idx < number_training_images(); ++image_idx) {
+ // Then, match those features against our training data.
+ std::vector<std::vector<cv::DMatch>> matches;
+ if (!FLAGS_skip_sift) {
+ matchers_[image_idx].knnMatch(/* queryDescriptors */ descriptors, matches,
+ /* k */ 2);
}
- const int training_image = match[0].imgIdx;
- CHECK_LT(training_image, static_cast<int>(per_image_matches.size()));
- PerImageMatches *const per_image = &per_image_matches[training_image];
- per_image->matches.push_back(&match);
- per_image->training_points.push_back(
- Training2dPoint(training_image, match[0].trainIdx));
- per_image->training_points_3d.push_back(
- Training3dPoint(training_image, match[0].trainIdx));
+ // Pull out the good matches which we want for each image.
+ // Discard the bad matches per Lowe's ratio test.
+ // (Lowe originally proposed 0.7 ratio, but 0.75 was later proposed as a
+ // better option. We'll go with the more conservative (fewer, better
+ // matches) for now).
+ for (const std::vector<cv::DMatch> &match : matches) {
+ CHECK_EQ(2u, match.size());
+ CHECK_LE(match[0].distance, match[1].distance);
+ CHECK_EQ(match[0].imgIdx, 0);
+ CHECK_EQ(match[1].imgIdx, 0);
+ CHECK_EQ(match[0].queryIdx, match[1].queryIdx);
+ if (!(match[0].distance < 0.7 * match[1].distance)) {
+ continue;
+ }
- const cv::KeyPoint &keypoint = keypoints[match[0].queryIdx];
- per_image->query_points.push_back(keypoint.pt);
+ const int training_image = image_idx;
+ CHECK_LT(training_image, static_cast<int>(per_image_matches.size()));
+ PerImageMatches *const per_image = &per_image_matches[training_image];
+ per_image->matches.push_back(match);
+ per_image->matches.back()[0].imgIdx = image_idx;
+ per_image->matches.back()[1].imgIdx = image_idx;
+ per_image->training_points.push_back(
+ Training2dPoint(training_image, match[0].trainIdx));
+ per_image->training_points_3d.push_back(
+ Training3dPoint(training_image, match[0].trainIdx));
+
+ const cv::KeyPoint &keypoint = keypoints[match[0].queryIdx];
+ per_image->query_points.push_back(keypoint.pt);
+ }
}
// The minimum number of matches in a training image for us to use it.
@@ -355,12 +370,13 @@
// Build list of target point and radius for each good match
std::vector<cv::Point2f> target_point_vector;
std::vector<float> target_radius_vector;
+ std::vector<int> training_image_indices;
// Iterate through matches for each training image
for (size_t i = 0; i < per_image_matches.size(); ++i) {
const PerImageMatches &per_image = per_image_matches[i];
- VLOG(2) << "Number of matches to start: " << per_image_matches.size()
+ VLOG(2) << "Number of matches to start: " << per_image.matches.size()
<< "\n";
// If we don't have enough matches to start, skip this set of matches
if (per_image.matches.size() < kMinimumMatchCount) {
@@ -392,8 +408,7 @@
}
// Add this to our collection of all matches that passed our criteria
- all_good_matches.push_back(
- static_cast<std::vector<cv::DMatch>>(*per_image.matches[j]));
+ all_good_matches.push_back(per_image.matches[j]);
// Fill out the data for matches per image that made it past
// homography check, for later use
@@ -415,8 +430,7 @@
// Collect training target location, so we can map it to matched image
cv::Point2f target_point;
float target_radius;
- TargetLocation((*(per_image_good_match.matches[0]))[0].imgIdx, target_point,
- target_radius);
+ TargetLocation(i, target_point, target_radius);
// Store target_point in vector for use by perspectiveTransform
std::vector<cv::Point2f> src_target_pt;
@@ -494,6 +508,8 @@
const_cast<void *>(static_cast<const void *>(
FieldToTarget(i)->data()->data())));
+ training_image_indices.push_back(i);
+
const cv::Mat R_field_target =
H_field_target(cv::Range(0, 3), cv::Range(0, 3));
const cv::Mat T_field_target =
@@ -532,11 +548,11 @@
SendImageMatchResult(image, keypoints, descriptors, all_good_matches,
camera_target_list, field_camera_list,
target_point_vector, target_radius_vector,
- &detailed_result_sender_, true);
+ training_image_indices, &detailed_result_sender_, true);
SendImageMatchResult(image, keypoints, descriptors, all_good_matches,
camera_target_list, field_camera_list,
target_point_vector, target_radius_vector,
- &result_sender_, false);
+ training_image_indices, &result_sender_, false);
}
void CameraReader::ReadImage() {
@@ -656,7 +672,7 @@
V4L2Reader v4l2_reader(&event_loop, "/dev/video0");
CameraReader camera_reader(&event_loop, &training_data.message(),
- &v4l2_reader, &matcher);
+ &v4l2_reader, index_params, search_params);
event_loop.Run();
}
diff --git a/y2020/vision/tools/python_code/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json b/y2020/vision/tools/python_code/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json
index a144853..39c7911 100644
--- a/y2020/vision/tools/python_code/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json
+++ b/y2020/vision/tools/python_code/calib_files/calibration_pi-971-1_2021-09-11_17-49-00.000000000.json
@@ -1,6 +1,6 @@
{
"node_name": "pi1",
- "team_number": 7971,
+ "team_number": 971,
"intrinsics": [
392.276093,
0.0,
diff --git a/y2020/vision/tools/python_code/calib_files/reference_cal_pi_cam.json b/y2020/vision/tools/python_code/calib_files/reference_cal_pi_cam.json
new file mode 100644
index 0000000..8c9472e
--- /dev/null
+++ b/y2020/vision/tools/python_code/calib_files/reference_cal_pi_cam.json
@@ -0,0 +1,23 @@
+{
+ "node_name": "reference",
+ "team_number": 1971,
+ "intrinsics": [
+ 388.0,
+ 0.0,
+ 320.0,
+ 0.0,
+ 388.0,
+ 240.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ],
+ "dist_coeffs": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0
+ ],
+ "calibration_timestamp": 1597994992500905688
+}
diff --git a/y2020/vision/tools/python_code/camera_definition.py b/y2020/vision/tools/python_code/camera_definition.py
index 33eb1a9..6172ae7 100644
--- a/y2020/vision/tools/python_code/camera_definition.py
+++ b/y2020/vision/tools/python_code/camera_definition.py
@@ -7,7 +7,7 @@
import define_training_data as dtd
-glog.setLevel("WARN")
+glog.setLevel("INFO")
class CameraIntrinsics:
@@ -36,24 +36,14 @@
def load_camera_definitions():
### CAMERA DEFINITIONS
+ # We only load in cameras that have a calibration file
+ # These are stored in y2020/vision/tools/python_code/calib_files
+ # See reference_calibration_pi_cam.json for an example default file
+ #
+ # Or better yet, use //y2020/vision:calibration to calibrate the camera
+ # using a Charuco target board
- # Robot camera has:
- # FOV_H = 93.*math.pi()/180.
- # FOV_V = 70.*math.pi()/180.
-
- # Create fake camera (based on USB webcam params)
- fx = 810.
- fy = 810.
- cx = 320.
- cy = 240.
-
- # Define a web_cam
- web_cam_int = CameraIntrinsics()
- web_cam_int.camera_matrix = np.asarray([[fx, 0, cx], [0, fy, cy],
- [0, 0, 1]])
- web_cam_int.dist_coeffs = np.zeros((5, 1))
-
- web_cam_ext = CameraExtrinsics()
+ # Extrinsic definition
# Camera rotation from robot x,y,z to opencv (z, -x, -y)
# This is extrinsics for the turret camera
# camera pose relative to center, base of the turret
@@ -63,37 +53,30 @@
[[np.cos(camera_pitch), 0.0, -np.sin(camera_pitch)], [0.0, 1.0, 0.0],
[np.sin(camera_pitch), 0.0,
np.cos(camera_pitch)]])
- web_cam_ext.R = np.array(
+ turret_cam_ext = CameraExtrinsics()
+ turret_cam_ext.R = np.array(
camera_pitch_matrix *
np.matrix([[0., 0., 1.], [-1, 0, 0], [0, -1., 0]]))
- #web_cam_ext.T = np.array([0., 0., 0.])
- web_cam_ext.T = np.array([2.0 * 0.0254, -6.0 * 0.0254, 41.0 * 0.0254])
- fixed_ext = CameraExtrinsics()
- fixed_ext.R = np.array([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0],
- [0.0, 0.0, 1.0]])
- fixed_ext.T = np.array([0.0, 0.0, 0.0])
+ turret_cam_ext.T = np.array([15.0 * 0.0254, 15.0 * 0.0254, 41.0 * 0.0254])
+ default_cam_ext = CameraExtrinsics()
+ default_cam_ext.R = np.array([[-1.0, 0.0, 0.0], [0.0, -1.0, 0.0],
+ [0.0, 0.0, 1.0]])
+ default_cam_ext.T = np.array([0.0, 0.0, 0.0])
- web_cam_params = CameraParameters()
- web_cam_params.camera_int = web_cam_int
- # Fixed extrinsics are for the turret, which is centered on the robot and
- # pointed straight backwards.
- web_cam_params.camera_ext = fixed_ext
- web_cam_params.turret_ext = web_cam_ext
+ default_cam_params = CameraParameters()
+ # Currently, all cameras have this same set of extrinsics
+ default_cam_params.camera_ext = default_cam_ext
+ default_cam_params.turret_ext = turret_cam_ext
camera_list = []
- # TODO<Jim>: Should probably make this a dict to make replacing easier
- for team_number in (971, 7971, 8971, 9971):
- for node_name in ("pi0", "pi1", "pi2", "pi3", "pi4", "pi5"):
- camera_base = copy.deepcopy(web_cam_params)
- camera_base.node_name = node_name
- camera_base.team_number = team_number
- camera_list.append(camera_base)
-
dir_name = dtd.bazel_name_fix('calib_files')
+ glog.debug("Searching for calibration files in " + dir_name)
for filename in os.listdir(dir_name):
glog.debug("Inspecting %s", filename)
- if ("cam-calib-int" in filename or 'calibration' in filename) and filename.endswith(".json"):
+ if ("cam-calib-int" in filename
+ or 'calibration' in filename) and filename.endswith(".json"):
+
# Extract intrinsics from file
calib_file = open(dir_name + "/" + filename, 'r')
calib_dict = json.loads(calib_file.read())
@@ -104,6 +87,8 @@
# JSON.
# See which one we have and parse accordingly.
if 'hostname' in calib_dict:
+ glog.warn("WARNING: Using older calibration file.")
+ glog.warn("Preferred usage is y2020/vision:calibration")
hostname_split = calib_dict["hostname"].split("-")
team_number = int(hostname_split[1])
node_name = hostname_split[0] + hostname_split[2]
@@ -117,14 +102,13 @@
dist_coeffs = np.asarray(calib_dict["dist_coeffs"]).reshape(
(1, 5))
- # Look for match, and replace camera_intrinsics
- for camera_calib in camera_list:
- if camera_calib.node_name == node_name and camera_calib.team_number == team_number:
- glog.info("Found calib for %s, team #%d" % (node_name,
- team_number))
- camera_calib.camera_int.camera_matrix = copy.copy(
- camera_matrix)
- camera_calib.camera_int.dist_coeffs = copy.copy(
- dist_coeffs)
+ glog.info("Found calib for " + node_name + ", team #" +
+ str(team_number))
+ camera_base = copy.deepcopy(default_cam_params)
+ camera_base.node_name = node_name
+ camera_base.team_number = team_number
+ camera_base.camera_int.camera_matrix = copy.copy(camera_matrix)
+ camera_base.camera_int.dist_coeffs = copy.copy(dist_coeffs)
+ camera_list.append(camera_base)
return camera_list
diff --git a/y2020/vision/tools/python_code/target_definition.py b/y2020/vision/tools/python_code/target_definition.py
index bb19b9e..900c72a 100644
--- a/y2020/vision/tools/python_code/target_definition.py
+++ b/y2020/vision/tools/python_code/target_definition.py
@@ -10,7 +10,7 @@
import train_and_match as tam
# TODO<Jim>: Allow command-line setting of logging level
-glog.setLevel("WARN")
+glog.setLevel("INFO")
global VISUALIZE_KEYPOINTS
VISUALIZE_KEYPOINTS = False
@@ -62,8 +62,8 @@
# Filter and project points for each polygon in the list
filtered_keypoints, _, _, _, keep_list = dtd.filter_keypoints_by_polygons(
keypoint_list, None, [self.polygon_list[poly_ind]])
- glog.info("Filtering kept %d of %d features" %
- (len(keep_list), len(keypoint_list)))
+ glog.debug("Filtering kept %d of %d features" %
+ (len(keep_list), len(keypoint_list)))
filtered_point_array = np.asarray([
(keypoint.pt[0], keypoint.pt[1])
for keypoint in filtered_keypoints
@@ -131,28 +131,28 @@
# NOTE: Since we don't have an "ideal" (e.g., graphic) version, we're
# just using the same image as the training image.
ideal_power_port_taped = TargetData(
- 'test_images/train_power_port_taped-2020-08-20-13-27-50.png')
+ 'test_images/partial_971_power_port_red_daytime.png')
# Start at lower left corner, and work around clockwise
# These are taken by manually finding the points in gimp for this image
- power_port_taped_main_panel_polygon_points_2d = [(181, 423), (186, 192),
- (45, 191), (188, 93),
- (310, 99), (314, 196),
- (321, 414)]
+ power_port_taped_main_panel_polygon_points_2d = [(198, 473), (203, 154),
+ (23, 156), (204, 19),
+ (374, 16), (381, 152),
+ (397, 467)]
# These are "virtual" 3D points based on the expected geometry
power_port_taped_main_panel_polygon_points_3d = [
- (field_length / 2., -c_power_port_edge_y, 0.),
- (field_length / 2., -c_power_port_edge_y,
+ (-field_length / 2., c_power_port_edge_y, 0.),
+ (-field_length / 2., c_power_port_edge_y,
c_power_port_bottom_wing_height),
- (field_length / 2., -c_power_port_edge_y + c_power_port_wing_width,
+ (-field_length / 2., c_power_port_edge_y - c_power_port_wing_width,
c_power_port_bottom_wing_height),
- (field_length / 2., -c_power_port_edge_y, c_power_port_total_height),
- (field_length / 2., -c_power_port_edge_y - c_power_port_width,
+ (-field_length / 2., c_power_port_edge_y, c_power_port_total_height),
+ (-field_length / 2., c_power_port_edge_y + c_power_port_width,
c_power_port_total_height),
- (field_length / 2., -c_power_port_edge_y - c_power_port_width,
+ (-field_length / 2., c_power_port_edge_y + c_power_port_width,
c_power_port_bottom_wing_height),
- (field_length / 2., -c_power_port_edge_y - c_power_port_width, 0.)
+ (-field_length / 2., c_power_port_edge_y + c_power_port_width, 0.)
]
power_port_taped_wing_panel_polygon_points_2d = [(312, 99), (438, 191),
@@ -176,22 +176,22 @@
ideal_power_port_taped.polygon_list_3d.append(
power_port_taped_main_panel_polygon_points_3d)
# Including the wing panel
- ideal_power_port_taped.polygon_list.append(
- power_port_taped_wing_panel_polygon_points_2d)
- ideal_power_port_taped.polygon_list_3d.append(
- power_port_taped_wing_panel_polygon_points_3d)
+ #ideal_power_port_taped.polygon_list.append(
+ # power_port_taped_wing_panel_polygon_points_2d)
+ #ideal_power_port_taped.polygon_list_3d.append(
+ # power_port_taped_wing_panel_polygon_points_3d)
# Location of target. Rotation is pointing in -x direction
- ideal_power_port_taped.target_rotation = np.identity(3, np.double)
+ ideal_power_port_taped.target_rotation = -np.identity(3, np.double)
ideal_power_port_taped.target_position = np.array([
- field_length / 2., -c_power_port_edge_y - c_power_port_width / 2.,
+ -field_length / 2., c_power_port_edge_y + c_power_port_width / 2.,
c_power_port_target_height
])
- ideal_power_port_taped.target_point_2d = np.float32([[250, 146]]).reshape(
- -1, 1, 2) # train_power_port_taped-2020-08-20-13-27-50.png
+ ideal_power_port_taped.target_point_2d = np.float32([[290, 87]]).reshape(
+ -1, 1, 2) # partial_971_power_port_red_daytime.png
training_target_power_port_taped = TargetData(
- 'test_images/train_power_port_taped-2020-08-20-13-27-50.png')
+ 'test_images/partial_971_power_port_red_daytime.png')
training_target_power_port_taped.target_rotation = ideal_power_port_taped.target_rotation
training_target_power_port_taped.target_position = ideal_power_port_taped.target_position
training_target_power_port_taped.target_radius = target_radius_default
@@ -267,7 +267,7 @@
# And add the training image we'll actually use to the training list
training_target_power_port_red = TargetData(
- 'test_images/train_power_port_red_webcam.png')
+ 'test_images/train_power_port_red.png')
#'test_images/train_power_port_red_pi-7971-3.png')
training_target_power_port_red.target_rotation = ideal_power_port_red.target_rotation
training_target_power_port_red.target_position = ideal_power_port_red.target_position
@@ -354,10 +354,10 @@
]
# Populate the blue power port
- # ideal_power_port_blue.polygon_list.append(
- # power_port_blue_main_panel_polygon_points_2d)
- # ideal_power_port_blue.polygon_list_3d.append(
- # power_port_blue_main_panel_polygon_points_3d)
+ ideal_power_port_blue.polygon_list.append(
+ power_port_blue_main_panel_polygon_points_2d)
+ ideal_power_port_blue.polygon_list_3d.append(
+ power_port_blue_main_panel_polygon_points_3d)
# Including the wing panel
ideal_power_port_blue.polygon_list.append(
power_port_blue_wing_panel_polygon_points_2d)
@@ -375,7 +375,6 @@
training_target_power_port_blue = TargetData(
'test_images/train_power_port_blue.png')
- # 'test_images/image_from_ios-scaled.jpg')
training_target_power_port_blue.target_rotation = ideal_power_port_blue.target_rotation
training_target_power_port_blue.target_position = ideal_power_port_blue.target_position
training_target_power_port_blue.target_radius = target_radius_default
@@ -427,10 +426,10 @@
# targets based on all the definitions above
######################################################################
- ### Taped power port (not currently used)
- #glog.info("Adding hacked/taped up power port to the model list")
- #ideal_target_list.append(ideal_power_port_taped)
- #training_target_list.append(training_target_power_port_taped)
+ ### Taped power port
+ glog.info("Adding hacked/taped up power port to the model list")
+ ideal_target_list.append(ideal_power_port_taped)
+ training_target_list.append(training_target_power_port_taped)
### Red Power Port
glog.info("Adding red power port to the model list")
@@ -438,19 +437,19 @@
training_target_list.append(training_target_power_port_red)
### Red Loading Bay
- glog.info("Adding red loading bay to the model list")
- ideal_target_list.append(ideal_loading_bay_red)
- training_target_list.append(training_target_loading_bay_red)
+ #glog.info("Adding red loading bay to the model list")
+ #ideal_target_list.append(ideal_loading_bay_red)
+ #training_target_list.append(training_target_loading_bay_red)
### Blue Power Port
- #glog.info("Adding blue power port to the model list")
- #ideal_target_list.append(ideal_power_port_blue)
- #training_target_list.append(training_target_power_port_blue)
+ glog.info("Adding blue power port to the model list")
+ ideal_target_list.append(ideal_power_port_blue)
+ training_target_list.append(training_target_power_port_blue)
### Blue Loading Bay
- glog.info("Adding blue loading bay to the model list")
- ideal_target_list.append(ideal_loading_bay_blue)
- training_target_list.append(training_target_loading_bay_blue)
+ #glog.info("Adding blue loading bay to the model list")
+ #ideal_target_list.append(ideal_loading_bay_blue)
+ #training_target_list.append(training_target_loading_bay_blue)
return ideal_target_list, training_target_list
@@ -461,12 +460,12 @@
# Create feature extractor
feature_extractor = tam.load_feature_extractor()
- # Use webcam parameters for now
+ # Use default parameters for now
camera_params = camera_definition.load_camera_definitions()[0]
for ideal_target in ideal_target_list:
- glog.info("\nPreparing target for image %s" %
- ideal_target.image_filename)
+ glog.debug("\nPreparing target for image %s" %
+ ideal_target.image_filename)
ideal_target.extract_features(feature_extractor)
ideal_target.filter_keypoints_by_polygons()
ideal_target.compute_reprojection_maps()
@@ -515,8 +514,8 @@
AUTO_PROJECTION = True
if AUTO_PROJECTION:
- glog.info(
- "\n\nAuto projection of training keypoints to 3D using ideal images"
+ glog.debug(
+ "Doing auto projection of training keypoints to 3D using ideal images"
)
# Match the captured training image against the "ideal" training image
# and use those matches to pin down the 3D locations of the keypoints
@@ -526,8 +525,8 @@
training_target = training_target_list[target_ind]
ideal_target = ideal_target_list[target_ind]
- glog.info("\nPreparing target for image %s" %
- training_target.image_filename)
+ glog.debug("\nPreparing target for image %s" %
+ training_target.image_filename)
# Extract keypoints and descriptors for model
training_target.extract_features(feature_extractor)
@@ -562,14 +561,14 @@
training_target.target_point_2d = training_target_point_2d.reshape(
-1, 1, 2)
- glog.info("Started with %d keypoints" %
- len(training_target.keypoint_list))
+ glog.debug("Started with %d keypoints" %
+ len(training_target.keypoint_list))
training_target.keypoint_list, training_target.descriptor_list, rejected_keypoint_list, rejected_descriptor_list, _ = dtd.filter_keypoints_by_polygons(
training_target.keypoint_list, training_target.descriptor_list,
training_target.polygon_list)
- glog.info("After filtering by polygons, had %d keypoints" %
- len(training_target.keypoint_list))
+ glog.debug("After filtering by polygons, had %d keypoints" %
+ len(training_target.keypoint_list))
if VISUALIZE_KEYPOINTS:
tam.show_keypoints(training_target.image,
training_target.keypoint_list)
diff --git a/y2020/vision/tools/python_code/test_images/partial_971_power_port_red.png b/y2020/vision/tools/python_code/test_images/partial_971_power_port_red.png
new file mode 100644
index 0000000..ec402c0
--- /dev/null
+++ b/y2020/vision/tools/python_code/test_images/partial_971_power_port_red.png
Binary files differ
diff --git a/y2020/vision/tools/python_code/test_images/partial_971_power_port_red_daytime.png b/y2020/vision/tools/python_code/test_images/partial_971_power_port_red_daytime.png
new file mode 100644
index 0000000..be771b1
--- /dev/null
+++ b/y2020/vision/tools/python_code/test_images/partial_971_power_port_red_daytime.png
Binary files differ
diff --git a/y2020/vision/tools/python_code/test_images/partial_971_power_port_red_nighttime.png b/y2020/vision/tools/python_code/test_images/partial_971_power_port_red_nighttime.png
new file mode 100644
index 0000000..ec402c0
--- /dev/null
+++ b/y2020/vision/tools/python_code/test_images/partial_971_power_port_red_nighttime.png
Binary files differ
diff --git a/y2020/vision/tools/python_code/train_and_match.py b/y2020/vision/tools/python_code/train_and_match.py
index ad1bbde..5f895bb 100644
--- a/y2020/vision/tools/python_code/train_and_match.py
+++ b/y2020/vision/tools/python_code/train_and_match.py
@@ -183,7 +183,7 @@
matches_mask_list.append([])
continue
- glog.info(
+ glog.debug(
"Got good number of matches for model %d: %d (needed only %d)" %
(i, len(good_matches), MIN_MATCH_COUNT))
# Extract and bundle keypoint locations for computations
diff --git a/y2020/wpilib_interface.cc b/y2020/wpilib_interface.cc
index 914c9c6..bdc16fa 100644
--- a/y2020/wpilib_interface.cc
+++ b/y2020/wpilib_interface.cc
@@ -48,10 +48,16 @@
#include "frc971/wpilib/pdp_fetcher.h"
#include "frc971/wpilib/sensor_reader.h"
#include "frc971/wpilib/wpilib_robot_base.h"
+#include "gflags/gflags.h"
#include "y2020/constants.h"
+#include "y2020/control_loops/superstructure/shooter/shooter_tuning_readings_generated.h"
#include "y2020/control_loops/superstructure/superstructure_output_generated.h"
#include "y2020/control_loops/superstructure/superstructure_position_generated.h"
+DEFINE_bool(shooter_tuning, true,
+ "If true, reads from ball beambreak sensors and sends shooter "
+ "tuning readings");
+
using ::aos::monotonic_clock;
using ::y2020::constants::Values;
namespace superstructure = ::y2020::control_loops::superstructure;
@@ -119,7 +125,10 @@
drivetrain_position_sender_(
event_loop
->MakeSender<::frc971::control_loops::drivetrain::Position>(
- "/drivetrain")) {
+ "/drivetrain")),
+ shooter_tuning_readings_sender_(
+ event_loop->MakeSender<superstructure::shooter::TuningReadings>(
+ "/superstructure")) {
// Set to filter out anything shorter than 1/4 of the minimum pulse width
// we should ever see.
UpdateFastEncoderFilterHz(kMaxFastEncoderPulsesPerSecond);
@@ -186,13 +195,6 @@
right_accelerator_encoder_ = ::std::move(encoder);
}
- // Control Panel
-
- void set_control_panel_encoder(::std::unique_ptr<frc::Encoder> encoder) {
- fast_encoder_filter_.Add(encoder.get());
- control_panel_encoder_ = ::std::move(encoder);
- }
-
// Auto mode switches.
void set_autonomous_mode(int i, ::std::unique_ptr<frc::DigitalInput> sensor) {
medium_encoder_filter_.Add(sensor.get());
@@ -201,6 +203,20 @@
void set_imu(frc971::wpilib::ADIS16470 *imu) { imu_ = imu; }
+ void set_ball_beambreak_inputs(::std::unique_ptr<frc::DigitalInput> sensor1,
+ ::std::unique_ptr<frc::DigitalInput> sensor2) {
+ ball_beambreak_inputs_[0] = ::std::move(sensor1);
+ ball_beambreak_inputs_[1] = ::std::move(sensor2);
+ ball_beambreak_reader_.set_input_one(ball_beambreak_inputs_[0].get());
+ ball_beambreak_reader_.set_input_two(ball_beambreak_inputs_[1].get());
+ }
+
+ void Start() override {
+ if (FLAGS_shooter_tuning) {
+ AddToDMA(&ball_beambreak_reader_);
+ }
+ }
+
void RunIteration() override {
CHECK_NOTNULL(imu_)->DoReads();
@@ -252,14 +268,6 @@
flatbuffers::Offset<frc971::PotAndAbsolutePosition> turret_offset =
frc971::PotAndAbsolutePosition::Pack(*builder.fbb(), &turret);
- // Control Panel
- frc971::RelativePositionT control_panel;
- CopyPosition(*control_panel_encoder_, &control_panel,
- Values::kControlPanelEncoderCountsPerRevolution(),
- Values::kControlPanelEncoderRatio(), false);
- flatbuffers::Offset<frc971::RelativePosition> control_panel_offset =
- frc971::RelativePosition::Pack(*builder.fbb(), &control_panel);
-
// Shooter
y2020::control_loops::superstructure::ShooterPositionT shooter;
shooter.theta_finisher =
@@ -286,7 +294,6 @@
position_builder.add_intake_joint(intake_joint_offset);
position_builder.add_turret(turret_offset);
position_builder.add_shooter(shooter_offset);
- position_builder.add_control_panel(control_panel_offset);
builder.Send(position_builder.Finish());
}
@@ -308,6 +315,22 @@
builder.Send(auto_mode_builder.Finish());
}
+
+ if (FLAGS_shooter_tuning) {
+ // Distance between beambreak sensors, in meters.
+ constexpr double kDistanceBetweenBeambreaks = 0.4813;
+
+ if (ball_beambreak_reader_.pulses_detected() > balls_detected_) {
+ balls_detected_ = ball_beambreak_reader_.pulses_detected();
+
+ auto builder = shooter_tuning_readings_sender_.MakeBuilder();
+ auto shooter_tuning_readings_builder =
+ builder.MakeBuilder<superstructure::shooter::TuningReadings>();
+ shooter_tuning_readings_builder.add_velocity_ball(
+ kDistanceBetweenBeambreaks / ball_beambreak_reader_.last_width());
+ builder.Send(shooter_tuning_readings_builder.Finish());
+ }
+ }
}
private:
@@ -315,6 +338,8 @@
::aos::Sender<superstructure::Position> superstructure_position_sender_;
::aos::Sender<::frc971::control_loops::drivetrain::Position>
drivetrain_position_sender_;
+ ::aos::Sender<superstructure::shooter::TuningReadings>
+ shooter_tuning_readings_sender_;
::frc971::wpilib::AbsoluteEncoderAndPotentiometer turret_encoder_;
@@ -323,12 +348,18 @@
::frc971::wpilib::AbsoluteEncoder intake_joint_encoder_;
::std::unique_ptr<::frc::Encoder> finisher_encoder_,
- left_accelerator_encoder_, right_accelerator_encoder_,
- control_panel_encoder_;
-
+ left_accelerator_encoder_, right_accelerator_encoder_;
::std::array<::std::unique_ptr<frc::DigitalInput>, 2> autonomous_modes_;
frc971::wpilib::ADIS16470 *imu_ = nullptr;
+
+ // Used to interface with the two beam break sensors that the ball for tuning
+ // shooter parameters has to pass through.
+ // We will time how long it takes to pass between the two sensors to get its
+ // velocity.
+ std::array<std::unique_ptr<frc::DigitalInput>, 2> ball_beambreak_inputs_;
+ frc971::wpilib::DMAPulseSeparationReader ball_beambreak_reader_;
+ int balls_detected_ = 0;
};
class SuperstructureWriter
@@ -384,11 +415,6 @@
}
}
- void set_washing_machine_control_panel_victor(
- ::std::unique_ptr<::frc::VictorSP> t) {
- washing_machine_control_panel_victor_ = ::std::move(t);
- }
-
void set_accelerator_left_falcon(::std::unique_ptr<::frc::TalonFX> t) {
accelerator_left_falcon_ = ::std::move(t);
}
@@ -430,7 +456,7 @@
kMaxBringupPower) /
12.0);
- turret_victor_->SetSpeed(std::clamp(-output.turret_voltage(),
+ turret_victor_->SetSpeed(std::clamp(output.turret_voltage(),
-kMaxBringupPower, kMaxBringupPower) /
12.0);
@@ -438,12 +464,6 @@
std::clamp(output.feeder_voltage(), -kMaxBringupPower,
kMaxBringupPower) /
12.0);
- if (washing_machine_control_panel_victor_) {
- washing_machine_control_panel_victor_->SetSpeed(
- std::clamp(-output.washing_machine_spinner_voltage(),
- -kMaxBringupPower, kMaxBringupPower) /
- 12.0);
- }
accelerator_left_falcon_->SetSpeed(
std::clamp(-output.accelerator_left_voltage(), -kMaxBringupPower,
@@ -478,9 +498,6 @@
hood_victor_->SetDisabled();
intake_joint_victor_->SetDisabled();
turret_victor_->SetDisabled();
- if (washing_machine_control_panel_victor_) {
- washing_machine_control_panel_victor_->SetDisabled();
- }
accelerator_left_falcon_->SetDisabled();
accelerator_right_falcon_->SetDisabled();
finisher_falcon0_->SetDisabled();
@@ -492,7 +509,7 @@
}
::std::unique_ptr<::frc::VictorSP> hood_victor_, intake_joint_victor_,
- turret_victor_, washing_machine_control_panel_victor_;
+ turret_victor_;
::std::unique_ptr<::frc::TalonFX> accelerator_left_falcon_,
accelerator_right_falcon_, finisher_falcon0_, finisher_falcon1_;
@@ -550,8 +567,10 @@
sensor_reader.set_left_accelerator_encoder(make_encoder(4));
sensor_reader.set_right_accelerator_encoder(make_encoder(3));
- sensor_reader.set_control_panel_encoder(
- make_unique<frc::Encoder>(5, 6, false, frc::Encoder::k1X));
+ if (FLAGS_shooter_tuning) {
+ sensor_reader.set_ball_beambreak_inputs(
+ make_unique<frc::DigitalInput>(6), make_unique<frc::DigitalInput>(7));
+ }
// Note: If ADIS16470 is plugged in directly to the roboRIO SPI port without
// the Spartan Board, then trigger is on 26, reset 27, and chip select is
@@ -592,8 +611,6 @@
superstructure_writer.set_turret_victor(make_unique<frc::VictorSP>(7));
superstructure_writer.set_feeder_falcon(
make_unique<ctre::phoenix::motorcontrol::can::TalonFX>(1));
- superstructure_writer.set_washing_machine_control_panel_victor(
- make_unique<frc::VictorSP>(6));
superstructure_writer.set_accelerator_left_falcon(
make_unique<::frc::TalonFX>(5));
superstructure_writer.set_accelerator_right_falcon(
diff --git a/y2020/www/BUILD b/y2020/www/BUILD
index 8760fde..be3bfac 100644
--- a/y2020/www/BUILD
+++ b/y2020/www/BUILD
@@ -78,6 +78,7 @@
":camera_main_bundle",
":field_main_bundle",
":files",
+ "//frc971/analysis:plot_index_bundle.min.js",
],
dir = "www",
target_compatible_with = ["@platforms//os:linux"],
diff --git a/y2020/www/field_handler.ts b/y2020/www/field_handler.ts
index 4aaa05f..1a0b56a 100644
--- a/y2020/www/field_handler.ts
+++ b/y2020/www/field_handler.ts
@@ -191,6 +191,8 @@
ctx.beginPath();
ctx.moveTo(0.5, 0.5);
ctx.lineTo(0, 0);
+ ctx.lineTo(100.0, 0);
+ ctx.lineTo(0, 0);
ctx.lineTo(0.5, -0.5);
ctx.stroke();
ctx.beginPath();
@@ -218,13 +220,13 @@
// Draw line in circle to show forwards.
ctx.beginPath();
ctx.moveTo(0, 0);
- ctx.lineTo(turretRadius, 0);
+ ctx.lineTo(1000.0 * turretRadius, 0);
ctx.stroke();
ctx.restore();
}
ctx.beginPath();
ctx.moveTo(0, 0);
- ctx.lineTo(ROBOT_LENGTH / 2, 0);
+ ctx.lineTo(100.0 * ROBOT_LENGTH / 2, 0);
ctx.stroke();
ctx.restore();
}
diff --git a/y2020/www/index.html b/y2020/www/index.html
index f9ceca2..a105832 100644
--- a/y2020/www/index.html
+++ b/y2020/www/index.html
@@ -1,6 +1,7 @@
<html>
<body>
<a href="camera_viewer.html">Camera Viewer</a><br>
- <a href="field.html">Field Visualization</a>
+ <a href="field.html">Field Visualization</a><br>
+ <a href="plotter.html">Plots</a>
</body>
</html>
diff --git a/y2020/www/plotter.html b/y2020/www/plotter.html
new file mode 100644
index 0000000..629ceaa
--- /dev/null
+++ b/y2020/www/plotter.html
@@ -0,0 +1,7 @@
+<html>
+ <head>
+ <script src="plot_index_bundle.min.js" defer></script>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/y2020/y2020_roborio.json b/y2020/y2020_roborio.json
index 5243d5a..67916b6 100644
--- a/y2020/y2020_roborio.json
+++ b/y2020/y2020_roborio.json
@@ -190,12 +190,36 @@
},
{
"name": "/superstructure",
+ "type": "y2020.control_loops.superstructure.shooter.TuningReadings",
+ "source_node": "roborio",
+ "frequency": 200,
+ "num_senders": 2,
+ "max_size": 64
+ },
+ {
+ "name": "/superstructure",
+ "type": "y2020.control_loops.superstructure.shooter.TuningParams",
+ "source_node": "roborio",
+ "frequency": 5,
+ "num_senders": 2,
+ "max_size": 256
+ },
+ {
+ "name": "/superstructure",
"type": "y2020.joysticks.Setpoint",
"source_node": "roborio",
"num_senders": 2
},
{
"name": "/drivetrain",
+ "type": "y2020.control_loops.drivetrain.LocalizerDebug",
+ "source_node": "roborio",
+ "frequency": 250,
+ "max_size": 512,
+ "num_senders": 2
+ },
+ {
+ "name": "/drivetrain",
"type": "frc971.IMUValuesBatch",
"source_node": "roborio",
"frequency": 250,
diff --git a/y2021_bot3/control_loops/python/BUILD b/y2021_bot3/control_loops/python/BUILD
index 7ade878..15dacaa 100644
--- a/y2021_bot3/control_loops/python/BUILD
+++ b/y2021_bot3/control_loops/python/BUILD
@@ -5,8 +5,10 @@
srcs = [
"drivetrain.py",
],
+ legacy_create_init = False,
target_compatible_with = ["@platforms//cpu:x86_64"],
deps = [
+ ":python_init",
"//external:python-gflags",
"//external:python-glog",
"//frc971/control_loops/python:drivetrain",
@@ -19,8 +21,10 @@
"drivetrain.py",
"polydrivetrain.py",
],
+ legacy_create_init = False,
target_compatible_with = ["@platforms//cpu:x86_64"],
deps = [
+ ":python_init",
"//external:python-gflags",
"//external:python-glog",
"//frc971/control_loops/python:polydrivetrain",
@@ -43,3 +47,11 @@
"//frc971/control_loops/python:polydrivetrain",
],
)
+
+py_library(
+ name = "python_init",
+ srcs = ["__init__.py"],
+ target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
+ deps = ["//y2020/control_loops:python_init"],
+)
diff --git a/y2021_bot3/control_loops/python/__init__.py b/y2021_bot3/control_loops/python/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2021_bot3/control_loops/python/__init__.py
diff --git a/y2021_bot3/control_loops/superstructure/BUILD b/y2021_bot3/control_loops/superstructure/BUILD
index 404555f..77cdf45 100644
--- a/y2021_bot3/control_loops/superstructure/BUILD
+++ b/y2021_bot3/control_loops/superstructure/BUILD
@@ -76,3 +76,29 @@
"//aos/events:shm_event_loop",
],
)
+
+cc_test(
+ name = "superstructure_lib_test",
+ srcs = [
+ "superstructure_lib_test.cc",
+ ],
+ data = [
+ "//y2021_bot3:config",
+ ],
+ deps = [
+ ":superstructure_goal_fbs",
+ ":superstructure_lib",
+ ":superstructure_output_fbs",
+ ":superstructure_position_fbs",
+ ":superstructure_status_fbs",
+ "//aos:math",
+ "//aos/events/logging:log_writer",
+ "//aos/testing:googletest",
+ "//aos/time",
+ "//frc971/control_loops:capped_test_plant",
+ "//frc971/control_loops:control_loop_test",
+ "//frc971/control_loops:position_sensor_sim",
+ "//frc971/control_loops:team_number_test_environment",
+ "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
+ ],
+)
\ No newline at end of file
diff --git a/y2021_bot3/control_loops/superstructure/superstructure.cc b/y2021_bot3/control_loops/superstructure/superstructure.cc
index aaf3def..22c7c44 100644
--- a/y2021_bot3/control_loops/superstructure/superstructure.cc
+++ b/y2021_bot3/control_loops/superstructure/superstructure.cc
@@ -16,7 +16,7 @@
event_loop->SetRuntimeRealtimePriority(30);
}
-void Superstructure::RunIteration(const Goal * /*unsafe_goal*/,
+void Superstructure::RunIteration(const Goal *unsafe_goal,
const Position * /*position*/,
aos::Sender<Output>::Builder *output,
aos::Sender<Status>::Builder *status) {
@@ -24,8 +24,13 @@
AOS_LOG(ERROR, "WPILib reset, restarting\n");
}
- if (output != nullptr) {
+ if (output != nullptr && unsafe_goal != nullptr) {
OutputT output_struct;
+
+ output_struct.intake_volts =
+ std::clamp(unsafe_goal->intake_speed(), -12.0, 12.0);
+ output_struct.outtake_volts =
+ std::clamp(unsafe_goal->outtake_speed(), -12.0, 12.0);
output->Send(Output::Pack(*output->fbb(), &output_struct));
}
@@ -34,6 +39,11 @@
status_builder.add_zeroed(true);
status_builder.add_estopped(false);
+ if (unsafe_goal != nullptr) {
+ status_builder.add_intake_speed(unsafe_goal->intake_speed());
+ status_builder.add_outtake_speed(unsafe_goal->outtake_speed());
+ }
+
status->Send(status_builder.Finish());
}
diff --git a/y2021_bot3/control_loops/superstructure/superstructure_lib_test.cc b/y2021_bot3/control_loops/superstructure/superstructure_lib_test.cc
new file mode 100644
index 0000000..cd031c3
--- /dev/null
+++ b/y2021_bot3/control_loops/superstructure/superstructure_lib_test.cc
@@ -0,0 +1,158 @@
+#include <chrono>
+#include <memory>
+
+#include "aos/events/logging/log_writer.h"
+#include "frc971/control_loops/capped_test_plant.h"
+#include "frc971/control_loops/control_loop_test.h"
+#include "frc971/control_loops/position_sensor_sim.h"
+#include "frc971/control_loops/team_number_test_environment.h"
+#include "gtest/gtest.h"
+#include "y2021_bot3/control_loops/superstructure/superstructure.h"
+
+namespace y2021_bot3 {
+namespace control_loops {
+namespace superstructure {
+namespace testing {
+
+class SuperstructureTest : public ::frc971::testing::ControlLoopTest {
+ public:
+ SuperstructureTest()
+ : ::frc971::testing::ControlLoopTest(
+ aos::configuration::ReadConfig("y2021_bot3/config.json"),
+ std::chrono::microseconds(5050)),
+ superstructure_event_loop(MakeEventLoop("Superstructure")),
+ superstructure_(superstructure_event_loop.get()),
+ test_event_loop_(MakeEventLoop("test")),
+ superstructure_goal_fetcher_(
+ test_event_loop_->MakeFetcher<Goal>("/superstructure")),
+ superstructure_goal_sender_(
+ test_event_loop_->MakeSender<Goal>("/superstructure")),
+ superstructure_status_fetcher_(
+ test_event_loop_->MakeFetcher<Status>("/superstructure")),
+ superstructure_output_fetcher_(
+ test_event_loop_->MakeFetcher<Output>("/superstructure")),
+ superstructure_position_fetcher_(
+ test_event_loop_->MakeFetcher<Position>("/superstructure")),
+ superstructure_position_sender_(
+ test_event_loop_->MakeSender<Position>("/superstructure")) {
+ set_team_id(frc971::control_loops::testing::kTeamNumber);
+ SetEnabled(true);
+
+ phased_loop_handle_ = test_event_loop_->AddPhasedLoop(
+ [this](int) { SendPositionMessage(); }, dt());
+ }
+
+ void VerifyResults(double intake_voltage, double outtake_voltage,
+ double intake_speed, double outtake_speed) {
+ superstructure_output_fetcher_.Fetch();
+ superstructure_status_fetcher_.Fetch();
+ ASSERT_TRUE(superstructure_output_fetcher_.get() != nullptr);
+ ASSERT_TRUE(superstructure_status_fetcher_.get() != nullptr);
+ EXPECT_EQ(superstructure_output_fetcher_->intake_volts(), intake_voltage);
+ EXPECT_EQ(superstructure_output_fetcher_->outtake_volts(), outtake_voltage);
+ EXPECT_EQ(superstructure_status_fetcher_->intake_speed(), intake_speed);
+ EXPECT_EQ(superstructure_status_fetcher_->outtake_speed(), outtake_speed);
+ EXPECT_EQ(superstructure_status_fetcher_->estopped(), false);
+ EXPECT_EQ(superstructure_status_fetcher_->zeroed(), true);
+ }
+
+ void SendPositionMessage() {
+ auto builder = superstructure_position_sender_.MakeBuilder();
+ Position::Builder position_builder = builder.MakeBuilder<Position>();
+ builder.Send(position_builder.Finish());
+ }
+
+ ::std::unique_ptr<::aos::EventLoop> superstructure_event_loop;
+ ::y2021_bot3::control_loops::superstructure::Superstructure superstructure_;
+ ::std::unique_ptr<::aos::EventLoop> test_event_loop_;
+ ::aos::PhasedLoopHandler *phased_loop_handle_ = nullptr;
+ ::aos::Fetcher<Goal> superstructure_goal_fetcher_;
+ ::aos::Sender<Goal> superstructure_goal_sender_;
+ ::aos::Fetcher<Status> superstructure_status_fetcher_;
+ ::aos::Fetcher<Output> superstructure_output_fetcher_;
+ ::aos::Fetcher<Position> superstructure_position_fetcher_;
+ ::aos::Sender<Position> superstructure_position_sender_;
+};
+
+// Tests running the intake and outtake separately
+TEST_F(SuperstructureTest, RunIntakeOrOuttake) {
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ goal_builder.add_intake_speed(10.0);
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ SendPositionMessage();
+ RunFor(dt() * 2);
+ VerifyResults(10.0, 0.0, 10.0, 0.0);
+ }
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ goal_builder.add_outtake_speed(10.0);
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ RunFor(dt() * 2);
+ VerifyResults(0.0, 10.0, 0.0, 10.0);
+ }
+}
+
+// Tests running both the intake and the outtake simultaneously
+TEST_F(SuperstructureTest, RunIntakeAndOuttake) {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ goal_builder.add_intake_speed(10.0);
+ goal_builder.add_outtake_speed(5.0);
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ RunFor(dt() * 2);
+ VerifyResults(10.0, 5.0, 10.0, 5.0);
+}
+
+// Tests for an invalid voltage (over 12 or under -12) to check that it defaults
+// to 12 or -12
+TEST_F(SuperstructureTest, InvalidVoltage) {
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ goal_builder.add_intake_speed(20.0);
+ goal_builder.add_outtake_speed(15.0);
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ RunFor(dt() * 2);
+ VerifyResults(12.0, 12.0, 20.0, 15.0);
+ }
+
+ {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ goal_builder.add_intake_speed(-20.0);
+ goal_builder.add_outtake_speed(-15.0);
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ RunFor(dt() * 2);
+ VerifyResults(-12.0, -12.0, -20.0, -15.0);
+ }
+}
+
+// Tests that there is no output when the goal is null
+TEST_F(SuperstructureTest, GoalIsNull) {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ RunFor(dt() * 2);
+ VerifyResults(0.0, 0.0, 0.0, 0.0);
+}
+
+// Tests that the robot behaves properly when disabled
+TEST_F(SuperstructureTest, Disabled) {
+ auto builder = superstructure_goal_sender_.MakeBuilder();
+ Goal::Builder goal_builder = builder.MakeBuilder<Goal>();
+ goal_builder.add_intake_speed(6.0);
+ goal_builder.add_outtake_speed(5.0);
+ ASSERT_TRUE(builder.Send(goal_builder.Finish()));
+ SetEnabled(false);
+ RunFor(dt() * 2);
+ VerifyResults(0.0, 0.0, 6.0, 5.0);
+}
+
+} // namespace testing
+} // namespace superstructure
+} // namespace control_loops
+} // namespace y2021_bot3
\ No newline at end of file