blob: a820215aeeec65354660ec7351126f8314c8293c [file] [log] [blame]
#ifndef AOS_CONTROLS_CONTROL_LOOP_TEST_H_
#define AOS_CONTROLS_CONTROL_LOOP_TEST_H_
#include <chrono>
#include <string_view>
#include "aos/events/simulated_event_loop.h"
#include "aos/flatbuffers.h"
#include "aos/json_to_flatbuffer.h"
#include "aos/testing/test_logging.h"
#include "aos/time/time.h"
#include "frc971/input/joystick_state_generated.h"
#include "frc971/input/robot_state_generated.h"
#include "glog/logging.h"
#include "gtest/gtest.h"
namespace frc971 {
namespace testing {
// Handles setting up the environment that all control loops need to actually
// run.
// This includes sending the queue messages and Clear()ing the queues when
// appropriate.
// It also includes dealing with ::aos::time.
template <typename TestBaseClass>
class ControlLoopTestTemplated : public TestBaseClass {
public:
ControlLoopTestTemplated(
aos::FlatbufferDetachedBuffer<aos::Configuration> configuration,
::std::chrono::nanoseconds dt = kTimeTick)
: configuration_(std::move(configuration)),
event_loop_factory_(&configuration_.message()),
dt_(dt),
robot_status_event_loop_(MakeEventLoop(
"robot_status",
aos::configuration::MultiNode(event_loop_factory_.configuration())
? aos::configuration::GetNode(
event_loop_factory_.configuration(), "roborio")
: nullptr)) {
aos::testing::EnableTestLogging();
robot_state_sender_ =
robot_status_event_loop_->MakeSender<::aos::RobotState>("/aos");
joystick_state_sender_ =
robot_status_event_loop_->MakeSender<::aos::JoystickState>("/aos");
// Schedule the robot status send 1 nanosecond before the loop runs.
send_robot_state_phased_loop_ = robot_status_event_loop_->AddPhasedLoop(
[this](int) { SendRobotState(); }, dt_,
dt - ::std::chrono::nanoseconds(1));
send_joystick_state_timer_ =
robot_status_event_loop_->AddTimer([this]() { SendJoystickState(); });
robot_status_event_loop_->OnRun([this]() {
send_joystick_state_timer_->Setup(
robot_status_event_loop_->monotonic_now(), dt_);
});
}
virtual ~ControlLoopTestTemplated() {}
void set_team_id(uint16_t team_id) { team_id_ = team_id; }
uint16_t team_id() const { return team_id_; }
// Sets the enabled/disabled bit and (potentially) rebroadcasts the robot
// state messages.
void SetEnabled(bool enabled) {
if (enabled_ != enabled) {
enabled_ = enabled;
SendJoystickState();
SendRobotState();
send_joystick_state_timer_->Setup(
robot_status_event_loop_->monotonic_now(), dt_);
}
}
// Simulate a reset of the process reading sensors, which tells loops that all
// index counts etc will be reset.
void SimulateSensorReset() { ++reader_pid_; }
// Sets the battery voltage in robot_state.
void set_battery_voltage(double battery_voltage) {
battery_voltage_ = battery_voltage;
}
::std::unique_ptr<::aos::EventLoop> MakeEventLoop(
std::string_view name, const aos::Node *node = nullptr) {
return event_loop_factory_.MakeEventLoop(name, node);
}
void set_send_delay(std::chrono::nanoseconds send_delay) {
event_loop_factory_.set_send_delay(send_delay);
}
void RunFor(aos::monotonic_clock::duration duration) {
event_loop_factory_.RunFor(duration);
}
::aos::monotonic_clock::time_point monotonic_now() {
return robot_status_event_loop_->monotonic_now();
}
::std::chrono::nanoseconds dt() const { return dt_; }
const aos::Configuration *configuration() const {
return &configuration_.message();
}
aos::SimulatedEventLoopFactory *event_loop_factory() {
return &event_loop_factory_;
}
private:
// Sends out all of the required queue messages.
void SendJoystickState() {
if (monotonic_now() >= kDSPacketTime + last_ds_time_ ||
last_enabled_ != enabled_) {
auto new_state = joystick_state_sender_.MakeBuilder();
::aos::JoystickState::Builder builder =
new_state.template MakeBuilder<::aos::JoystickState>();
builder.add_fake(true);
builder.add_enabled(enabled_);
builder.add_autonomous(false);
builder.add_team_id(team_id_);
CHECK_EQ(new_state.Send(builder.Finish()),
aos::RawSender::Error::kOk);
last_ds_time_ = monotonic_now();
last_enabled_ = enabled_;
}
}
bool last_enabled_ = false;
void SendRobotState() {
auto new_state = robot_state_sender_.MakeBuilder();
::aos::RobotState::Builder builder =
new_state.template MakeBuilder<::aos::RobotState>();
builder.add_reader_pid(reader_pid_);
builder.add_outputs_enabled(enabled_);
builder.add_browned_out(false);
builder.add_is_3v3_active(true);
builder.add_is_5v_active(true);
builder.add_voltage_3v3(3.3);
builder.add_voltage_5v(5.0);
builder.add_voltage_roborio_in(battery_voltage_);
builder.add_voltage_battery(battery_voltage_);
new_state.CheckOk(new_state.Send(builder.Finish()));
}
static constexpr ::std::chrono::microseconds kTimeTick{5000};
static constexpr ::std::chrono::milliseconds kDSPacketTime{20};
aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_;
aos::SimulatedEventLoopFactory event_loop_factory_;
const ::std::chrono::nanoseconds dt_;
uint16_t team_id_ = 971;
int32_t reader_pid_ = 1;
double battery_voltage_ = 12.4;
::aos::monotonic_clock::time_point last_ds_time_ =
::aos::monotonic_clock::min_time;
bool enabled_ = false;
::std::unique_ptr<::aos::EventLoop> robot_status_event_loop_;
::aos::Sender<::aos::RobotState> robot_state_sender_;
::aos::Sender<::aos::JoystickState> joystick_state_sender_;
::aos::PhasedLoopHandler *send_robot_state_phased_loop_ = nullptr;
::aos::TimerHandler *send_joystick_state_timer_ = nullptr;
};
typedef ControlLoopTestTemplated<::testing::Test> ControlLoopTest;
template <typename TestBaseClass>
constexpr ::std::chrono::microseconds
ControlLoopTestTemplated<TestBaseClass>::kTimeTick;
template <typename TestBaseClass>
constexpr ::std::chrono::milliseconds
ControlLoopTestTemplated<TestBaseClass>::kDSPacketTime;
} // namespace testing
} // namespace frc971
#endif // AOS_CONTROLS_CONTROL_LOOP_TEST_H_