blob: 6f5bf3d52a32a7a8af3c315152837c974ec251d9 [file] [log] [blame]
James Kuszmaul3431d622019-02-17 17:07:44 -08001#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_FIELD_ESTIMATOR_H_
2#define FRC971_CONTROL_LOOPS_DRIVETRAIN_FIELD_ESTIMATOR_H_
3
Alex Perrycb7da4b2019-08-28 19:35:56 -07004#include "aos/events/event_loop.h"
James Kuszmaul3431d622019-02-17 17:07:44 -08005#include "frc971/control_loops/drivetrain/drivetrain_config.h"
James Kuszmaul3c5b4d32020-02-11 17:22:14 -08006#include "frc971/control_loops/drivetrain/drivetrain_status_generated.h"
James Kuszmaul3431d622019-02-17 17:07:44 -08007#include "frc971/control_loops/drivetrain/hybrid_ekf.h"
James Kuszmaul5bc6fc92019-03-01 21:50:06 -08008#include "frc971/control_loops/pose.h"
James Kuszmaul3431d622019-02-17 17:07:44 -08009
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -080010namespace frc971::control_loops::drivetrain {
James Kuszmaul3431d622019-02-17 17:07:44 -080011
James Kuszmaul5bc6fc92019-03-01 21:50:06 -080012// An interface for target selection. This provides an object that will take in
13// state updates and then determine what poes we should be driving to.
14class TargetSelectorInterface {
15 public:
James Kuszmaul055fe762023-03-03 21:14:01 -080016 typedef RobotSide Side;
James Kuszmaulc29f4572023-02-25 17:02:33 -080017 virtual ~TargetSelectorInterface() {}
James Kuszmaul5bc6fc92019-03-01 21:50:06 -080018 // Take the state as [x, y, theta, left_vel, right_vel]
19 // If unable to determine what target to go for, returns false. If a viable
20 // target is selected, then returns true and sets target_pose.
James Kuszmaulc4bd1612019-03-10 11:35:06 -070021 // command_speed is the goal speed of the current drivetrain, generally
22 // generated from the throttle and meant to signify driver intent.
James Kuszmaul5bc6fc92019-03-01 21:50:06 -080023 // TODO(james): Some implementations may also want a drivetrain goal so that
24 // driver intent can be divined more directly.
James Kuszmaulc4bd1612019-03-10 11:35:06 -070025 virtual bool UpdateSelection(const ::Eigen::Matrix<double, 5, 1> &state,
26 double command_speed) = 0;
James Kuszmaul5bc6fc92019-03-01 21:50:06 -080027 // Gets the current target pose. Should only be called if UpdateSelection has
28 // returned true.
29 virtual TypedPose<double> TargetPose() const = 0;
James Kuszmaul4f05d792023-03-05 14:14:18 -080030 // For the "radii" below, we have two possible modes:
31 // 1) Akin to 2019, we can place with either edge of the game piece, so
32 // the line following code will have to automatically detect which edge
33 // (right or left) to aim to have intersect the target.
34 // 2) As in 2023, the game piece itself is offset in the robot and so we care
35 // which of left vs. right we are using.
36 // In situation (1), SignedRadii() should return false and the *Radius()
37 // functions should return a non-negative number (technically I think the
38 // math may work for negative numbers, but may have weird implications
39 // physically...). For (2) SignedRadii()
40 // should return true and the sign of the *Radius() functions will be
41 // respected by the line following code.
42 virtual bool SignedRadii() const = 0;
James Kuszmaule093f512019-03-20 06:14:05 -070043 // The "radius" of the target--for y2019, we wanted to drive in so that a disc
44 // with radius r would hit the plane of the target at an offset of exactly r
45 // from the TargetPose--this is distinct from wanting the center of the
46 // robot to project straight onto the center of the target.
47 virtual double TargetRadius() const = 0;
James Kuszmaul4f05d792023-03-05 14:14:18 -080048 // the "radius" of the robot/game piece to place.
49 virtual double GamePieceRadius() const = 0;
James Kuszmaul055fe762023-03-03 21:14:01 -080050 // Which direction we want the robot to drive to get to the target.
51 virtual Side DriveDirection() const = 0;
James Kuszmaulc0163792023-03-04 14:13:31 -080052 // Indicates that the line following *must* drive to the currently selected
53 // target, regardless of any hysteresis we try to use to protect the driver.
54 virtual bool ForceReselectTarget() const = 0;
James Kuszmaul5bc6fc92019-03-01 21:50:06 -080055};
56
James Kuszmaul3431d622019-02-17 17:07:44 -080057// Defines an interface for classes that provide field-global localization.
58class LocalizerInterface {
James Kuszmaul5398fae2020-02-17 16:44:03 -080059 public:
James Kuszmaul2db5d882020-02-16 16:49:26 -080060 typedef HybridEkf<double> Ekf;
61 typedef typename Ekf::StateIdx StateIdx;
James Kuszmaul5398fae2020-02-17 16:44:03 -080062
James Kuszmaul531609d2020-02-18 17:12:23 -080063 virtual ~LocalizerInterface() {}
64
James Kuszmaul3431d622019-02-17 17:07:44 -080065 // Perform a single step of the filter, using the information that is
66 // available on every drivetrain iteration.
67 // The user should pass in the U that the real system experienced from the
68 // previous timestep until now; internally, any filters will first perform a
69 // prediction step to get the estimate at time now, and then will apply
70 // corrections based on the encoder/gyro/accelerometer values from time now.
71 // TODO(james): Consider letting implementations subscribe to the sensor
72 // values themselves, and then only passing in U. This requires more
73 // coordination on timing, however.
74 virtual void Update(const ::Eigen::Matrix<double, 2, 1> &U,
75 ::aos::monotonic_clock::time_point now,
76 double left_encoder, double right_encoder,
James Kuszmaul3c5b4d32020-02-11 17:22:14 -080077 double gyro_rate, const Eigen::Vector3d &accel) = 0;
James Kuszmaulbcd96fc2020-10-12 20:29:32 -070078 virtual void Reset(aos::monotonic_clock::time_point t,
79 const Ekf::State &state) = 0;
James Kuszmaulef428a02019-03-02 22:19:41 -080080 // Reset the absolute position of the estimator.
James Kuszmaul074429e2019-03-23 16:01:49 -070081 virtual void ResetPosition(::aos::monotonic_clock::time_point t, double x,
James Kuszmaul518640d2019-04-13 15:50:50 -070082 double y, double theta, double theta_uncertainty,
83 bool reset_theta) = 0;
James Kuszmaul3c5b4d32020-02-11 17:22:14 -080084 flatbuffers::Offset<LocalizerState> PopulateStatus(
85 flatbuffers::FlatBufferBuilder *fbb) {
86 LocalizerState::Builder builder(*fbb);
87 builder.add_x(x());
88 builder.add_y(y());
89 builder.add_theta(theta());
90 builder.add_left_velocity(left_velocity());
91 builder.add_right_velocity(right_velocity());
92 builder.add_left_encoder(left_encoder());
93 builder.add_right_encoder(right_encoder());
94 builder.add_left_voltage_error(left_voltage_error());
95 builder.add_right_voltage_error(right_voltage_error());
96 builder.add_angular_error(angular_error());
97 builder.add_longitudinal_velocity_offset(longitudinal_velocity_offset());
98 builder.add_lateral_velocity(lateral_velocity());
99 return builder.Finish();
100 }
James Kuszmaul2db5d882020-02-16 16:49:26 -0800101 virtual Ekf::State Xhat() const = 0;
James Kuszmaul3431d622019-02-17 17:07:44 -0800102 // There are several subtly different norms floating around for state
James Kuszmaul3c5b4d32020-02-11 17:22:14 -0800103 // matrices. In order to avoid that mess, we just provide direct accessors for
James Kuszmaul3431d622019-02-17 17:07:44 -0800104 // the values that most people care about.
James Kuszmaul2db5d882020-02-16 16:49:26 -0800105 double x() const { return Xhat()(StateIdx::kX); }
106 double y() const { return Xhat()(StateIdx::kY); }
107 double theta() const { return Xhat()(StateIdx::kTheta); }
108 double left_velocity() const { return Xhat()(StateIdx::kLeftVelocity); }
109 double right_velocity() const { return Xhat()(StateIdx::kRightVelocity); }
110 double left_encoder() const { return Xhat()(StateIdx::kLeftEncoder); }
111 double right_encoder() const { return Xhat()(StateIdx::kRightEncoder); }
112 double left_voltage_error() const {
113 return Xhat()(StateIdx::kLeftVoltageError);
114 }
115 double right_voltage_error() const {
116 return Xhat()(StateIdx::kRightVoltageError);
117 }
118 double angular_error() const { return Xhat()(StateIdx::kAngularError); }
119 double longitudinal_velocity_offset() const {
120 return Xhat()(StateIdx::kLongitudinalVelocityOffset);
121 }
122 double lateral_velocity() const { return Xhat()(StateIdx::kLateralVelocity); }
123
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800124 virtual TargetSelectorInterface *target_selector() = 0;
125};
126
127// A target selector, primarily for testing purposes, that just lets a user
128// manually set the target selector state.
129class TrivialTargetSelector : public TargetSelectorInterface {
130 public:
James Kuszmaulc29f4572023-02-25 17:02:33 -0800131 virtual ~TrivialTargetSelector() {}
James Kuszmaulc4bd1612019-03-10 11:35:06 -0700132 bool UpdateSelection(const ::Eigen::Matrix<double, 5, 1> &, double) override {
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800133 return has_target_;
134 }
135 TypedPose<double> TargetPose() const override { return pose_; }
James Kuszmaul4f05d792023-03-05 14:14:18 -0800136 bool SignedRadii() const override { return signed_radii_; }
James Kuszmaule093f512019-03-20 06:14:05 -0700137 double TargetRadius() const override { return target_radius_; }
James Kuszmaul4f05d792023-03-05 14:14:18 -0800138 double GamePieceRadius() const override { return game_piece_radius_; }
James Kuszmaul055fe762023-03-03 21:14:01 -0800139 Side DriveDirection() const override { return drive_direction_; }
James Kuszmaulc0163792023-03-04 14:13:31 -0800140 bool ForceReselectTarget() const override { return force_reselect_; }
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800141
142 void set_pose(const TypedPose<double> &pose) { pose_ = pose; }
James Kuszmaule093f512019-03-20 06:14:05 -0700143 void set_target_radius(double radius) { target_radius_ = radius; }
James Kuszmaul4f05d792023-03-05 14:14:18 -0800144 void set_game_piece_radius(double radius) { game_piece_radius_ = radius; }
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800145 void set_has_target(bool has_target) { has_target_ = has_target; }
James Kuszmaul055fe762023-03-03 21:14:01 -0800146 void set_drive_direction(Side side) { drive_direction_ = side; }
James Kuszmaulc0163792023-03-04 14:13:31 -0800147 void set_force_reselect(bool force_reselect) {
148 force_reselect_ = force_reselect;
149 }
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800150 bool has_target() const { return has_target_; }
151
152 private:
153 bool has_target_ = true;
James Kuszmaulc0163792023-03-04 14:13:31 -0800154 bool force_reselect_ = false;
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800155 TypedPose<double> pose_;
James Kuszmaul4f05d792023-03-05 14:14:18 -0800156 bool signed_radii_ = false;
James Kuszmaule093f512019-03-20 06:14:05 -0700157 double target_radius_ = 0.0;
James Kuszmaul4f05d792023-03-05 14:14:18 -0800158 double game_piece_radius_ = 0.0;
James Kuszmaul055fe762023-03-03 21:14:01 -0800159 Side drive_direction_ = Side::DONT_CARE;
James Kuszmaul3431d622019-02-17 17:07:44 -0800160};
161
162// Uses the generic HybridEkf implementation to provide a basic field estimator.
163// This provides no method for using cameras or the such to get global
164// measurements and just assumes that you can dead-reckon perfectly.
165class DeadReckonEkf : public LocalizerInterface {
James Kuszmaul3431d622019-02-17 17:07:44 -0800166 public:
Austin Schuh9fe68f72019-08-10 19:32:03 -0700167 DeadReckonEkf(::aos::EventLoop *event_loop,
168 const DrivetrainConfig<double> &dt_config)
169 : ekf_(dt_config) {
170 event_loop->OnRun([this, event_loop]() {
171 ekf_.ResetInitialState(event_loop->monotonic_now(), Ekf::State::Zero(),
172 ekf_.P());
173 });
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800174 target_selector_.set_has_target(false);
James Kuszmaul3431d622019-02-17 17:07:44 -0800175 }
176
James Kuszmaul531609d2020-02-18 17:12:23 -0800177 virtual ~DeadReckonEkf() {}
178
James Kuszmaul3431d622019-02-17 17:07:44 -0800179 void Update(const ::Eigen::Matrix<double, 2, 1> &U,
Austin Schuh9fe68f72019-08-10 19:32:03 -0700180 ::aos::monotonic_clock::time_point now, double left_encoder,
181 double right_encoder, double gyro_rate,
James Kuszmaul3c5b4d32020-02-11 17:22:14 -0800182 const Eigen::Vector3d &accel) override {
183 ekf_.UpdateEncodersAndGyro(left_encoder, right_encoder, gyro_rate, U, accel,
184 now);
James Kuszmaul3431d622019-02-17 17:07:44 -0800185 }
186
James Kuszmaulbcd96fc2020-10-12 20:29:32 -0700187 void Reset(aos::monotonic_clock::time_point t,
188 const HybridEkf<double>::State &state) override {
189 ekf_.ResetInitialState(t, state, ekf_.P());
190 }
191
James Kuszmaul074429e2019-03-23 16:01:49 -0700192 void ResetPosition(::aos::monotonic_clock::time_point t, double x, double y,
James Kuszmaul518640d2019-04-13 15:50:50 -0700193 double theta, double /*theta_override*/,
194 bool /*reset_theta*/) override {
James Kuszmaulfedc4612019-03-10 11:24:51 -0700195 const double left_encoder = ekf_.X_hat(StateIdx::kLeftEncoder);
196 const double right_encoder = ekf_.X_hat(StateIdx::kRightEncoder);
James Kuszmaul3c5b4d32020-02-11 17:22:14 -0800197 ekf_.ResetInitialState(t,
198 (Ekf::State() << x, y, theta, left_encoder, 0,
199 right_encoder, 0, 0, 0, 0, 0, 0)
200 .finished(),
James Kuszmaul074429e2019-03-23 16:01:49 -0700201 ekf_.P());
James Kuszmaul2db5d882020-02-16 16:49:26 -0800202 }
James Kuszmaulef428a02019-03-02 22:19:41 -0800203
James Kuszmaul2db5d882020-02-16 16:49:26 -0800204 Ekf::State Xhat() const override { return ekf_.X_hat(); }
James Kuszmaul3431d622019-02-17 17:07:44 -0800205
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800206 TrivialTargetSelector *target_selector() override {
207 return &target_selector_;
208 }
209
James Kuszmaul3431d622019-02-17 17:07:44 -0800210 private:
211 Ekf ekf_;
James Kuszmaul5bc6fc92019-03-01 21:50:06 -0800212 TrivialTargetSelector target_selector_;
James Kuszmaul3431d622019-02-17 17:07:44 -0800213};
214
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -0800215} // namespace frc971::control_loops::drivetrain
James Kuszmaul3431d622019-02-17 17:07:44 -0800216
217#endif // FRC971_CONTROL_LOOPS_DRIVETRAIN_FIELD_ESTIMATOR_H_