Merge "Undistort points before running pose estimation"
diff --git a/.bazelignore b/.bazelignore
index 3665125..0b2e110 100644
--- a/.bazelignore
+++ b/.bazelignore
@@ -10,3 +10,4 @@
scouting/www/shift_schedule/node_modules
scouting/www/view/node_modules
scouting/www/pit_scouting/node_modules
+scouting/www/scan/node_modules
diff --git a/WORKSPACE b/WORKSPACE
index 82a0dd4..1aabe54 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -919,6 +919,7 @@
"@//scouting/www/match_list:package.json",
"@//scouting/www/notes:package.json",
"@//scouting/www/rpc:package.json",
+ "@//scouting/www/scan:package.json",
"@//scouting/www/shift_schedule:package.json",
"@//scouting/www/view:package.json",
],
@@ -988,6 +989,11 @@
http_archive(
name = "aspect_rules_cypress",
+ patch_args = ["-p1"],
+ patches = [
+ "//third_party:rules_cypress/0001-fix-incorrect-linux-checksums.patch",
+ "//third_party:rules_cypress/0002-Add-support-for-cypress-13.6.6.patch",
+ ],
sha256 = "76947778d8e855eee3c15931e1fcdc1c2a25d56d6c0edd110b2227c05b794d08",
strip_prefix = "rules_cypress-0.3.2",
urls = [
@@ -1002,7 +1008,7 @@
cypress_register_toolchains(
name = "cypress",
- cypress_version = "12.3.0",
+ cypress_version = "13.3.1",
)
# Copied from:
@@ -1013,7 +1019,7 @@
# LASTCHANGE_URL="https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2FLAST_CHANGE?alt=media"
# CHROME_REVISION=$(curl -s -S $LASTCHANGE_URL)
# echo "latest CHROME_REVISION_LINUX is $CHROME_REVISION"
-CHROME_REVISION_LINUX = "1072361"
+CHROME_REVISION_LINUX = "1264932"
http_archive(
name = "chrome_linux",
@@ -1022,7 +1028,7 @@
srcs = glob(["**"]),
visibility = ["//visibility:public"],
)""",
- sha256 = "0df22f743facd1e090eff9b7f8d8bdc293fb4dc31ce9156d2ef19b515974a72b",
+ sha256 = "4de54f43b2fc4812b9fad4145e44df6ed3063969174a8883ea42ed4c1ee58301",
strip_prefix = "chrome-linux",
urls = [
"https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/Linux_x64%2F" + CHROME_REVISION_LINUX + "%2Fchrome-linux.zip?alt=media",
@@ -1207,6 +1213,14 @@
url = "https://software.frc971.org/Build-Dependencies/2021-10-03_superstructure_shoot_balls.tar.gz",
)
+http_file(
+ name = "opencv_wasm",
+ sha256 = "447244d0e67e411f91e7c225c07f104437104e3e753085248a0c527a25bd8807",
+ urls = [
+ "https://docs.opencv.org/4.9.0/opencv.js",
+ ],
+)
+
http_archive(
name = "opencv_k8",
build_file = "@//debian:opencv.BUILD",
diff --git a/aos/starter/irq_affinity.cc b/aos/starter/irq_affinity.cc
index 8b6bb2a..a188127 100644
--- a/aos/starter/irq_affinity.cc
+++ b/aos/starter/irq_affinity.cc
@@ -109,7 +109,7 @@
}
}
- void ConfigurePid(pid_t pid) const {
+ void ConfigurePid(pid_t pid, std::string_view name) const {
struct sched_param param;
param.sched_priority = priority;
int new_scheduler;
@@ -126,7 +126,11 @@
default:
LOG(FATAL) << "Unknown scheduler";
}
- PCHECK(sched_setscheduler(pid, new_scheduler, ¶m) == 0);
+ PCHECK(sched_setscheduler(pid, new_scheduler, ¶m) == 0)
+ << ", Failed to set " << name << "(" << pid << ") to "
+ << (new_scheduler == SCHED_OTHER
+ ? "SCHED_OTHER"
+ : (new_scheduler == SCHED_RR ? "SCHED_RR" : "SCHED_FIFO"));
if (scheduler == starter::Scheduler::SCHEDULER_OTHER && nice.has_value()) {
PCHECK(setpriority(PRIO_PROCESS, pid, *nice) == 0)
@@ -172,14 +176,14 @@
if (reading.second.kthread) {
for (const ParsedKThreadConfig &match : kthreads_) {
if (match.Matches(reading.second.name)) {
- match.ConfigurePid(reading.first);
+ match.ConfigurePid(reading.first, reading.second.name);
break;
}
}
} else {
for (const ParsedKThreadConfig &match : threads_) {
if (match.Matches(reading.second.name)) {
- match.ConfigurePid(reading.first);
+ match.ConfigurePid(reading.first, reading.second.name);
break;
}
}
diff --git a/aos/util/BUILD b/aos/util/BUILD
index 7bb4061..599d477 100644
--- a/aos/util/BUILD
+++ b/aos/util/BUILD
@@ -578,3 +578,25 @@
"//aos/testing:path",
],
)
+
+static_flatbuffer(
+ name = "filesystem_fbs",
+ srcs = ["filesystem.fbs"],
+)
+
+cc_static_flatbuffer(
+ name = "filesystem_schema",
+ function = "aos::util::FilesystemStatusSchema",
+ target = ":filesystem_fbs_reflection_out",
+)
+
+cc_binary(
+ name = "filesystem_monitor",
+ srcs = ["filesystem_monitor.cc"],
+ deps = [
+ ":filesystem_fbs",
+ "//aos:init",
+ "//aos/events:shm_event_loop",
+ "@com_google_absl//absl/strings",
+ ],
+)
diff --git a/aos/util/filesystem.fbs b/aos/util/filesystem.fbs
new file mode 100644
index 0000000..0571d15
--- /dev/null
+++ b/aos/util/filesystem.fbs
@@ -0,0 +1,23 @@
+namespace aos.util;
+
+table Filesystem {
+ // Mountpoint of the filesystem in question.
+ path: string (id: 0);
+ // Type (e.g., "ext4") of the filesystem.
+ type: string (id: 1);
+ // Total size of the filesystem, in bytes.
+ overall_space: uint64 (id: 2);
+ // Total free space on the filesystem, in bytes.
+ free_space: uint64 (id: 3);
+ // Total number of inodes on this filesystem.
+ overall_inodes: uint64 (id: 4);
+ // Total free inodes on this filesystem.
+ free_inodes: uint64 (id: 5);
+}
+
+// Table to track the current state of a compute platform's filesystem.
+table FilesystemStatus {
+ filesystems: [Filesystem] (id: 0);
+}
+
+root_type FilesystemStatus;
diff --git a/aos/util/filesystem_monitor.cc b/aos/util/filesystem_monitor.cc
new file mode 100644
index 0000000..4efb141
--- /dev/null
+++ b/aos/util/filesystem_monitor.cc
@@ -0,0 +1,140 @@
+#include <sys/statvfs.h>
+
+#include "absl/strings/str_split.h"
+#include "gflags/gflags.h"
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/util/filesystem_generated.h"
+
+DEFINE_string(config, "aos_config.json", "File path of aos configuration");
+
+namespace aos::util {
+namespace {
+std::optional<std::string> ReadShortFile(std::string_view file_name) {
+ // Open as input and seek to end immediately.
+ std::ifstream file(std::string(file_name), std::ios_base::in);
+ if (!file.good()) {
+ VLOG(1) << "Can't read " << file_name;
+ return std::nullopt;
+ }
+ const size_t kMaxLineLength = 4096;
+ char buffer[kMaxLineLength];
+ file.read(buffer, kMaxLineLength);
+ if (!file.eof()) {
+ return std::nullopt;
+ }
+ return std::string(buffer, file.gcount());
+}
+} // namespace
+
+// Periodically sends out the Filesystems message with filesystem utilization
+// info.
+class FilesystemMonitor {
+ public:
+ FilesystemMonitor(aos::EventLoop *event_loop)
+ : event_loop_(event_loop),
+ sender_(event_loop_->MakeSender<FilesystemStatus>("/aos")) {
+ periodic_timer_ =
+ event_loop_->AddTimer([this]() { PublishFilesystemStatus(); });
+ event_loop_->OnRun([this]() {
+ periodic_timer_->Schedule(event_loop_->monotonic_now(),
+ std::chrono::seconds(5));
+ });
+ }
+
+ private:
+ void PublishFilesystemStatus() {
+ aos::Sender<FilesystemStatus>::Builder builder = sender_.MakeBuilder();
+
+ std::optional<std::string> contents = ReadShortFile("/proc/self/mountinfo");
+
+ CHECK(contents.has_value());
+
+ std::vector<flatbuffers::Offset<Filesystem>> filesystems;
+
+ // Iterate through /proc/self/mounts to find all the filesystems.
+ for (std::string_view line :
+ absl::StrSplit(std::string_view(contents->c_str(), contents->size()),
+ '\n', absl::SkipWhitespace())) {
+ // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt for
+ // the format.
+ std::vector<std::string_view> elements =
+ absl::StrSplit(line, ' ', absl::SkipWhitespace());
+
+ // First thing after - is the filesystem type.
+ size_t i = 6;
+ while (elements[i] != "-") {
+ ++i;
+ CHECK_LT(i + 1, elements.size());
+ }
+
+ // Mount point is the 4th element.
+ std::string mount_point(elements[4]);
+ std::string_view type = elements[i + 1];
+
+ // Ignore filesystems without reasonable types.
+ if (type != "ext2" && type != "xfs" && type != "vfat" && type != "ext3" &&
+ type != "ext4" && type != "tmpfs" && type != "devtmpfs") {
+ continue;
+ }
+ VLOG(1) << mount_point << ", type " << type;
+
+ struct statvfs info;
+
+ PCHECK(statvfs(mount_point.c_str(), &info) == 0);
+
+ VLOG(1) << "overall size: " << info.f_frsize * info.f_blocks << ", free "
+ << info.f_bfree * info.f_bsize << ", inodes " << info.f_files
+ << ", free " << info.f_ffree;
+
+ flatbuffers::Offset<flatbuffers::String> path_offset =
+ builder.fbb()->CreateString(mount_point);
+ flatbuffers::Offset<flatbuffers::String> type_offset =
+ builder.fbb()->CreateString(type);
+ Filesystem::Builder filesystem_builder =
+ builder.MakeBuilder<Filesystem>();
+ filesystem_builder.add_path(path_offset);
+ filesystem_builder.add_type(type_offset);
+ filesystem_builder.add_overall_space(info.f_frsize * info.f_blocks);
+ filesystem_builder.add_free_space(info.f_bfree * info.f_bsize);
+ filesystem_builder.add_overall_inodes(info.f_files);
+ filesystem_builder.add_free_inodes(info.f_ffree);
+
+ filesystems.emplace_back(filesystem_builder.Finish());
+ }
+
+ flatbuffers::Offset<flatbuffers::Vector<flatbuffers::Offset<Filesystem>>>
+ filesystems_offset = builder.fbb()->CreateVector(filesystems);
+
+ FilesystemStatus::Builder filesystem_status_builder =
+ builder.MakeBuilder<FilesystemStatus>();
+
+ filesystem_status_builder.add_filesystems(filesystems_offset);
+
+ (void)builder.Send(filesystem_status_builder.Finish());
+ }
+
+ aos::EventLoop *event_loop_;
+
+ aos::Sender<FilesystemStatus> sender_;
+
+ aos::TimerHandler *periodic_timer_;
+};
+
+} // namespace aos::util
+
+int main(int argc, char **argv) {
+ aos::InitGoogle(&argc, &argv);
+
+ aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+ aos::configuration::ReadConfig(FLAGS_config);
+
+ aos::ShmEventLoop shm_event_loop(&config.message());
+
+ aos::util::FilesystemMonitor filesystem_monitor(&shm_event_loop);
+
+ shm_event_loop.Run();
+
+ return 0;
+}
diff --git a/frc971/analysis/trim_log_to_enabled.cc b/frc971/analysis/trim_log_to_enabled.cc
index 0853ea5..47e7703 100644
--- a/frc971/analysis/trim_log_to_enabled.cc
+++ b/frc971/analysis/trim_log_to_enabled.cc
@@ -9,12 +9,19 @@
DEFINE_string(output_folder, "/tmp/trimmed/",
"Name of the folder to write the trimmed log to.");
+DEFINE_string(node, "roborio", "");
DEFINE_double(pre_enable_time_sec, 10.0,
"Amount of time to leave in the new log before the first enable "
"signal happens.");
DEFINE_double(post_enable_time_sec, 1.0,
"Amount of time to leave in the new log after the final enable "
"signal ends.");
+DEFINE_double(force_start_monotonic, -1.0,
+ "If set, time, in seconds, at which to forcibly trim the start "
+ "of the log.");
+DEFINE_double(
+ force_end_monotonic, -1.0,
+ "If set, time, in seconds, at which to forcibly trim the end of the log.");
int main(int argc, char *argv[]) {
gflags::SetUsageMessage(
@@ -26,15 +33,16 @@
std::optional<aos::monotonic_clock::time_point> start_time;
std::optional<aos::monotonic_clock::time_point> end_time;
bool printed_match = false;
+ bool force_time_range = FLAGS_force_start_monotonic > 0;
// We need to do two passes through the logfile; one to figure out when the
// start/end times are, one to actually do the trimming.
- {
+ if (!force_time_range) {
aos::logger::LogReader reader(logfiles);
const aos::Node *roborio =
- aos::configuration::GetNode(reader.configuration(), "roborio");
+ aos::configuration::GetNode(reader.configuration(), FLAGS_node);
reader.Register();
std::unique_ptr<aos::EventLoop> event_loop =
- reader.event_loop_factory()->MakeEventLoop("roborio", roborio);
+ reader.event_loop_factory()->MakeEventLoop(FLAGS_node, roborio);
event_loop->MakeWatcher(
"/aos", [&start_time, &end_time, &printed_match,
&event_loop](const aos::JoystickState &msg) {
@@ -60,25 +68,33 @@
if (!printed_match) {
LOG(INFO) << "No match info.";
}
+ if (!start_time.has_value()) {
+ LOG(WARNING) << "Log does not ontain any JoystickState messages.";
+ return 1;
+ }
+ LOG(INFO) << "First enable at " << start_time.value();
+ LOG(INFO) << "Final enable at " << end_time.value();
+ start_time.value() -= std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::duration<double>(FLAGS_pre_enable_time_sec));
+ end_time.value() += std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::duration<double>(FLAGS_post_enable_time_sec));
+ } else {
+ CHECK_LT(FLAGS_force_start_monotonic, FLAGS_force_end_monotonic);
+ start_time = aos::monotonic_clock::time_point(
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::duration<double>(FLAGS_force_start_monotonic)));
+ end_time = aos::monotonic_clock::time_point(
+ std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::duration<double>(FLAGS_force_end_monotonic)));
}
- if (!start_time.has_value()) {
- LOG(WARNING) << "Log does not ontain any JoystickState messages.";
- return 1;
- }
- LOG(INFO) << "First enable at " << start_time.value();
- LOG(INFO) << "Final enable at " << end_time.value();
- start_time.value() -= std::chrono::duration_cast<std::chrono::nanoseconds>(
- std::chrono::duration<double>(FLAGS_pre_enable_time_sec));
- end_time.value() += std::chrono::duration_cast<std::chrono::nanoseconds>(
- std::chrono::duration<double>(FLAGS_post_enable_time_sec));
{
aos::logger::LogReader reader(logfiles);
const aos::Node *roborio =
- aos::configuration::GetNode(reader.configuration(), "roborio");
+ aos::configuration::GetNode(reader.configuration(), FLAGS_node);
reader.Register();
std::unique_ptr<aos::EventLoop> event_loop =
- reader.event_loop_factory()->MakeEventLoop("roborio", roborio);
+ reader.event_loop_factory()->MakeEventLoop(FLAGS_node, roborio);
auto exit_timer = event_loop->AddTimer(
[&reader]() { reader.event_loop_factory()->Exit(); });
exit_timer->Schedule(start_time.value());
diff --git a/frc971/control_loops/drivetrain/libspline.cc b/frc971/control_loops/drivetrain/libspline.cc
index 57abe69..b1af886 100644
--- a/frc971/control_loops/drivetrain/libspline.cc
+++ b/frc971/control_loops/drivetrain/libspline.cc
@@ -120,8 +120,9 @@
int num_distance) {
return new Trajectory(
DistanceSpline(*spline),
- ::y2020::control_loops::drivetrain::GetDrivetrainConfig(), nullptr, -1,
- vmax, num_distance);
+ std::make_unique<DrivetrainConfig<double>>(
+ ::y2020::control_loops::drivetrain::GetDrivetrainConfig()),
+ nullptr, -1, vmax, num_distance);
}
void deleteTrajectory(Trajectory *t) { delete t; }
diff --git a/frc971/control_loops/drivetrain/splinedrivetrain.cc b/frc971/control_loops/drivetrain/splinedrivetrain.cc
index 508967c..f90e7e0 100644
--- a/frc971/control_loops/drivetrain/splinedrivetrain.cc
+++ b/frc971/control_loops/drivetrain/splinedrivetrain.cc
@@ -74,7 +74,7 @@
void SplineDrivetrain::AddTrajectory(const fb::Trajectory *trajectory) {
CHECK_LT(trajectories_.size(), trajectories_.capacity());
- trajectories_.emplace_back(dt_config_, trajectory, velocity_drivetrain_);
+ trajectories_.emplace_back(&dt_config_, trajectory, velocity_drivetrain_);
UpdateSplineHandles(commanded_spline_);
}
diff --git a/frc971/control_loops/drivetrain/trajectory.cc b/frc971/control_loops/drivetrain/trajectory.cc
index 36d0947..a876ae3 100644
--- a/frc971/control_loops/drivetrain/trajectory.cc
+++ b/frc971/control_loops/drivetrain/trajectory.cc
@@ -33,7 +33,7 @@
} // namespace
FinishedTrajectory::FinishedTrajectory(
- const DrivetrainConfig<double> &config, const fb::Trajectory *buffer,
+ const DrivetrainConfig<double> *config, const fb::Trajectory *buffer,
std::shared_ptr<
StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>
@@ -80,15 +80,15 @@
BaseTrajectory::BaseTrajectory(
const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
- const DrivetrainConfig<double> &config,
+ const DrivetrainConfig<double> *config,
std::shared_ptr<
StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>
velocity_drivetrain)
: velocity_drivetrain_(std::move(velocity_drivetrain)),
config_(config),
- robot_radius_l_(config.robot_radius),
- robot_radius_r_(config.robot_radius),
+ robot_radius_l_(config->robot_radius),
+ robot_radius_r_(config->robot_radius),
lateral_acceleration_(
ConstraintValue(constraints, ConstraintType::LATERAL_ACCELERATION)),
longitudinal_acceleration_(ConstraintValue(
@@ -96,7 +96,7 @@
voltage_limit_(ConstraintValue(constraints, ConstraintType::VOLTAGE)) {}
Trajectory::Trajectory(const SplineGoal &spline_goal,
- const DrivetrainConfig<double> &config)
+ const DrivetrainConfig<double> *config)
: Trajectory(DistanceSpline{spline_goal.spline()}, config,
spline_goal.spline()->constraints(),
spline_goal.spline_idx()) {
@@ -104,7 +104,7 @@
}
Trajectory::Trajectory(
- DistanceSpline &&input_spline, const DrivetrainConfig<double> &config,
+ DistanceSpline &&input_spline, const DrivetrainConfig<double> *config,
const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
int spline_idx, double vmax, int num_distance)
: BaseTrajectory(constraints, config),
@@ -127,6 +127,15 @@
}
}
+Trajectory::Trajectory(
+ DistanceSpline &&spline, std::unique_ptr<DrivetrainConfig<double>> config,
+ const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
+ int spline_idx, double vmax, int num_distance)
+ : Trajectory(std::move(spline), config.get(), constraints, spline_idx, vmax,
+ num_distance) {
+ owned_config_ = std::move(config);
+}
+
void Trajectory::LateralAccelPass() {
for (size_t i = 0; i < plan_.size(); ++i) {
const double distance = Distance(i);
@@ -751,7 +760,8 @@
// finite-horizon much longer (albeit with the extension just using the
// linearization for the infal point).
void Trajectory::CalculatePathGains() {
- const std::vector<Eigen::Matrix<double, 3, 1>> xva_plan = PlanXVA(config_.dt);
+ const std::vector<Eigen::Matrix<double, 3, 1>> xva_plan =
+ PlanXVA(config_->dt);
if (xva_plan.empty()) {
LOG(ERROR) << "Plan is empty--unable to plan trajectory.";
return;
@@ -783,7 +793,7 @@
PathRelativeContinuousSystem(distance, &A_continuous, &B_continuous);
Eigen::Matrix<double, 5, 5> A_discrete;
Eigen::Matrix<double, 5, 2> B_discrete;
- controls::C2D(A_continuous, B_continuous, config_.dt, &A_discrete,
+ controls::C2D(A_continuous, B_continuous, config_->dt, &A_discrete,
&B_discrete);
if (i == max_index) {
@@ -898,9 +908,9 @@
result(2, 0) = spline().Theta(distance);
result.block<2, 1>(3, 0) =
- config_.Tla_to_lr() * (Eigen::Matrix<double, 2, 1>() << velocity,
- spline().DThetaDt(distance, velocity))
- .finished();
+ config_->Tla_to_lr() * (Eigen::Matrix<double, 2, 1>() << velocity,
+ spline().DThetaDt(distance, velocity))
+ .finished();
return result;
}
diff --git a/frc971/control_loops/drivetrain/trajectory.h b/frc971/control_loops/drivetrain/trajectory.h
index a9f14f6..5964bf5 100644
--- a/frc971/control_loops/drivetrain/trajectory.h
+++ b/frc971/control_loops/drivetrain/trajectory.h
@@ -14,6 +14,11 @@
#include "frc971/control_loops/runge_kutta.h"
#include "frc971/control_loops/state_feedback_loop.h"
+// Note for all of these classes:
+// Whenever a pointer to a DrivetrainConfig is taken in the constructor, it must
+// live for the entire lifetime of the object. The classes here do not take
+// ownership of the pointer.
+
namespace frc971::control_loops::drivetrain {
template <typename F>
@@ -43,16 +48,16 @@
public:
BaseTrajectory(
const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
- const DrivetrainConfig<double> &config)
+ const DrivetrainConfig<double> *config)
: BaseTrajectory(constraints, config,
std::make_shared<StateFeedbackLoop<
2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>(
- config.make_hybrid_drivetrain_velocity_loop())) {}
+ config->make_hybrid_drivetrain_velocity_loop())) {}
BaseTrajectory(
const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
- const DrivetrainConfig<double> &config,
+ const DrivetrainConfig<double> *config,
std::shared_ptr<
StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>
@@ -192,7 +197,7 @@
HybridKalman<2, 2, 2>>>
velocity_drivetrain_;
- DrivetrainConfig<double> config_;
+ const DrivetrainConfig<double> *config_;
// Robot radiuses.
double robot_radius_l_;
@@ -209,20 +214,20 @@
// Note: The lifetime of the supplied buffer is assumed to be greater than
// that of this object.
explicit FinishedTrajectory(
- const DrivetrainConfig<double> &config, const fb::Trajectory *buffer,
+ const DrivetrainConfig<double> *config, const fb::Trajectory *buffer,
std::shared_ptr<
StateFeedbackLoop<2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>
velocity_drivetrain);
- explicit FinishedTrajectory(const DrivetrainConfig<double> &config,
+ explicit FinishedTrajectory(const DrivetrainConfig<double> *config,
const fb::Trajectory *buffer)
: FinishedTrajectory(
config, buffer,
std::make_shared<StateFeedbackLoop<
2, 2, 2, double, StateFeedbackHybridPlant<2, 2, 2>,
HybridKalman<2, 2, 2>>>(
- config.make_hybrid_drivetrain_velocity_loop())) {}
+ config->make_hybrid_drivetrain_velocity_loop())) {}
FinishedTrajectory(const FinishedTrajectory &) = delete;
FinishedTrajectory &operator=(const FinishedTrajectory &) = delete;
@@ -264,16 +269,23 @@
class Trajectory : public BaseTrajectory {
public:
Trajectory(const SplineGoal &spline_goal,
- const DrivetrainConfig<double> &config);
+ const DrivetrainConfig<double> *config);
Trajectory(
- DistanceSpline &&spline, const DrivetrainConfig<double> &config,
+ DistanceSpline &&spline, const DrivetrainConfig<double> *config,
const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
int spline_idx = 0, double vmax = 10.0, int num_distance = 0);
+ // Version that owns its own DrivetrainConfig.
+ Trajectory(
+ DistanceSpline &&spline, std::unique_ptr<DrivetrainConfig<double>> config,
+ const flatbuffers::Vector<flatbuffers::Offset<Constraint>> *constraints,
+ int spline_idx, double vmax, int num_distance);
+
virtual ~Trajectory() = default;
std::vector<Eigen::Matrix<double, 3, 1>> PlanXVA(std::chrono::nanoseconds dt);
+ const DrivetrainConfig<double> *drivetrain_config() { return config_; }
enum class VoltageLimit {
kConservative,
kAggressive,
@@ -390,7 +402,8 @@
// The spline we are planning for.
const DistanceSpline spline_;
- const DrivetrainConfig<double> config_;
+ std::unique_ptr<DrivetrainConfig<double>> owned_config_;
+ const DrivetrainConfig<double> *config_;
// Velocities in the plan (distance for each index is defined by Distance())
std::vector<double> plan_;
diff --git a/frc971/control_loops/drivetrain/trajectory_generator.cc b/frc971/control_loops/drivetrain/trajectory_generator.cc
index 8e3e0f8..e531167 100644
--- a/frc971/control_loops/drivetrain/trajectory_generator.cc
+++ b/frc971/control_loops/drivetrain/trajectory_generator.cc
@@ -14,7 +14,7 @@
}
void TrajectoryGenerator::HandleSplineGoal(const SplineGoal &goal) {
- Trajectory trajectory(goal, dt_config_);
+ Trajectory trajectory(goal, &dt_config_);
trajectory.Plan();
aos::Sender<fb::Trajectory>::Builder builder =
diff --git a/frc971/control_loops/drivetrain/trajectory_plot.cc b/frc971/control_loops/drivetrain/trajectory_plot.cc
index 8c3ef47..9eb95b5 100644
--- a/frc971/control_loops/drivetrain/trajectory_plot.cc
+++ b/frc971/control_loops/drivetrain/trajectory_plot.cc
@@ -50,7 +50,7 @@
(::Eigen::Matrix<double, 2, 4>() << 0.0, 1.2 * FLAGS_forward,
-0.2 * FLAGS_forward, FLAGS_forward, 0.0, 0.0, 1.0, 1.0)
.finished()))),
- config, nullptr);
+ &config, nullptr);
trajectory.set_lateral_acceleration(2.0);
trajectory.set_longitudinal_acceleration(1.0);
@@ -131,7 +131,7 @@
aos::FlatbufferDetachedBuffer<fb::Trajectory> trajectory_buffer(
fbb.Release());
- FinishedTrajectory finished_trajectory(config, &trajectory_buffer.message());
+ FinishedTrajectory finished_trajectory(&config, &trajectory_buffer.message());
::Eigen::Matrix<double, 5, 1> state = ::Eigen::Matrix<double, 5, 1>::Zero();
state(0, 0) = FLAGS_dx;
diff --git a/frc971/control_loops/drivetrain/trajectory_test.cc b/frc971/control_loops/drivetrain/trajectory_test.cc
index fbf8970..6e9089a 100644
--- a/frc971/control_loops/drivetrain/trajectory_test.cc
+++ b/frc971/control_loops/drivetrain/trajectory_test.cc
@@ -74,7 +74,7 @@
const int spline_index = 12345;
// Run lots of steps to make the feedforwards terms more accurate.
trajectory_ = ::std::unique_ptr<Trajectory>(new Trajectory(
- DistanceSpline(GetParam().control_points), dt_config_,
+ DistanceSpline(GetParam().control_points), &dt_config_,
/*constraints=*/nullptr, spline_index, GetParam().velocity_limit));
distance_spline_ = &trajectory_->spline();
trajectory_->set_lateral_acceleration(GetParam().lateral_acceleration);
@@ -111,7 +111,7 @@
EXPECT_EQ(spline_index, trajectory_buffer_->message().handle());
finished_trajectory_ = std::make_unique<FinishedTrajectory>(
- dt_config_, &trajectory_buffer_->message());
+ &dt_config_, &trajectory_buffer_->message());
}
void TearDown() {
diff --git a/frc971/control_loops/python/constants.py b/frc971/control_loops/python/constants.py
index 260a6ad..f96e2fe 100644
--- a/frc971/control_loops/python/constants.py
+++ b/frc971/control_loops/python/constants.py
@@ -148,7 +148,8 @@
return "y2022/actors/splines"
elif field.year == 2023:
return "y2023/autonomous/splines"
- #TODO: Update 2024 spline jsons
+ elif field.year == 2024:
+ return "y2024/autonomous/splines"
else:
return "frc971/control_loops/python/spline_jsons"
diff --git a/frc971/control_loops/python/path_edit.py b/frc971/control_loops/python/path_edit.py
index 1659e55..0cd41cb 100755
--- a/frc971/control_loops/python/path_edit.py
+++ b/frc971/control_loops/python/path_edit.py
@@ -311,7 +311,8 @@
file = filename.with_suffix(f".{index}.json")
print(f" {file.relative_to(export_folder)}")
with open(file, mode='w') as points_file:
- json.dump(multispline.toJsonObject(), points_file)
+ # Indent to make the file actually readable
+ json.dump(multispline.toJsonObject(), points_file, indent=4)
def import_json(self, file_name):
# Abort place mode
diff --git a/frc971/imu_fdcan/can_translator_lib.cc b/frc971/imu_fdcan/can_translator_lib.cc
index e4a927e..4336c82 100644
--- a/frc971/imu_fdcan/can_translator_lib.cc
+++ b/frc971/imu_fdcan/can_translator_lib.cc
@@ -3,7 +3,6 @@
using frc971::imu_fdcan::CANTranslator;
constexpr std::size_t kCanFrameSize = 64;
-constexpr int kImuCanId = 1;
CANTranslator::CANTranslator(aos::EventLoop *event_loop,
std::string_view canframe_channel)
@@ -13,21 +12,23 @@
can_translator_status_sender_(
event_loop->MakeSender<frc971::imu::CanTranslatorStatusStatic>(
"/imu")) {
+ packets_arrived_.fill(false);
// TODO(max): Update this with a proper priority
event_loop->SetRuntimeRealtimePriority(15);
event_loop->MakeWatcher(
canframe_channel, [this](const frc971::can_logger::CanFrame &can_frame) {
- if (can_frame.data()->size() / sizeof(uint8_t) != 64) {
+ if (can_frame.data()->size() / sizeof(uint8_t) != 8) {
invalid_packet_count_++;
+ return;
}
- if (can_frame.can_id() != kImuCanId) {
+ if (can_frame.can_id() == 0 || can_frame.can_id() > 8) {
invalid_can_id_count_++;
return;
}
- if (can_frame.data()->size() / sizeof(uint8_t) == 64) {
+ if (can_frame.data()->size() / sizeof(uint8_t) == 8) {
valid_packet_count_++;
HandleFrame(&can_frame);
}
@@ -41,6 +42,7 @@
status_builder->set_valid_packet_count(valid_packet_count_);
status_builder->set_invalid_packet_count(invalid_packet_count_);
status_builder->set_invalid_can_id_count(invalid_can_id_count_);
+ status_builder->set_out_of_order_count(out_of_order_count_);
status_builder.CheckOk(status_builder.Send());
},
@@ -61,10 +63,26 @@
// Values from the data field mapping table in
// https://docs.google.com/document/d/12AJUruW7DZ2pIrDzTyPC0qqFoia4QOSVlax6Jd7m4H0/edit?usp=sharing
void CANTranslator::HandleFrame(const frc971::can_logger::CanFrame *can_frame) {
+ const size_t frame_index = can_frame->can_id() - 1u;
+ packets_arrived_[frame_index] = true;
+ for (size_t index = 0; index <= frame_index; ++index) {
+ if (!packets_arrived_[index]) {
+ ++out_of_order_count_;
+ packets_arrived_.fill(false);
+ return;
+ }
+ }
+ // Should have already checked length.
+ CHECK_EQ(can_frame->data()->size(), 8u);
+ memcpy(current_frame_.data() + frame_index * 8, can_frame->data()->data(), 8);
+ if (frame_index < 7) {
+ return;
+ }
+
aos::Sender<frc971::imu::DualImuStatic>::StaticBuilder dual_imu_builder =
dual_imu_sender_.MakeStaticBuilder();
- std::span can_data(can_frame->data()->data(), kCanFrameSize);
+ std::span<const uint8_t> can_data(current_frame_.data(), kCanFrameSize);
frc971::imu::SingleImuStatic *murata = dual_imu_builder->add_murata();
diff --git a/frc971/imu_fdcan/can_translator_lib.h b/frc971/imu_fdcan/can_translator_lib.h
index 12acb0a..af05aba 100644
--- a/frc971/imu_fdcan/can_translator_lib.h
+++ b/frc971/imu_fdcan/can_translator_lib.h
@@ -21,9 +21,13 @@
aos::Sender<imu::DualImuStatic> dual_imu_sender_;
aos::Sender<imu::CanTranslatorStatusStatic> can_translator_status_sender_;
+ std::array<uint8_t, 64> current_frame_;
+ std::array<bool, 8> packets_arrived_;
+
uint64_t valid_packet_count_ = 0;
uint64_t invalid_packet_count_ = 0;
uint64_t invalid_can_id_count_ = 0;
+ uint64_t out_of_order_count_ = 0;
};
} // namespace frc971::imu_fdcan
diff --git a/frc971/imu_fdcan/can_translator_lib_test.cc b/frc971/imu_fdcan/can_translator_lib_test.cc
index f8e6d85..fcef6a4 100644
--- a/frc971/imu_fdcan/can_translator_lib_test.cc
+++ b/frc971/imu_fdcan/can_translator_lib_test.cc
@@ -49,80 +49,23 @@
aos::monotonic_clock::epoch() + std::chrono::seconds(0),
aos::realtime_clock::epoch() + std::chrono::seconds(100));
can_frame_event_loop_->OnRun([this] {
- aos::Sender<frc971::can_logger::CanFrameStatic>::StaticBuilder
- can_frame_builder = can_frame_sender_.MakeStaticBuilder();
+ std::array<uint8_t, 64> full_frame{
+ 226, 100, 108, 8, 152, 40, 202, 121, 202, 121, 202, 121, 85,
+ 85, 81, 189, 0, 0, 8, 189, 85, 213, 127, 191, 12, 189,
+ 34, 187, 255, 219, 220, 59, 147, 173, 5, 61, 88, 68, 205,
+ 188, 230, 92, 24, 189, 235, 1, 127, 191, 210, 7, 34, 54,
+ 86, 103, 133, 186, 100, 205, 101, 185, 29, 26, 26, 0};
+ for (size_t i = 0; i < 8; ++i) {
+ aos::Sender<frc971::can_logger::CanFrameStatic>::StaticBuilder
+ can_frame_builder = can_frame_sender_.MakeStaticBuilder();
- can_frame_builder->set_can_id(1);
- can_frame_builder->set_realtime_timestamp_ns(100e9 + 971);
- auto can_data = can_frame_builder->add_data();
- CHECK(can_data->reserve(sizeof(uint8_t) * 64));
+ can_frame_builder->set_can_id(i + 1);
+ can_frame_builder->set_realtime_timestamp_ns(100e9 + 971);
+ auto can_data = can_frame_builder->add_data();
+ CHECK(can_data->FromData(full_frame.data() + i * 8, 8));
- CHECK(can_data->emplace_back(226));
- CHECK(can_data->emplace_back(100));
- CHECK(can_data->emplace_back(108));
- CHECK(can_data->emplace_back(8));
- CHECK(can_data->emplace_back(152));
- CHECK(can_data->emplace_back(40));
- CHECK(can_data->emplace_back(202));
- CHECK(can_data->emplace_back(121));
- CHECK(can_data->emplace_back(202));
- CHECK(can_data->emplace_back(121));
- CHECK(can_data->emplace_back(202));
- CHECK(can_data->emplace_back(121));
- CHECK(can_data->emplace_back(85));
- CHECK(can_data->emplace_back(85));
- CHECK(can_data->emplace_back(81));
- CHECK(can_data->emplace_back(189));
- CHECK(can_data->emplace_back(0));
- CHECK(can_data->emplace_back(0));
- CHECK(can_data->emplace_back(8));
- CHECK(can_data->emplace_back(189));
- CHECK(can_data->emplace_back(85));
- CHECK(can_data->emplace_back(213));
- CHECK(can_data->emplace_back(127));
- CHECK(can_data->emplace_back(191));
- CHECK(can_data->emplace_back(12));
- CHECK(can_data->emplace_back(189));
- CHECK(can_data->emplace_back(34));
- CHECK(can_data->emplace_back(187));
- CHECK(can_data->emplace_back(255));
- CHECK(can_data->emplace_back(219));
- CHECK(can_data->emplace_back(220));
- CHECK(can_data->emplace_back(59));
- CHECK(can_data->emplace_back(147));
- CHECK(can_data->emplace_back(173));
- CHECK(can_data->emplace_back(5));
- CHECK(can_data->emplace_back(61));
- CHECK(can_data->emplace_back(88));
- CHECK(can_data->emplace_back(68));
- CHECK(can_data->emplace_back(205));
- CHECK(can_data->emplace_back(188));
- CHECK(can_data->emplace_back(230));
- CHECK(can_data->emplace_back(92));
- CHECK(can_data->emplace_back(24));
- CHECK(can_data->emplace_back(189));
- CHECK(can_data->emplace_back(235));
- CHECK(can_data->emplace_back(1));
- CHECK(can_data->emplace_back(127));
- CHECK(can_data->emplace_back(191));
- CHECK(can_data->emplace_back(210));
- CHECK(can_data->emplace_back(7));
- CHECK(can_data->emplace_back(34));
- CHECK(can_data->emplace_back(54));
- CHECK(can_data->emplace_back(86));
- CHECK(can_data->emplace_back(103));
- CHECK(can_data->emplace_back(133));
- CHECK(can_data->emplace_back(186));
- CHECK(can_data->emplace_back(100));
- CHECK(can_data->emplace_back(205));
- CHECK(can_data->emplace_back(101));
- CHECK(can_data->emplace_back(185));
- CHECK(can_data->emplace_back(29));
- CHECK(can_data->emplace_back(26));
- CHECK(can_data->emplace_back(26));
- CHECK(can_data->emplace_back(0));
-
- can_frame_builder.CheckOk(can_frame_builder.Send());
+ can_frame_builder.CheckOk(can_frame_builder.Send());
+ }
});
event_loop_factory_.RunFor(std::chrono::milliseconds(200));
@@ -132,7 +75,7 @@
ASSERT_FALSE(can_translator_status_fetcher_->invalid_packet_count() > 0);
ASSERT_FALSE(can_translator_status_fetcher_->invalid_can_id_count() > 0);
- EXPECT_EQ(can_translator_status_fetcher_->valid_packet_count(), 1);
+ EXPECT_EQ(can_translator_status_fetcher_->valid_packet_count(), 8);
EXPECT_EQ(dual_imu_fetcher_->board_timestamp_us(), 141321442);
EXPECT_EQ(dual_imu_fetcher_->packet_counter(), 10392);
@@ -177,12 +120,11 @@
aos::Sender<frc971::can_logger::CanFrameStatic>::StaticBuilder
can_frame_builder = can_frame_sender_.MakeStaticBuilder();
- can_frame_builder->set_can_id(2);
+ can_frame_builder->set_can_id(10);
can_frame_builder->set_realtime_timestamp_ns(100);
auto can_data = can_frame_builder->add_data();
- CHECK(can_data->reserve(sizeof(uint8_t) * 1));
-
- CHECK(can_data->emplace_back(0));
+ CHECK(can_data->reserve(sizeof(uint8_t) * 8));
+ can_data->resize(8);
can_frame_builder.CheckOk(can_frame_builder.Send());
});
@@ -192,6 +134,6 @@
ASSERT_TRUE(can_translator_status_fetcher_.Fetch());
ASSERT_FALSE(dual_imu_fetcher_.Fetch());
- EXPECT_EQ(can_translator_status_fetcher_->invalid_packet_count(), 1);
+ EXPECT_EQ(can_translator_status_fetcher_->invalid_packet_count(), 0);
EXPECT_EQ(can_translator_status_fetcher_->invalid_can_id_count(), 1);
}
diff --git a/frc971/imu_fdcan/can_translator_status.fbs b/frc971/imu_fdcan/can_translator_status.fbs
index 232f3ba..63273bc 100644
--- a/frc971/imu_fdcan/can_translator_status.fbs
+++ b/frc971/imu_fdcan/can_translator_status.fbs
@@ -7,6 +7,8 @@
invalid_packet_count: uint64 (id: 1);
// Number of times we've gotten an invalid can id
invalid_can_id_count: uint64 (id: 2);
+ // Number of times that we have observed an out of order can id.
+ out_of_order_count: uint64 (id: 3);
}
root_type CanTranslatorStatus;
diff --git a/frc971/imu_fdcan/dual_imu_blender_lib.cc b/frc971/imu_fdcan/dual_imu_blender_lib.cc
index 09655e3..2627739 100644
--- a/frc971/imu_fdcan/dual_imu_blender_lib.cc
+++ b/frc971/imu_fdcan/dual_imu_blender_lib.cc
@@ -14,6 +14,7 @@
// Coefficient to multiply the saturation values by to give some room on where
// we switch to tdk.
static constexpr double kSaturationCoeff = 0.9;
+static constexpr size_t kSaturationCounterThreshold = 20;
using frc971::imu_fdcan::DualImuBlender;
@@ -66,6 +67,12 @@
if (std::abs(dual_imu->tdk()->gyro_z()) >=
kSaturationCoeff * kMurataGyroSaturation) {
+ ++saturated_counter_;
+ } else {
+ saturated_counter_ = 0;
+ }
+
+ if (saturated_counter_ > kSaturationCounterThreshold) {
dual_imu_blender_status_builder->set_gyro_z(imu::ImuType::TDK);
imu_values->set_gyro_z(dual_imu->tdk()->gyro_z());
} else {
diff --git a/frc971/imu_fdcan/dual_imu_blender_lib.h b/frc971/imu_fdcan/dual_imu_blender_lib.h
index 04044d2..b3aa5c0 100644
--- a/frc971/imu_fdcan/dual_imu_blender_lib.h
+++ b/frc971/imu_fdcan/dual_imu_blender_lib.h
@@ -20,6 +20,7 @@
private:
aos::Sender<IMUValuesBatchStatic> imu_values_batch_sender_;
aos::Sender<imu::DualImuBlenderStatusStatic> dual_imu_blender_status_sender_;
+ size_t saturated_counter_ = 0;
};
} // namespace frc971::imu_fdcan
diff --git a/frc971/imu_fdcan/dual_imu_blender_lib_test.cc b/frc971/imu_fdcan/dual_imu_blender_lib_test.cc
index b3a3015..4237bd1 100644
--- a/frc971/imu_fdcan/dual_imu_blender_lib_test.cc
+++ b/frc971/imu_fdcan/dual_imu_blender_lib_test.cc
@@ -30,6 +30,7 @@
dual_imu_blender_(dual_imu_blender_event_loop_.get()) {}
void CheckImuType(frc971::imu::ImuType type) {
+ dual_imu_blender_status_fetcher_.Fetch();
EXPECT_EQ(dual_imu_blender_status_fetcher_->gyro_x(), type);
EXPECT_EQ(dual_imu_blender_status_fetcher_->gyro_y(), type);
EXPECT_EQ(dual_imu_blender_status_fetcher_->gyro_z(), type);
@@ -182,41 +183,43 @@
CheckImuType(frc971::imu::ImuType::MURATA);
// Make sure we switch to TDK on saturation
- dual_imu_blender_event_loop_->OnRun([this] {
- aos::Sender<frc971::imu::DualImuStatic>::StaticBuilder dual_imu_builder =
- dual_imu_sender_.MakeStaticBuilder();
+ dual_imu_blender_event_loop_->AddPhasedLoop(
+ [this](int) {
+ aos::Sender<frc971::imu::DualImuStatic>::StaticBuilder
+ dual_imu_builder = dual_imu_sender_.MakeStaticBuilder();
- frc971::imu::SingleImuStatic *murata = dual_imu_builder->add_murata();
+ frc971::imu::SingleImuStatic *murata = dual_imu_builder->add_murata();
- auto *murata_chip_states = murata->add_chip_states();
- frc971::imu::ChipStateStatic *murata_uno_chip_state =
- murata_chip_states->emplace_back();
+ auto *murata_chip_states = murata->add_chip_states();
+ frc971::imu::ChipStateStatic *murata_uno_chip_state =
+ murata_chip_states->emplace_back();
- frc971::imu::SingleImuStatic *tdk = dual_imu_builder->add_tdk();
+ frc971::imu::SingleImuStatic *tdk = dual_imu_builder->add_tdk();
- dual_imu_builder->set_board_timestamp_us(1);
- dual_imu_builder->set_kernel_timestamp(1);
+ dual_imu_builder->set_board_timestamp_us(1);
+ dual_imu_builder->set_kernel_timestamp(1);
- tdk->set_gyro_x(6.0);
- tdk->set_gyro_y(6.0);
- tdk->set_gyro_z(6.0);
+ tdk->set_gyro_x(6.0);
+ tdk->set_gyro_y(6.0);
+ tdk->set_gyro_z(6.0);
- murata->set_gyro_x(5.2);
- murata->set_gyro_y(5.2);
- murata->set_gyro_z(5.2);
+ murata->set_gyro_x(5.2);
+ murata->set_gyro_y(5.2);
+ murata->set_gyro_z(5.2);
- tdk->set_accelerometer_x(6.2);
- tdk->set_accelerometer_y(6.3);
- tdk->set_accelerometer_z(6.5);
+ tdk->set_accelerometer_x(6.2);
+ tdk->set_accelerometer_y(6.3);
+ tdk->set_accelerometer_z(6.5);
- murata->set_accelerometer_x(5.5);
- murata->set_accelerometer_y(5.5);
- murata->set_accelerometer_z(5.5);
+ murata->set_accelerometer_x(5.5);
+ murata->set_accelerometer_y(5.5);
+ murata->set_accelerometer_z(5.5);
- murata_uno_chip_state->set_temperature(20);
+ murata_uno_chip_state->set_temperature(20);
- dual_imu_builder.CheckOk(dual_imu_builder.Send());
- });
+ dual_imu_builder.CheckOk(dual_imu_builder.Send());
+ },
+ std::chrono::milliseconds(1));
event_loop_factory_.RunFor(std::chrono::milliseconds(200));
@@ -243,41 +246,43 @@
CheckImuType(frc971::imu::ImuType::TDK);
// Check negative values as well
- dual_imu_blender_event_loop_->OnRun([this] {
- aos::Sender<frc971::imu::DualImuStatic>::StaticBuilder dual_imu_builder =
- dual_imu_sender_.MakeStaticBuilder();
+ dual_imu_blender_event_loop_->AddPhasedLoop(
+ [this](int) {
+ aos::Sender<frc971::imu::DualImuStatic>::StaticBuilder
+ dual_imu_builder = dual_imu_sender_.MakeStaticBuilder();
- frc971::imu::SingleImuStatic *murata = dual_imu_builder->add_murata();
+ frc971::imu::SingleImuStatic *murata = dual_imu_builder->add_murata();
- auto *murata_chip_states = murata->add_chip_states();
- frc971::imu::ChipStateStatic *murata_uno_chip_state =
- murata_chip_states->emplace_back();
+ auto *murata_chip_states = murata->add_chip_states();
+ frc971::imu::ChipStateStatic *murata_uno_chip_state =
+ murata_chip_states->emplace_back();
- frc971::imu::SingleImuStatic *tdk = dual_imu_builder->add_tdk();
+ frc971::imu::SingleImuStatic *tdk = dual_imu_builder->add_tdk();
- dual_imu_builder->set_board_timestamp_us(1);
- dual_imu_builder->set_kernel_timestamp(1);
+ dual_imu_builder->set_board_timestamp_us(1);
+ dual_imu_builder->set_kernel_timestamp(1);
- tdk->set_gyro_x(-6.0);
- tdk->set_gyro_y(-6.0);
- tdk->set_gyro_z(-6.0);
+ tdk->set_gyro_x(-6.0);
+ tdk->set_gyro_y(-6.0);
+ tdk->set_gyro_z(-6.0);
- murata->set_gyro_x(-5.2);
- murata->set_gyro_y(-5.2);
- murata->set_gyro_z(-5.2);
+ murata->set_gyro_x(-5.2);
+ murata->set_gyro_y(-5.2);
+ murata->set_gyro_z(-5.2);
- tdk->set_accelerometer_x(-6.2);
- tdk->set_accelerometer_y(-6.3);
- tdk->set_accelerometer_z(-6.5);
+ tdk->set_accelerometer_x(-6.2);
+ tdk->set_accelerometer_y(-6.3);
+ tdk->set_accelerometer_z(-6.5);
- murata->set_accelerometer_x(-5.5);
- murata->set_accelerometer_y(-5.5);
- murata->set_accelerometer_z(-5.5);
+ murata->set_accelerometer_x(-5.5);
+ murata->set_accelerometer_y(-5.5);
+ murata->set_accelerometer_z(-5.5);
- murata_uno_chip_state->set_temperature(20);
+ murata_uno_chip_state->set_temperature(20);
- dual_imu_builder.CheckOk(dual_imu_builder.Send());
- });
+ dual_imu_builder.CheckOk(dual_imu_builder.Send());
+ },
+ std::chrono::milliseconds(1));
event_loop_factory_.RunFor(std::chrono::milliseconds(200));
diff --git a/frc971/imu_fdcan/dual_imu_test_config_source.json b/frc971/imu_fdcan/dual_imu_test_config_source.json
index eda06da..c425be2 100644
--- a/frc971/imu_fdcan/dual_imu_test_config_source.json
+++ b/frc971/imu_fdcan/dual_imu_test_config_source.json
@@ -12,7 +12,7 @@
{
"name": "/imu",
"type": "frc971.imu.DualImu",
- "frequency": 200
+ "frequency": 2000
},
{
"name": "/can",
@@ -32,7 +32,7 @@
{
"name": "/imu",
"type": "frc971.imu.DualImuBlenderStatus",
- "frequency": 100
+ "frequency": 2000
},
]
}
diff --git a/frc971/math/flatbuffers_matrix.h b/frc971/math/flatbuffers_matrix.h
index 57013c9..cfca897 100644
--- a/frc971/math/flatbuffers_matrix.h
+++ b/frc971/math/flatbuffers_matrix.h
@@ -121,6 +121,22 @@
return true;
}
+template <int Rows, int Cols,
+ fbs::StorageOrder StorageOrder = fbs::StorageOrder::ColMajor>
+flatbuffers::Offset<fbs::Matrix> FromEigen(
+ const typename EigenMatrix<Rows, Cols, StorageOrder>::type &matrix,
+ flatbuffers::FlatBufferBuilder *fbb) {
+ constexpr size_t kSize = Rows * Cols;
+ flatbuffers::Offset<flatbuffers::Vector<double>> data_offset =
+ fbb->CreateVector(matrix.data(), kSize);
+ fbs::Matrix::Builder builder(*fbb);
+ builder.add_rows(Rows);
+ builder.add_cols(Cols);
+ builder.add_storage_order(StorageOrder);
+ builder.add_data(data_offset);
+ return builder.Finish();
+}
+
template <typename T>
bool FromEigen(const T &matrix, fbs::MatrixStatic *flatbuffer) {
return FromEigen<T::RowsAtCompileTime, T::ColsAtCompileTime,
diff --git a/frc971/math/flatbuffers_matrix_test.cc b/frc971/math/flatbuffers_matrix_test.cc
index 2807309..b2587f3 100644
--- a/frc971/math/flatbuffers_matrix_test.cc
+++ b/frc971/math/flatbuffers_matrix_test.cc
@@ -24,11 +24,18 @@
const Eigen::Matrix<double, 3, 4> expected{
{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
aos::fbs::Builder<fbs::MatrixStatic> builder;
+ flatbuffers::FlatBufferBuilder fbb;
ASSERT_TRUE(FromEigen(expected, builder.get()));
+ fbb.Finish(FromEigen<3, 4>(expected, &fbb));
EXPECT_EQ(
"{ \"rows\": 3, \"cols\": 4, \"storage_order\": \"ColMajor\", \"data\": "
"[ 0.0, 4.0, 8.0, 1.0, 5.0, 9.0, 2.0, 6.0, 10.0, 3.0, 7.0, 11.0 ] }",
aos::FlatbufferToJson(builder.AsFlatbufferSpan()));
+ EXPECT_EQ(
+ "{ \"rows\": 3, \"cols\": 4, \"storage_order\": \"ColMajor\", \"data\": "
+ "[ 0.0, 4.0, 8.0, 1.0, 5.0, 9.0, 2.0, 6.0, 10.0, 3.0, 7.0, 11.0 ] }",
+ aos::FlatbufferToJson(
+ aos::FlatbufferDetachedBuffer<fbs::Matrix>(fbb.Release())));
const Eigen::Matrix<double, 3, 4> result =
ToEigenOrDie<3, 4>(builder->AsFlatbuffer());
diff --git a/frc971/orin/BUILD b/frc971/orin/BUILD
index 06b1d6c..3ce9040 100644
--- a/frc971/orin/BUILD
+++ b/frc971/orin/BUILD
@@ -1,5 +1,7 @@
load("//frc971:halide.bzl", "halide_library")
+exports_files(["orin_irq_config.json"])
+
halide_library(
name = "ycbcr",
src = "crcv_generator.cc",
diff --git a/frc971/orin/gpu_apriltag.cc b/frc971/orin/gpu_apriltag.cc
index 55c5cd5..19bee47 100644
--- a/frc971/orin/gpu_apriltag.cc
+++ b/frc971/orin/gpu_apriltag.cc
@@ -26,7 +26,7 @@
"1.0.");
DEFINE_double(min_decision_margin, 50.0,
"Minimum decision margin (confidence) for an apriltag detection");
-DEFINE_int32(pixel_border, 10,
+DEFINE_int32(pixel_border, 150,
"Size of image border within which to reject detected corners");
DEFINE_uint64(pose_estimation_iterations, 50,
"Number of iterations for apriltag pose estimation.");
@@ -299,8 +299,10 @@
before_pose_estimation)
.count()
<< " seconds for pose estimation";
- VLOG(1) << "Pose err 1: " << pose_error_1;
- VLOG(1) << "Pose err 2: " << pose_error_2;
+ VLOG(1) << "Pose err 1: " << std::setprecision(20) << std::fixed
+ << pose_error_1 << " " << (pose_error_1 < 1e-6 ? "Good" : "Bad");
+ VLOG(1) << "Pose err 2: " << std::setprecision(20) << std::fixed
+ << pose_error_2 << " " << (pose_error_2 < 1e-6 ? "Good" : "Bad");
// Send undistorted corner points in pink
std::vector<cv::Point2f> corner_points = MakeCornerVector(gpu_detection);
@@ -409,7 +411,7 @@
timeprofile_display(tag_detector_->tp);
}
- VLOG(1) << "Took " << chrono::duration<double>(end_time - start_time).count()
+ VLOG(2) << "Took " << chrono::duration<double>(end_time - start_time).count()
<< " seconds to detect overall";
return;
diff --git a/frc971/orin/orin_irq_config.json b/frc971/orin/orin_irq_config.json
index 0edb72b..66699c8 100644
--- a/frc971/orin/orin_irq_config.json
+++ b/frc971/orin/orin_irq_config.json
@@ -328,11 +328,6 @@
"priority": 51
},
{
- "name": "ivc/*.rtc",
- "scheduler": "SCHEDULER_FIFO",
- "nice": 49
- },
- {
"name": "nvgpu_nvs_ga10b",
"scheduler": "SCHEDULER_OTHER",
"affinity": [2, 3],
diff --git a/frc971/vision/foxglove_image_converter_lib.cc b/frc971/vision/foxglove_image_converter_lib.cc
index 920eaf7..649f1bb 100644
--- a/frc971/vision/foxglove_image_converter_lib.cc
+++ b/frc971/vision/foxglove_image_converter_lib.cc
@@ -6,6 +6,8 @@
DEFINE_int32(jpeg_quality, 60,
"Compression quality of JPEGs, 0-100; lower numbers mean lower "
"quality and resulting image sizes.");
+DEFINE_uint32(max_period_ms, 100,
+ "Fastest period at which to convert images, to limit CPU usage.");
namespace frc971::vision {
std::string_view ExtensionForCompression(ImageCompression compression) {
@@ -49,9 +51,13 @@
event_loop_, input_channel,
[this, compression](const cv::Mat image,
const aos::monotonic_clock::time_point eof) {
- auto builder = sender_.MakeBuilder();
- builder.CheckOk(builder.Send(
- CompressImage(image, eof, builder.fbb(), compression)));
+ if (event_loop_->monotonic_now() >
+ (std::chrono::milliseconds(FLAGS_max_period_ms) +
+ sender_.monotonic_sent_time())) {
+ auto builder = sender_.MakeBuilder();
+ builder.CheckOk(builder.Send(
+ CompressImage(image, eof, builder.fbb(), compression)));
+ }
}),
sender_(
event_loop_->MakeSender<foxglove::CompressedImage>(output_channel)) {}
diff --git a/frc971/vision/target_mapper.cc b/frc971/vision/target_mapper.cc
index c14b032..f5c8dab 100644
--- a/frc971/vision/target_mapper.cc
+++ b/frc971/vision/target_mapper.cc
@@ -23,6 +23,8 @@
DEFINE_double(outlier_std_devs, 1.0,
"Number of standard deviations above average error needed for a "
"constraint to be considered an outlier and get removed.");
+DEFINE_bool(do_map_fitting, false,
+ "Whether to do a final fit of the solved map to the original map");
namespace frc971::vision {
Eigen::Affine3d PoseUtils::Pose3dToAffine3d(
@@ -448,36 +450,38 @@
CHECK(SolveOptimizationProblem(&target_pose_problem_2))
<< "The target pose solve 2 was not successful, exiting.";
- LOG(INFO) << "Solving the overall map's best alignment to the previous map";
- ceres::Problem map_fitting_problem(
- {.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP});
- std::unique_ptr<ceres::CostFunction> map_fitting_cost_function =
- BuildMapFittingOptimizationProblem(&map_fitting_problem);
- CHECK(SolveOptimizationProblem(&map_fitting_problem))
- << "The map fitting solve was not successful, exiting.";
- map_fitting_cost_function.release();
+ if (FLAGS_do_map_fitting) {
+ LOG(INFO) << "Solving the overall map's best alignment to the previous map";
+ ceres::Problem map_fitting_problem(
+ {.loss_function_ownership = ceres::DO_NOT_TAKE_OWNERSHIP});
+ std::unique_ptr<ceres::CostFunction> map_fitting_cost_function =
+ BuildMapFittingOptimizationProblem(&map_fitting_problem);
+ CHECK(SolveOptimizationProblem(&map_fitting_problem))
+ << "The map fitting solve was not successful, exiting.";
+ map_fitting_cost_function.release();
- Eigen::Affine3d H_frozen_actual = T_frozen_actual_ * R_frozen_actual_;
- LOG(INFO) << "H_frozen_actual: "
- << PoseUtils::Affine3dToPose3d(H_frozen_actual);
+ Eigen::Affine3d H_frozen_actual = T_frozen_actual_ * R_frozen_actual_;
+ LOG(INFO) << "H_frozen_actual: "
+ << PoseUtils::Affine3dToPose3d(H_frozen_actual);
- auto H_world_frozen =
- PoseUtils::Pose3dToAffine3d(target_poses_[FLAGS_frozen_target_id]);
- auto H_world_frozenactual = H_world_frozen * H_frozen_actual;
+ auto H_world_frozen =
+ PoseUtils::Pose3dToAffine3d(target_poses_[FLAGS_frozen_target_id]);
+ auto H_world_frozenactual = H_world_frozen * H_frozen_actual;
- // Offset the solved poses to become the actual ones
- for (auto &[id, pose] : target_poses_) {
- // Don't offset targets we didn't solve for
- if (id < FLAGS_min_target_id || id > FLAGS_max_target_id) {
- continue;
+ // Offset the solved poses to become the actual ones
+ for (auto &[id, pose] : target_poses_) {
+ // Don't offset targets we didn't solve for
+ if (id < FLAGS_min_target_id || id > FLAGS_max_target_id) {
+ continue;
+ }
+
+ // Take the delta between the frozen target and the solved target, and put
+ // that on top of the actual pose of the frozen target
+ auto H_world_solved = PoseUtils::Pose3dToAffine3d(pose);
+ auto H_frozen_solved = H_world_frozen.inverse() * H_world_solved;
+ auto H_world_actual = H_world_frozenactual * H_frozen_solved;
+ pose = PoseUtils::Affine3dToPose3d(H_world_actual);
}
-
- // Take the delta between the frozen target and the solved target, and put
- // that on top of the actual pose of the frozen target
- auto H_world_solved = PoseUtils::Pose3dToAffine3d(pose);
- auto H_frozen_solved = H_world_frozen.inverse() * H_world_solved;
- auto H_world_actual = H_world_frozenactual * H_frozen_solved;
- pose = PoseUtils::Affine3dToPose3d(H_world_actual);
}
auto map_json = MapToJson(field_name);
diff --git a/frc971/wpilib/pdp_fetcher.cc b/frc971/wpilib/pdp_fetcher.cc
index 5b02bb5..b8c5bf9 100644
--- a/frc971/wpilib/pdp_fetcher.cc
+++ b/frc971/wpilib/pdp_fetcher.cc
@@ -26,7 +26,8 @@
PDPFetcher::~PDPFetcher() {}
void PDPFetcher::Loop(int iterations) {
- if (iterations != 1) {
+ // Only pollute the logs if we've missed many iterations.
+ if (iterations > 3) {
AOS_LOG(DEBUG, "PDPFetcher skipped %d iterations\n", iterations - 1);
}
std::array<double, 16> currents;
diff --git a/frc971/wpilib/talonfx.cc b/frc971/wpilib/talonfx.cc
index 7959f2f..ba88548 100644
--- a/frc971/wpilib/talonfx.cc
+++ b/frc971/wpilib/talonfx.cc
@@ -62,6 +62,7 @@
current_limits.StatorCurrentLimitEnable = true;
current_limits.SupplyCurrentLimit = supply_current_limit_;
current_limits.SupplyCurrentLimitEnable = true;
+ current_limits.SupplyTimeThreshold = 0.0;
ctre::phoenix6::configs::MotorOutputConfigs output_configs;
output_configs.NeutralMode = neutral_mode_;
diff --git a/package.json b/package.json
index df5df67..6853a14 100644
--- a/package.json
+++ b/package.json
@@ -11,18 +11,23 @@
"@angular/core": "v16-lts",
"@angular/forms": "v16-lts",
"@angular/platform-browser": "v16-lts",
+ "@angular/service-worker": "v16-lts",
"@angular/cli": "v16-lts",
"@babel/cli": "^7.16.0",
"@babel/core": "^7.16.0",
"@types/jasmine": "3.10.3",
"@types/babel__core": "^7.20.5",
"@types/babel__generator": "^7.6.8",
+ "@types/pako": "2.0.3",
+ "angularx-qrcode": "^16.0.2",
"html-insert-assets": "0.14.3",
- "cypress": "12.3.0",
+ "cypress": "13.3.1",
+ "pako": "2.1.0",
"prettier": "2.6.1",
"requirejs": "2.3.6",
"rollup": "4.12.0",
"rxjs": "7.5.7",
+ "dexie": "^3.2.5",
"@rollup/plugin-node-resolve": "15.2.3",
"@types/flatbuffers": "1.10.0",
"@types/node": "20.11.19",
@@ -30,4 +35,4 @@
"terser": "5.16.4",
"zone.js": "^0.13.0"
}
-}
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8f37b3b..bf6a429 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,6 +32,9 @@
'@angular/platform-browser':
specifier: v16-lts
version: 16.2.12(@angular/animations@16.2.12)(@angular/common@16.2.12)(@angular/core@16.2.12)
+ '@angular/service-worker':
+ specifier: v16-lts
+ version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)
'@babel/cli':
specifier: ^7.16.0
version: 7.23.9(@babel/core@7.23.9)
@@ -56,12 +59,24 @@
'@types/node':
specifier: 20.11.19
version: 20.11.19
+ '@types/pako':
+ specifier: 2.0.3
+ version: 2.0.3
+ angularx-qrcode:
+ specifier: ^16.0.2
+ version: 16.0.2(@angular/core@16.2.12)
cypress:
- specifier: 12.3.0
- version: 12.3.0
+ specifier: 13.3.1
+ version: 13.3.1
+ dexie:
+ specifier: ^3.2.5
+ version: 3.2.5(karma@6.4.3)
html-insert-assets:
specifier: 0.14.3
version: 0.14.3
+ pako:
+ specifier: 2.1.0
+ version: 2.1.0
prettier:
specifier: 2.6.1
version: 2.6.1
@@ -102,6 +117,12 @@
'@org_frc971/scouting/www/counter_button':
specifier: workspace:*
version: link:../counter_button
+ '@types/pako':
+ specifier: 2.0.3
+ version: 2.0.3
+ pako:
+ specifier: 2.1.0
+ version: 2.1.0
scouting/www/match_list:
dependencies:
@@ -126,6 +147,18 @@
scouting/www/rpc: {}
+ scouting/www/scan:
+ dependencies:
+ '@angular/forms':
+ specifier: v16-lts
+ version: 16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12)(@angular/platform-browser@16.2.12)(rxjs@7.5.7)
+ '@types/pako':
+ specifier: 2.0.3
+ version: 2.0.3
+ pako:
+ specifier: 2.1.0
+ version: 2.1.0
+
scouting/www/shift_schedule:
dependencies:
'@angular/forms':
@@ -314,6 +347,19 @@
'@angular/core': 16.2.12(rxjs@7.5.7)(zone.js@0.13.3)
tslib: 2.6.0
+ /@angular/service-worker@16.2.12(@angular/common@16.2.12)(@angular/core@16.2.12):
+ resolution: {integrity: sha512-o0z0s4c76NmRASa+mUHn/q6vUKQNa06mGmLBDKm84vRQ1sQ2TJv+R1p8K9WkiM5mGy6tjQCDOgaz13TcxMFWOQ==}
+ engines: {node: ^16.14.0 || >=18.10.0}
+ hasBin: true
+ peerDependencies:
+ '@angular/common': 16.2.12
+ '@angular/core': 16.2.12
+ dependencies:
+ '@angular/common': 16.2.12(@angular/core@16.2.12)(rxjs@7.5.7)
+ '@angular/core': 16.2.12(rxjs@7.5.7)(zone.js@0.13.3)
+ tslib: 2.6.0
+ dev: true
+
/@babel/cli@7.23.9(@babel/core@7.23.9):
resolution: {integrity: sha512-vB1UXmGDNEhcf1jNAHKT9IlYk1R+hehVTLFlCLHBi8gfuHQGP6uRjgXVYU0EVlI/qwAWpstqkBdf2aez3/z/5Q==}
engines: {node: '>=6.9.0'}
@@ -567,10 +613,9 @@
engines: {node: '>=0.1.90'}
requiresBuild: true
dev: true
- optional: true
- /@cypress/request@2.88.11:
- resolution: {integrity: sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==}
+ /@cypress/request@3.0.1:
+ resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==}
engines: {node: '>= 6'}
dependencies:
aws-sign2: 0.7.0
@@ -588,7 +633,7 @@
performance-now: 2.1.0
qs: 6.10.4
safe-buffer: 5.2.1
- tough-cookie: 2.5.0
+ tough-cookie: 4.1.3
tunnel-agent: 0.6.0
uuid: 8.3.2
dev: true
@@ -925,6 +970,10 @@
- supports-color
dev: true
+ /@socket.io/component-emitter@3.1.0:
+ resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==}
+ dev: true
+
/@tootallnate/once@2.0.0:
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
engines: {node: '>= 10'}
@@ -972,6 +1021,16 @@
'@babel/types': 7.23.9
dev: true
+ /@types/cookie@0.4.1:
+ resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
+ dev: true
+
+ /@types/cors@2.8.17:
+ resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==}
+ dependencies:
+ '@types/node': 20.11.19
+ dev: true
+
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
@@ -984,8 +1043,10 @@
resolution: {integrity: sha512-SWyMrjgdAUHNQmutvDcKablrJhkDLy4wunTme8oYLjKp41GnHGxMRXr2MQMvy/qy8H3LdzwQk9gH4hZ6T++H8g==}
dev: true
- /@types/node@14.18.53:
- resolution: {integrity: sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==}
+ /@types/node@18.19.22:
+ resolution: {integrity: sha512-p3pDIfuMg/aXBmhkyanPshdfJuX5c5+bQjYLIikPLXAUycEogij/c50n/C+8XOA5L93cU4ZRXtn+dNQGi0IZqQ==}
+ dependencies:
+ undici-types: 5.26.5
dev: true
/@types/node@20.11.19:
@@ -994,6 +1055,9 @@
undici-types: 5.26.5
dev: true
+ /@types/pako@2.0.3:
+ resolution: {integrity: sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==}
+
/@types/resolve@1.20.2:
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
dev: true
@@ -1022,6 +1086,14 @@
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: true
+ /accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+ dev: true
+
/acorn@8.9.0:
resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==}
engines: {node: '>=0.4.0'}
@@ -1072,6 +1144,16 @@
uri-js: 4.4.1
dev: true
+ /angularx-qrcode@16.0.2(@angular/core@16.2.12):
+ resolution: {integrity: sha512-FztOM7vjNu88sGxUU5jG2I+A9TxZBXXYBWINjpwIBbTL+COMgrtzXnScG7TyQeNknv5w3WFJWn59PcngRRYVXA==}
+ peerDependencies:
+ '@angular/core': ^16.0.0
+ dependencies:
+ '@angular/core': 16.2.12(rxjs@7.5.7)(zone.js@0.13.3)
+ qrcode: 1.5.3
+ tslib: 2.6.0
+ dev: true
+
/ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'}
@@ -1182,6 +1264,11 @@
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
dev: true
+ /base64id@2.0.0:
+ resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
+ engines: {node: ^4.5.0 || >= 5.9}
+ dev: true
+
/bcrypt-pbkdf@1.0.2:
resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==}
dependencies:
@@ -1209,6 +1296,26 @@
resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
dev: true
+ /body-parser@1.20.2:
+ resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.11.0
+ raw-body: 2.5.2
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
@@ -1266,6 +1373,11 @@
semver: 7.6.0
dev: true
+ /bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/cacache@16.1.3:
resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -1318,10 +1430,15 @@
/call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
- function-bind: 1.1.1
+ function-bind: 1.1.2
get-intrinsic: 1.2.1
dev: true
+ /camelcase@5.3.1:
+ resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+ engines: {node: '>=6'}
+ dev: true
+
/caniuse-lite@1.0.30001588:
resolution: {integrity: sha512-+hVY9jE44uKLkH0SrUTqxjxqNTOWHsbnQDIKjwkZ3lNTzUUVdBLBGXtj/q5Mp5u98r3droaZAewQuEDzjQdZlQ==}
dev: true
@@ -1420,6 +1537,22 @@
engines: {node: '>= 10'}
dev: true
+ /cliui@6.0.0:
+ resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 6.2.0
+ dev: true
+
+ /cliui@7.0.4:
+ resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+ dev: true
+
/cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -1480,8 +1613,8 @@
engines: {node: '>= 6'}
dev: true
- /commander@5.1.0:
- resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
+ /commander@6.2.1:
+ resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
engines: {node: '>= 6'}
dev: true
@@ -1494,10 +1627,27 @@
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
+ /connect@3.7.0:
+ resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==}
+ engines: {node: '>= 0.10.0'}
+ dependencies:
+ debug: 2.6.9
+ finalhandler: 1.1.2
+ parseurl: 1.3.3
+ utils-merge: 1.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
dev: true
+ /content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: true
@@ -1506,10 +1656,23 @@
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
dev: true
+ /cookie@0.4.2:
+ resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/core-util-is@1.0.2:
resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==}
dev: true
+ /cors@2.8.5:
+ resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+ dev: true
+
/cross-spawn@7.0.3:
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
engines: {node: '>= 8'}
@@ -1519,15 +1682,19 @@
which: 2.0.2
dev: true
- /cypress@12.3.0:
- resolution: {integrity: sha512-ZQNebibi6NBt51TRxRMYKeFvIiQZ01t50HSy7z/JMgRVqBUey3cdjog5MYEbzG6Ktti5ckDt1tfcC47lmFwXkw==}
- engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
+ /custom-event@1.0.1:
+ resolution: {integrity: sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==}
+ dev: true
+
+ /cypress@13.3.1:
+ resolution: {integrity: sha512-g4mJLZxYN+UAF2LMy3Znd4LBnUmS59Vynd81VES59RdW48Yt+QtR2cush3melOoVNz0PPbADpWr8DcUx6mif8Q==}
+ engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
hasBin: true
requiresBuild: true
dependencies:
- '@cypress/request': 2.88.11
+ '@cypress/request': 3.0.1
'@cypress/xvfb': 1.2.4(supports-color@8.1.1)
- '@types/node': 14.18.53
+ '@types/node': 18.19.22
'@types/sinonjs__fake-timers': 8.1.1
'@types/sizzle': 2.3.3
arch: 2.2.0
@@ -1539,7 +1706,7 @@
check-more-types: 2.24.0
cli-cursor: 3.1.0
cli-table3: 0.6.3
- commander: 5.1.0
+ commander: 6.2.1
common-tags: 1.8.2
dayjs: 1.11.9
debug: 4.3.4(supports-color@8.1.1)
@@ -1560,9 +1727,10 @@
minimist: 1.2.8
ospath: 1.2.2
pretty-bytes: 5.6.0
+ process: 0.11.10
proxy-from-env: 1.0.0
request-progress: 3.0.0
- semver: 7.5.3
+ semver: 7.6.0
supports-color: 8.1.1
tmp: 0.2.1
untildify: 4.0.0
@@ -1576,10 +1744,26 @@
assert-plus: 1.0.0
dev: true
+ /date-format@4.0.14:
+ resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==}
+ engines: {node: '>=4.0'}
+ dev: true
+
/dayjs@1.11.9:
resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==}
dev: true
+ /debug@2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.0.0
+ dev: true
+
/debug@3.2.7(supports-color@8.1.1):
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@@ -1605,6 +1789,11 @@
supports-color: 8.1.1
dev: true
+ /decamelize@1.2.0:
+ resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/deepmerge@4.3.1:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
@@ -1630,6 +1819,42 @@
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: true
+ /depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
+ /destroy@1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dev: true
+
+ /dexie@3.2.5(karma@6.4.3):
+ resolution: {integrity: sha512-MA7vYQvXxWN2+G50D0GLS4FqdYUyRYQsN0FikZIVebOmRoNCSCL9+eUbIF80dqrfns3kmY+83+hE2GN9CnAGyA==}
+ engines: {node: '>=6.0'}
+ dependencies:
+ karma-safari-launcher: 1.0.0(karma@6.4.3)
+ transitivePeerDependencies:
+ - karma
+ dev: true
+
+ /di@0.0.1:
+ resolution: {integrity: sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==}
+ dev: true
+
+ /dijkstrajs@1.0.3:
+ resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
+ dev: true
+
+ /dom-serialize@2.2.1:
+ resolution: {integrity: sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==}
+ dependencies:
+ custom-event: 1.0.1
+ ent: 2.2.0
+ extend: 3.0.2
+ void-elements: 2.0.1
+ dev: true
+
/eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
@@ -1641,6 +1866,10 @@
safer-buffer: 2.1.2
dev: true
+ /ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+ dev: true
+
/electron-to-chromium@1.4.679:
resolution: {integrity: sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==}
dev: true
@@ -1653,6 +1882,15 @@
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
dev: true
+ /encode-utf8@1.0.3:
+ resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
+ dev: true
+
+ /encodeurl@1.0.2:
+ resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/encoding@0.1.13:
resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
requiresBuild: true
@@ -1667,6 +1905,31 @@
once: 1.4.0
dev: true
+ /engine.io-parser@5.2.2:
+ resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==}
+ engines: {node: '>=10.0.0'}
+ dev: true
+
+ /engine.io@6.5.4:
+ resolution: {integrity: sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==}
+ engines: {node: '>=10.2.0'}
+ dependencies:
+ '@types/cookie': 0.4.1
+ '@types/cors': 2.8.17
+ '@types/node': 20.11.19
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cookie: 0.4.2
+ cors: 2.8.5
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io-parser: 5.2.2
+ ws: 8.11.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
/enquirer@2.3.6:
resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==}
engines: {node: '>=8.6'}
@@ -1674,6 +1937,10 @@
ansi-colors: 4.1.3
dev: true
+ /ent@2.2.0:
+ resolution: {integrity: sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==}
+ dev: true
+
/env-paths@2.2.1:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
@@ -1688,6 +1955,10 @@
engines: {node: '>=6'}
dev: true
+ /escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+ dev: true
+
/escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@@ -1701,6 +1972,10 @@
resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==}
dev: true
+ /eventemitter3@4.0.7:
+ resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
+ dev: true
+
/execa@4.1.0:
resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==}
engines: {node: '>=10'}
@@ -1783,6 +2058,43 @@
to-regex-range: 5.0.1
dev: true
+ /finalhandler@1.1.2:
+ resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ debug: 2.6.9
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ on-finished: 2.3.0
+ parseurl: 1.3.3
+ statuses: 1.5.0
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+ dev: true
+
+ /flatted@3.3.1:
+ resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
+ dev: true
+
+ /follow-redirects@1.15.5:
+ resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ dev: true
+
/foreground-child@3.1.1:
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
engines: {node: '>=14'}
@@ -1804,6 +2116,15 @@
mime-types: 2.1.35
dev: true
+ /fs-extra@8.1.0:
+ resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
+ engines: {node: '>=6 <7 || >=8'}
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+ dev: true
+
/fs-extra@9.1.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
engines: {node: '>=10'}
@@ -1879,7 +2200,7 @@
/get-intrinsic@1.2.1:
resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
dependencies:
- function-bind: 1.1.1
+ function-bind: 1.1.2
has: 1.0.3
has-proto: 1.0.1
has-symbols: 1.0.3
@@ -2018,6 +2339,17 @@
resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==}
dev: true
+ /http-errors@2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+ dev: true
+
/http-proxy-agent@5.0.0:
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
engines: {node: '>= 6'}
@@ -2029,6 +2361,17 @@
- supports-color
dev: true
+ /http-proxy@1.18.1:
+ resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==}
+ engines: {node: '>=8.0.0'}
+ dependencies:
+ eventemitter3: 4.0.7
+ follow-redirects: 1.15.5
+ requires-port: 1.0.0
+ transitivePeerDependencies:
+ - debug
+ dev: true
+
/http-signature@1.3.6:
resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==}
engines: {node: '>=0.10'}
@@ -2254,6 +2597,11 @@
is-docker: 2.2.1
dev: true
+ /isbinaryfile@4.0.10:
+ resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==}
+ engines: {node: '>= 8.0.0'}
+ dev: true
+
/isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
@@ -2312,6 +2660,12 @@
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
dev: true
+ /jsonfile@4.0.0:
+ resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+ optionalDependencies:
+ graceful-fs: 4.2.11
+ dev: true
+
/jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
dependencies:
@@ -2335,6 +2689,50 @@
verror: 1.10.0
dev: true
+ /karma-safari-launcher@1.0.0(karma@6.4.3):
+ resolution: {integrity: sha512-qmypLWd6F2qrDJfAETvXDfxHvKDk+nyIjpH9xIeI3/hENr0U3nuqkxaftq73PfXZ4aOuOChA6SnLW4m4AxfRjQ==}
+ peerDependencies:
+ karma: '>=0.9'
+ dependencies:
+ karma: 6.4.3
+ dev: true
+
+ /karma@6.4.3:
+ resolution: {integrity: sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==}
+ engines: {node: '>= 10'}
+ hasBin: true
+ dependencies:
+ '@colors/colors': 1.5.0
+ body-parser: 1.20.2
+ braces: 3.0.2
+ chokidar: 3.5.3
+ connect: 3.7.0
+ di: 0.0.1
+ dom-serialize: 2.2.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ http-proxy: 1.18.1
+ isbinaryfile: 4.0.10
+ lodash: 4.17.21
+ log4js: 6.9.1
+ mime: 2.6.0
+ minimatch: 3.1.2
+ mkdirp: 0.5.6
+ qjobs: 1.2.0
+ range-parser: 1.2.1
+ rimraf: 3.0.2
+ socket.io: 4.7.4
+ source-map: 0.6.1
+ tmp: 0.2.1
+ ua-parser-js: 0.7.37
+ yargs: 16.2.0
+ transitivePeerDependencies:
+ - bufferutil
+ - debug
+ - supports-color
+ - utf-8-validate
+ dev: true
+
/lazy-ass@1.6.0:
resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==}
engines: {node: '> 0.8'}
@@ -2360,6 +2758,13 @@
wrap-ansi: 7.0.0
dev: true
+ /locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+ dependencies:
+ p-locate: 4.1.0
+ dev: true
+
/lodash.once@4.1.1:
resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
dev: true
@@ -2386,6 +2791,19 @@
wrap-ansi: 6.2.0
dev: true
+ /log4js@6.9.1:
+ resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ date-format: 4.0.14
+ debug: 4.3.4(supports-color@8.1.1)
+ flatted: 3.3.1
+ rfdc: 1.3.0
+ streamroller: 3.1.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/lru-cache@10.2.0:
resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==}
engines: {node: 14 || >=16.14}
@@ -2472,6 +2890,11 @@
- supports-color
dev: true
+ /media-typer@0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
@@ -2488,6 +2911,12 @@
mime-db: 1.52.0
dev: true
+ /mime@2.6.0:
+ resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
+ engines: {node: '>=4.0.0'}
+ hasBin: true
+ dev: true
+
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -2599,12 +3028,23 @@
yallist: 4.0.0
dev: true
+ /mkdirp@0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+ dependencies:
+ minimist: 1.2.8
+ dev: true
+
/mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
dev: true
+ /ms@2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+ dev: true
+
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
@@ -2748,10 +3188,29 @@
set-blocking: 2.0.0
dev: true
+ /object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/object-inspect@1.12.3:
resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
dev: true
+ /on-finished@2.3.0:
+ resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ ee-first: 1.1.1
+ dev: true
+
+ /on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ ee-first: 1.1.1
+ dev: true
+
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@@ -2798,6 +3257,20 @@
resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
dev: true
+ /p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+ dependencies:
+ p-try: 2.2.0
+ dev: true
+
+ /p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+ dependencies:
+ p-limit: 2.3.0
+ dev: true
+
/p-map@4.0.0:
resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
engines: {node: '>=10'}
@@ -2805,6 +3278,11 @@
aggregate-error: 3.1.0
dev: true
+ /p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+ dev: true
+
/pacote@15.2.0:
resolution: {integrity: sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -2833,10 +3311,23 @@
- supports-color
dev: true
+ /pako@2.1.0:
+ resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
+
/parse5@6.0.1:
resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
dev: true
+ /parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
+ /path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+ dev: true
+
/path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
@@ -2886,6 +3377,11 @@
engines: {node: '>=6'}
dev: true
+ /pngjs@5.0.0:
+ resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
+ engines: {node: '>=10.13.0'}
+ dev: true
+
/prettier@2.6.1:
resolution: {integrity: sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==}
engines: {node: '>=10.13.0'}
@@ -2902,6 +3398,11 @@
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dev: true
+ /process@0.11.10:
+ resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
+ engines: {node: '>= 0.6.0'}
+ dev: true
+
/promise-inflight@1.0.1:
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
peerDependencies:
@@ -2939,6 +3440,22 @@
engines: {node: '>=6'}
dev: true
+ /qjobs@1.2.0:
+ resolution: {integrity: sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==}
+ engines: {node: '>=0.9'}
+ dev: true
+
+ /qrcode@1.5.3:
+ resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
+ engines: {node: '>=10.13.0'}
+ hasBin: true
+ dependencies:
+ dijkstrajs: 1.0.3
+ encode-utf8: 1.0.3
+ pngjs: 5.0.0
+ yargs: 15.4.1
+ dev: true
+
/qs@6.10.4:
resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==}
engines: {node: '>=0.6'}
@@ -2946,6 +3463,32 @@
side-channel: 1.0.4
dev: true
+ /qs@6.11.0:
+ resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.4
+ dev: true
+
+ /querystringify@2.2.0:
+ resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+ dev: true
+
+ /range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /raw-body@2.5.2:
+ resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+ dev: true
+
/read-package-json-fast@3.0.2:
resolution: {integrity: sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -3000,12 +3543,20 @@
engines: {node: '>=0.10.0'}
dev: true
+ /require-main-filename@2.0.0:
+ resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+ dev: true
+
/requirejs@2.3.6:
resolution: {integrity: sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
+ /requires-port@1.0.0:
+ resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+ dev: true
+
/resolve@1.22.2:
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
hasBin: true
@@ -3096,14 +3647,6 @@
hasBin: true
dev: true
- /semver@7.5.3:
- resolution: {integrity: sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==}
- engines: {node: '>=10'}
- hasBin: true
- dependencies:
- lru-cache: 6.0.0
- dev: true
-
/semver@7.5.4:
resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
engines: {node: '>=10'}
@@ -3124,6 +3667,10 @@
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
dev: true
+ /setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ dev: true
+
/shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -3195,6 +3742,44 @@
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
dev: true
+ /socket.io-adapter@2.5.4:
+ resolution: {integrity: sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==}
+ dependencies:
+ debug: 4.3.4(supports-color@8.1.1)
+ ws: 8.11.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
+ /socket.io-parser@4.2.4:
+ resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+ engines: {node: '>=10.0.0'}
+ dependencies:
+ '@socket.io/component-emitter': 3.1.0
+ debug: 4.3.4(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /socket.io@4.7.4:
+ resolution: {integrity: sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==}
+ engines: {node: '>=10.2.0'}
+ dependencies:
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cors: 2.8.5
+ debug: 4.3.4(supports-color@8.1.1)
+ engine.io: 6.5.4
+ socket.io-adapter: 2.5.4
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+ dev: true
+
/socks-proxy-agent@7.0.0:
resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
engines: {node: '>= 10'}
@@ -3283,6 +3868,27 @@
minipass: 3.3.6
dev: true
+ /statuses@1.5.0:
+ resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /statuses@2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
+ /streamroller@3.1.5:
+ resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ date-format: 4.0.14
+ debug: 4.3.4(supports-color@8.1.1)
+ fs-extra: 8.1.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -3414,12 +4020,19 @@
is-number: 7.0.0
dev: true
- /tough-cookie@2.5.0:
- resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==}
- engines: {node: '>=0.8'}
+ /toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+ dev: true
+
+ /tough-cookie@4.1.3:
+ resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==}
+ engines: {node: '>=6'}
dependencies:
psl: 1.9.0
punycode: 2.3.0
+ universalify: 0.2.0
+ url-parse: 1.5.10
dev: true
/tslib@2.6.0:
@@ -3451,12 +4064,24 @@
engines: {node: '>=10'}
dev: true
+ /type-is@1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+ dev: true
+
/typescript@5.1.6:
resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
+ /ua-parser-js@0.7.37:
+ resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==}
+ dev: true
+
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: true
@@ -3489,11 +4114,26 @@
imurmurhash: 0.1.4
dev: true
+ /universalify@0.1.2:
+ resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+ engines: {node: '>= 4.0.0'}
+ dev: true
+
+ /universalify@0.2.0:
+ resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
+ engines: {node: '>= 4.0.0'}
+ dev: true
+
/universalify@2.0.0:
resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
engines: {node: '>= 10.0.0'}
dev: true
+ /unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/untildify@4.0.0:
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
engines: {node: '>=8'}
@@ -3516,10 +4156,22 @@
punycode: 2.3.0
dev: true
+ /url-parse@1.5.10:
+ resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+ dependencies:
+ querystringify: 2.2.0
+ requires-port: 1.0.0
+ dev: true
+
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
+ /utils-merge@1.0.1:
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+ engines: {node: '>= 0.4.0'}
+ dev: true
+
/uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -3539,6 +4191,11 @@
builtins: 5.0.1
dev: true
+ /vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/verror@1.10.0:
resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==}
engines: {'0': node >=0.6.0}
@@ -3548,12 +4205,21 @@
extsprintf: 1.3.0
dev: true
+ /void-elements@2.0.1:
+ resolution: {integrity: sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/wcwidth@1.0.1:
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.4
dev: true
+ /which-module@2.0.1:
+ resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+ dev: true
+
/which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -3607,6 +4273,23 @@
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
+ /ws@8.11.0:
+ resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: ^5.0.2
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+ dev: true
+
+ /y18n@4.0.3:
+ resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+ dev: true
+
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -3620,11 +4303,54 @@
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: true
+ /yargs-parser@18.1.3:
+ resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
+ engines: {node: '>=6'}
+ dependencies:
+ camelcase: 5.3.1
+ decamelize: 1.2.0
+ dev: true
+
+ /yargs-parser@20.2.9:
+ resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+ engines: {node: '>=10'}
+ dev: true
+
/yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
dev: true
+ /yargs@15.4.1:
+ resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
+ engines: {node: '>=8'}
+ dependencies:
+ cliui: 6.0.0
+ decamelize: 1.2.0
+ find-up: 4.1.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ require-main-filename: 2.0.0
+ set-blocking: 2.0.0
+ string-width: 4.2.3
+ which-module: 2.0.1
+ y18n: 4.0.3
+ yargs-parser: 18.1.3
+ dev: true
+
+ /yargs@16.2.0:
+ resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+ engines: {node: '>=10'}
+ dependencies:
+ cliui: 7.0.4
+ escalade: 3.1.1
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 20.2.9
+ dev: true
+
/yargs@17.7.2:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
diff --git a/scouting/BUILD b/scouting/BUILD
index 82f058c..ba6ab6b 100644
--- a/scouting/BUILD
+++ b/scouting/BUILD
@@ -38,6 +38,22 @@
],
)
+# The QR code test is separate from scouting_test because it's slow. Most of
+# the time folks will want to iterate on `scouting_test`.
+cypress_test(
+ name = "scouting_qrcode_test",
+ size = "large",
+ data = [
+ "scouting_qrcode_test.cy.js",
+ "//scouting/testing:scouting_test_servers",
+ "//scouting/testing/camera_simulator",
+ ],
+ runner = "scouting_test_runner.js",
+ tags = [
+ "no-remote-cache",
+ ],
+)
+
apache_wrapper(
name = "https",
binary = ":scouting",
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 5d7a56c..016ca95 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -86,6 +86,7 @@
NotesDropped int32
Penalties int32
AvgCycle int64
+ RobotDied bool
Park, OnStage, Harmony, TrapNote, Spotlight bool
// The username of the person who collected these statistics.
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 369ea58..882dfbc 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -149,7 +149,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 0, Amp: 5, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "emma",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
},
Stats2024{
PreScouting: false, TeamNumber: "942",
@@ -157,7 +157,7 @@
SpeakerAuto: 2, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 0, Amp: 5, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "harry",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "harry",
},
Stats2024{
PreScouting: false, TeamNumber: "432",
@@ -165,7 +165,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 2, Amp: 1, SpeakerAmplified: 3, AmpAmplified: 0,
NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "henry",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "henry",
},
Stats2024{
PreScouting: false, TeamNumber: "52A",
@@ -173,7 +173,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 1, SpeakerAmplified: 2, AmpAmplified: 3,
NotesDropped: 2, Penalties: 0, TrapNote: true, Spotlight: false, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "jordan",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "jordan",
},
Stats2024{
PreScouting: false, TeamNumber: "745",
@@ -181,7 +181,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 5, Amp: 0, SpeakerAmplified: 2, AmpAmplified: 1,
NotesDropped: 1, Penalties: 1, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "taylor",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "taylor",
},
Stats2024{
PreScouting: false, TeamNumber: "934",
@@ -189,7 +189,7 @@
SpeakerAuto: 1, AmpAuto: 3, NotesDroppedAuto: 0, MobilityAuto: true,
Speaker: 0, Amp: 3, SpeakerAmplified: 2, AmpAmplified: 2,
NotesDropped: 0, Penalties: 3, TrapNote: true, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: false, Harmony: true, CollectedBy: "katie",
+ Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "katie",
},
}
@@ -236,7 +236,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 0, Amp: 5, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "emma",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
}
// Attempt to insert the non-pre-scouted data and make sure it fails.
@@ -265,7 +265,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 0, Amp: 5, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "emma",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "emma",
},
Stats2024{
PreScouting: false, TeamNumber: "978",
@@ -273,7 +273,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 1, Amp: 2, SpeakerAmplified: 0, AmpAmplified: 2,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "emma",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
},
Stats2024{
PreScouting: false, TeamNumber: "328A",
@@ -281,7 +281,7 @@
SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
Speaker: 0, Amp: 1, SpeakerAmplified: 1, AmpAmplified: 5,
NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: true, AvgCycle: 0,
- Park: false, OnStage: false, Harmony: true, CollectedBy: "emma",
+ Park: false, OnStage: false, Harmony: true, RobotDied: true, CollectedBy: "emma",
},
}
@@ -700,7 +700,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 1, Amp: 3, SpeakerAmplified: 1, AmpAmplified: 3,
NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "bailey",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "bailey",
},
Stats2024{
PreScouting: false, TeamNumber: "645",
@@ -708,7 +708,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 1, Amp: 2, SpeakerAmplified: 0, AmpAmplified: 1,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "kate",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "kate",
},
Stats2024{
PreScouting: false, TeamNumber: "323",
@@ -716,7 +716,7 @@
SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
Speaker: 0, Amp: 0, SpeakerAmplified: 2, AmpAmplified: 1,
NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "tyler",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "tyler",
},
Stats2024{
PreScouting: false, TeamNumber: "542",
@@ -724,7 +724,7 @@
SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 1, Amp: 2, SpeakerAmplified: 2, AmpAmplified: 1,
NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: false, Harmony: true, CollectedBy: "max",
+ Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "max",
},
}
@@ -735,7 +735,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 1, Amp: 3, SpeakerAmplified: 1, AmpAmplified: 3,
NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "bailey",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "bailey",
},
}
@@ -1154,7 +1154,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 0, Amp: 5, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "emma",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "emma",
},
Stats2024{
PreScouting: false, TeamNumber: "942",
@@ -1162,7 +1162,7 @@
SpeakerAuto: 2, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 0, Amp: 5, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 0, Penalties: 2, TrapNote: true, Spotlight: false, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "harry",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "harry",
},
Stats2024{
PreScouting: false, TeamNumber: "432",
@@ -1170,7 +1170,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 2, MobilityAuto: true,
Speaker: 2, Amp: 1, SpeakerAmplified: 3, AmpAmplified: 0,
NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "henry",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "henry",
},
Stats2024{
PreScouting: false, TeamNumber: "52A",
@@ -1178,7 +1178,7 @@
SpeakerAuto: 1, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 1, SpeakerAmplified: 2, AmpAmplified: 3,
NotesDropped: 2, Penalties: 0, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "jordan",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "jordan",
},
}
diff --git a/scouting/scouting_qrcode_test.cy.js b/scouting/scouting_qrcode_test.cy.js
new file mode 100644
index 0000000..668cba8
--- /dev/null
+++ b/scouting/scouting_qrcode_test.cy.js
@@ -0,0 +1,157 @@
+/// <reference types="cypress" />
+
+// On the 3rd row of matches (index 2) click on the third team
+// (index 2) which resolves to team 333 in quals match 3.
+const QUALS_MATCH_3_TEAM_333 = 2 * 6 + 2;
+
+function disableAlerts() {
+ cy.get('#block_alerts').check({force: true}).should('be.checked');
+}
+
+function switchToTab(tabName) {
+ cy.contains('.nav-link', tabName).click();
+}
+
+function headerShouldBe(text) {
+ cy.get('.header').should('have.text', text);
+}
+
+function clickButton(buttonName) {
+ cy.contains('button', buttonName).click();
+}
+
+// Wrapper around cy.exec() because it truncates the output of the subprocess
+// if it fails. This is a work around to manually print the full error on the
+// console if a failure happends.
+function exec(command) {
+ cy.exec(command, {failOnNonZeroExit: false}).then((result) => {
+ if (result.code) {
+ throw new Error(`Execution of "${command}" failed
+ Exit code: ${result.code}
+ Stdout:\n${result.stdout}
+ Stderr:\n${result.stderr}`);
+ }
+ });
+}
+
+// Prepares data entry so that we _could_ hit Submit.
+//
+// Options:
+// matchButtonKey: The index into the big matchlist table that we want to
+// click on to start the data entry.
+// teamNumber: The team number that matches the button that we click on as
+// specified by `matchButtonKey`.
+//
+// TODO(phil): Deduplicate with scouting_test.cy.js.
+function prepareDataScouting(options) {
+ const {matchButtonKey = SEMI_FINAL_2_MATCH_3_TEAM_5254, teamNumber = 5254} =
+ options;
+
+ // Click on a random team in the Match list. The exact details here are not
+ // important, but we need to know what they are. This could as well be any
+ // other team from any other match.
+ cy.get('button.match-item').eq(matchButtonKey).click();
+
+ // Select Starting Position.
+ headerShouldBe(teamNumber + ' Init ');
+ cy.get('[type="radio"]').first().check();
+ clickButton('Start Match');
+
+ // Pick and Place Note in Auto.
+ clickButton('NOTE');
+ clickButton('AMP');
+
+ // Pick and Place Cube in Teleop.
+ clickButton('Start Teleop');
+ clickButton('NOTE');
+ clickButton('AMP AMPLIFIED');
+
+ // Generate some extra actions so that we are guaranteed to have at least 2
+ // QR codes.
+ for (let i = 0; i < 5; i++) {
+ clickButton('NOTE');
+ clickButton('AMP');
+ }
+
+ // Robot dead and revive.
+ clickButton('DEAD');
+ clickButton('Revive');
+
+ // Endgame.
+ clickButton('Endgame');
+ cy.contains(/Harmony/).click();
+
+ clickButton('End Match');
+ headerShouldBe(teamNumber + ' Review and Submit ');
+ cy.get('#review_data li')
+ .eq(0)
+ .should('have.text', ' Started match at position 1 ');
+ cy.get('#review_data li').eq(1).should('have.text', 'Picked up Note');
+ cy.get('#review_data li')
+ .last()
+ .should(
+ 'have.text',
+ ' Ended Match; stageType: kHARMONY, trapNote: false, spotlight: false '
+ );
+}
+
+before(() => {
+ cy.visit('/');
+ disableAlerts();
+ cy.title().should('eq', 'FRC971 Scouting Application');
+});
+
+beforeEach(() => {
+ cy.visit('/');
+ disableAlerts();
+});
+
+describe('Scouting app tests', () => {
+ // This test collects some scouting data and then generates the corresponding
+ // QR codes. The test takes screenshots of those QR codes. The QR codes get
+ // turned into a little video file for the browser to use as a fake camera
+ // input. The test then switches to the Scan tab to scan the QR codes from
+ // the "camera". We then make sure that the data gets submitted.
+ it('should: be able to generate and scan QR codes.', () => {
+ prepareDataScouting({
+ matchButtonKey: QUALS_MATCH_3_TEAM_333,
+ teamNumber: 333,
+ });
+ clickButton('Create QR Code');
+ headerShouldBe('333 QR Code ');
+
+ cy.get('#qr_code_piece_size').select('150');
+
+ // Go into a mobile-phone view so that we can guarantee that the QR code is
+ // visible.
+ cy.viewport(400, 660);
+
+ cy.get('.qrcode-buttons > li > a')
+ .should('have.length.at.least', 4)
+ .each(($button, index, $buttons) => {
+ if (index == 0 || index + 1 == $buttons.length) {
+ // Skip the "Previous" and "Next" buttons.
+ return;
+ }
+ // Click on the button to switch to that particular QR code.
+ // We use force:true here because without bootstrap (inside the
+ // sandbox) the buttons overlap one another a bit.
+ cy.wrap($button).click({force: true});
+ cy.get('div.qrcode').screenshot(`qrcode_${index}_screenshot`);
+ });
+
+ exec('./testing/camera_simulator/camera_simulator_/camera_simulator');
+
+ switchToTab('Scan');
+
+ // Since we cannot reliably predict how long it will take to scan all the
+ // QR codes, we use a really long timeout here.
+ cy.get('.progress_message', {timeout: 80000}).should('contain', 'Success!');
+
+ // Now that the data is submitted, the button should be disabled.
+ switchToTab('Match List');
+ cy.get('button.match-item')
+ .eq(QUALS_MATCH_3_TEAM_333)
+ .should('be.disabled');
+ });
+});
diff --git a/scouting/scouting_test.cy.js b/scouting/scouting_test.cy.js
index 31ca935..d15cdfc 100644
--- a/scouting/scouting_test.cy.js
+++ b/scouting/scouting_test.cy.js
@@ -117,15 +117,27 @@
clickButton('Submit');
headerShouldBe(teamNumber + ' Success ');
}
+function visit(path) {
+ cy.visit(path, {
+ onBeforeLoad(win) {
+ // The service worker seems to interfere with Cypress somehow. There
+ // doesn't seem to be a proper fix for this issue. Work around it with
+ // this hack that disables the service worker.
+ // https://github.com/cypress-io/cypress/issues/16192#issuecomment-870421667
+ // https://github.com/cypress-io/cypress/issues/702#issuecomment-587127275
+ delete win.navigator.__proto__.serviceWorker;
+ },
+ });
+}
before(() => {
- cy.visit('/');
+ visit('/');
disableAlerts();
cy.title().should('eq', 'FRC971 Scouting Application');
});
beforeEach(() => {
- cy.visit('/');
+ visit('/');
disableAlerts();
});
diff --git a/scouting/testing/camera_simulator/BUILD b/scouting/testing/camera_simulator/BUILD
new file mode 100644
index 0000000..b6e4bf3
--- /dev/null
+++ b/scouting/testing/camera_simulator/BUILD
@@ -0,0 +1,16 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "camera_simulator_lib",
+ srcs = ["camera_simulator.go"],
+ importpath = "github.com/frc971/971-Robot-Code/scouting/testing/camera_simulator",
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+ visibility = ["//visibility:private"],
+)
+
+go_binary(
+ name = "camera_simulator",
+ embed = [":camera_simulator_lib"],
+ target_compatible_with = ["@platforms//cpu:x86_64"],
+ visibility = ["//visibility:public"],
+)
diff --git a/scouting/testing/camera_simulator/camera_simulator.go b/scouting/testing/camera_simulator/camera_simulator.go
new file mode 100644
index 0000000..a54e79f
--- /dev/null
+++ b/scouting/testing/camera_simulator/camera_simulator.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "image/jpeg"
+ _ "image/png"
+ "log"
+ "os"
+ "path/filepath"
+ "sort"
+)
+
+// Chrome plays back MJPEG files at a (hard-coded) 30 fps.
+const CHROME_FAKE_VIDEO_FPS = 30
+
+// For how many seconds to display a single image.
+const IMAGE_DURATION = 3
+
+// For how many frames (at CHROME_FAKE_VIDEO_FPS) to display a single image.
+const IMAGE_DURATION_FRAMES = int(CHROME_FAKE_VIDEO_FPS * IMAGE_DURATION)
+
+func checkErr(err error, message string) {
+ if err != nil {
+ log.Println(message)
+ log.Fatal(err)
+ }
+}
+
+func main() {
+ output_dir := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR")
+
+ // The output file is at a fixed path as expected by
+ // `tools/build_rules/js/cypress.config.js`.
+ outName := output_dir + "/fake_camera.mjpeg"
+
+ // The Cypress test is expected to dump all the screenshots in this
+ // directory.
+ screenshotDir := output_dir + "/screenshots/scouting_qrcode_test.cy.js"
+ log.Printf("Looking for screenshots in %s", screenshotDir)
+
+ // Create a movie from images.
+ matches, err := filepath.Glob(screenshotDir + "/qrcode_*_screenshot.png")
+ checkErr(err, "Failed to glob for the screenshots")
+ sort.Strings(matches)
+
+ log.Println("Found images:", matches)
+ if len(matches) < 2 {
+ // For the purposes of the test, we expect at least 2 QR codes.
+ // If something goes wrong, then this is an opportunity to bail
+ // early.
+ log.Fatalf("Only found %d images", len(matches))
+ }
+
+ mjpeg, err := os.Create(outName)
+ checkErr(err, "Failed to open output file")
+ defer mjpeg.Close()
+
+ // MJPEG is litterally a bunch of JPEGs concatenated together. Read in
+ // each frame and append it to the output file.
+ for _, name := range matches {
+ reader, err := os.Open(name)
+ checkErr(err, "Could not open "+name)
+ defer reader.Close()
+
+ img, _, err := image.Decode(reader)
+ checkErr(err, "Could not decode image")
+
+ buffer := &bytes.Buffer{}
+ checkErr(jpeg.Encode(buffer, img, nil), "Failed to encode as jpeg")
+
+ // In order to show a single picture for 1 second, we need to
+ // inject CHROME_FAKE_VIDEO_FPS copies of the same image.
+ for i := 0; i < IMAGE_DURATION_FRAMES; i++ {
+ _, err = mjpeg.Write(buffer.Bytes())
+ checkErr(err, "Failed to write to mjpeg")
+ }
+ }
+
+ fmt.Printf("%s was written successfully.\n", outName)
+}
diff --git a/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
index c36174d..f450d38 100644
--- a/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_2024_data_scouting_response.fbs
@@ -26,6 +26,7 @@
on_stage: bool (id:16);
harmony: bool (id:17);
spotlight: bool (id:22);
+ robot_died: bool (id:23);
pre_scouting:bool (id:20);
collected_by:string (id:21);
diff --git a/scouting/webserver/requests/messages/submit_2024_actions.fbs b/scouting/webserver/requests/messages/submit_2024_actions.fbs
index e85563f..9462fbe 100644
--- a/scouting/webserver/requests/messages/submit_2024_actions.fbs
+++ b/scouting/webserver/requests/messages/submit_2024_actions.fbs
@@ -9,6 +9,7 @@
kAMP_AMPLIFIED,
kSPEAKER,
kSPEAKER_AMPLIFIED,
+ kDROPPED,
}
table MobilityAction {
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 76d632d..7d6e98e 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -452,7 +452,7 @@
PreScouting: submit2024Actions.PreScouting(), TeamNumber: string(submit2024Actions.TeamNumber()), MatchNumber: submit2024Actions.MatchNumber(), SetNumber: submit2024Actions.SetNumber(), CompLevel: string(submit2024Actions.CompLevel()),
StartingQuadrant: 0, SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 0, SpeakerAmplified: 0, AmpAmplified: 0, NotesDropped: 0, Penalties: 0,
- TrapNote: false, Spotlight: false, AvgCycle: 0, Park: false, OnStage: false, Harmony: false, CollectedBy: "",
+ TrapNote: false, Spotlight: false, AvgCycle: 0, Park: false, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "",
}
// Loop over all actions.
for i := 0; i < submit2024Actions.ActionsListLength(); i++ {
@@ -481,19 +481,15 @@
penaltyAction.Init(actionTable.Bytes, actionTable.Pos)
stat.Penalties += penaltyAction.Penalties()
+ } else if action_type == submit_2024_actions.ActionTypeRobotDeathAction {
+ var robotDeathAction submit_2024_actions.RobotDeathAction
+ robotDeathAction.Init(actionTable.Bytes, actionTable.Pos)
+ stat.RobotDied = true
+
} else if action_type == submit_2024_actions.ActionTypePickupNoteAction {
var pick_up_action submit_2024_actions.PickupNoteAction
pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
- if picked_up == true {
- auto := pick_up_action.Auto()
- if auto == false {
- stat.NotesDropped += 1
- } else {
- stat.NotesDroppedAuto += 1
- }
- } else {
- picked_up = true
- }
+ picked_up = true
} else if action_type == submit_2024_actions.ActionTypePlaceNoteAction {
var place_action submit_2024_actions.PlaceNoteAction
place_action.Init(actionTable.Bytes, actionTable.Pos)
@@ -514,6 +510,10 @@
stat.SpeakerAuto += 1
} else if score_type == submit_2024_actions.ScoreTypekSPEAKER_AMPLIFIED && !auto {
stat.SpeakerAmplified += 1
+ } else if score_type == submit_2024_actions.ScoreTypekDROPPED && auto {
+ stat.NotesDroppedAuto += 1
+ } else if score_type == submit_2024_actions.ScoreTypekDROPPED && !auto {
+ stat.NotesDropped += 1
} else {
return db.Stats2024{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
}
@@ -595,6 +595,7 @@
Park: stat.Park,
OnStage: stat.OnStage,
Harmony: stat.Harmony,
+ RobotDied: stat.RobotDied,
CollectedBy: stat.CollectedBy,
})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index ebe73f3..26fad0f 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -137,8 +137,8 @@
MatchNumber: 1, SetNumber: 1, CompLevel: "qm", StartingQuadrant: 3,
SpeakerAuto: 2, AmpAuto: 4, NotesDroppedAuto: 1, MobilityAuto: true,
Speaker: 0, Amp: 1, SpeakerAmplified: 2, AmpAmplified: 1,
- NotesDropped: 0, Penalties: 01, TrapNote: true, Spotlight: false, AvgCycle: 233,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "alex",
+ NotesDropped: 0, Penalties: 1, TrapNote: true, Spotlight: false, AvgCycle: 233,
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "alex",
},
{
PreScouting: false, TeamNumber: "973",
@@ -146,7 +146,7 @@
SpeakerAuto: 0, AmpAuto: 2, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 4, SpeakerAmplified: 3, AmpAmplified: 1,
NotesDropped: 0, Penalties: 1, TrapNote: true, Spotlight: false, AvgCycle: 120,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "bob",
+ Park: true, OnStage: false, Harmony: false, RobotDied: true, CollectedBy: "bob",
},
},
}
@@ -215,7 +215,7 @@
SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: true,
Speaker: 4, Amp: 2, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 2, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "alex",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "alex",
},
{
PreScouting: false, TeamNumber: "982",
@@ -223,7 +223,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 2, SpeakerAmplified: 3, AmpAmplified: 2,
NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: true, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "george",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "george",
},
},
}
@@ -248,7 +248,7 @@
SpeakerAuto: 1, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: true,
Speaker: 4, Amp: 2, SpeakerAmplified: 1, AmpAmplified: 0,
NotesDropped: 2, Penalties: 2, TrapNote: true, Spotlight: true, AvgCycle: 0,
- Park: true, OnStage: false, Harmony: false, CollectedBy: "alex",
+ Park: true, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "alex",
},
{
PreScouting: false, TeamNumber: "982",
@@ -256,7 +256,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 2, SpeakerAmplified: 3, AmpAmplified: 2,
NotesDropped: 1, Penalties: 0, TrapNote: false, Spotlight: true, AvgCycle: 0,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "george",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "george",
},
},
}
@@ -441,6 +441,43 @@
Auto: false,
},
},
+ Timestamp: 3200,
+ },
+ {
+ ActionTaken: &submit_2024_actions.ActionTypeT{
+ Type: submit_2024_actions.ActionTypePlaceNoteAction,
+ Value: &submit_2024_actions.PlaceNoteActionT{
+ ScoreType: submit_2024_actions.ScoreTypekDROPPED,
+ Auto: false,
+ },
+ },
+ Timestamp: 3300,
+ },
+ {
+ ActionTaken: &submit_2024_actions.ActionTypeT{
+ Type: submit_2024_actions.ActionTypeRobotDeathAction,
+ Value: &submit_2024_actions.RobotDeathActionT{
+ RobotDead: true,
+ },
+ },
+ Timestamp: 3400,
+ },
+ {
+ ActionTaken: &submit_2024_actions.ActionTypeT{
+ Type: submit_2024_actions.ActionTypeRobotDeathAction,
+ Value: &submit_2024_actions.RobotDeathActionT{
+ RobotDead: false,
+ },
+ },
+ Timestamp: 3450,
+ },
+ {
+ ActionTaken: &submit_2024_actions.ActionTypeT{
+ Type: submit_2024_actions.ActionTypePickupNoteAction,
+ Value: &submit_2024_actions.PickupNoteActionT{
+ Auto: false,
+ },
+ },
Timestamp: 3500,
},
{
@@ -478,10 +515,10 @@
expected := db.Stats2024{
PreScouting: false, TeamNumber: "4244",
MatchNumber: 3, SetNumber: 1, CompLevel: "quals", StartingQuadrant: 2,
- SpeakerAuto: 0, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
+ SpeakerAuto: 0, AmpAuto: 1, NotesDroppedAuto: 0, MobilityAuto: true,
Speaker: 0, Amp: 0, SpeakerAmplified: 1, AmpAmplified: 1,
- NotesDropped: 0, Penalties: 5, TrapNote: false, Spotlight: false, AvgCycle: 950,
- Park: false, OnStage: false, Harmony: true, CollectedBy: "",
+ NotesDropped: 1, Penalties: 5, TrapNote: false, Spotlight: false, AvgCycle: 633,
+ Park: false, OnStage: false, Harmony: true, RobotDied: true, CollectedBy: "",
}
if expected != response {
@@ -1202,7 +1239,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 1, Amp: 0, SpeakerAmplified: 0, AmpAmplified: 0,
NotesDropped: 0, Penalties: 0, TrapNote: false, Spotlight: false, AvgCycle: 0,
- Park: false, OnStage: false, Harmony: false, CollectedBy: "debug_cli",
+ Park: false, OnStage: false, Harmony: false, RobotDied: false, CollectedBy: "debug_cli",
},
}
@@ -1448,7 +1485,7 @@
SpeakerAuto: 0, AmpAuto: 1, NotesDroppedAuto: 1, MobilityAuto: true,
Speaker: 0, Amp: 1, SpeakerAmplified: 1, AmpAmplified: 1,
NotesDropped: 0, Penalties: 1, TrapNote: true, Spotlight: false, AvgCycle: 233,
- Park: false, OnStage: false, Harmony: true, CollectedBy: "alek",
+ Park: false, OnStage: false, Harmony: true, RobotDied: false, CollectedBy: "alek",
},
{
PreScouting: false, TeamNumber: "244",
@@ -1456,7 +1493,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 0, SpeakerAmplified: 3, AmpAmplified: 1,
NotesDropped: 0, Penalties: 1, TrapNote: false, Spotlight: false, AvgCycle: 120,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "kacey",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "kacey",
},
},
actions: []db.Action{
@@ -1520,7 +1557,7 @@
SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Speaker: 0, Amp: 0, SpeakerAmplified: 3, AmpAmplified: 1,
NotesDropped: 0, Penalties: 1, TrapNote: false, Spotlight: false, AvgCycle: 120,
- Park: false, OnStage: true, Harmony: false, CollectedBy: "kacey",
+ Park: false, OnStage: true, Harmony: false, RobotDied: false, CollectedBy: "kacey",
},
}
diff --git a/scouting/www/BUILD b/scouting/www/BUILD
index a403bf3..a6ca0a1 100644
--- a/scouting/www/BUILD
+++ b/scouting/www/BUILD
@@ -1,25 +1,49 @@
load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
+load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_run_binary")
+load("@npm//:@angular/service-worker/package_json.bzl", angular_service_worker = "bin")
load("@npm//:defs.bzl", "npm_link_all_packages")
load("//tools/build_rules:js.bzl", "ng_application")
-load(":defs.bzl", "assemble_static_files")
+load(":defs.bzl", "assemble_service_worker_files", "assemble_static_files")
npm_link_all_packages(name = "node_modules")
+assemble_service_worker_files(
+ name = "service_worker_files",
+ outs = [
+ "ngsw-worker.js",
+ ],
+)
+
+OPENCV_VERSION = "4.9.0"
+
+copy_file(
+ name = "opencv.js",
+ src = "@opencv_wasm//file",
+ out = "assets/opencv_{}/opencv.js".format(OPENCV_VERSION),
+)
+
ng_application(
name = "app",
+ assets = [
+ "manifest.json",
+ ":opencv.js",
+ ],
extra_srcs = [
"app/common.css",
],
html_assets = [
"favicon.ico",
+ "assets/971_144.png",
],
deps = [
"//:node_modules/@angular/animations",
+ "//:node_modules/@angular/service-worker",
"//scouting/www/driver_ranking",
"//scouting/www/entry",
"//scouting/www/match_list",
"//scouting/www/notes",
"//scouting/www/pit_scouting",
+ "//scouting/www/scan",
"//scouting/www/shift_schedule",
"//scouting/www/view",
],
@@ -30,6 +54,8 @@
app_files = ":app",
pictures = [
"//third_party/y2024/field:pictures",
+ ":ngsw-worker.js",
+ ":ngsw.json",
],
replace_prefixes = {
"prod": "",
@@ -48,3 +74,35 @@
out = "app/common.css",
visibility = ["//scouting/www:__subpackages__"],
)
+
+angular_service_worker.ngsw_config_binary(
+ name = "ngsw_config_binary",
+)
+
+js_binary(
+ name = "ngsw_config_wrapper",
+ data = [
+ ":ngsw_config_binary",
+ ],
+ entry_point = "ngsw_config_wrapper.js",
+)
+
+js_run_binary(
+ name = "ngsw_config",
+ srcs = [
+ "manifest.json",
+ "ngsw-config.json",
+ ":app",
+ ":ngsw_config_binary",
+ ],
+ outs = [
+ "ngsw.json",
+ ],
+ args = [
+ "$(rootpath :ngsw_config_binary)",
+ "$(rootpath :ngsw.json)",
+ "$(rootpath :prod)",
+ "$(rootpath ngsw-config.json)",
+ ],
+ tool = ":ngsw_config_wrapper",
+)
diff --git a/scouting/www/app/app.module.ts b/scouting/www/app/app.module.ts
index bf393e4..decd1f3 100644
--- a/scouting/www/app/app.module.ts
+++ b/scouting/www/app/app.module.ts
@@ -1,6 +1,7 @@
-import {NgModule} from '@angular/core';
+import {NgModule, isDevMode} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {ServiceWorkerModule} from '@angular/service-worker';
import {App} from './app';
import {EntryModule} from '../entry';
@@ -10,12 +11,19 @@
import {ViewModule} from '../view';
import {DriverRankingModule} from '../driver_ranking';
import {PitScoutingModule} from '../pit_scouting';
+import {ScanModule} from '../scan';
@NgModule({
declarations: [App],
imports: [
BrowserModule,
BrowserAnimationsModule,
+ ServiceWorkerModule.register('./ngsw-worker.js', {
+ enabled: !isDevMode(),
+ // Register the ServiceWorker as soon as the application is stable
+ // or after 30 seconds (whichever comes first).
+ registrationStrategy: 'registerWhenStable:30000',
+ }),
EntryModule,
NotesModule,
MatchListModule,
@@ -23,6 +31,7 @@
DriverRankingModule,
ViewModule,
PitScoutingModule,
+ ScanModule,
],
exports: [App],
bootstrap: [App],
diff --git a/scouting/www/app/app.ng.html b/scouting/www/app/app.ng.html
index 5f7ea9f..ef37c07 100644
--- a/scouting/www/app/app.ng.html
+++ b/scouting/www/app/app.ng.html
@@ -73,6 +73,15 @@
Pit
</a>
</li>
+ <li class="nav-item">
+ <a
+ class="nav-link"
+ [class.active]="tabIs('Scan')"
+ (click)="switchTabToGuarded('Scan')"
+ >
+ Scan
+ </a>
+ </li>
</ul>
<ng-container [ngSwitch]="tab">
@@ -93,4 +102,5 @@
<shift-schedule *ngSwitchCase="'ShiftSchedule'"></shift-schedule>
<app-view *ngSwitchCase="'View'"></app-view>
<app-pit-scouting *ngSwitchCase="'Pit'"></app-pit-scouting>
+ <app-scan *ngSwitchCase="'Scan'"></app-scan>
</ng-container>
diff --git a/scouting/www/app/app.ts b/scouting/www/app/app.ts
index 597e5c5..ab15ae5 100644
--- a/scouting/www/app/app.ts
+++ b/scouting/www/app/app.ts
@@ -1,4 +1,4 @@
-import {Component, ElementRef, ViewChild} from '@angular/core';
+import {Component, ElementRef, ViewChild, isDevMode} from '@angular/core';
type Tab =
| 'MatchList'
@@ -7,10 +7,11 @@
| 'DriverRanking'
| 'ShiftSchedule'
| 'View'
- | 'Pit';
+ | 'Pit'
+ | 'Scan';
// Ignore the guard for tabs that don't require the user to enter any data.
-const unguardedTabs: Tab[] = ['MatchList', 'View'];
+const unguardedTabs: Tab[] = ['MatchList', 'Scan', 'View'];
type TeamInMatch = {
teamNumber: string;
@@ -39,6 +40,8 @@
@ViewChild('block_alerts') block_alerts: ElementRef;
constructor() {
+ console.log(`Using development mode: ${isDevMode()}`);
+
window.addEventListener('beforeunload', (e) => {
if (!unguardedTabs.includes(this.tab)) {
if (!this.block_alerts.nativeElement.checked) {
diff --git a/scouting/www/assets/971_144.png b/scouting/www/assets/971_144.png
new file mode 100644
index 0000000..881edfa
--- /dev/null
+++ b/scouting/www/assets/971_144.png
Binary files differ
diff --git a/scouting/www/defs.bzl b/scouting/www/defs.bzl
index 828f30a..e7fb44a 100644
--- a/scouting/www/defs.bzl
+++ b/scouting/www/defs.bzl
@@ -27,6 +27,7 @@
),
"pictures": attr.label_list(
mandatory = True,
+ allow_files = True,
),
"replace_prefixes": attr.string_dict(
mandatory = True,
@@ -34,3 +35,45 @@
},
toolchains = ["@aspect_bazel_lib//lib:copy_to_directory_toolchain_type"],
)
+
+def _assemble_service_worker_files_impl(ctx):
+ args = ctx.actions.args()
+ args.add_all(ctx.attr._package.files, before_each = "--input_dir", expand_directories = False)
+ args.add_all(ctx.outputs.outs, before_each = "--output")
+ args.add_all(ctx.attr.outs_as_strings, before_each = "--relative_output")
+ ctx.actions.run(
+ inputs = ctx.attr._package.files,
+ outputs = ctx.outputs.outs,
+ executable = ctx.executable._tool,
+ arguments = [args],
+ mnemonic = "AssembleAngularServiceWorker",
+ )
+
+_assemble_service_worker_files = rule(
+ implementation = _assemble_service_worker_files_impl,
+ attrs = {
+ "outs": attr.output_list(
+ allow_empty = False,
+ mandatory = True,
+ ),
+ "outs_as_strings": attr.string_list(
+ allow_empty = False,
+ mandatory = True,
+ ),
+ "_package": attr.label(
+ default = "//:node_modules/@angular/service-worker",
+ ),
+ "_tool": attr.label(
+ default = "//tools/build_rules/js:assemble_service_worker_files",
+ cfg = "exec",
+ executable = True,
+ ),
+ },
+)
+
+def assemble_service_worker_files(outs, **kwargs):
+ _assemble_service_worker_files(
+ outs = outs,
+ outs_as_strings = outs,
+ **kwargs
+ )
diff --git a/scouting/www/entry/BUILD b/scouting/www/entry/BUILD
index 98b457b..48732f9 100644
--- a/scouting/www/entry/BUILD
+++ b/scouting/www/entry/BUILD
@@ -10,6 +10,10 @@
],
deps = [
":node_modules/@angular/forms",
+ "//:node_modules/@angular/platform-browser",
+ "//:node_modules/@types/pako",
+ "//:node_modules/angularx-qrcode",
+ "//:node_modules/pako",
"//scouting/webserver/requests/messages:error_response_ts_fbs",
"//scouting/webserver/requests/messages:request_all_matches_response_ts_fbs",
"//scouting/webserver/requests/messages:submit_2024_actions_ts_fbs",
diff --git a/scouting/www/entry/entry.component.css b/scouting/www/entry/entry.component.css
index 9c50ee0..e646a25 100644
--- a/scouting/www/entry/entry.component.css
+++ b/scouting/www/entry/entry.component.css
@@ -20,5 +20,51 @@
}
#EndGame > div > label {
- padding: 0;
+ padding: 0px;
+}
+
+.button_row {
+ display: flex;
+ width: 90vw;
+ justify-content: space-between;
+}
+
+.qr-container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ padding: 0px;
+}
+
+.qrcode-buttons > li.page-item {
+ padding: 0px;
+}
+
+/* Using deprecated ::ng-deep here, but couldn't find a better way to do it.
+ * The qrcode container generates a canvas without any style markers or
+ * classes. Angular's view encapsulation prevents this style from applying.
+ * Maybe https://developer.mozilla.org/en-US/docs/Web/CSS/::slotted ? */
+:host ::ng-deep .qr-container canvas {
+ /* Make the QR code take up as much space as possible. Can't take up more
+ * than the screen size, however, because you can't scan a QR code while
+ * scrolling. It needs to be fully visibile. */
+ width: 100% !important;
+ height: 100% !important;
+ aspect-ratio: 1 / 1;
+}
+
+/* Make the UI a little more compact. The QR code itself already has a good
+ * amount of margin. */
+
+.qrcode-nav {
+ padding: 0px;
+}
+
+.qrcode-buttons {
+ padding: 0px;
+ margin: 0px;
+}
+
+.qrcode {
+ padding: 0px;
}
diff --git a/scouting/www/entry/entry.component.ts b/scouting/www/entry/entry.component.ts
index b5bc51e..8ede3a2 100644
--- a/scouting/www/entry/entry.component.ts
+++ b/scouting/www/entry/entry.component.ts
@@ -26,6 +26,7 @@
} from '../../webserver/requests/messages/submit_2024_actions_generated';
import {Match} from '../../webserver/requests/messages/request_all_matches_response_generated';
import {MatchListRequestor} from '../rpc';
+import * as pako from 'pako';
type Section =
| 'Team Selection'
@@ -35,6 +36,7 @@
| 'Endgame'
| 'Dead'
| 'Review and Submit'
+ | 'QR Code'
| 'Success';
// TODO(phil): Deduplicate with match_list.component.ts.
@@ -50,6 +52,13 @@
f: 'Finals',
};
+// The maximum number of bytes per QR code. The user can adjust this value to
+// make the QR code contain less information, but easier to scan.
+const QR_CODE_PIECE_SIZES = [150, 300, 450, 600, 750, 900];
+
+// The default index into QR_CODE_PIECE_SIZES.
+const DEFAULT_QR_CODE_PIECE_SIZE_INDEX = QR_CODE_PIECE_SIZES.indexOf(750);
+
type ActionT =
| {
type: 'startMatchAction';
@@ -75,7 +84,7 @@
| {
type: 'robotDeathAction';
timestamp?: number;
- robotOn: boolean;
+ robotDead: boolean;
}
| {
type: 'penaltyAction';
@@ -112,6 +121,7 @@
// of radio buttons.
readonly COMP_LEVELS = COMP_LEVELS;
readonly COMP_LEVEL_LABELS = COMP_LEVEL_LABELS;
+ readonly QR_CODE_PIECE_SIZES = QR_CODE_PIECE_SIZES;
readonly ScoreType = ScoreType;
readonly StageType = StageType;
@@ -129,6 +139,8 @@
errorMessage: string = '';
autoPhase: boolean = true;
mobilityCompleted: boolean = false;
+ selectedValue = 0;
+ nextTeamNumber = '';
preScouting: boolean = false;
matchStartTimestamp: number = 0;
@@ -136,16 +148,29 @@
teamSelectionIsValid = false;
+ // When the user chooses to generate QR codes, we convert the flatbuffer into
+ // a long string. Since we frequently have more data than we can display in a
+ // single QR code, we break the data into multiple QR codes. The data for
+ // each QR code ("pieces") is stored in the `qrCodeValuePieces` list below.
+ // The `qrCodeValueIndex` keeps track of which QR code we're currently
+ // displaying.
+ qrCodeValuePieceSize = QR_CODE_PIECE_SIZES[DEFAULT_QR_CODE_PIECE_SIZE_INDEX];
+ qrCodeValuePieces: string[] = [];
+ qrCodeValueIndex: number = 0;
+
constructor(private readonly matchListRequestor: MatchListRequestor) {}
ngOnInit() {
// When the user navigated from the match list, we can skip the team
// selection. I.e. we trust that the user clicked the correct button.
this.section = this.skipTeamSelection ? 'Init' : 'Team Selection';
+ this.fetchMatchList();
+ }
- if (this.section == 'Team Selection') {
- this.fetchMatchList();
- }
+ goToNextTeam() {
+ this.ngOnInit();
+ this.teamNumber = this.nextTeamNumber;
+ this.nextTeamNumber = '';
}
async fetchMatchList() {
@@ -174,19 +199,18 @@
if (this.teamNumber == null) {
return false;
}
- const teamNumber = this.teamNumber;
for (const match of this.matchList) {
if (
this.matchNumber == match.matchNumber() &&
this.setNumber == match.setNumber() &&
this.compLevel == match.compLevel() &&
- (teamNumber === match.r1() ||
- teamNumber === match.r2() ||
- teamNumber === match.r3() ||
- teamNumber === match.b1() ||
- teamNumber === match.b2() ||
- teamNumber === match.b3())
+ (this.teamNumber === match.r1() ||
+ this.teamNumber === match.r2() ||
+ this.teamNumber === match.r3() ||
+ this.teamNumber === match.b1() ||
+ this.teamNumber === match.b2() ||
+ this.teamNumber === match.b3())
) {
return true;
}
@@ -219,7 +243,8 @@
}
if (action.type == 'endMatchAction') {
- // endMatchAction occurs at the same time as penaltyAction so add to its timestamp to make it unique.
+ // endMatchAction occurs at the same time as penaltyAction so add to its
+ // timestamp to make it unique.
action.timestamp += 1;
}
@@ -282,6 +307,11 @@
this.errorMessage = '';
this.progressMessage = '';
+ // For the QR code screen, we need to make the value to encode available.
+ if (target == 'QR Code') {
+ this.updateQrCodeValuePieceSize();
+ }
+
this.section = target;
}
@@ -291,7 +321,7 @@
this.header.nativeElement.scrollIntoView();
}
- async submit2024Actions() {
+ createActionsBuffer() {
const builder = new Builder();
const actionOffsets: number[] = [];
@@ -363,7 +393,7 @@
case 'robotDeathAction':
const robotDeathActionOffset =
- RobotDeathAction.createRobotDeathAction(builder, action.robotOn);
+ RobotDeathAction.createRobotDeathAction(builder, action.robotDead);
actionOffset = Action.createAction(
builder,
BigInt(action.timestamp || 0),
@@ -419,16 +449,117 @@
Submit2024Actions.addPreScouting(builder, this.preScouting);
builder.finish(Submit2024Actions.endSubmit2024Actions(builder));
- const buffer = builder.asUint8Array();
+ return builder.asUint8Array();
+ }
+
+ // Same as createActionsBuffer, but encoded as Base64. It's also split into
+ // a number of pieces so that each piece is roughly limited to
+ // `qrCodeValuePieceSize` bytes.
+ createBase64ActionsBuffers(): string[] {
+ const originalBuffer = this.createActionsBuffer();
+ const deflatedData = pako.deflate(originalBuffer, {level: 9});
+
+ const pieceSize = this.qrCodeValuePieceSize;
+ const fullValue = btoa(String.fromCharCode(...deflatedData));
+ const numPieces = Math.ceil(fullValue.length / pieceSize);
+
+ let splitData: string[] = [];
+ for (let i = 0; i < numPieces; i++) {
+ const splitPiece = fullValue.slice(i * pieceSize, (i + 1) * pieceSize);
+ splitData.push(`${i}_${numPieces}_${pieceSize}_${splitPiece}`);
+ }
+ return splitData;
+ }
+
+ setQrCodeValueIndex(index: number) {
+ this.qrCodeValueIndex = Math.max(
+ 0,
+ Math.min(index, this.qrCodeValuePieces.length - 1)
+ );
+ }
+
+ updateQrCodeValuePieceSize() {
+ this.qrCodeValuePieces = this.createBase64ActionsBuffers();
+ this.qrCodeValueIndex = 0;
+ }
+
+ async submit2024Actions() {
const res = await fetch('/requests/submit/submit_2024_actions', {
method: 'POST',
- body: buffer,
+ body: this.createActionsBuffer(),
});
if (res.ok) {
// We successfully submitted the data. Report success.
this.section = 'Success';
this.actionList = [];
+
+ // Keep track of the position of the last robot, use to figure out what the next robot in the same position is.
+
+ let lastTeamPos = '0';
+ for (const match of this.matchList) {
+ if (
+ this.matchNumber === match.matchNumber() &&
+ this.setNumber === match.setNumber() &&
+ this.compLevel === match.compLevel()
+ ) {
+ this.teamNumber = this.teamNumber;
+ if (this.teamNumber == match.r1()) {
+ lastTeamPos = 'r1';
+ } else if (this.teamNumber == match.r2()) {
+ lastTeamPos = 'r2';
+ } else if (this.teamNumber == match.r3()) {
+ lastTeamPos = 'r3';
+ } else if (this.teamNumber == match.b1()) {
+ lastTeamPos = 'b1';
+ } else if (this.teamNumber == match.b2()) {
+ lastTeamPos = 'b2';
+ } else if (this.teamNumber == match.b3()) {
+ lastTeamPos = 'b3';
+ } else {
+ console.log('Position of scouted team not found.');
+ }
+ break;
+ }
+ }
+ if (lastTeamPos != '0') {
+ this.matchNumber += 1;
+ for (const match of this.matchList) {
+ if (
+ this.matchNumber == match.matchNumber() &&
+ this.setNumber == match.setNumber() &&
+ this.compLevel == match.compLevel()
+ ) {
+ if (lastTeamPos == 'r1') {
+ this.nextTeamNumber = match.r1();
+ } else if (lastTeamPos == 'r2') {
+ this.nextTeamNumber = match.r2();
+ } else if (lastTeamPos == 'r3') {
+ this.nextTeamNumber = match.r3();
+ } else if (lastTeamPos == 'b1') {
+ this.nextTeamNumber = match.b1();
+ } else if (lastTeamPos == 'b2') {
+ this.nextTeamNumber = match.b2();
+ } else if (lastTeamPos == 'b3') {
+ this.nextTeamNumber = match.b3();
+ } else {
+ console.log('Position of last team not found.');
+ }
+ break;
+ }
+ }
+ } else {
+ console.log('Last team position not found.');
+ }
+ this.matchList = [];
+ this.progressMessage = '';
+ this.errorMessage = '';
+ this.autoPhase = true;
+ this.actionList = [];
+ this.mobilityCompleted = false;
+ this.preScouting = false;
+ this.matchStartTimestamp = 0;
+ this.selectedValue = 0;
} else {
const resBuffer = await res.arrayBuffer();
const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
diff --git a/scouting/www/entry/entry.module.ts b/scouting/www/entry/entry.module.ts
index c74d4d0..bcba4ee 100644
--- a/scouting/www/entry/entry.module.ts
+++ b/scouting/www/entry/entry.module.ts
@@ -2,10 +2,11 @@
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {EntryComponent} from './entry.component';
+import {QRCodeModule} from 'angularx-qrcode';
@NgModule({
declarations: [EntryComponent],
exports: [EntryComponent],
- imports: [CommonModule, FormsModule],
+ imports: [CommonModule, FormsModule, QRCodeModule],
})
export class EntryModule {}
diff --git a/scouting/www/entry/entry.ng.html b/scouting/www/entry/entry.ng.html
index ea44ad3..553fa1e 100644
--- a/scouting/www/entry/entry.ng.html
+++ b/scouting/www/entry/entry.ng.html
@@ -123,7 +123,7 @@
<button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
<button
class="btn btn-danger"
- (click)="changeSectionTo('Dead'); addAction({type: 'robotDeathAction', robotOn: false});"
+ (click)="changeSectionTo('Dead'); addAction({type: 'robotDeathAction', robotDead: true});"
>
DEAD
</button>
@@ -183,15 +183,21 @@
selection and keep all buttons visible without scrolling on most devices.
-->
<div
- [ngClass]="{'d-grid': true, 'gap-3': autoPhase === true, 'gap-5': autoPhase === false}"
+ [ngClass]="{'d-grid': true, 'gap-4': autoPhase === true, 'gap-3': autoPhase === false}"
>
<button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
<button
class="btn btn-danger"
- (click)="changeSectionTo('Dead'); addAction({type: 'robotDeathAction', robotOn: false});"
+ (click)="changeSectionTo('Dead'); addAction({type: 'robotDeathAction', robotDead: true});"
>
DEAD
</button>
+ <button
+ class="btn btn-info"
+ (click)="changeSectionTo('Pickup'); addAction({type: 'placeNoteAction', scoreType: ScoreType.kDROPPED});"
+ >
+ Dropped
+ </button>
<div *ngIf="!autoPhase" class="d-grid gap-1" style="padding: 0">
<div
style="
@@ -298,40 +304,54 @@
<button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
<button
class="btn btn-danger"
- (click)="changeSectionTo('Dead'); addAction({type: 'robotDeathAction', robotOn: false});"
+ (click)="changeSectionTo('Dead'); addAction({type: 'robotDeathAction', robotDead: true});"
>
DEAD
</button>
- <label>
- <input
- #park
- type="radio"
- id="option1"
- name="endgameaction"
- value="park"
- />
- Park
- </label>
- <label>
- <input
- #onStage
- type="radio"
- id="option2"
- name="endgameaction"
- value="onStage"
- />
- On Stage
- </label>
- <label>
- <input
- #harmony
- type="radio"
- id="option3"
- name="endgameaction"
- value="harmony"
- />
- Harmony
- </label>
+ <div class="button_row">
+ <label>
+ <input
+ #park
+ type="radio"
+ id="option1"
+ name="endgameaction"
+ value="park"
+ />
+ Park
+ </label>
+ <label>
+ <input
+ #onStage
+ type="radio"
+ id="option2"
+ name="endgameaction"
+ value="onStage"
+ />
+ On Stage
+ </label>
+ </div>
+ <div class="button_row">
+ <label>
+ <input
+ #harmony
+ type="radio"
+ id="option3"
+ name="endgameaction"
+ value="harmony"
+ />
+ Harmony
+ </label>
+ <label>
+ <input
+ #na
+ type="radio"
+ id="option2"
+ name="endgameaction"
+ value="na"
+ />
+ N/A
+ </label>
+ </div>
<label>
<input
#trapNote
@@ -352,6 +372,7 @@
/>
Spotlight
</label>
+
<div style="display: flex">
<h5>Penalties :</h5>
<button
@@ -381,13 +402,38 @@
</div>
<div *ngSwitchCase="'Dead'" id="Dead" class="container-fluid">
<h2>Robot is dead</h2>
- <div class="d-grid gap-2">
+ <div class="d-grid gap-3">
+ <button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
+ <div style="display: flex">
+ <h5>Penalties :</h5>
+ <button
+ class="btn-light"
+ style="width: 40px; margin-right: 15px"
+ (click)="removePenalty()"
+ >
+ -
+ </button>
+ <p>{{this.penalties}}</p>
+ <button
+ class="btn-light"
+ style="width: 40px; margin-left: 15px"
+ (click)="addPenalty()"
+ >
+ +
+ </button>
+ </div>
<button
class="btn btn-success"
- (click)="changeSectionTo('Pickup'); addAction({type: 'robotDeathAction', robotOn: true}); "
+ (click)="changeSectionTo('Pickup'); addAction({type: 'robotDeathAction', robotDead: false}); "
>
Revive
</button>
+ <button
+ class="btn btn-info"
+ (click)="changeSectionTo('Review and Submit'); addPenalties(); addAction({type: 'endMatchAction', stageType: (park.checked ? StageType.kPARK : onStage.checked ? StageType.kON_STAGE : harmony.checked ? StageType.kHARMONY : StageType.kMISSING), trapNote: trapNote.checked, spotlight: spotlight.checked});"
+ >
+ End Match
+ </button>
</div>
</div>
<div *ngSwitchCase="'Review and Submit'" id="Review" class="container-fluid">
@@ -414,7 +460,7 @@
spotlight: {{action.spotlight}}
</span>
<span *ngSwitchCase="'robotDeathAction'">
- Robot on: {{action.robotOn}}
+ Robot dead: {{action.robotDead}}
</span>
<span *ngSwitchCase="'mobilityAction'">
Mobility: {{action.mobility}}
@@ -429,6 +475,9 @@
</div>
<div class="d-grid gap-5">
<button class="btn btn-secondary" (click)="undoLastAction()">UNDO</button>
+ <button class="btn btn-info" (click)="changeSectionTo('QR Code');">
+ Create QR Code
+ </button>
<button class="btn btn-warning" (click)="submit2024Actions();">
Submit
</button>
@@ -436,6 +485,80 @@
</div>
<div *ngSwitchCase="'Success'" id="Success" class="container-fluid">
<h2>Successfully submitted data.</h2>
+ <div class="d-grid gap-5" *ngIf="nextTeamNumber != ''">
+ <button class="btn btn-primary" (click)="goToNextTeam();">
+ SCOUT NEXT TEAM
+ </button>
+ </div>
+ </div>
+ <div *ngSwitchCase="'QR Code'" id="QR Code" class="container-fluid">
+ <span>Density:</span>
+ <select
+ [(ngModel)]="qrCodeValuePieceSize"
+ (ngModelChange)="updateQrCodeValuePieceSize()"
+ type="number"
+ id="qr_code_piece_size"
+ >
+ <option
+ *ngFor="let pieceSize of QR_CODE_PIECE_SIZES"
+ [ngValue]="pieceSize"
+ >
+ {{pieceSize}}
+ </option>
+ </select>
+ <div class="qr-container">
+ <qrcode
+ [qrdata]="qrCodeValuePieces[qrCodeValueIndex]"
+ [width]="1000"
+ [errorCorrectionLevel]="'M'"
+ [margin]="6"
+ class="qrcode"
+ ></qrcode>
+ </div>
+ <nav class="qrcode-nav">
+ <ul
+ class="qrcode-buttons pagination pagination-lg justify-content-center"
+ >
+ <li class="page-item">
+ <a
+ class="page-link"
+ href="#"
+ aria-label="Previous"
+ (click)="setQrCodeValueIndex(qrCodeValueIndex - 1)"
+ >
+ <span aria-hidden="true">«</span>
+ <span class="visually-hidden">Previous</span>
+ </a>
+ </li>
+ <li *ngFor="let _ of qrCodeValuePieces; index as i" class="page-item">
+ <a
+ class="page-link"
+ href="#"
+ (click)="setQrCodeValueIndex(i)"
+ [class.active]="qrCodeValueIndex == i"
+ >
+ {{i + 1}}
+ </a>
+ </li>
+ <li class="page-item">
+ <a
+ class="page-link"
+ href="#"
+ aria-label="Next"
+ (click)="setQrCodeValueIndex(qrCodeValueIndex + 1)"
+ >
+ <span aria-hidden="true">»</span>
+ <span class="visually-hidden">Next</span>
+ </a>
+ </li>
+ </ul>
+ </nav>
+ <button
+ class="btn btn-secondary"
+ (click)="changeSectionTo('Review and Submit')"
+ >
+ BACK
+ </button>
</div>
<span class="progress_message" role="alert">{{ progressMessage }}</span>
diff --git a/scouting/www/entry/package.json b/scouting/www/entry/package.json
index d37ce10..a02c0a6 100644
--- a/scouting/www/entry/package.json
+++ b/scouting/www/entry/package.json
@@ -2,6 +2,8 @@
"name": "@org_frc971/scouting/www/entry",
"private": true,
"dependencies": {
+ "pako": "2.1.0",
+ "@types/pako": "2.0.3",
"@org_frc971/scouting/www/counter_button": "workspace:*",
"@angular/forms": "v16-lts"
}
diff --git a/scouting/www/index.html b/scouting/www/index.html
index 46779cc..821acf2 100644
--- a/scouting/www/index.html
+++ b/scouting/www/index.html
@@ -4,6 +4,8 @@
<meta charset="utf-8" />
<title>FRC971 Scouting Application</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
+ <meta name="theme-color" content="#000000" />
+ <link rel="manifest" href="/manifest.json" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css"
@@ -16,8 +18,17 @@
integrity="d8824f7067cdfea38afec7e9ffaf072125266824206d69ef1f112d72153a505e"
/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
+ <script>
+ // In order to hook into WASM's "finished loading" event, we interact
+ // with the global Module variable. Maybe some day there'll be a better
+ // way to interact with it.
+ var Module = {};
+ </script>
</head>
<body>
<my-app></my-app>
+ <noscript>
+ Please enable JavaScript to continue using this application.
+ </noscript>
</body>
</html>
diff --git a/scouting/www/manifest.json b/scouting/www/manifest.json
new file mode 100644
index 0000000..9366399
--- /dev/null
+++ b/scouting/www/manifest.json
@@ -0,0 +1,17 @@
+{
+ "name": "FRC971 Scouting App",
+ "short_name": "scouting",
+ "theme_color": "#1976d2",
+ "background_color": "#fafafa",
+ "display": "standalone",
+ "scope": "./",
+ "start_url": "./",
+ "icons": [
+ {
+ "src": "assets/971_144.png",
+ "sizes": "144x144",
+ "type": "image/png",
+ "purpose": "maskable any"
+ }
+ ]
+}
diff --git a/scouting/www/ngsw-config.json b/scouting/www/ngsw-config.json
new file mode 100644
index 0000000..78d921d
--- /dev/null
+++ b/scouting/www/ngsw-config.json
@@ -0,0 +1,32 @@
+{
+ "$schema": "./node_modules/@angular/service-worker/config/schema.json",
+ "index": "/index.html",
+ "assetGroups": [
+ {
+ "name": "app",
+ "installMode": "prefetch",
+ "resources": {
+ "files": [
+ "/favicon.ico",
+ "/index.html",
+ "/manifest.json",
+ "/*.css",
+ "/bundle-*/*.js",
+ "/*.js"
+ ]
+ }
+ },
+ {
+ "name": "assets",
+ "installMode": "lazy",
+ "updateMode": "prefetch",
+ "resources": {
+ "files": [
+ "/assets/**",
+ "/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
+ ]
+ }
+ }
+ ]
+ }
+
diff --git a/scouting/www/ngsw_config_wrapper.js b/scouting/www/ngsw_config_wrapper.js
new file mode 100644
index 0000000..0bc7f19
--- /dev/null
+++ b/scouting/www/ngsw_config_wrapper.js
@@ -0,0 +1,37 @@
+const fs = require('fs');
+const path = require('path');
+const {spawnSync} = require('child_process');
+
+const output_dir = path.join(
+ process.env.BAZEL_BINDIR,
+ process.env.BAZEL_PACKAGE
+);
+console.log(output_dir);
+console.log(process.argv[2]);
+console.log(process.cwd());
+const ngsw_config = process.argv[2];
+console.log(`Trying to run ${ngsw_config} ${process.argv.slice(4).join(' ')}`);
+const result = spawnSync(ngsw_config, process.argv.slice(4), {
+ stdout: 'inherit',
+ stderr: 'inherit',
+});
+
+if (result.status || result.error || result.signal) {
+ console.log("Failed to run 'ngsw_config'");
+ console.log(`status: ${result.status}`);
+ console.log(`error: ${result.error}`);
+ console.log(`signal: ${result.signal}`);
+ console.log(`stdout: ${result.stdout}`);
+ console.log(`stderr: ${result.stderr}`);
+ process.exit(1);
+}
+
+const currentDirectory = process.cwd();
+
+// Read the contents of the current directory
+console.log(`Contents of the current directory: ${currentDirectory}`);
+fs.readdirSync(currentDirectory).forEach((file) => {
+ console.log(file);
+});
+
+fs.copyFileSync(path.join(process.argv[4], 'ngsw.json'), process.argv[3]);
diff --git a/scouting/www/notes/notes.component.ts b/scouting/www/notes/notes.component.ts
index 3848c22..431ec71 100644
--- a/scouting/www/notes/notes.component.ts
+++ b/scouting/www/notes/notes.component.ts
@@ -56,8 +56,8 @@
const KEYWORD_CHECKBOX_LABELS = {
goodDriving: 'Good Driving',
badDriving: 'Bad Driving',
- solidPlacing: 'Solid Placing',
- sketchyPlacing: 'Sketchy Placing',
+ solidPlacing: 'Solid Shooting',
+ sketchyPlacing: 'Sketchy Shooting',
goodDefense: 'Good Defense',
badDefense: 'Bad Defense',
easilyDefended: 'Easily Defended',
diff --git a/scouting/www/rpc/BUILD b/scouting/www/rpc/BUILD
index d1367ea..592735c 100644
--- a/scouting/www/rpc/BUILD
+++ b/scouting/www/rpc/BUILD
@@ -10,6 +10,7 @@
],
generate_public_api = False,
deps = [
+ "//:node_modules/dexie",
"//scouting/webserver/requests/messages:error_response_ts_fbs",
"//scouting/webserver/requests/messages:request_2024_data_scouting_response_ts_fbs",
"//scouting/webserver/requests/messages:request_2024_data_scouting_ts_fbs",
diff --git a/scouting/www/rpc/db.ts b/scouting/www/rpc/db.ts
new file mode 100644
index 0000000..789ac0e
--- /dev/null
+++ b/scouting/www/rpc/db.ts
@@ -0,0 +1,18 @@
+import Dexie, {Table} from 'dexie';
+
+export interface MatchListData {
+ id?: number;
+ data: Uint8Array;
+}
+
+export class AppDB extends Dexie {
+ matchListData!: Table<MatchListData, number>;
+
+ constructor() {
+ super('ngdexieliveQuery');
+ this.version(1).stores({
+ matchListData: 'id,data',
+ });
+ }
+}
+export const db = new AppDB();
diff --git a/scouting/www/rpc/match_list_requestor.ts b/scouting/www/rpc/match_list_requestor.ts
index fa2dcbd..0a812ce 100644
--- a/scouting/www/rpc/match_list_requestor.ts
+++ b/scouting/www/rpc/match_list_requestor.ts
@@ -6,78 +6,82 @@
Match,
RequestAllMatchesResponse,
} from '../../webserver/requests/messages/request_all_matches_response_generated';
-
+import {db, MatchListData} from './db';
const MATCH_TYPE_ORDERING = ['qm', 'ef', 'qf', 'sf', 'f'];
-
@Injectable({providedIn: 'root'})
export class MatchListRequestor {
async fetchMatchList(): Promise<Match[]> {
const builder = new Builder();
RequestAllMatches.startRequestAllMatches(builder);
builder.finish(RequestAllMatches.endRequestAllMatches(builder));
-
const buffer = builder.asUint8Array();
const res = await fetch('/requests/request/all_matches', {
method: 'POST',
body: buffer,
});
-
if (res.ok) {
const resBuffer = await res.arrayBuffer();
- const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
- const parsedResponse =
- RequestAllMatchesResponse.getRootAsRequestAllMatchesResponse(fbBuffer);
-
- // Convert the flatbuffer list into an array. That's more useful.
- const matchList = [];
- for (let i = 0; i < parsedResponse.matchListLength(); i++) {
- matchList.push(parsedResponse.matchList(i));
- }
-
- // Sort the list so it is in chronological order.
- matchList.sort((a, b) => {
- // First sort by match type. E.g. finals are last.
- const aMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(a.compLevel());
- const bMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(b.compLevel());
- if (aMatchTypeIndex < bMatchTypeIndex) {
- return -1;
- }
- if (aMatchTypeIndex > bMatchTypeIndex) {
- return 1;
- }
- // Then sort by match number. E.g. in semi finals, all match 1 rounds
- // are done first. Then come match 2 rounds. And then, if necessary,
- // the match 3 rounds.
- const aMatchNumber = a.matchNumber();
- const bMatchNumber = b.matchNumber();
- if (aMatchNumber < bMatchNumber) {
- return -1;
- }
- if (aMatchNumber > bMatchNumber) {
- return 1;
- }
- // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
- // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
- // Semi Final 2 Match 2.
- const aSetNumber = a.setNumber();
- const bSetNumber = b.setNumber();
- if (aSetNumber < bSetNumber) {
- return -1;
- }
- if (aSetNumber > bSetNumber) {
- return 1;
- }
- return 0;
- });
-
- return matchList;
+ const u8Buffer = new Uint8Array(resBuffer);
+ // Cache the response.
+ await db.matchListData.put({id: 1, data: u8Buffer});
+ return this.parseMatchList(u8Buffer);
} else {
+ const cachedResult = await db.matchListData.where({id: 1}).toArray();
+ if (cachedResult && cachedResult.length == 1) {
+ const u8Buffer = cachedResult[0].data;
+ return this.parseMatchList(u8Buffer);
+ }
const resBuffer = await res.arrayBuffer();
const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
-
const errorMessage = parsedResponse.errorMessage();
throw `Received ${res.status} ${res.statusText}: "${errorMessage}"`;
}
}
+ parseMatchList(u8Buffer: Uint8Array): Match[] {
+ const fbBuffer = new ByteBuffer(u8Buffer);
+ const parsedResponse =
+ RequestAllMatchesResponse.getRootAsRequestAllMatchesResponse(fbBuffer);
+ // Convert the flatbuffer list into an array. That's more useful.
+ const matchList = [];
+ for (let i = 0; i < parsedResponse.matchListLength(); i++) {
+ matchList.push(parsedResponse.matchList(i));
+ }
+ // Sort the list so it is in chronological order.
+ matchList.sort((a, b) => {
+ // First sort by match type. E.g. finals are last.
+ const aMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(a.compLevel());
+ const bMatchTypeIndex = MATCH_TYPE_ORDERING.indexOf(b.compLevel());
+ if (aMatchTypeIndex < bMatchTypeIndex) {
+ return -1;
+ }
+ if (aMatchTypeIndex > bMatchTypeIndex) {
+ return 1;
+ }
+ // Then sort by match number. E.g. in semi finals, all match 1 rounds
+ // are done first. Then come match 2 rounds. And then, if necessary,
+ // the match 3 rounds.
+ const aMatchNumber = a.matchNumber();
+ const bMatchNumber = b.matchNumber();
+ if (aMatchNumber < bMatchNumber) {
+ return -1;
+ }
+ if (aMatchNumber > bMatchNumber) {
+ return 1;
+ }
+ // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
+ // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
+ // Semi Final 2 Match 2.
+ const aSetNumber = a.setNumber();
+ const bSetNumber = b.setNumber();
+ if (aSetNumber < bSetNumber) {
+ return -1;
+ }
+ if (aSetNumber > bSetNumber) {
+ return 1;
+ }
+ return 0;
+ });
+ return matchList;
+ }
}
diff --git a/scouting/www/scan/BUILD b/scouting/www/scan/BUILD
new file mode 100644
index 0000000..88b2822
--- /dev/null
+++ b/scouting/www/scan/BUILD
@@ -0,0 +1,18 @@
+load("@npm//:defs.bzl", "npm_link_all_packages")
+load("//tools/build_rules:js.bzl", "ng_pkg")
+
+npm_link_all_packages(name = "node_modules")
+
+ng_pkg(
+ name = "scan",
+ extra_srcs = [
+ "//scouting/www:app_common_css",
+ ],
+ deps = [
+ ":node_modules/@angular/forms",
+ ":node_modules/@types/pako",
+ ":node_modules/pako",
+ "//scouting/webserver/requests/messages:error_response_ts_fbs",
+ "@com_github_google_flatbuffers//ts:flatbuffers_ts",
+ ],
+)
diff --git a/scouting/www/scan/package.json b/scouting/www/scan/package.json
new file mode 100644
index 0000000..a5950c3
--- /dev/null
+++ b/scouting/www/scan/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@org_frc971/scouting/www/scan",
+ "private": true,
+ "dependencies": {
+ "pako": "2.1.0",
+ "@types/pako": "2.0.3",
+ "@angular/forms": "v16-lts"
+ }
+}
diff --git a/scouting/www/scan/scan.component.css b/scouting/www/scan/scan.component.css
new file mode 100644
index 0000000..11cfe09
--- /dev/null
+++ b/scouting/www/scan/scan.component.css
@@ -0,0 +1,20 @@
+video {
+ width: 100%;
+ aspect-ratio: 1 / 1;
+}
+
+canvas {
+ /* We don't want to show the frames that we are scanning for QR codes. It's
+ * nicer to just see the video stream. */
+ display: none;
+}
+
+ul {
+ margin: 0px;
+}
+
+li > a.active {
+ /* Set the scanned QR codes to a green color. */
+ background-color: #198754;
+ border-color: #005700;
+}
diff --git a/scouting/www/scan/scan.component.ts b/scouting/www/scan/scan.component.ts
new file mode 100644
index 0000000..98a7b61
--- /dev/null
+++ b/scouting/www/scan/scan.component.ts
@@ -0,0 +1,236 @@
+import {Component, NgZone, OnInit, ViewChild, ElementRef} from '@angular/core';
+import {ErrorResponse} from '../../webserver/requests/messages/error_response_generated';
+import {Builder, ByteBuffer} from 'flatbuffers';
+import * as pako from 'pako';
+
+declare var cv: any;
+declare var Module: any;
+
+// The number of milliseconds between QR code scans.
+const SCAN_PERIOD = 500;
+
+@Component({
+ selector: 'app-scan',
+ templateUrl: './scan.ng.html',
+ styleUrls: ['../app/common.css', './scan.component.css'],
+})
+export class ScanComponent implements OnInit {
+ @ViewChild('video')
+ public video: ElementRef;
+
+ @ViewChild('canvas')
+ public canvas: ElementRef;
+
+ errorMessage: string = '';
+ progressMessage: string = 'Waiting for QR code(s)';
+ scanComplete: boolean = false;
+ videoStartedSuccessfully = false;
+
+ qrCodeValuePieces: string[] = [];
+ qrCodeValuePieceSize = 0;
+
+ scanStream: MediaStream | null = null;
+ scanTimer: ReturnType<typeof setTimeout> | null = null;
+
+ constructor(private ngZone: NgZone) {}
+
+ ngOnInit() {
+ // If the user switched away from this tab, then the onRuntimeInitialized
+ // attribute will already be set. No need to load OpenCV again. If it's not
+ // loaded, however, we need to load it.
+ if (!Module['onRuntimeInitialized']) {
+ Module['onRuntimeInitialized'] = () => {
+ // Since the WASM code doesn't know about the Angular zone, we force
+ // it into the correct zone so that the UI gets updated properly.
+ this.ngZone.run(() => {
+ this.startScanning();
+ });
+ };
+ // Now that we set up the hook, we can load OpenCV.
+ this.loadOpenCv();
+ } else {
+ this.startScanning();
+ }
+ }
+
+ ngOnDestroy() {
+ clearInterval(this.scanTimer);
+
+ // Explicitly stop the streams so that the camera isn't being locked
+ // unnecessarily. I.e. other processes can use it too.
+ if (this.scanStream) {
+ this.scanStream.getTracks().forEach((track) => {
+ track.stop();
+ });
+ }
+ }
+
+ public ngAfterViewInit() {
+ // Start the video playback.
+ // It would be nice to let the user select which camera gets used. For now,
+ // we give the "environment" hint so that it faces away from the user.
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+ navigator.mediaDevices
+ .getUserMedia({video: {facingMode: 'environment'}})
+ .then(
+ (stream) => {
+ this.scanStream = stream;
+ this.video.nativeElement.srcObject = stream;
+ this.video.nativeElement.play();
+ this.videoStartedSuccessfully = true;
+ },
+ (reason) => {
+ this.progressMessage = '';
+ this.errorMessage = `Failed to start video: ${reason}`;
+ }
+ );
+ }
+ }
+
+ async scan() {
+ if (!this.videoStartedSuccessfully) {
+ return;
+ }
+
+ // Take a capture of the video stream. That capture can then be used by
+ // OpenCV to perform the QR code detection. Due to my inexperience, I could
+ // only make this code work if I size the (invisible) canvas to match the
+ // video element. Otherwise, I'd get cropped images.
+ // Can we stream the video directly into the canvas?
+ const width = this.video.nativeElement.clientWidth;
+ const height = this.video.nativeElement.clientHeight;
+ this.canvas.nativeElement.width = width;
+ this.canvas.nativeElement.height = height;
+ this.canvas.nativeElement
+ .getContext('2d')
+ .drawImage(this.video.nativeElement, 0, 0, width, height);
+
+ // Perform the QR code detection. We use the Aruco-variant of the detector
+ // here because it appears to detect QR codes much more reliably than the
+ // standard detector.
+ let mat = cv.imread('canvas');
+ let qrDecoder = new cv.QRCodeDetectorAruco();
+ const result = qrDecoder.detectAndDecode(mat);
+ mat.delete();
+
+ // Handle the result.
+ if (result) {
+ await this.scanSuccessHandler(result);
+ } else {
+ await this.scanFailureHandler();
+ }
+ }
+
+ async scanSuccessHandler(scanResult: string) {
+ // Reverse the conversion and obtain the original Uint8Array. In other
+ // words, undo the work in `scouting/www/entry/entry.component.ts`.
+ const [indexStr, numPiecesStr, pieceSizeStr, splitPiece] = scanResult.split(
+ '_',
+ 4
+ );
+
+ // If we didn't get enough data, then maybe we scanned some non-scouting
+ // related QR code? Try to give a hint to the user.
+ if (!indexStr || !numPiecesStr || !pieceSizeStr || !splitPiece) {
+ this.progressMessage = '';
+ this.errorMessage = `Couldn't find scouting data in the QR code.`;
+ return;
+ }
+
+ const index = Number(indexStr);
+ const numPieces = Number(numPiecesStr);
+ const pieceSize = Number(pieceSizeStr);
+
+ if (
+ numPieces != this.qrCodeValuePieces.length ||
+ pieceSize != this.qrCodeValuePieceSize
+ ) {
+ // The number of pieces or the piece size changed. We need to reset our accounting.
+ this.qrCodeValuePieces = new Array<string>(numPieces);
+ this.qrCodeValuePieceSize = pieceSize;
+ }
+
+ this.qrCodeValuePieces[index] = splitPiece;
+ this.progressMessage = `Scanned QR code ${index + 1} out of ${
+ this.qrCodeValuePieces.length
+ }`;
+
+ // Count up the number of missing pieces so we can give a progress update.
+ let numMissingPieces = 0;
+ for (const piece of this.qrCodeValuePieces) {
+ if (!piece) {
+ numMissingPieces++;
+ }
+ }
+ if (numMissingPieces > 0) {
+ this.progressMessage = `Waiting for ${numMissingPieces} out of ${this.qrCodeValuePieces.length} QR codes.`;
+ this.errorMessage = '';
+ return;
+ }
+
+ // Stop scanning now that we have all the pieces.
+ this.progressMessage = 'Scanned all QR codes. Submitting.';
+ this.scanComplete = true;
+ clearInterval(this.scanTimer);
+
+ const encodedData = this.qrCodeValuePieces.join('');
+ const deflatedData = Uint8Array.from(atob(encodedData), (c) =>
+ c.charCodeAt(0)
+ );
+ const actionBuffer = pako.inflate(deflatedData);
+
+ const res = await fetch('/requests/submit/submit_2024_actions', {
+ method: 'POST',
+ body: actionBuffer,
+ });
+
+ if (res.ok) {
+ // We successfully submitted the data. Report success.
+ this.progressMessage = 'Success!';
+ this.errorMessage = '';
+ } else {
+ const resBuffer = await res.arrayBuffer();
+ const fbBuffer = new ByteBuffer(new Uint8Array(resBuffer));
+ const parsedResponse = ErrorResponse.getRootAsErrorResponse(fbBuffer);
+
+ const errorMessage = parsedResponse.errorMessage();
+ this.progressMessage = '';
+ this.errorMessage = `Submission failed with ${res.status} ${res.statusText}: "${errorMessage}"`;
+ }
+ }
+
+ async scanFailureHandler() {
+ this.progressMessage = '';
+ this.errorMessage = 'Failed to scan!';
+ }
+
+ loadOpenCv() {
+ // Make the browser load OpenCV.
+ let body = <HTMLDivElement>document.body;
+ let script = document.createElement('script');
+ script.innerHTML = '';
+ script.src = 'assets/opencv_4.9.0/opencv.js';
+ script.async = false;
+ script.defer = true;
+ script.onerror = (error) => {
+ this.progressMessage = '';
+ if (typeof error === 'string') {
+ this.errorMessage = `OpenCV failed to load: ${error}`;
+ } else {
+ this.errorMessage = 'OpenCV failed to load.';
+ }
+ // Since we use the onRuntimeInitialized property as a flag to see if we
+ // need to perform loading, we need to delete the property. When the user
+ // switches away from this tab and then switches back, we want to attempt
+ // loading again.
+ delete Module['onRuntimeInitialized'];
+ };
+ body.appendChild(script);
+ }
+
+ startScanning() {
+ this.scanTimer = setInterval(() => {
+ this.scan();
+ }, SCAN_PERIOD);
+ }
+}
diff --git a/scouting/www/scan/scan.module.ts b/scouting/www/scan/scan.module.ts
new file mode 100644
index 0000000..00a9c58
--- /dev/null
+++ b/scouting/www/scan/scan.module.ts
@@ -0,0 +1,10 @@
+import {NgModule} from '@angular/core';
+import {CommonModule} from '@angular/common';
+import {ScanComponent} from './scan.component';
+
+@NgModule({
+ declarations: [ScanComponent],
+ exports: [ScanComponent],
+ imports: [CommonModule],
+})
+export class ScanModule {}
diff --git a/scouting/www/scan/scan.ng.html b/scouting/www/scan/scan.ng.html
new file mode 100644
index 0000000..f9b82b3
--- /dev/null
+++ b/scouting/www/scan/scan.ng.html
@@ -0,0 +1,21 @@
+<h1>Scan</h1>
+<span class="progress_message" role="alert">{{ progressMessage }}</span>
+<span class="error_message" role="alert">{{ errorMessage }}</span>
+<nav class="qrcode-progress" *ngIf="!scanComplete">
+ <ul class="pagination pagination-lg justify-content-center">
+ <li *ngFor="let piece of qrCodeValuePieces" class="page-item">
+ <a class="page-link" href="#" [class.active]="piece">
+ <i *ngIf="piece" class="bi bi-check">
+ <span class="visually-hidden">✓</span>
+ </i>
+ <i *ngIf="!piece" class="bi bi-camera">
+ <span class="visually-hidden">☒</span>
+ </i>
+ </a>
+ </li>
+ </ul>
+</nav>
+<div *ngIf="!scanComplete">
+ <video #video id="video"></video>
+</div>
+<canvas #canvas id="canvas"></canvas>
diff --git a/third_party/rules_cypress/0001-fix-incorrect-linux-checksums.patch b/third_party/rules_cypress/0001-fix-incorrect-linux-checksums.patch
new file mode 100644
index 0000000..b69e828
--- /dev/null
+++ b/third_party/rules_cypress/0001-fix-incorrect-linux-checksums.patch
@@ -0,0 +1,42 @@
+From 500a658a00cbd7839d2431e51003429d542b7f4a Mon Sep 17 00:00:00 2001
+From: Jack Vincent <50340752+jackvincentnz@users.noreply.github.com>
+Date: Fri, 19 Jan 2024 21:12:12 -0800
+Subject: [PATCH] fix: incorrect linux checksums
+
+---
+ cypress/private/versions.bzl | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/cypress/private/versions.bzl b/cypress/private/versions.bzl
+index 8fad4be..f4b3a1f 100644
+--- a/cypress/private/versions.bzl
++++ b/cypress/private/versions.bzl
+@@ -6,22 +6,22 @@ TOOL_VERSIONS = {
+ "13.3.1": {
+ "darwin-x64": "417c5f1d77e15c8aef0a55f155c16c3dbbc637f918c1e51f8fec6eb1c73a9ba9",
+ "darwin-arm64": "143d905779c0b0a8a9049b0eb68b4b156db3d838d4546ce5082a8f7bd5dc5232",
+- "linux-x64": "a165aa290d23d03f191852df2e8c3cb25ddccf1d4e226518a0a217f4ec405328",
+- "linux-arm64": "b51810d7fda748f67d5d875d5e265ee22bb394af2bb4a545b78c99f1aae91cb0",
++ "linux-x64": "bb0ddd980bd82792a477b1c39ce8a0a7b275481031c559c065c94f1131151b0c",
++ "linux-arm64": "fbca9958e2a153f3f1ffdef1bb506db65401a8586229b9d9424cd16228d0353d",
+ "win32-x64": "acf1e478634e4da254bd7c3695d9032010c2ed17955e7339e1ea7d79cf8c9f7b",
+ },
+ "12.12.0": {
+ "darwin-x64": "53ddd917112a2c5c3c22d12f5bcffda596474c7cd0932a997e575b2b97ae36c0",
+ "darwin-arm64": "2daadfe4431a98f9dc6442c155239aaed2746b12a59721424e3b5fdaaf27c766",
+- "linux-x64": "18bf251f683e0b0ca70918c2a51b7a457be6e5298be52203bd16d4e0eb361837",
+- "linux-arm64": "1f754c912eb719d4ac4abe31f3cc6d5b65cf08e0266cf6808f376c099928c88e",
++ "linux-x64": "7f41d45da094380cc0d6716c8357b60f9c9304c2475cf502ea60649f040d52ad",
++ "linux-arm64": "55531b5ba8d03a979a5ef92981d27964583897c652eec3788f24ec8677d05dd2",
+ "win32-x64": "ffc47314ce5f74888066bc4a15ee18d375ee9680f9cca1b94eda7293e1dea4e5",
+ },
+ "12.3.0": {
+ "darwin-x64": "beae3678dd859ce89cc45c377eef97b67558ee1f2a0079e9b4c824260ef3801a",
+ "darwin-arm64": "bb08f247110dda9b180d2552a661b8669441f931b0332d818c306a14e8c7071a",
+- "linux-x64": "57dd85936373e6ce2ae5378f9035a3ad118899341e0c6e71783c3e58c039ce92",
+- "linux-arm64": "47c1188506b11644a332ab0949eab0b33179a64e4857e561d3c836c6f6f2cadf",
++ "linux-x64": "3a300d6c903a8f5fced488183dcc7faa06e9df14c946d6dab4b5822ec738e9cd",
++ "linux-arm64": "501671011a63fd450b87e1cae1b3ba3fabccf37e9c1c8c26e1d5f189f9afe688",
+ "win32-x64": "639a0e0ca5498fc5330064c3fa441c741e6b6cd01234bfa9851de9a33f4f56a6",
+ },
+ "10.8.0": {
diff --git a/third_party/rules_cypress/0002-Add-support-for-cypress-13.6.6.patch b/third_party/rules_cypress/0002-Add-support-for-cypress-13.6.6.patch
new file mode 100644
index 0000000..d4d9144
--- /dev/null
+++ b/third_party/rules_cypress/0002-Add-support-for-cypress-13.6.6.patch
@@ -0,0 +1,27 @@
+From 2c8ec34c8fd4f6f485a7c7036e73fa82cd99859d Mon Sep 17 00:00:00 2001
+From: Philipp Schrader <philipp.schrader@gmail.com>
+Date: Sat, 24 Feb 2024 16:08:46 -0800
+Subject: [PATCH] Add support for cypress 13.6.6
+
+---
+ cypress/private/versions.bzl | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/cypress/private/versions.bzl b/cypress/private/versions.bzl
+index f4b3a1f..6a3e665 100644
+--- a/cypress/private/versions.bzl
++++ b/cypress/private/versions.bzl
+@@ -3,6 +3,13 @@
+ # Use /scripts/mirror_release.sh to add a newer version below.
+ # Versions should be descending order so TOOL_VERSIONS.keys()[0] is the latest version.
+ TOOL_VERSIONS = {
++ "13.6.6": {
++ "darwin-x64": "4b845921f35f520b8217a4ebb9106180c56de37a9db7f57f14352060c9eddca6",
++ "darwin-arm64": "4c8818534bdab028a82aefc1936a552481fa0efb2a7c5c64350e9d9d1c709c85",
++ "linux-x64": "0fe6f52c41912245a71f71c0978a3a64e0dcc9dec4449809c5bf0442032a9912",
++ "linux-arm64": "0fc709f4ae9121f5f7f1bb68423b8ec9c9d9f6c9baf85fcbe0cde3ee9627cc38",
++ "win32-x64": "a31751f41f74af9c3f74860c00d50d0a0e5d5a5fa7c9049db9d56c6082382483",
++ },
+ "13.3.1": {
+ "darwin-x64": "417c5f1d77e15c8aef0a55f155c16c3dbbc637f918c1e51f8fec6eb1c73a9ba9",
+ "darwin-arm64": "143d905779c0b0a8a9049b0eb68b4b156db3d838d4546ce5082a8f7bd5dc5232",
diff --git a/tools/build_rules/js.bzl b/tools/build_rules/js.bzl
index 51fe987..c2468f8 100644
--- a/tools/build_rules/js.bzl
+++ b/tools/build_rules/js.bzl
@@ -378,7 +378,7 @@
copy_file(
name = name + "_config",
- out = "cypress.config.js",
+ out = name + "_cypress.config.js",
src = "//tools/build_rules/js:cypress.config.js",
visibility = ["//visibility:private"],
)
@@ -392,7 +392,7 @@
name = name,
args = [
"run",
- "--config-file=cypress.config.js",
+ "--config-file=%s_cypress.config.js" % name,
"--browser=" + chrome_location,
],
browsers = ["@chrome_linux//:all"],
diff --git a/tools/build_rules/js/BUILD b/tools/build_rules/js/BUILD
index 1bbe769..86a0288 100644
--- a/tools/build_rules/js/BUILD
+++ b/tools/build_rules/js/BUILD
@@ -24,3 +24,11 @@
"//:node_modules/@types/node",
],
)
+
+py_binary(
+ name = "assemble_service_worker_files",
+ srcs = [
+ "assemble_service_worker_files.py",
+ ],
+ visibility = ["//visibility:public"],
+)
diff --git a/tools/build_rules/js/assemble_service_worker_files.py b/tools/build_rules/js/assemble_service_worker_files.py
new file mode 100644
index 0000000..d4485d5
--- /dev/null
+++ b/tools/build_rules/js/assemble_service_worker_files.py
@@ -0,0 +1,27 @@
+import argparse
+import shutil
+import sys
+from pathlib import Path
+
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--input_dir", type=Path, action="append", default=[])
+ parser.add_argument("--output", type=Path, action="append", default=[])
+ parser.add_argument("--relative_output",
+ type=Path,
+ action="append",
+ default=[])
+ args = parser.parse_args(argv[1:])
+
+ for relative_output, output in zip(args.relative_output, args.output):
+ for input_dir in args.input_dir:
+ input_file = input_dir / relative_output
+ if input_file.exists():
+ print(f"Copying {input_file} to {output}")
+ shutil.copy(input_file, output)
+ break
+
+
+if __name__ == "__main__":
+ sys.exit(main(sys.argv))
diff --git a/tools/build_rules/js/cypress.config.js b/tools/build_rules/js/cypress.config.js
index e991016..5d2ec1c 100644
--- a/tools/build_rules/js/cypress.config.js
+++ b/tools/build_rules/js/cypress.config.js
@@ -9,6 +9,14 @@
launchOptions.args.push('--disable-gpu-shader-disk-cache');
launchOptions.args.push('--enable-logging');
launchOptions.args.push('--v=stderr');
+
+ // Point the browser at a video file to use as a webcam. This lets us
+ // validate things like QR code scanning.
+ launchOptions.args.push('--use-fake-ui-for-media-stream');
+ launchOptions.args.push('--use-fake-device-for-media-stream');
+ const fakeCameraVideo = `${process.env.TEST_UNDECLARED_OUTPUTS_DIR}/fake_camera.mjpeg`;
+ launchOptions.args.push(`--use-file-for-fake-video-capture=${fakeCameraVideo}`);
+
return launchOptions;
});
diff --git a/tools/dependency_rewrite b/tools/dependency_rewrite
index 1cd117a..14980dd 100644
--- a/tools/dependency_rewrite
+++ b/tools/dependency_rewrite
@@ -18,6 +18,7 @@
rewrite cdn.cypress.io/(.*) software.frc971.org/Build-Dependencies/cdn.cypress.io/$1
rewrite www.googleapis.com/(.*) software.frc971.org/Build-Dependencies/www.googleapis.com/$1
rewrite www.johnvansickle.com/(.*) software.frc971.org/Build-Dependencies/www.johnvansickle.com/$1
+rewrite docs.opencv.org/(.*) software.frc971.org/Build-Dependencies/docs.opencv.org/$1
allow crates.io
allow golang.org
allow go.dev
diff --git a/y2019/control_loops/drivetrain/localizer_test.cc b/y2019/control_loops/drivetrain/localizer_test.cc
index 7f0b13d..7c33579 100644
--- a/y2019/control_loops/drivetrain/localizer_test.cc
+++ b/y2019/control_loops/drivetrain/localizer_test.cc
@@ -173,7 +173,7 @@
spline_goal_buffer(fbb.Release());
frc971::control_loops::drivetrain::Trajectory trajectory(
- spline_goal_buffer.message(), dt_config_);
+ spline_goal_buffer.message(), &dt_config_);
trajectory.Plan();
flatbuffers::FlatBufferBuilder traj_fbb;
diff --git a/y2024/BUILD b/y2024/BUILD
index 212f70e..704a5a4 100644
--- a/y2024/BUILD
+++ b/y2024/BUILD
@@ -55,6 +55,7 @@
"//aos/util:foxglove_websocket",
"//frc971/image_streamer:image_streamer",
"//frc971/vision:intrinsics_calibration",
+ "//aos/util:filesystem_monitor",
"//y2024/vision:viewer",
"//y2024/constants:constants_sender",
"//y2024/localizer:localizer_main",
@@ -63,7 +64,7 @@
],
data = [
":aos_config",
- "//frc971/rockpi:rockpi_config.json",
+ "//frc971/orin:orin_irq_config.json",
"//y2024/constants:constants.json",
"//y2024/vision:image_streamer_start",
"//y2024/www:www_files",
@@ -124,6 +125,7 @@
"//y2024/localizer:status_fbs",
"//y2024/localizer:visualization_fbs",
"//aos/network:timestamp_fbs",
+ "//aos/util:filesystem_fbs",
"//aos/network:remote_message_fbs",
"//frc971/vision:calibration_fbs",
"//frc971/vision:target_map_fbs",
@@ -183,6 +185,7 @@
"//y2024/localizer:visualization_fbs",
"//frc971/vision:target_map_fbs",
"//frc971/vision:vision_fbs",
+ "//aos/util:filesystem_fbs",
"@com_github_foxglove_schemas//:schemas",
],
target_compatible_with = ["@platforms//os:linux"],
diff --git a/y2024/constants/971.json b/y2024/constants/971.json
index 5cee276..62015a3 100644
--- a/y2024/constants/971.json
+++ b/y2024/constants/971.json
@@ -8,13 +8,13 @@
{
"cameras": [
{
- "calibration": {% include 'y2024/constants/calib_files/calibration_orin1-971-0_cam-24-05_2024-03-01_11-01-05.102438041.json' %}
+ "calibration": {% include 'y2024/constants/calib_files/calibration_orin-971-c-1_cam-24-05_2024-03-08_17-16-02.325066070.json' %}
},
{
- "calibration": {% include 'y2024/constants/calib_files/calibration_orin1-971-1_cam-24-06_2024-03-01_11-01-20.409861949.json' %}
+ "calibration": {% include 'y2024/constants/calib_files/calibration_orin-971-1-0_cam-24-06_2024-03-08_17-16-02.325390283.json' %}
},
{
- "calibration": {% include 'y2024/constants/calib_files/calibration_imu-971-0_cam-24-07_2024-03-01_11-01-32.895328333.json' %}
+ "calibration": {% include 'y2024/constants/calib_files/calibration_orin-971-1-1_cam-24-07_2024-03-08_17-16-02.325267121.json' %}
},
{
"calibration": {% include 'y2024/constants/calib_files/calibration_imu-971-1_cam-24-08_2024-03-01_11-02-11.982641320.json' %}
@@ -23,23 +23,23 @@
"robot": {
{% set _ = intake_pivot_zero.update(
{
- "measured_absolute_position" : 3.2990161941868
+ "measured_absolute_position" : 3.26046279713726
}
) %}
"intake_constants": {{ intake_pivot_zero | tojson(indent=2)}},
"climber_constants": {
{% set _ = climber_zero.update(
{
- "measured_absolute_position" : 0.00260967415741875
+ "measured_absolute_position" : 0.0143601265619493
}
) %}
"zeroing_constants": {{ climber_zero | tojson(indent=2)}},
- "potentiometer_offset": {{ -0.935529777248618 + 1.83632555414775 + 0.0431080619919798 - 0.493015437796464 }}
+ "potentiometer_offset": {{ -0.935529777248618 + 1.83632555414775 + 0.0431080619919798 - 0.493015437796464 + 0.001602382648064 +0.00194716776942403 }}
},
"catapult_constants": {
{% set _ = catapult_zero.update(
{
- "measured_absolute_position" : 0.741253220327565
+ "measured_absolute_position" : 0.72750793510745
}
) %}
"zeroing_constants": {{ catapult_zero | tojson(indent=2)}},
@@ -48,29 +48,29 @@
"altitude_constants": {
{% set _ = altitude_zero.update(
{
- "measured_absolute_position" : 0.130841088837793
+ "measured_absolute_position" : 0.1964
}
) %}
"zeroing_constants": {{ altitude_zero | tojson(indent=2)}},
- "potentiometer_offset": -0.15316323147786
+ "potentiometer_offset": -0.16416323147786
},
"turret_constants": {
{% set _ = turret_zero.update(
{
- "measured_absolute_position" : 0.138686395993591
+ "measured_absolute_position" : 0.210464386547614
}
) %}
"zeroing_constants": {{ turret_zero | tojson(indent=2)}},
- "potentiometer_offset": {{ -6.47164779835404 }}
+ "potentiometer_offset": {{ -6.47164779835404 - 0.0711209027239817 }}
},
"extend_constants": {
{% set _ = extend_zero.update(
{
- "measured_absolute_position" : 0.0314256815130559
+ "measured_absolute_position" : 0.135593394632399
}
) %}
"zeroing_constants": {{ extend_zero | tojson(indent=2)}},
- "potentiometer_offset": {{ -0.2574404033256 + 0.0170793439542 - 0.177097393974999 }}
+ "potentiometer_offset": {{ -0.2574404033256 + 0.0170793439542 - 0.177097393974999 + 0.3473623911879 }}
}
},
{% include 'y2024/constants/common.json' %}
diff --git a/y2024/constants/calib_files/calibration_imu-971-0_cam-24-07_2024-03-01_11-01-32.895328333.json b/y2024/constants/calib_files/calibration_imu-971-0_cam-24-07_2024-03-01_11-01-32.895328333.json
deleted file mode 100755
index d013e2a..0000000
--- a/y2024/constants/calib_files/calibration_imu-971-0_cam-24-07_2024-03-01_11-01-32.895328333.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "node_name": "imu",
- "team_number": 971,
- "intrinsics": [
- 647.822815,
- 0.0,
- 715.37616,
- 0.0,
- 647.799316,
- 494.638641,
- 0.0,
- 0.0,
- 1.0
- ],
- "fixed_extrinsics": {
- "data": [
- 1.0,
- -0.0,
- 0.0,
- 0.111049,
- 0.0,
- 0.258819,
- 0.965926,
- 0.263806,
- -0.0,
- -0.965926,
- 0.258819,
- 0.347685,
- 0.0,
- 0.0,
- 0.0,
- 1.0
- ]
- },
- "dist_coeffs": [
- -0.2423,
- 0.057169,
- 0.000302,
- 0.000016,
- -0.005638
- ],
- "calibration_timestamp": 1708833147338466592,
- "camera_id": "24-07",
- "camera_number": 0,
- "reprojection_error": 1.362672
-}
\ No newline at end of file
diff --git a/y2024/constants/calib_files/calibration_imu-971-1_cam-24-08_2024-03-01_11-02-11.982641320.json b/y2024/constants/calib_files/calibration_imu-971-1_cam-24-08_2024-03-01_11-02-11.982641320.json
index fbe79d5..c0ec0d8 100755
--- a/y2024/constants/calib_files/calibration_imu-971-1_cam-24-08_2024-03-01_11-02-11.982641320.json
+++ b/y2024/constants/calib_files/calibration_imu-971-1_cam-24-08_2024-03-01_11-02-11.982641320.json
@@ -41,6 +41,6 @@
],
"calibration_timestamp": 1708820514420797344,
"camera_id": "24-08",
- "camera_number": 1,
+ "camera_number": 0,
"reprojection_error": 1.591953
-}
\ No newline at end of file
+}
diff --git a/y2024/constants/calib_files/calibration_orin1-971-1_cam-24-06_2024-03-01_11-01-20.409861949.json b/y2024/constants/calib_files/calibration_orin-971-1-0_cam-24-06_2024-03-08_17-16-02.325390283.json
similarity index 61%
rename from y2024/constants/calib_files/calibration_orin1-971-1_cam-24-06_2024-03-01_11-01-20.409861949.json
rename to y2024/constants/calib_files/calibration_orin-971-1-0_cam-24-06_2024-03-08_17-16-02.325390283.json
index 0eb10db..48da468 100755
--- a/y2024/constants/calib_files/calibration_orin1-971-1_cam-24-06_2024-03-01_11-01-20.409861949.json
+++ b/y2024/constants/calib_files/calibration_orin-971-1-0_cam-24-06_2024-03-08_17-16-02.325390283.json
@@ -14,18 +14,18 @@
],
"fixed_extrinsics": {
"data": [
- -1.0,
- 0.0,
- 0.0,
- 0.111049,
- -0.0,
- -0.258819,
- -0.965926,
- -0.263806,
- 0.0,
- -0.965926,
- 0.258819,
- 0.347685,
+ -0.997807,
+ 0.015704,
+ -0.064302,
+ 0.134715,
+ 0.058111,
+ -0.25731,
+ -0.96458,
+ -0.273849,
+ -0.031694,
+ -0.966201,
+ 0.255833,
+ 0.295681,
0.0,
0.0,
0.0,
@@ -39,8 +39,8 @@
0.000005,
-0.006342
],
- "calibration_timestamp": 409229245444672,
+ "calibration_timestamp": 1709946962325390283,
"camera_id": "24-06",
- "camera_number": 1,
+ "camera_number": 0,
"reprojection_error": 1.344104
}
\ No newline at end of file
diff --git a/y2024/constants/calib_files/calibration_orin-971-1-1_cam-24-07_2024-03-08_17-16-02.325267121.json b/y2024/constants/calib_files/calibration_orin-971-1-1_cam-24-07_2024-03-08_17-16-02.325267121.json
new file mode 100755
index 0000000..56072d4
--- /dev/null
+++ b/y2024/constants/calib_files/calibration_orin-971-1-1_cam-24-07_2024-03-08_17-16-02.325267121.json
@@ -0,0 +1,46 @@
+{
+ "node_name": "orin1",
+ "team_number": 971,
+ "intrinsics": [
+ 647.822815,
+ 0.0,
+ 715.37616,
+ 0.0,
+ 647.799316,
+ 494.638641,
+ 0.0,
+ 0.0,
+ 1.0
+ ],
+ "fixed_extrinsics": {
+ "data": [
+ 0.016995,
+ 0.002328,
+ 0.999853,
+ 0.35278,
+ 0.999839,
+ -0.005785,
+ -0.016981,
+ 0.237526,
+ 0.005745,
+ 0.999981,
+ -0.002426,
+ 0.387276,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ "dist_coeffs": [
+ -0.2423,
+ 0.057169,
+ 0.000302,
+ 0.000016,
+ -0.005638
+ ],
+ "calibration_timestamp": 1709946962325267121,
+ "camera_id": "24-07",
+ "camera_number": 1,
+ "reprojection_error": 1.362672
+}
\ No newline at end of file
diff --git a/y2024/constants/calib_files/calibration_orin-971-c-1_cam-24-05_2024-03-08_17-16-02.325066070.json b/y2024/constants/calib_files/calibration_orin-971-c-1_cam-24-05_2024-03-08_17-16-02.325066070.json
new file mode 100755
index 0000000..c1d4e60
--- /dev/null
+++ b/y2024/constants/calib_files/calibration_orin-971-c-1_cam-24-05_2024-03-08_17-16-02.325066070.json
@@ -0,0 +1,46 @@
+{
+ "node_name": "imu",
+ "team_number": 971,
+ "intrinsics": [
+ 648.360168,
+ 0.0,
+ 729.818665,
+ 0.0,
+ 648.210327,
+ 641.988037,
+ 0.0,
+ 0.0,
+ 1.0
+ ],
+ "fixed_extrinsics": {
+ "data": [
+ 0.999556,
+ -0.026613,
+ 0.013428,
+ 0.146979,
+ -0.006922,
+ 0.230925,
+ 0.972947,
+ 0.30388,
+ -0.028994,
+ -0.972608,
+ 0.230638,
+ 0.32572,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ "dist_coeffs": [
+ -0.255473,
+ 0.068444,
+ 0.000028,
+ -0.000078,
+ -0.008004
+ ],
+ "calibration_timestamp": 1709946962325066070,
+ "camera_id": "24-05",
+ "camera_number": 1,
+ "reprojection_error": 1.058851
+}
\ No newline at end of file
diff --git a/y2024/constants/calib_files/calibration_orin1-971-0_cam-24-05_2024-03-01_11-01-05.102438041.json b/y2024/constants/calib_files/calibration_orin1-971-0_cam-24-05_2024-03-01_11-01-05.102438041.json
deleted file mode 100755
index 317e453..0000000
--- a/y2024/constants/calib_files/calibration_orin1-971-0_cam-24-05_2024-03-01_11-01-05.102438041.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "node_name": "orin1",
- "team_number": 971,
- "intrinsics": [
- 648.360168,
- 0.0,
- 729.818665,
- 0.0,
- 648.210327,
- 641.988037,
- 0.0,
- 0.0,
- 1.0
- ],
- "fixed_extrinsics": {
- "data": [
- 0.0,
- 0.0,
- 1.0,
- 0.284397,
- -1.0,
- 0.0,
- 0.0,
- 0.226771,
- 0.0,
- -1.0,
- 0.0,
- 0.442951,
- 0.0,
- 0.0,
- 0.0,
- 1.0
- ]
- },
- "dist_coeffs": [
- -0.255473,
- 0.068444,
- 0.000028,
- -0.000078,
- -0.008004
- ],
- "calibration_timestamp": 409227793683328,
- "camera_id": "24-05",
- "camera_number": 0,
- "reprojection_error": 1.058851
-}
\ No newline at end of file
diff --git a/y2024/constants/common.json b/y2024/constants/common.json
index 1050815..a36844b 100644
--- a/y2024/constants/common.json
+++ b/y2024/constants/common.json
@@ -4,30 +4,55 @@
"target_map": {% include 'y2024/vision/maps/target_map.json' %},
"shooter_interpolation_table": [
{
- "distance_from_goal": 5.0,
+ "distance_from_goal": 0.8,
"shot_params": {
- "shot_velocity": 0.0,
- "shot_altitude_angle": 0.0,
- "shot_catapult_angle": 0.0,
- "shot_speed_over_ground": 2.0
+ "shot_altitude_angle": 0.85,
+ "shot_speed_over_ground": 4.0
}
},
{
- "distance_from_goal": 10.0,
+ "distance_from_goal": 1.34,
"shot_params": {
- "shot_velocity": 0.0,
- "shot_altitude_angle": 0.0,
- "shot_catapult_angle": 0.0,
+ "shot_altitude_angle": 0.85,
+ "shot_speed_over_ground": 4.0
+ }
+ },
+ {
+ "distance_from_goal": 2.004,
+ "shot_params": {
+ "shot_altitude_angle": 0.73,
+ "shot_speed_over_ground": 4.0
+ }
+ },
+ // 2.2 -> high.
+ {
+ "distance_from_goal": 2.844,
+ "shot_params": {
+ "shot_altitude_angle": 0.62,
+ "shot_speed_over_ground": 4.0
+ }
+ },
+ {
+ "distance_from_goal": 3.374,
+ "shot_params": {
+ "shot_altitude_angle": 0.58,
+ "shot_speed_over_ground": 4.0
+ }
+ },
+ {
+ "distance_from_goal": 4.10,
+ "shot_params": {
+ "shot_altitude_angle": 0.54,
"shot_speed_over_ground": 4.0
}
}
],
"intake_roller_voltages": {
- "spitting": -4.0,
- "intaking": 12.0
+ "spitting": -6.0,
+ "intaking": 9.0
},
"intake_pivot_set_points": {
- "extended": -0.03,
+ "extended": 0.045,
"retracted": 1.73
},
"intake_pivot": {
@@ -38,8 +63,8 @@
"max_acceleration": 3.0
},
"default_profile_params":{
- "max_velocity": 4.0,
- "max_acceleration": 10.0
+ "max_velocity": 6.0,
+ "max_acceleration": 40.0
},
"range": {
"lower_hard": -0.2,
@@ -56,21 +81,21 @@
"intake_pivot_supply_current_limit": 40,
"intake_pivot_stator_current_limit": 100,
"intake_roller_supply_current_limit": 20,
- "intake_roller_stator_current_limit": 50,
+ "intake_roller_stator_current_limit": 100,
"transfer_roller_supply_current_limit": 20,
"transfer_roller_stator_current_limit": 50,
- "drivetrain_supply_current_limit": 35,
- "drivetrain_stator_current_limit": 60,
+ "drivetrain_supply_current_limit": 50,
+ "drivetrain_stator_current_limit": 200,
"climber_supply_current_limit": 30,
"climber_stator_current_limit": 100,
- "extend_supply_current_limit": 20,
- "extend_stator_current_limit": 100,
- "extend_roller_supply_current_limit": 60,
- "extend_roller_stator_current_limit": 200,
- "turret_supply_current_limit": 20,
- "turret_stator_current_limit": 40,
- "altitude_supply_current_limit": 10,
- "altitude_stator_current_limit": 60,
+ "extend_supply_current_limit": 30,
+ "extend_stator_current_limit": 180,
+ "extend_roller_supply_current_limit": 50,
+ "extend_roller_stator_current_limit": 180,
+ "turret_supply_current_limit": 30,
+ "turret_stator_current_limit": 80,
+ "altitude_supply_current_limit": 30,
+ "altitude_stator_current_limit": 150,
"catapult_supply_current_limit": 60,
"catapult_stator_current_limit": 250,
"retention_roller_stator_current_limit": 20,
@@ -79,18 +104,18 @@
"retention_roller_supply_current_limit": 10
},
"transfer_roller_voltages": {
- "transfer_in": 12.0,
+ "transfer_in": 9.0,
"transfer_out": -4.0,
"extend_moving": 4.0
},
"extend_roller_voltages": {
- "scoring": 12.0,
+ "scoring": 6.0,
"reversing": -4.0
},
"climber_set_points": {
"full_extend": -0.005,
- "stowed": -0.35,
- "retract": -0.478
+ "stowed": -0.442,
+ "retract": -0.472
},
"climber": {
"zeroing_voltage": 3.0,
@@ -104,9 +129,9 @@
"max_acceleration": 3.0
},
"range": {
- "lower_hard": -0.488,
+ "lower_hard": -0.495,
"upper_hard": 0.005,
- "lower": -0.478,
+ "lower": -0.492,
"upper": -0.005
},
"loop": {% include 'y2024/control_loops/superstructure/climber/integral_climber_plant.json' %}
@@ -139,8 +164,8 @@
"max_acceleration": 3.0
},
"default_profile_params":{
- "max_velocity": 3.0,
- "max_acceleration": 5.0
+ "max_velocity": 5.0,
+ "max_acceleration": 30.0
},
"range": {
"lower_hard": -0.01,
@@ -158,8 +183,8 @@
"max_acceleration": 3.0
},
"default_profile_params":{
- "max_velocity": 2.0,
- "max_acceleration": 5.0
+ "max_velocity": 12.0,
+ "max_acceleration": 30.0
},
"range": {
"lower_hard": -4.8,
@@ -177,8 +202,8 @@
"max_acceleration": 3.0
},
"default_profile_params":{
- "max_velocity": 0.1,
- "max_acceleration": 0.3
+ "max_velocity": 2.0,
+ "max_acceleration": 10.0
},
"range": {
"lower_hard": -0.005,
@@ -196,7 +221,7 @@
"storage_order": "ColMajor",
// The data field contains the x, y and z
// coordinates of the speaker on the red alliance
- "data": [8.0645, 1.4435, 2.0705]
+ "data": [8.309, 1.4435, 2.0705]
},
"theta": 0.0
},
@@ -207,7 +232,7 @@
"storage_order": "ColMajor",
// The data field contains the x, y and z
// coordinates of the speaker on the blue alliance
- "data": [-8.0645, 1.4435, 2.0705]
+ "data": [-8.309, 1.4435, 2.0705]
},
"theta": 0.0
}
@@ -215,7 +240,7 @@
"altitude_loading_position": 0.02,
"turret_loading_position": 0.58,
"catapult_return_position": 0.0,
- "min_altitude_shooting_angle": 0.55,
+ "min_altitude_shooting_angle": 0.4,
"max_altitude_shooting_angle": 0.89,
"retention_roller_voltages": {
"retaining": 1.5,
@@ -223,8 +248,8 @@
},
// TODO(Filip): Update the speaker and amp shooter setpoints
"shooter_speaker_set_point": {
- "turret_position": 0.0,
- "altitude_position": 0.75,
+ "turret_position": 0.22,
+ "altitude_position": 0.85,
"shot_velocity": 0.0
},
"shooter_podium_set_point":{
@@ -233,10 +258,15 @@
"shot_velocity": 0.0
},
"extend_set_points": {
- "trap": 0.46,
- "amp": 0.2,
+ "trap": 0.40,
+ "amp": 0.35,
"catapult": 0.017,
"retracted": 0.017
},
- "turret_avoid_extend_collision_position": 0.0
+ "turret_avoid_extend_collision_position": 0.0,
+ "autonomous_mode": "FOUR_PIECE",
+ "ignore_targets": {
+ "red": [1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16],
+ "blue": [1, 2, 5, 6, 9, 10, 11, 12, 13, 14, 15, 16]
+ }
}
diff --git a/y2024/constants/constants.fbs b/y2024/constants/constants.fbs
index 31e2057..d33a161 100644
--- a/y2024/constants/constants.fbs
+++ b/y2024/constants/constants.fbs
@@ -115,6 +115,16 @@
retracted:double (id: 3);
}
+enum AutonomousMode : ubyte {
+ NONE = 0,
+ // Simple test S-spline auto mode
+ SPLINE_AUTO = 1,
+ // Simple drive-and-shoot to pick up at most one game piece.
+ MOBILITY_AND_SHOOT = 2,
+ // Auto to pick up four game pieces.
+ FOUR_PIECE = 3,
+}
+
table RobotConstants {
intake_constants:frc971.zeroing.AbsoluteEncoderZeroingConstants (id: 0);
climber_constants:PotAndAbsEncoderConstants (id: 1);
@@ -147,6 +157,13 @@
spitting:double (id: 1);
}
+// Set of april tag targets, by april tag ID, to ignore when on a
+// given alliance.
+table IgnoreTargets {
+ red:[uint64] (id: 0);
+ blue:[uint64] (id: 1);
+}
+
// Common table for constants unrelated to the robot
table Common {
target_map:frc971.vision.TargetMap (id: 0);
@@ -173,10 +190,12 @@
max_altitude_shooting_angle: double (id: 25);
shooter_speaker_set_point: ShooterSetPoint (id: 21);
shooter_podium_set_point: ShooterSetPoint (id: 22);
- extend_set_points:ExtendSetPoints (id: 23);
+ extend_set_points:ExtendSetPoints (id: 23);
// The position to move the turret to when avoiding collision
// with the extend when the extend is moving to amp/trap position.
turret_avoid_extend_collision_position: double (id: 24);
+ autonomous_mode:AutonomousMode (id: 26);
+ ignore_targets:IgnoreTargets (id: 27);
}
table Constants {
diff --git a/y2024/control_loops/python/altitude.py b/y2024/control_loops/python/altitude.py
index d302b79..544eddc 100644
--- a/y2024/control_loops/python/altitude.py
+++ b/y2024/control_loops/python/altitude.py
@@ -24,7 +24,7 @@
motor=control_loop.KrakenFOC(),
G=(16.0 / 60.0) * (16.0 / 162.0),
# 4340 in^ lb
- J=1.27,
+ J=1.2,
q_pos=0.60,
q_vel=8.0,
kalman_q_pos=0.12,
diff --git a/y2024/control_loops/superstructure/shooter.cc b/y2024/control_loops/superstructure/shooter.cc
index cc00454..7fdf748 100644
--- a/y2024/control_loops/superstructure/shooter.cc
+++ b/y2024/control_loops/superstructure/shooter.cc
@@ -44,7 +44,7 @@
const double extend_goal, double *max_extend_position,
double *min_extend_position, const double intake_pivot_position,
double *max_intake_pivot_position, double *min_intake_pivot_position,
- flatbuffers::FlatBufferBuilder *fbb,
+ NoteGoal requested_note_goal, flatbuffers::FlatBufferBuilder *fbb,
aos::monotonic_clock::time_point monotonic_now) {
drivetrain_status_fetcher_.Fetch();
@@ -96,8 +96,16 @@
bool aiming = false;
- if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
- (!piece_loaded && state_ == CatapultState::READY)) {
+ if (requested_note_goal == NoteGoal::AMP) {
+ // Being asked to amp, lift the altitude up.
+ PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ turret_goal_builder.get(),
+ robot_constants_->common()->turret_loading_position());
+
+ PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
+ altitude_goal_builder.get(), 0.3);
+ } else if (shooter_goal == nullptr || !shooter_goal->auto_aim() ||
+ (!piece_loaded && state_ == CatapultState::READY)) {
// We don't have the note so we should be ready to intake it.
PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
turret_goal_builder.get(),
@@ -106,7 +114,6 @@
PopulateStaticZeroingSingleDOFProfiledSubsystemGoal(
altitude_goal_builder.get(),
robot_constants_->common()->altitude_loading_position());
-
} else {
// We have a game piece, lets start aiming.
if (drivetrain_status_fetcher_.get() != nullptr) {
@@ -140,13 +147,18 @@
? shooter_goal->altitude_position()
: &altitude_goal_builder->AsFlatbuffer();
- bool subsystems_in_range =
+ const bool turret_in_range =
(std::abs(turret_.estimated_position() - turret_goal->unsafe_goal()) <
- kCatapultActivationThreshold &&
- std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
- kCatapultActivationThreshold &&
- altitude_.estimated_position() >
- robot_constants_->common()->min_altitude_shooting_angle());
+ kCatapultActivationThreshold);
+ const bool altitude_in_range =
+ (std::abs(altitude_.estimated_position() - altitude_goal->unsafe_goal()) <
+ kCatapultActivationThreshold);
+ const bool altitude_above_min_angle =
+ (altitude_.estimated_position() >
+ robot_constants_->common()->min_altitude_shooting_angle());
+
+ bool subsystems_in_range =
+ (turret_in_range && altitude_in_range && altitude_above_min_angle);
const bool disabled = turret_.Correct(turret_goal, position->turret(),
turret_output == nullptr);
@@ -301,6 +313,9 @@
status_builder.add_altitude(altitude_status_offset);
status_builder.add_catapult(catapult_status_offset);
status_builder.add_catapult_state(state_);
+ status_builder.add_turret_in_range(turret_in_range);
+ status_builder.add_altitude_in_range(altitude_in_range);
+ status_builder.add_altitude_above_min_angle(altitude_above_min_angle);
if (aiming) {
status_builder.add_aimer(aimer_offset);
}
diff --git a/y2024/control_loops/superstructure/shooter.h b/y2024/control_loops/superstructure/shooter.h
index 2571cab..12078ff 100644
--- a/y2024/control_loops/superstructure/shooter.h
+++ b/y2024/control_loops/superstructure/shooter.h
@@ -106,7 +106,7 @@
const double extend_goal, double *max_extend_position,
double *min_extend_position, const double intake_pivot_position,
double *max_turret_intake_position, double *min_intake_pivot_position,
- flatbuffers::FlatBufferBuilder *fbb,
+ NoteGoal requested_note_goal, flatbuffers::FlatBufferBuilder *fbb,
aos::monotonic_clock::time_point monotonic_now);
private:
diff --git a/y2024/control_loops/superstructure/superstructure.cc b/y2024/control_loops/superstructure/superstructure.cc
index dc9369e..a541a55 100644
--- a/y2024/control_loops/superstructure/superstructure.cc
+++ b/y2024/control_loops/superstructure/superstructure.cc
@@ -297,6 +297,7 @@
extend_moving = false;
extend_goal_location = ExtendStatus::CATAPULT;
extend_roller_status = ExtendRollerStatus::TRANSFERING_TO_CATAPULT;
+ transfer_roller_status = TransferRollerStatus::EXTEND_MOVING;
// If we lost the game piece, reset state to idle.
if (((timestamp - loading_catapult_start_time_) >
@@ -563,7 +564,8 @@
extend_goal_position, extend_.estimated_position(),
&max_extend_position, &min_extend_position,
intake_pivot_.estimated_position(), &max_intake_pivot_position,
- &min_intake_pivot_position, status->fbb(), timestamp);
+ &min_intake_pivot_position, requested_note_goal_, status->fbb(),
+ timestamp);
intake_pivot_.set_min_position(min_intake_pivot_position);
intake_pivot_.set_max_position(max_intake_pivot_position);
diff --git a/y2024/control_loops/superstructure/superstructure_goal.fbs b/y2024/control_loops/superstructure/superstructure_goal.fbs
index 42caf9b..13cf4e0 100644
--- a/y2024/control_loops/superstructure/superstructure_goal.fbs
+++ b/y2024/control_loops/superstructure/superstructure_goal.fbs
@@ -12,6 +12,11 @@
SPIT = 2,
}
+enum IntakePivotGoal : ubyte {
+ UP = 0,
+ DOWN = 1,
+}
+
// Represents goal for climber
// FULL_EXTEND is for fully extending the climber
// RETRACT is for retracting the climber
@@ -54,9 +59,13 @@
table Goal {
intake_goal:IntakeGoal = NONE (id: 0);
+ intake_pivot:IntakePivotGoal = UP (id: 5);
climber_goal:ClimberGoal (id: 1);
shooter_goal:ShooterGoal (id: 2);
note_goal:NoteGoal (id: 3);
fire: bool (id: 4);
+
+ // Tells the climber to go absurdly slow on FULL_EXTEND
+ slow_climber: bool = false (id: 6);
}
root_type Goal;
diff --git a/y2024/control_loops/superstructure/superstructure_lib_test.cc b/y2024/control_loops/superstructure/superstructure_lib_test.cc
index 56452c8..0b39ec6 100644
--- a/y2024/control_loops/superstructure/superstructure_lib_test.cc
+++ b/y2024/control_loops/superstructure/superstructure_lib_test.cc
@@ -1184,6 +1184,7 @@
ExtendRollerStatus::IDLE);
// Should now be loaded.
+ superstructure_output_fetcher_.Fetch();
EXPECT_EQ(superstructure_output_fetcher_->transfer_roller_voltage(), 0.0);
EXPECT_NEAR(superstructure_status_fetcher_->shooter()->altitude()->position(),
diff --git a/y2024/control_loops/superstructure/superstructure_status.fbs b/y2024/control_loops/superstructure/superstructure_status.fbs
index 8d6b14f..fd19e8d 100644
--- a/y2024/control_loops/superstructure/superstructure_status.fbs
+++ b/y2024/control_loops/superstructure/superstructure_status.fbs
@@ -77,6 +77,13 @@
// Status of the aimer
aimer:AimerStatus (id: 4);
+
+ // True if auto-aiming.
+ auto_aiming:bool (id: 5);
+
+ turret_in_range:bool (id: 6);
+ altitude_in_range:bool (id: 7);
+ altitude_above_min_angle:bool (id: 8);
}
// Contains status of transfer rollers
@@ -174,6 +181,9 @@
extend_beambreak:bool (id: 19);
catapult_beambreak:bool (id: 20);
+
+ // Number of shots we have taken.
+ shot_count:uint32 (id:21);
}
root_type Status;
diff --git a/y2024/vision/BUILD b/y2024/vision/BUILD
index d36c956..4904554 100644
--- a/y2024/vision/BUILD
+++ b/y2024/vision/BUILD
@@ -60,6 +60,7 @@
"//aos/events:shm_event_loop",
"//aos/events/logging:log_writer",
"//aos/logging:log_namer",
+ "//aos/util:filesystem_fbs",
"//frc971/input:joystick_state_fbs",
"@com_github_gflags_gflags//:gflags",
"@com_github_google_glog//:glog",
diff --git a/y2024/vision/image_logger.cc b/y2024/vision/image_logger.cc
index 55a4e12..2984824 100644
--- a/y2024/vision/image_logger.cc
+++ b/y2024/vision/image_logger.cc
@@ -9,6 +9,7 @@
#include "aos/events/shm_event_loop.h"
#include "aos/init.h"
#include "aos/logging/log_namer.h"
+#include "aos/util/filesystem_generated.h"
#include "frc971/input/joystick_state_generated.h"
DEFINE_string(config, "aos_config.json", "Config file to use.");
@@ -48,10 +49,13 @@
aos::ShmEventLoop event_loop(&config.message());
+ aos::Fetcher<aos::util::FilesystemStatus> filesystem_status =
+ event_loop.MakeFetcher<aos::util::FilesystemStatus>("/aos");
+
bool logging = false;
bool enabled = false;
aos::monotonic_clock::time_point last_disable_time =
- event_loop.monotonic_now();
+ aos::monotonic_clock::min_time;
aos::monotonic_clock::time_point last_rotation_time =
event_loop.monotonic_now();
aos::logger::Logger logger(&event_loop);
@@ -76,13 +80,36 @@
event_loop.MakeWatcher(
"/imu/aos", [&](const aos::JoystickState &joystick_state) {
const auto timestamp = event_loop.context().monotonic_event_time;
+ filesystem_status.Fetch();
+
// Store the last time we got disabled
if (enabled && !joystick_state.enabled()) {
last_disable_time = timestamp;
}
enabled = joystick_state.enabled();
- if (!logging && enabled) {
+ bool enough_space = true;
+
+ if (filesystem_status.get() != nullptr) {
+ enough_space = false;
+ for (const aos::util::Filesystem *fs :
+ *filesystem_status->filesystems()) {
+ CHECK(fs->has_path());
+ if (fs->path()->string_view() == "/") {
+ if (fs->free_space() > 50ull * 1024ull * 1024ull * 1024ull) {
+ enough_space = true;
+ }
+ }
+ }
+ }
+
+ const bool should_be_logging =
+ (enabled ||
+ timestamp < last_disable_time + std::chrono::duration<double>(
+ FLAGS_disabled_time)) &&
+ enough_space;
+
+ if (!logging && should_be_logging) {
auto log_namer = MakeLogNamer(&event_loop);
if (log_namer == nullptr) {
return;
@@ -93,9 +120,7 @@
logger.StartLogging(std::move(log_namer));
logging = true;
last_rotation_time = event_loop.monotonic_now();
- } else if (logging && !enabled &&
- (timestamp - last_disable_time) >
- std::chrono::duration<double>(FLAGS_disabled_time)) {
+ } else if (logging && !should_be_logging) {
// Stop logging if we've been disabled for a non-negligible amount of
// time
LOG(INFO) << "Stopping logging";
diff --git a/y2024/wpilib_interface.cc b/y2024/wpilib_interface.cc
index d286939..8060a69 100644
--- a/y2024/wpilib_interface.cc
+++ b/y2024/wpilib_interface.cc
@@ -521,6 +521,11 @@
current_limits->catapult_stator_current_limit(),
current_limits->catapult_supply_current_limit());
+ transfer_roller->set_neutral_mode(
+ ctre::phoenix6::signals::NeutralModeValue::Coast);
+ intake_roller->set_neutral_mode(
+ ctre::phoenix6::signals::NeutralModeValue::Coast);
+
ctre::phoenix::platform::can::CANComm_SetRxSchedPriority(
constants::Values::kDrivetrainRxPriority, true, "Drivetrain Bus");
ctre::phoenix::platform::can::CANComm_SetTxSchedPriority(
diff --git a/y2024/www/BUILD b/y2024/www/BUILD
index 5ff91d6..5cb57c4 100644
--- a/y2024/www/BUILD
+++ b/y2024/www/BUILD
@@ -39,6 +39,8 @@
"//frc971/control_loops/drivetrain/localization:localizer_output_ts_fbs",
"//frc971/vision:target_map_ts_fbs",
"//y2024/control_loops/superstructure:superstructure_status_ts_fbs",
+ "//y2024/localizer:status_ts_fbs",
+ "//y2024/localizer:visualization_ts_fbs",
"@com_github_google_flatbuffers//ts:flatbuffers_ts",
],
)
diff --git a/y2024/www/field.html b/y2024/www/field.html
index cfb8778..8fd4ebb 100644
--- a/y2024/www/field.html
+++ b/y2024/www/field.html
@@ -11,15 +11,8 @@
<div>
<div id="field"> </div>
<div id="legend"> </div>
- <div id="vision_readouts">
- </div>
- <div id="message_bridge_status">
- <div>
- <div>Node</div>
- <div>Client</div>
- <div>Server</div>
- </div>
- </div>
+ <h3>Zeroing Faults:</h3>
+ <p id="zeroing_faults"> NA </p>
</div>
<div>
<table>
@@ -114,6 +107,18 @@
<td>Altitude at Loading Position</td>
<td id="altitude_ready_for_load">FALSE</td>
</tr>
+ <tr>
+ <td>Turret in Range of Goal</td>
+ <td id="turret_in_range">FALSE</td>
+ </tr>
+ <tr>
+ <td>Altitude in Range of Goal</td>
+ <td id="altitude_in_range">FALSE</td>
+ </tr>
+ <tr>
+ <td>Altitude Above Minimum Angle </td>
+ <td id="altitude_above_min_angle">FALSE</td>
+ </tr>
</table>
<table>
<tr>
@@ -136,9 +141,6 @@
<td id="shot_distance"> NA </td>
</tr>
</table>
-
- <h3>Zeroing Faults:</h3>
- <p id="zeroing_faults"> NA </p>
</div>
<div>
<table>
@@ -266,6 +268,14 @@
</table>
</div>
</div>
+ <div id="message_bridge_status">
+ <div>
+ <div>Node</div>
+ <div>Client</div>
+ <div>Server</div>
+ </div>
+ </div>
+ <div id="vision_readouts"> </div>
</body>
</html>
\ No newline at end of file
diff --git a/y2024/www/field_handler.ts b/y2024/www/field_handler.ts
index 949b34b..1b4ad1e 100644
--- a/y2024/www/field_handler.ts
+++ b/y2024/www/field_handler.ts
@@ -9,6 +9,8 @@
import {SuperstructureState, IntakeRollerStatus, CatapultState, TransferRollerStatus, ExtendRollerStatus, ExtendStatus, NoteStatus, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated'
import {TargetMap} from '../../frc971/vision/target_map_generated'
+import {RejectionReason} from '../localizer/status_generated'
+import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated'
import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
@@ -20,6 +22,9 @@
const ROBOT_WIDTH = 29 * IN_TO_M;
const ROBOT_LENGTH = 32 * IN_TO_M;
+const CAMERA_COLORS = ['#ff00ff', '#ffff00', '#00ffff', '#ffa500'];
+const CAMERAS = ['/orin1/camera0', '/orin1/camera1', '/imu/camera0', '/imu/camera1'];
+
export class FieldHandler {
private canvas = document.createElement('canvas');
private localizerOutput: LocalizerOutput|null = null;
@@ -28,11 +33,26 @@
private drivetrainCANPosition: DrivetrainCANPosition|null = null;
private superstructureStatus: SuperstructureStatus|null = null;
+ // Image information indexed by timestamp (seconds since the epoch), so that
+ // we can stop displaying images after a certain amount of time.
+ private localizerImageMatches = new Map<number, Visualization>();
private x: HTMLElement = (document.getElementById('x') as HTMLElement);
private y: HTMLElement = (document.getElementById('y') as HTMLElement);
private theta: HTMLElement =
(document.getElementById('theta') as HTMLElement);
+ private imagesAcceptedCounter: HTMLElement =
+ (document.getElementById('images_accepted') as HTMLElement);
+ // HTML elements for rejection reasons for individual cameras. Indices
+ // corresponding to RejectionReason enum values will be for those reasons. The
+ // final row will account for images rejected by the aprilrobotics detector
+ // instead of the localizer.
+ private rejectionReasonCells: HTMLElement[][] = [];
+ private messageBridgeDiv: HTMLElement =
+ (document.getElementById('message_bridge_status') as HTMLElement);
+ private clientStatuses = new Map<string, HTMLElement>();
+ private serverStatuses = new Map<string, HTMLElement>();
+
private fieldImage: HTMLImageElement = new Image();
private zeroingFaults: HTMLElement =
@@ -70,6 +90,13 @@
private altitude_ready_for_load: HTMLElement =
(document.getElementById('altitude_ready_for_load') as HTMLElement);
+ private turret_in_range: HTMLElement =
+ (document.getElementById('turret_in_range') as HTMLElement);
+ private altitude_in_range: HTMLElement =
+ (document.getElementById('altitude_in_range') as HTMLElement);
+ private altitude_above_min_angle: HTMLElement =
+ (document.getElementById('altitude_above_min_angle') as HTMLElement);
+
private intakePivot: HTMLElement =
(document.getElementById('intake_pivot') as HTMLElement);
@@ -138,7 +165,82 @@
this.fieldImage.src = '2024.png';
+ // Construct a table header.
+ {
+ const row = document.createElement('div');
+ const nameCell = document.createElement('div');
+ nameCell.innerHTML = 'Rejection Reason';
+ row.appendChild(nameCell);
+ for (const camera of CAMERAS) {
+ const nodeCell = document.createElement('div');
+ nodeCell.innerHTML = camera;
+ row.appendChild(nodeCell);
+ }
+ document.getElementById('vision_readouts').appendChild(row);
+ }
+
+ for (const value in RejectionReason) {
+ // Typescript generates an iterator that produces both numbers and
+ // strings... don't do anything on the string iterations.
+ if (isNaN(Number(value))) {
+ continue;
+ }
+ const row = document.createElement('div');
+ const nameCell = document.createElement('div');
+ nameCell.innerHTML = RejectionReason[value];
+ row.appendChild(nameCell);
+ this.rejectionReasonCells.push([]);
+ for (const camera of CAMERAS) {
+ const valueCell = document.createElement('div');
+ valueCell.innerHTML = 'NA';
+ this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
+ valueCell);
+ row.appendChild(valueCell);
+ }
+ document.getElementById('vision_readouts').appendChild(row);
+ }
+
+ // Add rejection reason row for aprilrobotics rejections.
+ {
+ const row = document.createElement('div');
+ const nameCell = document.createElement('div');
+ nameCell.innerHTML = 'Rejected by aprilrobotics';
+ row.appendChild(nameCell);
+ this.rejectionReasonCells.push([]);
+ for (const camera of CAMERAS) {
+ const valueCell = document.createElement('div');
+ valueCell.innerHTML = 'NA';
+ this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(
+ valueCell);
+ row.appendChild(valueCell);
+ }
+ document.getElementById('vision_readouts').appendChild(row);
+ }
+
+ for (let ii = 0; ii < CAMERA_COLORS.length; ++ii) {
+ const legendEntry = document.createElement('div');
+ legendEntry.style.color = CAMERA_COLORS[ii];
+ legendEntry.innerHTML = CAMERAS[ii];
+ document.getElementById('legend').appendChild(legendEntry);
+ }
+
this.connection.addConfigHandler(() => {
+ // Visualization message is reliable so that we can see *all* the vision
+ // matches.
+ for (const camera in CAMERAS) {
+ this.connection.addHandler(
+ CAMERAS[camera], 'y2024.localizer.Visualization',
+ (data) => {
+ this.handleLocalizerDebug(Number(camera), data);
+ });
+ }
+ for (const camera in CAMERAS) {
+ // Make unreliable to reduce network spam.
+ this.connection.addHandler(
+ CAMERAS[camera], 'frc971.vision.TargetMap', (data) => {
+ this.handleCameraTargetMap(camera, data);
+ });
+ }
this.connection.addHandler(
'/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
@@ -161,8 +263,43 @@
(data) => {
this.handleSuperstructureStatus(data)
});
+ this.connection.addHandler(
+ '/aos', 'aos.message_bridge.ServerStatistics',
+ (data) => {this.handleServerStatistics(data)});
+ this.connection.addHandler(
+ '/aos', 'aos.message_bridge.ClientStatistics',
+ (data) => {this.handleClientStatistics(data)});
});
}
+ private handleLocalizerDebug(camera: number, data: Uint8Array): void {
+ const now = Date.now() / 1000.0;
+
+ const fbBuffer = new ByteBuffer(data);
+ this.localizerImageMatches.set(
+ now, Visualization.getRootAsVisualization(fbBuffer));
+
+ const debug = this.localizerImageMatches.get(now);
+
+ if (debug.statistics()) {
+ if ((debug.statistics().rejectionReasonsLength() + 1) ==
+ this.rejectionReasonCells.length) {
+ for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
+ ++ii) {
+ this.rejectionReasonCells[ii][camera].innerHTML =
+ debug.statistics().rejectionReasons(ii).count().toString();
+ }
+ } else {
+ console.error('Unexpected number of rejection reasons in counter.');
+ }
+ }
+ }
+
+ private handleCameraTargetMap(pi: string, data: Uint8Array): void {
+ const fbBuffer = new ByteBuffer(data);
+ const targetMap = TargetMap.getRootAsTargetMap(fbBuffer);
+ this.rejectionReasonCells[this.rejectionReasonCells.length - 1][pi]
+ .innerHTML = targetMap.rejections().toString();
+ }
private handleDrivetrainStatus(data: Uint8Array): void {
const fbBuffer = new ByteBuffer(data);
@@ -189,6 +326,67 @@
this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
}
+ private populateNodeConnections(nodeName: string): void {
+ const row = document.createElement('div');
+ this.messageBridgeDiv.appendChild(row);
+ const nodeDiv = document.createElement('div');
+ nodeDiv.innerHTML = nodeName;
+ row.appendChild(nodeDiv);
+ const clientDiv = document.createElement('div');
+ clientDiv.innerHTML = 'N/A';
+ row.appendChild(clientDiv);
+ const serverDiv = document.createElement('div');
+ serverDiv.innerHTML = 'N/A';
+ row.appendChild(serverDiv);
+ this.serverStatuses.set(nodeName, serverDiv);
+ this.clientStatuses.set(nodeName, clientDiv);
+ }
+
+ private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
+ void {
+ if (state === ConnectionState.CONNECTED) {
+ element.innerHTML = ConnectionState[state];
+ element.classList.remove('faulted');
+ element.classList.add('connected');
+ } else {
+ element.innerHTML = ConnectionState[state];
+ element.classList.remove('connected');
+ element.classList.add('faulted');
+ }
+ }
+
+ private handleServerStatistics(data: Uint8Array): void {
+ const fbBuffer = new ByteBuffer(data);
+ const serverStatistics =
+ ServerStatistics.getRootAsServerStatistics(fbBuffer);
+
+ for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
+ const connection = serverStatistics.connections(ii);
+ const nodeName = connection.node().name();
+ if (!this.serverStatuses.has(nodeName)) {
+ this.populateNodeConnections(nodeName);
+ }
+ this.setCurrentNodeState(
+ this.serverStatuses.get(nodeName), connection.state());
+ }
+ }
+
+ private handleClientStatistics(data: Uint8Array): void {
+ const fbBuffer = new ByteBuffer(data);
+ const clientStatistics =
+ ClientStatistics.getRootAsClientStatistics(fbBuffer);
+
+ for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
+ const connection = clientStatistics.connections(ii);
+ const nodeName = connection.node().name();
+ if (!this.clientStatuses.has(nodeName)) {
+ this.populateNodeConnections(nodeName);
+ }
+ this.setCurrentNodeState(
+ this.clientStatuses.get(nodeName), connection.state());
+ }
+ }
+
drawField(): void {
const ctx = this.canvas.getContext('2d');
ctx.save();
@@ -292,6 +490,9 @@
this.reset();
this.drawField();
+ // Draw the matches with debugging information from the localizer.
+ const now = Date.now() / 1000.0;
+
if (this.superstructureStatus) {
this.superstructureState.innerHTML =
SuperstructureState[this.superstructureStatus.state()];
@@ -323,6 +524,12 @@
this.setBoolean(this.extend_ready_for_catapult_transfer, this.superstructureStatus.extendReadyForCatapultTransfer());
+ this.setBoolean(this.turret_in_range, this.superstructureStatus.shooter().turretInRange())
+
+ this.setBoolean(this.altitude_in_range, this.superstructureStatus.shooter().altitudeInRange())
+
+ this.setBoolean(this.altitude_above_min_angle, this.superstructureStatus.shooter().altitudeAboveMinAngle())
+
if (this.superstructureStatus.shooter() &&
this.superstructureStatus.shooter().aimer()) {
this.turret_position.innerHTML = this.superstructureStatus.shooter()
@@ -536,6 +743,37 @@
this.drawRobot(
this.localizerOutput.x(), this.localizerOutput.y(),
this.localizerOutput.theta());
+
+ this.imagesAcceptedCounter.innerHTML =
+ this.localizerOutput.imageAcceptedCount().toString();
+ }
+
+ for (const [time, value] of this.localizerImageMatches) {
+ const age = now - time;
+ const kRemovalAge = 1.0;
+ if (age > kRemovalAge) {
+ this.localizerImageMatches.delete(time);
+ continue;
+ }
+ const kMaxImageAlpha = 0.5;
+ const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
+ for (let i = 0; i < value.targetsLength(); i++) {
+ const imageDebug = value.targets(i);
+ const x = imageDebug.impliedRobotX();
+ const y = imageDebug.impliedRobotY();
+ const theta = imageDebug.impliedRobotTheta();
+ const cameraX = imageDebug.cameraX();
+ const cameraY = imageDebug.cameraY();
+ const cameraTheta = imageDebug.cameraTheta();
+ const accepted = imageDebug.accepted();
+ // Make camera readings fade over time.
+ const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
+ const dashed = false;
+ const cameraRgb = CAMERA_COLORS[imageDebug.camera()];
+ const cameraRgba = cameraRgb + alpha;
+ this.drawRobot(x, y, theta, cameraRgba, dashed);
+ this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
+ }
}
window.requestAnimationFrame(() => this.draw());
diff --git a/y2024/www/styles.css b/y2024/www/styles.css
index 39b7519..a11187b 100644
--- a/y2024/www/styles.css
+++ b/y2024/www/styles.css
@@ -65,7 +65,8 @@
#vision_readouts > div > div {
display: table-cell;
padding: 5px;
- text-align: right;
+ text-align: left;
+ font: small;
}
#message_bridge_status > div {
diff --git a/y2024/y2024_imu.json b/y2024/y2024_imu.json
index 155edb4..62254e1 100644
--- a/y2024/y2024_imu.json
+++ b/y2024/y2024_imu.json
@@ -2,6 +2,12 @@
"channels": [
{
"name": "/imu/aos",
+ "type": "aos.util.FilesystemStatus",
+ "source_node": "imu",
+ "frequency": 2
+ },
+ {
+ "name": "/imu/aos",
"type": "aos.JoystickState",
"source_node": "imu",
"frequency": 100,
@@ -35,8 +41,8 @@
"type": "aos.timing.Report",
"source_node": "imu",
"frequency": 50,
- "num_senders": 20,
- "max_size": 6184
+ "num_senders": 30,
+ "max_size": 8192
},
{
"name": "/imu/aos",
@@ -274,7 +280,8 @@
"name": "/can/cana",
"type": "frc971.can_logger.CanFrame",
"source_node": "imu",
- "frequency": 6000,
+ "frequency": 9000,
+ "channel_storage_duration": 7000000000,
"num_senders": 2,
"max_size": 200
},
@@ -339,6 +346,22 @@
]
},
{
+ "name": "irq_affinity",
+ "executable_name": "irq_affinity",
+ "user": "root",
+ "args": ["--user=pi", "--irq_config=orin_irq_config.json"],
+ "nodes": [
+ "imu"
+ ]
+ },
+ {
+ "name": "filesystem_monitor",
+ "executable_name": "filesystem_monitor",
+ "nodes": [
+ "imu"
+ ]
+ },
+ {
"name": "joystick_republish",
"executable_name": "joystick_republish",
"user": "pi",
@@ -385,7 +408,7 @@
"name": "can_translator",
"executable_name": "can_translator",
"args": [
- "--channel=/can/canb"
+ "--channel=/can/cana"
],
"nodes": [
"imu"
@@ -480,7 +503,6 @@
"name": "argus_camera0",
"executable_name": "argus_camera",
"args": [
- "--enable_ftrace",
"--camera=0",
"--channel=/camera0"
],
@@ -493,7 +515,6 @@
"name": "argus_camera1",
"executable_name": "argus_camera",
"args": [
- "--enable_ftrace",
"--camera=1",
"--channel=/camera1"
],
diff --git a/y2024/y2024_orin1.json b/y2024/y2024_orin1.json
index a9f75d5..0b2c6af 100644
--- a/y2024/y2024_orin1.json
+++ b/y2024/y2024_orin1.json
@@ -2,10 +2,16 @@
"channels": [
{
"name": "/orin1/aos",
+ "type": "aos.util.FilesystemStatus",
+ "source_node": "orin1",
+ "frequency": 2
+ },
+ {
+ "name": "/orin1/aos",
"type": "aos.timing.Report",
"source_node": "orin1",
"frequency": 50,
- "num_senders": 20,
+ "num_senders": 30,
"max_size": 8192
},
{
@@ -261,7 +267,14 @@
"name": "irq_affinity",
"executable_name": "irq_affinity",
"user": "root",
- "args": ["--user=pi"],
+ "args": ["--user=pi", "--irq_config=orin_irq_config.json"],
+ "nodes": [
+ "orin1"
+ ]
+ },
+ {
+ "name": "filesystem_monitor",
+ "executable_name": "filesystem_monitor",
"nodes": [
"orin1"
]
@@ -344,7 +357,6 @@
"name": "argus_camera0",
"executable_name": "argus_camera",
"args": [
- "--enable_ftrace",
"--camera=0",
"--channel=/camera0",
],
@@ -357,7 +369,6 @@
"name": "argus_camera1",
"executable_name": "argus_camera",
"args": [
- "--enable_ftrace",
"--camera=1",
"--channel=/camera1",
],