blob: 45ca43710fb29ea362274fe6c2f5c8881fd9871f [file] [log] [blame]
#ifndef Y2024_LOCALIZER_LOCALIZER_H_
#define Y2024_LOCALIZER_LOCALIZER_H_
#include <array>
#include <map>
#include "aos/network/message_bridge_client_generated.h"
#include "aos/network/message_bridge_server_generated.h"
#include "frc971/constants/constants_sender_lib.h"
#include "frc971/control_loops/drivetrain/hybrid_ekf.h"
#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
#include "frc971/control_loops/drivetrain/localization/localizer_output_generated.h"
#include "frc971/control_loops/drivetrain/localization/utils.h"
#include "frc971/control_loops/drivetrain/localizer_generated.h"
#include "frc971/imu_reader/imu_watcher.h"
#include "frc971/vision/target_map_generated.h"
#include "y2024/constants/constants_generated.h"
#include "y2024/localizer/status_generated.h"
#include "y2024/localizer/visualization_static.h"
namespace y2024::localizer {
class Localizer {
public:
static constexpr size_t kNumCameras = 4;
using Pose = frc971::control_loops::Pose;
typedef Eigen::Matrix<double, 4, 4> Transform;
typedef frc971::control_loops::drivetrain::HybridEkf<double> HybridEkf;
typedef HybridEkf::State State;
typedef HybridEkf::Output Output;
typedef HybridEkf::Input Input;
typedef HybridEkf::StateIdx StateIdx;
Localizer(aos::EventLoop *event_loop);
private:
class Corrector : public HybridEkf::ExpectedObservationFunctor {
public:
// Indices used for each of the members of the output vector for this
// Corrector.
enum OutputIdx {
kHeading = 0,
kDistance = 1,
kSkew = 2,
};
Corrector(const State &state_at_capture, const Transform &H_field_target,
const Transform &H_robot_camera,
const Transform &H_camera_target);
using HMatrix = Eigen::Matrix<double, Localizer::HybridEkf::kNOutputs,
Localizer::HybridEkf::kNStates>;
Output H(const State &, const Input &) final;
HMatrix DHDX(const State &) final { return H_; }
const Eigen::Vector3d &observed() const { return observed_; }
const Eigen::Vector3d &expected() const { return expected_; }
const Pose &expected_robot_pose() const { return expected_robot_pose_; }
const Pose &expected_camera_pose() const { return expected_camera_; }
const Pose &observed_camera_pose() const { return observed_camera_; }
static Eigen::Vector3d HeadingDistanceSkew(const Pose &relative_pose);
static Corrector CalculateHeadingDistanceSkewH(
const State &state_at_capture, const Transform &H_field_target,
const Transform &H_robot_camera, const Transform &H_camera_target);
static void PopulateMeasurement(const Eigen::Vector3d &vector,
MeasurementStatic *builder) {
builder->set_heading(vector(kHeading));
builder->set_distance(vector(kDistance));
builder->set_skew(vector(kSkew));
}
private:
Corrector(const Pose &expected_robot_pose, const Pose &observed_camera,
const Pose &expected_camera, const Eigen::Vector3d &expected,
const Eigen::Vector3d &observed, const HMatrix &H)
: expected_robot_pose_(expected_robot_pose),
observed_camera_(observed_camera),
expected_camera_(expected_camera),
expected_(expected),
observed_(observed),
H_(H) {}
// For debugging.
const Pose expected_robot_pose_;
const Pose observed_camera_;
const Pose expected_camera_;
// Actually used.
const Eigen::Vector3d expected_;
const Eigen::Vector3d observed_;
const HMatrix H_;
};
// A corrector that just does x/y/theta based corrections rather than doing
// heading/distance/skew corrections.
class XyzCorrector : public HybridEkf::ExpectedObservationFunctor {
public:
// Indices used for each of the members of the output vector for this
// Corrector.
enum OutputIdx {
kX = 0,
kY = 1,
kTheta = 2,
};
XyzCorrector(const State &state_at_capture, const Eigen::Vector3d &Z)
: state_at_capture_(state_at_capture), Z_(Z) {
H_.setZero();
H_(kX, StateIdx::kX) = 1;
H_(kY, StateIdx::kY) = 1;
H_(kTheta, StateIdx::kTheta) = 1;
}
Output H(const State &, const Input &) final;
Eigen::Matrix<double, HybridEkf::kNOutputs, HybridEkf::kNStates> DHDX(
const State &) final {
return H_;
}
private:
Eigen::Matrix<double, HybridEkf::kNOutputs, HybridEkf::kNStates> H_;
const State state_at_capture_;
const Eigen::Vector3d &Z_;
};
struct CameraState {
aos::Sender<VisualizationStatic> debug_sender;
Transform extrinsics = Transform::Zero();
aos::util::ArrayErrorCounter<RejectionReason, RejectionCount>
rejection_counter;
size_t total_candidate_targets = 0;
size_t total_accepted_targets = 0;
};
// Returns true if we should use a lower weight for the specified april tag.
// This is used for tags where we do not trust the placement as much.
bool DeweightAprilTag(uint64_t target_id);
static std::array<CameraState, kNumCameras> MakeCameras(
const Constants &constants, aos::EventLoop *event_loop);
void HandleTarget(int camera_index,
const aos::monotonic_clock::time_point capture_time,
const frc971::vision::TargetPoseFbs &target,
TargetEstimateDebugStatic *debug_builder);
void HandleImu(aos::monotonic_clock::time_point sample_time_pico,
aos::monotonic_clock::time_point sample_time_pi,
std::optional<Eigen::Vector2d> encoders, Eigen::Vector3d gyro,
Eigen::Vector3d accel);
void RejectImage(int camera_index, RejectionReason reason,
TargetEstimateDebugStatic *builder);
void SendOutput();
static flatbuffers::Offset<frc971::control_loops::drivetrain::LocalizerState>
PopulateState(const State &X_hat, flatbuffers::FlatBufferBuilder *fbb);
flatbuffers::Offset<ImuStatus> PopulateImu(
flatbuffers::FlatBufferBuilder *fbb) const;
void SendStatus();
static flatbuffers::Offset<CumulativeStatistics> StatisticsForCamera(
const CameraState &camera, flatbuffers::FlatBufferBuilder *fbb);
static void StatisticsForCamera(const CameraState &camera,
CumulativeStatisticsStatic *builder);
bool UseAprilTag(uint64_t target_id);
void HandleControl(
const frc971::control_loops::drivetrain::LocalizerControl &msg);
aos::EventLoop *const event_loop_;
frc971::constants::ConstantsFetcher<Constants> constants_fetcher_;
const frc971::control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
std::array<CameraState, kNumCameras> cameras_;
const std::array<Transform, kNumCameras> camera_extrinsics_;
const std::map<uint64_t, Transform> target_poses_;
frc971::control_loops::drivetrain::DrivetrainUkf down_estimator_;
HybridEkf ekf_;
HybridEkf::ExpectedObservationAllocator<Corrector> observations_;
HybridEkf::ExpectedObservationAllocator<XyzCorrector> xyz_observations_;
frc971::controls::ImuWatcher imu_watcher_;
frc971::control_loops::drivetrain::LocalizationUtils utils_;
aos::Sender<Status> status_sender_;
aos::Sender<frc971::controls::LocalizerOutput> output_sender_;
aos::monotonic_clock::time_point t_ = aos::monotonic_clock::min_time;
size_t clock_resets_ = 0;
size_t total_candidate_targets_ = 0;
size_t total_accepted_targets_ = 0;
// For the status message.
std::optional<Eigen::Vector2d> last_encoder_readings_;
aos::Fetcher<aos::message_bridge::ServerStatistics>
server_statistics_fetcher_;
aos::Fetcher<aos::message_bridge::ClientStatistics>
client_statistics_fetcher_;
aos::Fetcher<frc971::control_loops::drivetrain::LocalizerControl>
control_fetcher_;
};
} // namespace y2024::localizer
#endif // Y2024_LOCALIZER_LOCALIZER_H_