Add fetcher-side utilities for constants code
This make it possible to use the ConstantsFetcher<> to actually get at
published constants easily.
Change-Id: Ib5a7b91ae69b91a221ecef36f1bdc117b89e9fc4
Signed-off-by: James Kuszmaul <jabukuszmaul+collab@gmail.com>
diff --git a/frc971/constants/BUILD b/frc971/constants/BUILD
index 891067c..c17dc31 100644
--- a/frc971/constants/BUILD
+++ b/frc971/constants/BUILD
@@ -4,10 +4,12 @@
"constants_sender_lib.h",
],
target_compatible_with = ["@platforms//os:linux"],
+ visibility = ["//visibility:public"],
deps = [
"//aos:flatbuffer_merge",
"//aos:json_to_flatbuffer",
"//aos/events:event_loop",
+ "//aos/events:shm_event_loop",
"//aos/network:team_number",
"@com_github_gflags_gflags//:gflags",
"@com_github_google_glog//:glog",
diff --git a/frc971/constants/constants_sender_lib.h b/frc971/constants/constants_sender_lib.h
index 92c14fa..48cacb5 100644
--- a/frc971/constants/constants_sender_lib.h
+++ b/frc971/constants/constants_sender_lib.h
@@ -2,6 +2,7 @@
#define FRC971_CONSTANTS_CONSTANTS_SENDER_H_
#include "aos/events/event_loop.h"
+#include "aos/events/shm_event_loop.h"
#include "aos/flatbuffer_merge.h"
#include "aos/json_to_flatbuffer.h"
#include "aos/network/team_number.h"
@@ -27,11 +28,9 @@
constants_path_(constants_path),
event_loop_(event_loop),
sender_(event_loop_->MakeSender<ConstantsData>(channel_name_)) {
- event_loop->OnRun([this]() {
- typename aos::Sender<ConstantsData>::Builder builder =
- sender_.MakeBuilder();
- builder.CheckOk(builder.Send(GetConstantsForTeamNumber(builder.fbb())));
- });
+ typename aos::Sender<ConstantsData>::Builder builder =
+ sender_.MakeBuilder();
+ builder.CheckOk(builder.Send(GetConstantsForTeamNumber(builder.fbb())));
}
private:
@@ -63,6 +62,56 @@
aos::Sender<ConstantsData> sender_;
};
+// This class fetches the current constants for the device, with appropriate
+// CHECKs to ensure that (a) the constants never change and (b) that the
+// constants are always available. This can be paired with WaitForConstants to
+// create the conditions for (b). In simulation, the constants should simply be
+// sent before starting up other EventLoops.
+template <typename ConstantsData>
+class ConstantsFetcher {
+ public:
+ ConstantsFetcher(aos::EventLoop *event_loop,
+ std::string_view channel = "/constants")
+ : fetcher_(event_loop->MakeFetcher<ConstantsData>(channel)) {
+ CHECK(fetcher_.Fetch())
+ << "Constants information must be available at startup.";
+ event_loop->MakeNoArgWatcher<ConstantsData>(channel, []() {
+ LOG(FATAL)
+ << "Don't know how to handle changes to constants information.";
+ });
+ }
+
+ const ConstantsData& constants() const {
+ return *fetcher_.get();
+ }
+
+ private:
+ aos::Fetcher<ConstantsData> fetcher_;
+};
+
+// Blocks until data is available on the requested channel using a ShmEventLoop.
+// This is for use during initialization in C++ binaries so that we can delay
+// initialization until everything is available. This allows applications to
+// depend on constants data during their initialization.
+template <typename ConstantsData>
+void WaitForConstants(const aos::Configuration *config,
+ std::string_view channel = "/constants") {
+ aos::ShmEventLoop event_loop(config);
+ aos::Fetcher fetcher = event_loop.MakeFetcher<ConstantsData>(channel);
+ event_loop.MakeNoArgWatcher<ConstantsData>(
+ channel, [&event_loop]() { event_loop.Exit(); });
+ event_loop.OnRun([&event_loop, &fetcher]() {
+ // If the constants were already published, we don't need to wait for them.
+ if (fetcher.Fetch()) {
+ event_loop.Exit();
+ }
+ });
+ LOG(INFO) << "Waiting for constants data on " << channel << " "
+ << ConstantsData::GetFullyQualifiedName();
+ event_loop.Run();
+ LOG(INFO) << "Got constants data.";
+}
+
} // namespace frc971::constants
#endif // FRC971_CONSTANTS_CONSTANTS_SENDER_H_
diff --git a/frc971/constants/constants_sender_test.cc b/frc971/constants/constants_sender_test.cc
index 3edc320..1441767 100644
--- a/frc971/constants/constants_sender_test.cc
+++ b/frc971/constants/constants_sender_test.cc
@@ -40,11 +40,10 @@
ConstantSender<testdata::ConstantsData, testdata::ConstantsList> test971(
constants_sender_event_loop_.get(),
"frc971/constants/testdata/test_constants.json", "/constants");
- test_event_loop->MakeWatcher("/constants",
- [](const testdata::ConstantsData &data) {
- EXPECT_EQ(data.max_roller_voltage(), 12);
- EXPECT_EQ(data.min_roller_voltage(), -12);
- });
+ ConstantsFetcher<testdata::ConstantsData> fetcher(test_event_loop.get());
+ EXPECT_EQ(fetcher.constants().max_roller_voltage(), 12);
+ EXPECT_EQ(fetcher.constants().min_roller_voltage(), -12);
+ // Ensure that the watcher in ConstantsFetcher never triggers.
event_loop_factory_.RunFor(std::chrono::seconds(1));
}
@@ -57,14 +56,42 @@
ConstantSender<testdata::ConstantsData, testdata::ConstantsList> test971(
constants_sender_event_loop_.get(),
"frc971/constants/testdata/test_constants.json", 9971, "/constants");
- test_event_loop->MakeWatcher("/constants",
- [](const testdata::ConstantsData &data) {
- EXPECT_EQ(data.max_roller_voltage(), 6);
- EXPECT_EQ(data.min_roller_voltage(), -6);
- });
+ ConstantsFetcher<testdata::ConstantsData> fetcher(test_event_loop.get());
+ EXPECT_EQ(fetcher.constants().max_roller_voltage(), 6);
+ EXPECT_EQ(fetcher.constants().min_roller_voltage(), -6);
event_loop_factory_.RunFor(std::chrono::seconds(1));
}
+// Tests that the ConstantsFetcher dies when there is no data available during
+// construction.
+TEST_F(ConstantSenderTest, NoDataOnStartup) {
+ std::unique_ptr<aos::EventLoop> test_event_loop =
+ event_loop_factory_.MakeEventLoop("constants");
+ EXPECT_DEATH(ConstantsFetcher<testdata::ConstantsData>(test_event_loop.get()),
+ "information must be available at startup");
+}
+
+// Tests that the ConstantsFetcher dies when there is a change to the constants
+// data.
+TEST_F(ConstantSenderTest, DieOnDataUpdate) {
+ std::unique_ptr<aos::EventLoop> test_event_loop =
+ event_loop_factory_.MakeEventLoop("constants");
+ ConstantSender<testdata::ConstantsData, testdata::ConstantsList> test971(
+ constants_sender_event_loop_.get(),
+ "frc971/constants/testdata/test_constants.json", 9971, "/constants");
+ ConstantsFetcher<testdata::ConstantsData> fetcher(test_event_loop.get());
+ auto sender =
+ constants_sender_event_loop_->MakeSender<testdata::ConstantsData>(
+ "/constants");
+ constants_sender_event_loop_->OnRun([&sender]() {
+ auto builder = sender.MakeBuilder();
+ builder.CheckOk(builder.Send(
+ builder.MakeBuilder<testdata::ConstantsData>().Finish()));
+ });
+ EXPECT_DEATH(event_loop_factory_.RunFor(std::chrono::seconds(1)),
+ "changes to constants");
+}
+
// When given a team number that it not recognized we kill the program.
TEST_F(ConstantSenderTest, TeamNotFound) {